* Copyright (C) 2026 Xiaomi Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
* This file contains code derived from MimiClaw (https://github.com/memovai/mimiclaw)
* Copyright (c) 2026 Ziboyan Wang, licensed under the MIT License.
* See NOTICE file for the original MIT License terms.
*/
#include "channels/nsh_commands.h"
#include "channels/cmd_channel.h"
#include "channels/cmd_llm.h"
#include "channels/cmd_voice.h"
#include "core/message_bus.h"
#include "infra/config_store.h"
#include "infra/cron_service.h"
#include "infra/heartbeat.h"
#include "llm/llm_proxy.h"
#ifdef CONFIG_AI_AGENT_MCP
#include "tools/mcp_client.h"
#endif
#include "core/memory_store.h"
#include "core/session_mgr.h"
#include "infra/network_manager.h"
#include "infra/http_proxy.h"
#include "infra/vela_tls.h"
#include "tools/tool_get_time.h"
#include "tools/tool_proxyquickapp.h"
#include "tools/tool_registry.h"
#include "tools/tool_web_search.h"
#include "agent_compat.h"
#include "agent_config.h"
#if AGENT_SKILL_SYNC_ENABLED
#include "tools/skill_sync.h"
#endif
#ifdef CONFIG_AI_AGENT_LVGL_UI
#include "ui/lvgl_ui_channel.h"
#endif
#ifdef CONFIG_AI_AGENT_TEST
#include "integs/test_vision_integ.h"
#include "core/arena_alloc.h"
#endif
#include <malloc.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#ifdef CONFIG_BOARDCTL_RESET
#include <sys/boardctl.h>
#endif
static const char* TAG = "cli";
#define MAX_ARGS 8
#define LINE_LEN 256
static int tokenise(char* line, char** argv, int max_argc)
{
int argc = 0;
char* tok = strtok(line, " \t\r\n");
while (tok && argc < max_argc) {
argv[argc++] = tok;
tok = strtok(NULL, " \t\r\n");
}
return argc;
}
static void cmd_help(void)
{
printf(
"Available commands:\n"
" net_status - Show network connection status\n"
" net_test - Test HTTPS connection to Baidu\n"
" set_feishu_app <app_id> <app_secret> - Set Feishu app credentials\n"
" set_feishu_user_token <token> - Set Feishu user_access_token for doc APIs\n"
" set_llm <preset|host> [model] [key] - Switch LLM backend (kimi/qwen/deepseek/glm/openai)\n"
" set_vision_llm <preset|host> [model] [key] - Set independent vision model\n"
" list_models [--free] [keyword] - List available models (openrouter)\n"
" memory_read - Read MEMORY.md\n"
" memory_write <text> - Write MEMORY.md (quote text)\n"
" session_list - List sessions\n"
" session_clear <id> - Clear a session\n"
" session_clear_all - Clear all sessions\n"
" heap_info - Show memory usage\n"
" set_proxy <host> <port> - Set HTTP proxy\n"
" clear_proxy - Remove proxy config\n"
" set_wifi <ssid> <pw> - Connect to WiFi (real hardware; saved for reboot)\n"
" wifi_reconnect - Re-join WiFi using saved credentials\n"
" set_search_key <key> - Set SerpAPI (Google) key\n"
" set_exa_key <key> - Set Exa AI search key\n"
" set_tavily_key <key> - Set Tavily search key\n"
" set_news_key <key> - Set NewsAPI key\n"
" set_tavily_key <key> - Set Tavily AI search key\n"
" config_show - Show current configuration\n"
" config_reset - Clear all runtime config\n"
" ask <text> - Chat with AI Agent\n"
" heartbeat_trigger - Manually trigger heartbeat check\n"
" cron_start - Start cron scheduler\n"
#ifdef CONFIG_AI_AGENT_NODE
" node_list - List connected remote Nodes\n"
" set_gateway <host> [port] [token] - Set OpenClaw Gateway\n"
" node_start - Connect to OpenClaw Gateway as Node\n"
" node_stop - Disconnect from OpenClaw Gateway\n"
#endif
" restart - Restart the device\n"
" quit - Exit agent\n"
" set_mqtt <broker> [client_id] - Set MQTT broker (host:port)\n"
" set_volc_key <api_key> - Set Doubao voice API key\n"
" set_volc_asr <id> <tok> <cluster> - Set ASR credentials\n"
" set_volc_speaker <id> - Set TTS voice (e.g. zh_female_cancan)\n"
" voice_start - Start voice channel\n"
" voice_stop - Stop voice channel\n"
" voice_test_tts <text> [out.pcm] - Test TTS synthesis\n"
" voice_test_asr <file> - Test ASR recognition\n"
" set_voice_tts <name> - Switch TTS backend\n"
" set_voice_asr <name> - Switch ASR backend\n"
" set_weixin_token <tok> - Set WeChat bot token\n"
" weixin_login - QR code login to WeChat\n"
#ifdef CONFIG_AI_AGENT_XIAOZHI
" set_xiaozhi <ota_url> - Set XiaoZhi OTA endpoint URL\n"
#endif
" router_status - Show LLM router status\n"
" router_set <preset> <key> - Add LLM backend (deepseek/kimi/qwen/openai...)\n"
" router_model <idx> <model> - Change model for a backend\n"
" router_profile <p> - Set routing profile (eco/auto/premium)\n"
" router_clear [idx] - Clear router backends\n"
" launch_app <pkg> - Test launch a QuickApp by package name\n"
" exit_app - Exit current QuickApp and go home\n"
" install_skill <name> <url|-> - Install skill from URL or stdin\n"
#if AGENT_SKILL_SYNC_ENABLED
" skill_sync - Sync skills from Feishu Bitable\n"
#endif
#ifdef CONFIG_AI_AGENT_MCP
" mcp_add <name> <url> [token] - Add remote MCP server\n"
" mcp_remove <name> - Remove remote MCP server\n"
" mcp_discover - Discover tools from remote servers\n"
" mcp_status - Show MCP client status\n"
" mcp_tools - List remote MCP tools\n"
#endif
#ifdef CONFIG_AI_AGENT_LVGL_UI
" show_chat - Show chat UI on screen\n"
#endif
#ifdef CONFIG_AI_AGENT_NET_RPMSG
" net_diag [ping|http] <target> Network diagnostics\n"
" net_reconnect Reconnect network\n"
" set_netproxy <mode> [cpu] Set network proxy mode\n"
" set_dns <primary> [secondary] Set DNS servers\n"
#endif
#ifdef CONFIG_AI_AGENT_TEST
" claw_test [V-XX] - Run vision integration tests\n"
#endif
" help - This message\n");
}
static void cmd_net_test(void)
{
char resp[1024];
printf("Testing HTTPS to www.baidu.com...\n");
int status = vela_https_get("www.baidu.com", "443", "/", resp, sizeof(resp));
if (status > 0) {
printf("SUCCESS! HTTP Status: %d\n", status);
} else {
printf("FAILED! TLS Error: 0x%x\n", -status);
}
}
static void cmd_net_status(void)
{
printf("Network connected: %s\n", network_is_connected() ? "yes" : "no");
printf("IP: %s\n", network_get_ip());
#ifdef CONFIG_AI_AGENT_NET_RPMSG
printf("State: %s\n",
network_get_state() == NET_STATE_CONNECTED ? "CONNECTED" : "DISCONNECTED");
printf("Active TCP conns: %d/%d\n",
network_get_active_conns(), NET_MAX_TCP_CONNS);
printf("IOB usage: %d%%\n", network_get_iob_usage());
printf("Proxy mode: %s\n", network_get_proxy_mode());
printf("RPMSG CPU: %s\n", network_get_rpmsg_cpu());
{
FILE* fp = fopen("/tmp/resolv.conf", "r");
if (fp) {
char line[128];
while (fgets(line, sizeof(line), fp)) {
line[strcspn(line, "\n")] = '\0';
printf("DNS: %s\n", line);
}
fclose(fp);
}
}
#endif
}
#ifdef CONFIG_AI_AGENT_NET_RPMSG
static void cmd_net_diag(int argc, char** argv)
{
if (argc < 2) {
printf("=== Network Diagnostics ===\n");
printf("Interface: %s\n",
network_is_connected() ? "UP" : "DOWN");
printf("IP: %s\n", network_get_ip());
printf("State: %s\n",
network_get_state() == NET_STATE_CONNECTED
? "CONNECTED"
: "DISCONNECTED");
printf("IOB: %d%%\n", network_get_iob_usage());
printf("===========================\n");
return;
}
if (strcmp(argv[1], "ping") == 0 && argc >= 3) {
char cmd[128];
snprintf(cmd, sizeof(cmd), "ping -c 3 %s", argv[2]);
system(cmd);
} else if (strcmp(argv[1], "http") == 0 && argc >= 3) {
printf("HTTP HEAD %s ...\n", argv[2]);
char resp[256] = { 0 };
struct timespec t0, t1;
clock_gettime(CLOCK_MONOTONIC, &t0);
int status = vela_https_get(argv[2], "443", "/",
resp, sizeof(resp));
clock_gettime(CLOCK_MONOTONIC, &t1);
long ms = (t1.tv_sec - t0.tv_sec) * 1000L
+ (t1.tv_nsec - t0.tv_nsec) / 1000000L;
printf("Status: %d, Latency: %ldms\n", status, ms);
} else {
printf("Usage: net_diag [ping <host> | http <host>]\n");
}
}
#endif
static void cmd_memory_read(void)
{
char* buf = malloc(4096);
if (!buf) {
printf("Out of memory.\n");
return;
}
if (memory_read_long_term(buf, 4096) == OK && buf[0]) {
printf("=== MEMORY.md ===\n%s\n=================\n", buf);
} else {
printf("MEMORY.md is empty or not found.\n");
}
free(buf);
}
static void cmd_memory_write(int argc, char** argv)
{
if (argc < 2) {
printf("Usage: memory_write <content>\n");
return;
}
memory_write_long_term(argv[1]);
printf("MEMORY.md updated.\n");
}
static void cmd_session_list(void)
{
printf("Sessions:\n");
session_list();
}
static void cmd_session_clear(int argc, char** argv)
{
if (argc < 2) {
printf("Usage: session_clear <chat_id> | session_clear_all\n");
return;
}
if (session_clear(argv[1]) == OK) {
printf("Session cleared.\n");
} else {
printf("Session not found.\n");
}
}
static void cmd_session_clear_all(void)
{
session_clear_all();
printf("All sessions cleared.\n");
}
static void cmd_heap_info(void)
{
struct mallinfo mi = mallinfo();
printf("Heap: arena=%d fordblks(free)=%d uordblks(used)=%d\n",
mi.arena, mi.fordblks, mi.uordblks);
}
static void cmd_set_proxy(int argc, char** argv)
{
if (argc < 3) {
printf("Usage: set_proxy <host> <port>\n");
return;
}
uint16_t port = (uint16_t)atoi(argv[2]);
http_proxy_set(argv[1], port);
printf("Proxy set to %s:%d.\n", argv[1], port);
}
static void cmd_clear_proxy(void)
{
http_proxy_clear();
printf("Proxy cleared. Restart to apply.\n");
}
static void cmd_set_wifi(int argc, char** argv)
{
if (argc < 3) {
printf("Usage: set_wifi <ssid> <password>\n");
return;
}
printf("Connecting to '%s' ...\n", argv[1]);
int err = network_wifi_connect(NULL, argv[1], argv[2]);
if (err == OK)
printf("WiFi connected: %s\n", network_get_ip());
else
printf("WiFi failed. Check SSID/password or run net_status.\n");
}
static void cmd_wifi_reconnect(void)
{
printf("Reconnecting WiFi ...\n");
int err = network_wifi_reconnect();
if (err == OK)
printf("Reconnected: %s\n", network_get_ip());
else if (err == ERROR)
printf("No saved credentials. Use: set_wifi <ssid> <pass>\n");
else
printf("WiFi reconnect failed.\n");
}
#ifdef CONFIG_AI_AGENT_NET_RPMSG
static void cmd_net_reconnect(void)
{
printf("Reconnecting...\n");
int ret = network_reconnect();
printf("Reconnect %s\n", ret == OK ? "triggered" : "failed");
}
#endif
#ifdef CONFIG_AI_AGENT_NET_RPMSG
static void cmd_set_netproxy(int argc, char** argv)
{
if (argc < 2) {
printf("Usage: set_netproxy <usrsock|tun> [cpu_name]\n");
return;
}
const char* cpu = (argc >= 3) ? argv[2] : NULL;
int ret = network_save_proxy_config(argv[1], cpu);
if (ret == OK) {
printf("Proxy config saved: mode=%s cpu=%s\n",
argv[1], cpu ? cpu : "(default)");
} else {
printf("Failed to save proxy config: %d\n", ret);
}
}
#endif
#ifdef CONFIG_AI_AGENT_NET_RPMSG
static void cmd_set_dns(int argc, char** argv)
{
if (argc < 2) {
printf("Usage: set_dns <primary> [secondary]\n");
return;
}
const char* secondary = (argc >= 3) ? argv[2] : NULL;
int ret = network_set_dns(argv[1], secondary);
if (ret == OK) {
printf("DNS configured: %s %s\n",
argv[1], secondary ? secondary : "");
} else {
printf("Failed to set DNS: %d\n", ret);
}
}
#endif
static void cmd_set_search_key(int argc, char** argv)
{
if (argc < 2) {
printf("Usage: set_search_key <key>\n");
return;
}
tool_web_search_set_serp_key(argv[1]);
printf("SerpAPI (Google) key saved.\n");
}
static void cmd_set_exa_key(int argc, char** argv)
{
if (argc < 2) {
printf("Usage: set_exa_key <key>\n");
return;
}
tool_web_search_set_exa_key(argv[1]);
printf("Exa AI key saved.\n");
}
static void cmd_set_tavily_key(int argc, char** argv)
{
if (argc < 2) {
printf("Usage: set_tavily_key <key>\n");
return;
}
tool_web_search_set_tavily_key(argv[1]);
printf("Tavily key saved.\n");
}
static void cmd_set_news_key(int argc, char** argv)
{
if (argc < 2) {
printf("Usage: set_news_key <key>\n");
return;
}
tool_web_search_set_news_key(argv[1]);
printf("NewsAPI key saved.\n");
}
static void cmd_config_show(void)
{
char val[128] = { 0 };
printf("=== Current Configuration ===\n");
#define SHOW_CFG(label, key, mask) \
do { \
memset(val, 0, sizeof(val)); \
if (claw_config_get((key), val, sizeof(val)) != OK || !val[0]) \
strcpy(val, "(not set)"); \
if ((mask) && strlen(val) > 6 && strcmp(val, "(not set)") != 0) { \
char masked[128]; \
snprintf(masked, sizeof(masked), "%.4s****", val); \
printf(" %-14s: %s\n", (label), masked); \
} else { \
printf(" %-14s: %s\n", (label), val); \
} \
} while (0)
SHOW_CFG("Feishu AppID", AGENT_CFG_KEY_FEISHU_APP_ID, false);
SHOW_CFG("Feishu Secret", AGENT_CFG_KEY_FEISHU_APP_SECRET, true);
SHOW_CFG("API Key", AGENT_CFG_KEY_API_KEY, true);
SHOW_CFG("Model", AGENT_CFG_KEY_MODEL, false);
SHOW_CFG("LLM Host", AGENT_CFG_KEY_LLM_HOST, false);
SHOW_CFG("LLM Path", AGENT_CFG_KEY_LLM_PATH, false);
SHOW_CFG("Vision Model", AGENT_CFG_KEY_VISION_MODEL, false);
SHOW_CFG("Vision Host", AGENT_CFG_KEY_VISION_HOST, false);
SHOW_CFG("Vision Key", AGENT_CFG_KEY_VISION_API_KEY, true);
SHOW_CFG("Proxy Host", AGENT_CFG_KEY_PROXY_HOST, false);
SHOW_CFG("Proxy Port", AGENT_CFG_KEY_PROXY_PORT, false);
SHOW_CFG("SerpAPI Key", AGENT_CFG_KEY_SERP_KEY, true);
SHOW_CFG("Exa Key", AGENT_CFG_KEY_EXA_KEY, true);
SHOW_CFG("Tavily Key", AGENT_CFG_KEY_TAVILY_KEY, true);
SHOW_CFG("News Key", AGENT_CFG_KEY_NEWS_KEY, true);
SHOW_CFG("Tavily Key", AGENT_CFG_KEY_TAVILY_KEY, true);
SHOW_CFG("Gateway", AGENT_CFG_KEY_GATEWAY_HOST, false);
SHOW_CFG("GW Port", AGENT_CFG_KEY_GATEWAY_PORT, false);
SHOW_CFG("GW Token", AGENT_CFG_KEY_GATEWAY_TOKEN, true);
SHOW_CFG("MQTT Broker", AGENT_CFG_KEY_MQTT_BROKER, false);
SHOW_CFG("Volc AppKey", AGENT_CFG_KEY_VOLC_APPKEY, true);
SHOW_CFG("Volc Token", AGENT_CFG_KEY_VOLC_TOKEN, true);
SHOW_CFG("Volc API Key", AGENT_CFG_KEY_VOLC_API_KEY, true);
SHOW_CFG("Volc Speaker", AGENT_CFG_KEY_VOLC_SPEAKER, false);
#undef SHOW_CFG
printf("Network: %s / %s\n",
network_is_connected() ? "connected" : "disconnected",
network_get_ip());
#ifdef CONFIG_AI_AGENT_NET_RPMSG
printf("Net Proxy: %s (CPU: %s)\n",
network_get_proxy_mode(), network_get_rpmsg_cpu());
printf("Net Timeout: connect=%ds read=%ds\n",
network_get_connect_timeout(), network_get_read_timeout());
printf("Net Retry: max=%d base=%ds\n",
network_get_retry_max(), network_get_retry_base_sec());
#endif
printf("=============================\n");
}
static void cmd_config_reset(void)
{
config_erase_all();
printf("All runtime config cleared. Build-time defaults will be used on restart.\n");
}
static void cmd_restart(void)
{
printf("Restarting...\n");
fflush(stdout);
#ifdef CONFIG_BOARDCTL_RESET
boardctl(BOARDIOC_RESET, 0);
#else
while (1)
sleep(1);
#endif
}
static void cmd_heartbeat_trigger(void)
{
if (heartbeat_trigger()) {
printf("Heartbeat: triggered agent check.\n");
} else {
printf("Heartbeat: no actionable tasks found.\n");
}
}
static void cmd_ask(int argc, char** argv)
{
if (argc < 2) {
printf("Usage: ask <message>\n");
return;
}
char content[256] = { 0 };
for (int i = 1; i < argc; i++) {
strncat(content, argv[i], sizeof(content) - strlen(content) - 1);
if (i < argc - 1)
strncat(content, " ", sizeof(content) - strlen(content) - 1);
}
agent_msg_t msg = { 0 };
strncpy(msg.channel, "cli", sizeof(msg.channel) - 1);
strncpy(msg.chat_id, "console", sizeof(msg.chat_id) - 1);
msg.content = strdup(content);
if (msg.content)
message_bus_push_inbound(&msg);
printf("Sent to agent: %s\n", content);
syslog(LOG_INFO, "[agent] ask: %s\n", content);
}
static void cmd_cron_start(void)
{
if (cron_service_start() == OK) {
printf("Cron scheduler started.\n");
} else {
printf("Cron scheduler already running or failed to start.\n");
}
}
#ifdef CONFIG_AI_AGENT_TEST
static void cmd_claw_test(int argc, char** argv)
{
const char* filter = argc >= 2 ? argv[1] : NULL;
if (!filter || strcmp(filter, "arena") == 0) {
printf("=== Arena Allocator Test ===\n");
arena_t a;
int rc = arena_init(&a, 4096);
printf(" init(4096): %s\n", rc == 0 ? "OK" : "FAIL");
if (rc == 0) {
void* p1 = arena_alloc(&a, 128);
void* p2 = arena_alloc(&a, 256);
void* p3 = arena_alloc(&a, 512);
printf(" alloc 128+256+512: p1=%p p2=%p p3=%p\n",
p1, p2, p3);
printf(" used=%zu remaining=%zu\n",
arena_used(&a), arena_remaining(&a));
bool ok = p1 && p2 && p3
&& (char*)p2 >= (char*)p1 + 128
&& (char*)p3 >= (char*)p2 + 256;
printf(" no-overlap: %s\n", ok ? "OK" : "FAIL");
arena_reset(&a);
printf(" reset: used=%zu (expect 0)\n", arena_used(&a));
void* big = arena_alloc(&a, 8192);
printf(" overflow(8192): %s (expect NULL)\n",
big == NULL ? "OK" : "FAIL");
arena_destroy(&a);
printf(" destroy: OK\n");
printf("Arena test: PASSED\n\n");
}
if (filter) return;
}
int rc = test_vision_integ_run(filter);
printf("Test suite %s\n", rc == 0 ? "PASSED" : "FAILED");
}
#endif
static void cmd_quit(void)
{
printf("Exiting agent...\n");
fflush(stdout);
agent_request_shutdown();
}
static void cmd_launch_app(int argc, char** argv)
{
if (argc < 2) {
printf("Usage: launch_app <package_name>\n");
return;
}
char input[256];
snprintf(input, sizeof(input), "{\"package_name\":\"%s\"}", argv[1]);
char output[512];
int ret = tool_launch_quickapp_execute(input, output, sizeof(output));
printf("Result(%d): %s\n", ret, output);
}
static void cmd_exit_app(void)
{
char output[512];
int ret = tool_exit_quickapp_execute("{}", output, sizeof(output));
printf("Result(%d): %s\n", ret, output);
}
#ifdef CONFIG_AI_AGENT_MCP
static void cmd_mcp_add(int argc, char** argv)
{
if (argc < 3) {
printf("Usage: mcp_add <name> <url> [token]\n"
" name: server name (e.g. amap)\n"
" url: MCP endpoint (e.g. http://host:port/mcp)\n"
" token: optional bearer token\n");
return;
}
const char* token = (argc >= 4) ? argv[3] : NULL;
int ret = mcp_client_add_server(argv[1], argv[2], token);
if (ret == OK) {
printf("Added MCP server: %s → %s\n", argv[1], argv[2]);
} else {
printf("Failed to add MCP server\n");
}
}
static void cmd_mcp_remove(int argc, char** argv)
{
if (argc < 2) {
printf("Usage: mcp_remove <name>\n");
return;
}
int ret = mcp_client_remove_server(argv[1]);
if (ret == OK) {
printf("Removed MCP server: %s\n", argv[1]);
} else {
printf("Server not found: %s\n", argv[1]);
}
}
static void cmd_mcp_discover(void)
{
printf("Discovering remote MCP tools...\n");
int n = mcp_client_discover();
if (n >= 0) {
printf("Discovered %d remote tools\n", n);
tool_registry_invalidate();
} else {
printf("Discovery failed\n");
}
}
static void cmd_mcp_status(void)
{
char* json = mcp_client_status_json();
if (json) {
printf("%s\n", json);
free(json);
} else {
printf("No MCP client data\n");
}
}
static void cmd_mcp_tools(void)
{
char* json = mcp_client_get_tools_json();
if (json) {
printf("%s\n", json);
free(json);
} else {
printf("No remote tools (run mcp_discover first)\n");
}
}
#endif
static void cmd_install_skill(int argc, char** argv)
{
if (argc < 3) {
printf("Usage: install_skill <name> <url>\n"
" name: skill filename (without .md)\n"
" url: HTTPS URL to download skill markdown\n");
return;
}
const char* name = argv[1];
const char* url = argv[2];
for (const char* p = name; *p; p++) {
if (!(*p >= 'a' && *p <= 'z') && !(*p >= '0' && *p <= '9')
&& *p != '-' && *p != '_') {
printf("Invalid skill name: use a-z, 0-9, hyphens\n");
return;
}
}
if (strncmp(url, "https://", 8) != 0) {
printf("Only HTTPS URLs are allowed\n");
return;
}
char path[128];
int n = snprintf(path, sizeof(path), "%s%s.md",
AGENT_SKILLS_DIR, name);
if (n < 0 || (size_t)n >= sizeof(path)) {
printf("Skill name too long\n");
return;
}
char* buf = malloc(8192);
if (!buf) {
printf("OOM\n");
return;
}
memset(buf, 0, 8192);
int rc = vela_https_get(url, "443", NULL, buf, 8192);
if (rc < 200 || rc >= 300) {
printf("Download failed: HTTP %d\n", rc);
free(buf);
return;
}
if (buf[0] == '\0') {
printf("Downloaded content is empty\n");
free(buf);
return;
}
const char* p = buf;
while (*p == ' ' || *p == '\n' || *p == '\r')
p++;
if (*p != '#' && strncmp(p, "---", 3) != 0) {
printf("Invalid skill format: must start with # or ---\n");
free(buf);
return;
}
size_t content_len = strlen(buf);
if (content_len < 10) {
printf("Downloaded content too small (%zu bytes)\n", content_len);
free(buf);
return;
}
FILE* f = fopen(path, "w");
if (!f) {
printf("Cannot write: %s\n", path);
free(buf);
return;
}
fputs(buf, f);
fclose(f);
free(buf);
printf("Skill installed: %s (%zu bytes)\n", path, content_len);
}
#if AGENT_SKILL_SYNC_ENABLED
static void cmd_skill_sync(void)
{
printf("Syncing skills from Bitable...\n");
int rc = skill_sync_from_bitable();
if (rc == OK) {
printf("Skill sync completed successfully.\n");
} else {
printf("Skill sync failed (check syslog for details).\n");
}
}
#endif
#ifdef CONFIG_AI_AGENT_XIAOZHI
static void cmd_set_xiaozhi(int argc, char** argv)
{
if (argc < 2) {
printf("Usage: set_xiaozhi <ota_url>\n"
" Example: set_xiaozhi https://api.tenclass.net/xiaozhi/ota/\n");
return;
}
claw_config_set(AGENT_CFG_KEY_XIAOZHI_ENDPOINT, argv[1]);
printf("XiaoZhi endpoint set to: %s\n", argv[1]);
printf("Restart ai_agent to connect.\n");
}
#endif
static void* cli_thread(void* arg)
{
(void)arg;
char line[LINE_LEN];
char* argv[MAX_ARGS];
syslog(LOG_INFO, "[%s] NSH CLI started. Type 'help' for commands.\n", TAG);
pthread_mutex_lock(&g_stdout_lock);
printf("vela> ");
fflush(stdout);
pthread_mutex_unlock(&g_stdout_lock);
while (fgets(line, sizeof(line), stdin) != NULL) {
size_t len = strlen(line);
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
line[--len] = '\0';
if (len == 0) {
pthread_mutex_lock(&g_stdout_lock);
printf("vela> ");
fflush(stdout);
pthread_mutex_unlock(&g_stdout_lock);
continue;
}
int argc = tokenise(line, argv, MAX_ARGS);
if (argc == 0) {
pthread_mutex_lock(&g_stdout_lock);
printf("vela> ");
fflush(stdout);
pthread_mutex_unlock(&g_stdout_lock);
continue;
}
const char* cmd = argv[0];
if (strcmp(cmd, "help") == 0)
cmd_help();
else if (strcmp(cmd, "net_status") == 0)
cmd_net_status();
else if (strcmp(cmd, "net_test") == 0)
cmd_net_test();
else if (strcmp(cmd, "set_feishu_app") == 0)
cmd_set_feishu_app(argc, argv);
else if (strcmp(cmd, "set_feishu_user_token") == 0)
cmd_set_feishu_user_token(argc, argv);
else if (strcmp(cmd, "set_llm") == 0)
cmd_set_llm(argc, argv);
else if (strcmp(cmd, "set_vision_llm") == 0)
cmd_set_vision_llm(argc, argv);
else if (strcmp(cmd, "list_models") == 0)
cmd_list_models(argc, argv);
else if (strcmp(cmd, "memory_read") == 0)
cmd_memory_read();
else if (strcmp(cmd, "memory_write") == 0)
cmd_memory_write(argc, argv);
else if (strcmp(cmd, "session_list") == 0)
cmd_session_list();
else if (strcmp(cmd, "session_clear") == 0)
cmd_session_clear(argc, argv);
else if (strcmp(cmd, "session_clear_all") == 0)
cmd_session_clear_all();
else if (strcmp(cmd, "heap_info") == 0)
cmd_heap_info();
else if (strcmp(cmd, "set_proxy") == 0)
cmd_set_proxy(argc, argv);
else if (strcmp(cmd, "clear_proxy") == 0)
cmd_clear_proxy();
else if (strcmp(cmd, "set_wifi") == 0)
cmd_set_wifi(argc, argv);
else if (strcmp(cmd, "wifi_reconnect") == 0)
cmd_wifi_reconnect();
#ifdef CONFIG_AI_AGENT_NET_RPMSG
else if (strcmp(cmd, "net_diag") == 0)
cmd_net_diag(argc, argv);
else if (strcmp(cmd, "net_reconnect") == 0)
cmd_net_reconnect();
else if (strcmp(cmd, "set_netproxy") == 0)
cmd_set_netproxy(argc, argv);
else if (strcmp(cmd, "set_dns") == 0)
cmd_set_dns(argc, argv);
#endif
else if (strcmp(cmd, "set_search_key") == 0)
cmd_set_search_key(argc, argv);
else if (strcmp(cmd, "set_exa_key") == 0)
cmd_set_exa_key(argc, argv);
else if (strcmp(cmd, "set_news_key") == 0)
cmd_set_news_key(argc, argv);
else if (strcmp(cmd, "set_tavily_key") == 0)
cmd_set_tavily_key(argc, argv);
else if (strcmp(cmd, "config_show") == 0)
cmd_config_show();
else if (strcmp(cmd, "config_reset") == 0)
cmd_config_reset();
else if (strcmp(cmd, "heartbeat_trigger") == 0)
cmd_heartbeat_trigger();
else if (strcmp(cmd, "ask") == 0)
cmd_ask(argc, argv);
else if (strcmp(cmd, "cron_start") == 0)
cmd_cron_start();
#ifdef CONFIG_AI_AGENT_NODE
else if (strcmp(cmd, "node_list") == 0)
cmd_node_list();
else if (strcmp(cmd, "set_gateway") == 0)
cmd_set_gateway(argc, argv);
else if (strcmp(cmd, "node_start") == 0)
cmd_node_start();
else if (strcmp(cmd, "node_stop") == 0)
cmd_node_stop();
#endif
else if (strcmp(cmd, "quit") == 0) {
cmd_quit();
break;
} else if (strcmp(cmd, "set_mqtt") == 0)
cmd_set_mqtt(argc, argv);
else if (strcmp(cmd, "set_volc_key") == 0)
cmd_set_volc_key(argc, argv);
else if (strcmp(cmd, "set_volc_asr") == 0)
cmd_set_volc_asr(argc, argv);
else if (strcmp(cmd, "set_volc_speaker") == 0)
cmd_set_volc_speaker(argc, argv);
else if (strcmp(cmd, "voice_start") == 0)
cmd_voice_start();
else if (strcmp(cmd, "voice_stop") == 0)
cmd_voice_stop();
else if (strcmp(cmd, "voice_test_tts") == 0)
cmd_voice_test_tts(argc, argv);
else if (strcmp(cmd, "voice_test_asr") == 0)
cmd_voice_test_asr(argc, argv);
else if (strcmp(cmd, "set_voice_tts") == 0)
cmd_set_voice_tts(argc, argv);
else if (strcmp(cmd, "set_voice_asr") == 0)
cmd_set_voice_asr(argc, argv);
else if (strcmp(cmd, "set_weixin_token") == 0)
cmd_set_weixin_token(argc, argv);
else if (strcmp(cmd, "weixin_login") == 0)
cmd_weixin_login();
#ifdef CONFIG_AI_AGENT_XIAOZHI
else if (strcmp(cmd, "set_xiaozhi") == 0)
cmd_set_xiaozhi(argc, argv);
#endif
else if (strcmp(cmd, "launch_app") == 0)
cmd_launch_app(argc, argv);
else if (strcmp(cmd, "exit_app") == 0)
cmd_exit_app();
else if (strcmp(cmd, "install_skill") == 0)
cmd_install_skill(argc, argv);
#if AGENT_SKILL_SYNC_ENABLED
else if (strcmp(cmd, "skill_sync") == 0)
cmd_skill_sync();
#endif
#ifdef CONFIG_AI_AGENT_MCP
else if (strcmp(cmd, "mcp_add") == 0)
cmd_mcp_add(argc, argv);
else if (strcmp(cmd, "mcp_remove") == 0)
cmd_mcp_remove(argc, argv);
else if (strcmp(cmd, "mcp_discover") == 0)
cmd_mcp_discover();
else if (strcmp(cmd, "mcp_status") == 0)
cmd_mcp_status();
else if (strcmp(cmd, "mcp_tools") == 0)
cmd_mcp_tools();
#endif
#ifdef CONFIG_AI_AGENT_LVGL_UI
else if (strcmp(cmd, "show_chat") == 0)
lvgl_ui_channel_show();
#endif
#ifdef CONFIG_AI_AGENT_TEST
else if (strcmp(cmd, "claw_test") == 0)
cmd_claw_test(argc, argv);
#endif
else if (strcmp(cmd, "router_status") == 0)
cmd_router_status();
else if (strcmp(cmd, "router_set") == 0)
cmd_router_set(argc, argv);
else if (strcmp(cmd, "router_model") == 0)
cmd_router_model(argc, argv);
else if (strcmp(cmd, "router_profile") == 0)
cmd_router_profile(argc, argv);
else if (strcmp(cmd, "router_clear") == 0)
cmd_router_clear(argc, argv);
else if (strcmp(cmd, "restart") == 0)
cmd_restart();
else
printf("Unknown command: %s (type 'help')\n", cmd);
pthread_mutex_lock(&g_stdout_lock);
printf("vela> ");
fflush(stdout);
pthread_mutex_unlock(&g_stdout_lock);
}
syslog(LOG_INFO, "[%s] CLI thread exiting\n", TAG);
return NULL;
}
int nsh_commands_init(void)
{
* Commands are statically defined; nothing to do at the moment.
* Kept as a hook for future dynamic command registration. */
syslog(LOG_INFO, "NSH commands registered");
return OK;
}
int nsh_commands_start(void)
{
return agent_task_create(cli_thread, "agent_cli", AGENT_CLI_STACK, NULL, AGENT_CLI_PRIO);
}