feat: add skill verification CLI commands and preflash SPIFFS image
This commit is contained in:
@@ -7,12 +7,12 @@
|
||||
#include "memory/session_mgr.h"
|
||||
#include "proxy/http_proxy.h"
|
||||
#include "tools/tool_web_search.h"
|
||||
#include "tools/tool_registry.h"
|
||||
#include "cron/cron_service.h"
|
||||
#include "heartbeat/heartbeat.h"
|
||||
#include "skills/skill_loader.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <dirent.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_console.h"
|
||||
#include "esp_system.h"
|
||||
@@ -253,6 +253,173 @@ static int cmd_wifi_scan(int argc, char **argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* --- skill_list command --- */
|
||||
static int cmd_skill_list(int argc, char **argv)
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
char *buf = malloc(4096);
|
||||
if (!buf) {
|
||||
printf("Out of memory.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t n = skill_loader_build_summary(buf, 4096);
|
||||
if (n == 0) {
|
||||
printf("No skills found under /spiffs/skills/.\n");
|
||||
} else {
|
||||
printf("=== Skills ===\n%s", buf);
|
||||
}
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* --- skill_show command --- */
|
||||
static struct {
|
||||
struct arg_str *name;
|
||||
struct arg_end *end;
|
||||
} skill_show_args;
|
||||
|
||||
static bool has_md_suffix(const char *name)
|
||||
{
|
||||
size_t len = strlen(name);
|
||||
return (len >= 3) && strcmp(name + len - 3, ".md") == 0;
|
||||
}
|
||||
|
||||
static bool build_skill_path(const char *name, char *out, size_t out_size)
|
||||
{
|
||||
if (!name || !name[0]) return false;
|
||||
if (strstr(name, "..") != NULL) return false;
|
||||
if (strchr(name, '/') != NULL || strchr(name, '\\') != NULL) return false;
|
||||
|
||||
if (has_md_suffix(name)) {
|
||||
snprintf(out, out_size, "/spiffs/skills/%s", name);
|
||||
} else {
|
||||
snprintf(out, out_size, "/spiffs/skills/%s.md", name);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int cmd_skill_show(int argc, char **argv)
|
||||
{
|
||||
int nerrors = arg_parse(argc, argv, (void **)&skill_show_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, skill_show_args.end, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char path[128];
|
||||
if (!build_skill_path(skill_show_args.name->sval[0], path, sizeof(path))) {
|
||||
printf("Invalid skill name.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f) {
|
||||
printf("Skill not found: %s\n", path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("=== %s ===\n", path);
|
||||
char line[256];
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
fputs(line, stdout);
|
||||
}
|
||||
fclose(f);
|
||||
printf("\n============\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* --- skill_search command --- */
|
||||
static struct {
|
||||
struct arg_str *keyword;
|
||||
struct arg_end *end;
|
||||
} skill_search_args;
|
||||
|
||||
static bool contains_nocase(const char *text, const char *keyword)
|
||||
{
|
||||
if (!text || !keyword || !keyword[0]) return false;
|
||||
|
||||
size_t key_len = strlen(keyword);
|
||||
for (const char *p = text; *p; p++) {
|
||||
size_t i = 0;
|
||||
while (i < key_len && p[i] &&
|
||||
tolower((unsigned char)p[i]) == tolower((unsigned char)keyword[i])) {
|
||||
i++;
|
||||
}
|
||||
if (i == key_len) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int cmd_skill_search(int argc, char **argv)
|
||||
{
|
||||
int nerrors = arg_parse(argc, argv, (void **)&skill_search_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, skill_search_args.end, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *keyword = skill_search_args.keyword->sval[0];
|
||||
DIR *dir = opendir("/spiffs");
|
||||
if (!dir) {
|
||||
printf("Cannot open /spiffs.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *prefix = "skills/";
|
||||
const size_t prefix_len = strlen(prefix);
|
||||
int matches = 0;
|
||||
|
||||
struct dirent *ent;
|
||||
while ((ent = readdir(dir)) != NULL) {
|
||||
const char *name = ent->d_name;
|
||||
size_t name_len = strlen(name);
|
||||
|
||||
if (strncmp(name, prefix, prefix_len) != 0) continue;
|
||||
if (name_len < prefix_len + 4) continue;
|
||||
if (strcmp(name + name_len - 3, ".md") != 0) continue;
|
||||
|
||||
char full_path[296];
|
||||
snprintf(full_path, sizeof(full_path), "/spiffs/%s", name);
|
||||
|
||||
bool file_matched = contains_nocase(name, keyword);
|
||||
int matched_line = 0;
|
||||
|
||||
FILE *f = fopen(full_path, "r");
|
||||
if (!f) continue;
|
||||
|
||||
char line[256];
|
||||
int line_no = 0;
|
||||
while (!file_matched && fgets(line, sizeof(line), f)) {
|
||||
line_no++;
|
||||
if (contains_nocase(line, keyword)) {
|
||||
file_matched = true;
|
||||
matched_line = line_no;
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
if (file_matched) {
|
||||
matches++;
|
||||
if (matched_line > 0) {
|
||||
printf("- %s (matched at line %d)\n", full_path, matched_line);
|
||||
} else {
|
||||
printf("- %s (matched in filename)\n", full_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
if (matches == 0) {
|
||||
printf("No skills matched keyword: %s\n", keyword);
|
||||
} else {
|
||||
printf("Total matches: %d\n", matches);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* --- config_show command --- */
|
||||
static void print_config(const char *label, const char *ns, const char *key,
|
||||
const char *build_val, bool mask)
|
||||
@@ -463,6 +630,36 @@ esp_err_t serial_cli_init(void)
|
||||
};
|
||||
esp_console_cmd_register(&provider_cmd);
|
||||
|
||||
/* skill_list */
|
||||
esp_console_cmd_t skill_list_cmd = {
|
||||
.command = "skill_list",
|
||||
.help = "List installed skills from /spiffs/skills/",
|
||||
.func = &cmd_skill_list,
|
||||
};
|
||||
esp_console_cmd_register(&skill_list_cmd);
|
||||
|
||||
/* skill_show */
|
||||
skill_show_args.name = arg_str1(NULL, NULL, "<name>", "Skill name (e.g. weather or weather.md)");
|
||||
skill_show_args.end = arg_end(1);
|
||||
esp_console_cmd_t skill_show_cmd = {
|
||||
.command = "skill_show",
|
||||
.help = "Print full content of one skill file",
|
||||
.func = &cmd_skill_show,
|
||||
.argtable = &skill_show_args,
|
||||
};
|
||||
esp_console_cmd_register(&skill_show_cmd);
|
||||
|
||||
/* skill_search */
|
||||
skill_search_args.keyword = arg_str1(NULL, NULL, "<keyword>", "Keyword to search in skills");
|
||||
skill_search_args.end = arg_end(1);
|
||||
esp_console_cmd_t skill_search_cmd = {
|
||||
.command = "skill_search",
|
||||
.help = "Search skill files by keyword (filename + content)",
|
||||
.func = &cmd_skill_search,
|
||||
.argtable = &skill_search_args,
|
||||
};
|
||||
esp_console_cmd_register(&skill_search_cmd);
|
||||
|
||||
/* memory_read */
|
||||
esp_console_cmd_t mem_read_cmd = {
|
||||
.command = "memory_read",
|
||||
|
||||
Reference in New Issue
Block a user