Merge pull request #105 from IRONICBo/feat/skills-to-spiffs
This commit is contained in:
@@ -8,145 +8,44 @@
|
|||||||
|
|
||||||
static const char *TAG = "skills";
|
static const char *TAG = "skills";
|
||||||
|
|
||||||
/* ── Built-in skill contents ─────────────────────────────────── */
|
/*
|
||||||
|
* Skills are stored as markdown files in spiffs_data/skills/
|
||||||
#define BUILTIN_WEATHER \
|
* and flashed into the SPIFFS partition at build time.
|
||||||
"# Weather\n" \
|
*/
|
||||||
"\n" \
|
|
||||||
"Get current weather and forecasts using web_search.\n" \
|
|
||||||
"\n" \
|
|
||||||
"## When to use\n" \
|
|
||||||
"When the user asks about weather, temperature, or forecasts.\n" \
|
|
||||||
"\n" \
|
|
||||||
"## How to use\n" \
|
|
||||||
"1. Use get_current_time to know the current date\n" \
|
|
||||||
"2. Use web_search with a query like \"weather in [city] today\"\n" \
|
|
||||||
"3. Extract temperature, conditions, and forecast from results\n" \
|
|
||||||
"4. Present in a concise, friendly format\n" \
|
|
||||||
"\n" \
|
|
||||||
"## Example\n" \
|
|
||||||
"User: \"What's the weather in Tokyo?\"\n" \
|
|
||||||
"→ get_current_time\n" \
|
|
||||||
"→ web_search \"weather Tokyo today February 2026\"\n" \
|
|
||||||
"→ \"Tokyo: 8°C, partly cloudy. High 12°C, low 4°C. Light wind from the north.\"\n"
|
|
||||||
|
|
||||||
#define BUILTIN_DAILY_BRIEFING \
|
|
||||||
"# Daily Briefing\n" \
|
|
||||||
"\n" \
|
|
||||||
"Compile a personalized daily briefing for the user.\n" \
|
|
||||||
"\n" \
|
|
||||||
"## When to use\n" \
|
|
||||||
"When the user asks for a daily briefing, morning update, or \"what's new today\".\n" \
|
|
||||||
"Also useful as a heartbeat/cron task.\n" \
|
|
||||||
"\n" \
|
|
||||||
"## How to use\n" \
|
|
||||||
"1. Use get_current_time for today's date\n" \
|
|
||||||
"2. Read " MIMI_SPIFFS_MEMORY_DIR "/MEMORY.md for user preferences and context\n" \
|
|
||||||
"3. Read today's daily note if it exists\n" \
|
|
||||||
"4. Use web_search for relevant news based on user interests\n" \
|
|
||||||
"5. Compile a concise briefing covering:\n" \
|
|
||||||
" - Date and time\n" \
|
|
||||||
" - Weather (if location known from USER.md)\n" \
|
|
||||||
" - Relevant news/updates based on user interests\n" \
|
|
||||||
" - Any pending tasks from memory\n" \
|
|
||||||
" - Any scheduled cron jobs\n" \
|
|
||||||
"\n" \
|
|
||||||
"## Format\n" \
|
|
||||||
"Keep it brief — 5-10 bullet points max. Use the user's preferred language.\n"
|
|
||||||
|
|
||||||
#define BUILTIN_SKILL_CREATOR \
|
|
||||||
"# Skill Creator\n" \
|
|
||||||
"\n" \
|
|
||||||
"Create new skills for MimiClaw.\n" \
|
|
||||||
"\n" \
|
|
||||||
"## When to use\n" \
|
|
||||||
"When the user asks to create a new skill, teach the bot something, or add a new capability.\n" \
|
|
||||||
"\n" \
|
|
||||||
"## How to create a skill\n" \
|
|
||||||
"1. Choose a short, descriptive name (lowercase, hyphens ok)\n" \
|
|
||||||
"2. Write a SKILL.md file with this structure:\n" \
|
|
||||||
" - `# Title` — clear name\n" \
|
|
||||||
" - Brief description paragraph\n" \
|
|
||||||
" - `## When to use` — trigger conditions\n" \
|
|
||||||
" - `## How to use` — step-by-step instructions\n" \
|
|
||||||
" - `## Example` — concrete example (optional but helpful)\n" \
|
|
||||||
"3. Save to `" MIMI_SKILLS_PREFIX "<name>.md` using write_file\n" \
|
|
||||||
"4. The skill will be automatically available after the next conversation\n" \
|
|
||||||
"\n" \
|
|
||||||
"## Best practices\n" \
|
|
||||||
"- Keep skills concise — the context window is limited\n" \
|
|
||||||
"- Focus on WHAT to do, not HOW (the agent is smart)\n" \
|
|
||||||
"- Include specific tool calls the agent should use\n" \
|
|
||||||
"- Test by asking the agent to use the new skill\n" \
|
|
||||||
"\n" \
|
|
||||||
"## Example\n" \
|
|
||||||
"To create a \"translate\" skill:\n" \
|
|
||||||
"write_file path=\"" MIMI_SKILLS_PREFIX "translate.md\" content=\"# Translate\\n\\nTranslate text between languages.\\n\\n" \
|
|
||||||
"## When to use\\nWhen the user asks to translate text.\\n\\n" \
|
|
||||||
"## How to use\\n1. Identify source and target languages\\n" \
|
|
||||||
"2. Translate directly using your language knowledge\\n" \
|
|
||||||
"3. For specialized terms, use web_search to verify\\n\"\n"
|
|
||||||
|
|
||||||
/* Built-in skill registry */
|
|
||||||
typedef struct {
|
|
||||||
const char *filename; /* e.g. "weather" */
|
|
||||||
const char *content;
|
|
||||||
} builtin_skill_t;
|
|
||||||
|
|
||||||
static const builtin_skill_t s_builtins[] = {
|
|
||||||
{ "weather", BUILTIN_WEATHER },
|
|
||||||
{ "daily-briefing", BUILTIN_DAILY_BRIEFING },
|
|
||||||
{ "skill-creator", BUILTIN_SKILL_CREATOR },
|
|
||||||
};
|
|
||||||
|
|
||||||
#define NUM_BUILTINS (sizeof(s_builtins) / sizeof(s_builtins[0]))
|
|
||||||
|
|
||||||
/* ── Install built-in skills if missing ──────────────────────── */
|
|
||||||
|
|
||||||
static void install_builtin(const builtin_skill_t *skill)
|
|
||||||
{
|
|
||||||
char path[64];
|
|
||||||
snprintf(path, sizeof(path), "%s%s.md", MIMI_SKILLS_PREFIX, skill->filename);
|
|
||||||
|
|
||||||
/* Check if already exists */
|
|
||||||
FILE *f = fopen(path, "r");
|
|
||||||
if (f) {
|
|
||||||
fclose(f);
|
|
||||||
ESP_LOGD(TAG, "Skill exists: %s", path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write built-in skill */
|
|
||||||
f = fopen(path, "w");
|
|
||||||
if (!f) {
|
|
||||||
ESP_LOGE(TAG, "Cannot write skill: %s", path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fputs(skill->content, f);
|
|
||||||
fclose(f);
|
|
||||||
ESP_LOGI(TAG, "Installed built-in skill: %s", path);
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t skill_loader_init(void)
|
esp_err_t skill_loader_init(void)
|
||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "Initializing skills system");
|
ESP_LOGI(TAG, "Initializing skills system");
|
||||||
|
|
||||||
for (size_t i = 0; i < NUM_BUILTINS; i++) {
|
DIR *dir = opendir(MIMI_SPIFFS_BASE);
|
||||||
install_builtin(&s_builtins[i]);
|
if (!dir) {
|
||||||
|
ESP_LOGW(TAG, "Cannot open SPIFFS — skills may not be available");
|
||||||
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Skills system ready (%d built-in)", (int)NUM_BUILTINS);
|
int count = 0;
|
||||||
|
struct dirent *ent;
|
||||||
|
while ((ent = readdir(dir)) != NULL) {
|
||||||
|
const char *name = ent->d_name;
|
||||||
|
size_t len = strlen(name);
|
||||||
|
if (strncmp(name, "skills/", 7) == 0 && len > 10 &&
|
||||||
|
strcmp(name + len - 3, ".md") == 0) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Skills system ready (%d skills on SPIFFS)", count);
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Build skills summary for system prompt ──────────────────── */
|
/* ── Build skills summary for system prompt ──────────────────── */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse first line as title: expects "# Title"
|
* Parse first line as title: expects "# Title".
|
||||||
* Returns pointer past "# " or the line itself if no prefix.
|
* Writes the title (without "# " prefix) into out.
|
||||||
*/
|
*/
|
||||||
static const char *extract_title(const char *line, size_t len, char *out, size_t out_size)
|
static void extract_title(const char *line, size_t len, char *out, size_t out_size)
|
||||||
{
|
{
|
||||||
const char *start = line;
|
const char *start = line;
|
||||||
if (len >= 2 && line[0] == '#' && line[1] == ' ') {
|
if (len >= 2 && line[0] == '#' && line[1] == ' ') {
|
||||||
@@ -162,7 +61,6 @@ static const char *extract_title(const char *line, size_t len, char *out, size_t
|
|||||||
size_t copy = len < out_size - 1 ? len : out_size - 1;
|
size_t copy = len < out_size - 1 ? len : out_size - 1;
|
||||||
memcpy(out, start, copy);
|
memcpy(out, start, copy);
|
||||||
out[copy] = '\0';
|
out[copy] = '\0';
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize skills system.
|
* Initialize skills system.
|
||||||
* Installs built-in skill files to SPIFFS if they don't already exist.
|
* Scans SPIFFS for available skill markdown files.
|
||||||
*/
|
*/
|
||||||
esp_err_t skill_loader_init(void);
|
esp_err_t skill_loader_init(void);
|
||||||
|
|
||||||
|
|||||||
22
spiffs_data/skills/daily-briefing.md
Normal file
22
spiffs_data/skills/daily-briefing.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Daily Briefing
|
||||||
|
|
||||||
|
Compile a personalized daily briefing for the user.
|
||||||
|
|
||||||
|
## When to use
|
||||||
|
When the user asks for a daily briefing, morning update, or "what's new today".
|
||||||
|
Also useful as a heartbeat/cron task.
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
1. Use get_current_time for today's date
|
||||||
|
2. Read /spiffs/memory/MEMORY.md for user preferences and context
|
||||||
|
3. Read today's daily note if it exists
|
||||||
|
4. Use web_search for relevant news based on user interests
|
||||||
|
5. Compile a concise briefing covering:
|
||||||
|
- Date and time
|
||||||
|
- Weather (if location known from USER.md)
|
||||||
|
- Relevant news/updates based on user interests
|
||||||
|
- Any pending tasks from memory
|
||||||
|
- Any scheduled cron jobs
|
||||||
|
|
||||||
|
## Format
|
||||||
|
Keep it brief — 5-10 bullet points max. Use the user's preferred language.
|
||||||
27
spiffs_data/skills/skill-creator.md
Normal file
27
spiffs_data/skills/skill-creator.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Skill Creator
|
||||||
|
|
||||||
|
Create new skills for MimiClaw.
|
||||||
|
|
||||||
|
## When to use
|
||||||
|
When the user asks to create a new skill, teach the bot something, or add a new capability.
|
||||||
|
|
||||||
|
## How to create a skill
|
||||||
|
1. Choose a short, descriptive name (lowercase, hyphens ok)
|
||||||
|
2. Write a SKILL.md file with this structure:
|
||||||
|
- `# Title` — clear name
|
||||||
|
- Brief description paragraph
|
||||||
|
- `## When to use` — trigger conditions
|
||||||
|
- `## How to use` — step-by-step instructions
|
||||||
|
- `## Example` — concrete example (optional but helpful)
|
||||||
|
3. Save to `/spiffs/skills/<name>.md` using write_file
|
||||||
|
4. The skill will be automatically available after the next conversation
|
||||||
|
|
||||||
|
## Best practices
|
||||||
|
- Keep skills concise — the context window is limited
|
||||||
|
- Focus on WHAT to do, not HOW (the agent is smart)
|
||||||
|
- Include specific tool calls the agent should use
|
||||||
|
- Test by asking the agent to use the new skill
|
||||||
|
|
||||||
|
## Example
|
||||||
|
To create a "translate" skill:
|
||||||
|
write_file path="/spiffs/skills/translate.md" content="# Translate\n\nTranslate text between languages.\n\n## When to use\nWhen the user asks to translate text.\n\n## How to use\n1. Identify source and target languages\n2. Translate directly using your language knowledge\n3. For specialized terms, use web_search to verify\n"
|
||||||
18
spiffs_data/skills/weather.md
Normal file
18
spiffs_data/skills/weather.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Weather
|
||||||
|
|
||||||
|
Get current weather and forecasts using web_search.
|
||||||
|
|
||||||
|
## When to use
|
||||||
|
When the user asks about weather, temperature, or forecasts.
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
1. Use get_current_time to know the current date
|
||||||
|
2. Use web_search with a query like "weather in [city] today"
|
||||||
|
3. Extract temperature, conditions, and forecast from results
|
||||||
|
4. Present in a concise, friendly format
|
||||||
|
|
||||||
|
## Example
|
||||||
|
User: "What's the weather in Tokyo?"
|
||||||
|
→ get_current_time
|
||||||
|
→ web_search "weather Tokyo today February 2026"
|
||||||
|
→ "Tokyo: 8°C, partly cloudy. High 12°C, low 4°C. Light wind from the north."
|
||||||
Reference in New Issue
Block a user