refactor: remove NVS/CLI config, use mimi_secrets.h as sole configuration method

All configuration is now done exclusively through mimi_secrets.h at build time.
Removed NVS read/write logic, CLI config commands (wifi_set, set_tg_token,
set_api_key, set_model, set_proxy, clear_proxy, set_search_key), and setter
functions from all modules. CLI retains debug/maintenance commands only.
Updated all documentation to reflect the change.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
crispyberry
2026-02-07 23:04:24 +08:00
parent 43ded65713
commit 82f93b617b
18 changed files with 63 additions and 547 deletions

View File

@@ -66,7 +66,7 @@ idf.py set-target esp32s3
### Configure
**Option A: Config file (recommended)** — fill in once, baked into firmware at build time:
All configuration is done through `mimi_secrets.h` at build time:
```bash
cp main/mimi_secrets.h.example main/mimi_secrets.h
@@ -91,39 +91,16 @@ idf.py build
idf.py -p /dev/ttyACM0 flash monitor
```
Config file values have the **highest priority** — they override anything set via CLI.
> **Note:** After editing `mimi_secrets.h`, run `touch main/mimi_config.h` before `idf.py build` to force recompilation.
**Option B: Serial CLI** — configure at runtime after flashing:
```bash
idf.py build
idf.py -p /dev/ttyACM0 flash monitor
```
```
mimi> wifi_set YourWiFiName YourWiFiPassword
mimi> set_tg_token 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
mimi> set_api_key sk-ant-api03-xxxxx
mimi> set_search_key BSA-xxxxx # optional: Brave Search API key for web_search
mimi> restart
```
CLI values are stored in NVS (persistent flash) and used when no config file value is set.
> **Important:** After editing `mimi_secrets.h`, you must do a full rebuild: `idf.py fullclean && idf.py build`
### CLI Commands
The serial CLI provides debug and maintenance commands:
```
mimi> wifi_set <ssid> <pass> # set WiFi credentials
mimi> wifi_status # am I connected?
mimi> set_tg_token <token> # set Telegram bot token
mimi> set_api_key <key> # set Anthropic API key
mimi> set_model claude-opus-4-6 # use a different model
mimi> set_search_key <key> # set Brave Search API key (for web_search tool)
mimi> set_proxy 10.0.0.1 7897 # route through HTTP proxy
mimi> clear_proxy # remove proxy, connect directly
mimi> memory_read # see what the bot remembers
mimi> memory_write "content" # write to MEMORY.md
mimi> heap_info # how much RAM is free?
mimi> session_list # list all chat sessions
mimi> session_clear 12345 # wipe a conversation
@@ -151,7 +128,7 @@ MimiClaw uses Anthropic's tool use protocol — Claude can call tools during a c
| `web_search` | Search the web via Brave Search API for current information |
| `get_current_time` | Fetch current date/time via HTTP and set the system clock |
To enable web search, set a [Brave Search API key](https://brave.com/search/api/) in your config file or via CLI (`set_search_key`).
To enable web search, set a [Brave Search API key](https://brave.com/search/api/) via `MIMI_SECRET_SEARCH_KEY` in `mimi_secrets.h`.
## Also Included

View File

@@ -66,7 +66,7 @@ idf.py set-target esp32s3
### 配置
**方式 A配置文件推荐** — 填一次,编译时写入固件
所有配置通过 `mimi_secrets.h`编译时写入:
```bash
cp main/mimi_secrets.h.example main/mimi_secrets.h
@@ -91,26 +91,7 @@ idf.py build
idf.py -p /dev/ttyACM0 flash monitor
```
配置文件的值**优先级最高** — 会覆盖 CLI 设置的值。
> **注意**:修改 `mimi_secrets.h` 后,需要先执行 `touch main/mimi_config.h` 再 `idf.py build`,否则不会重新编译。
**方式 B串口命令行** — 烧录后在运行时配置:
```bash
idf.py build
idf.py -p /dev/ttyACM0 flash monitor
```
```
mimi> wifi_set 你的WiFi名 你的WiFi密码
mimi> set_tg_token 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
mimi> set_api_key sk-ant-api03-xxxxx
mimi> set_search_key BSA-xxxxx # 可选Brave Search API key启用网页搜索
mimi> restart
```
CLI 设置的值存在 NVS持久 Flash仅在配置文件未设置对应值时生效。
> **重要**:修改 `mimi_secrets.h` 后必须完整重编译:`idf.py fullclean && idf.py build`
### 代理配置(国内用户)
@@ -118,34 +99,18 @@ CLI 设置的值存在 NVS持久 Flash仅在配置文件未设置对
**前提**:局域网内有一个支持 HTTP CONNECT 的代理Clash Verge、V2Ray 等),并开启了「允许局域网连接」。
推荐直接`mimi_secrets.h`配置代理(见上方方式 A也可以用命令行
```
mimi> set_proxy 10.0.0.1 7897
mimi> restart
```
清除代理恢复直连:
```
mimi> clear_proxy
mimi> restart
```
`mimi_secrets.h`设置 `MIMI_SECRET_PROXY_HOST``MIMI_SECRET_PROXY_PORT`。清除代理只需把这两项改回空字符串 `""`,然后重新编译。
> **提示**:确保 ESP32-S3 和代理机器在同一局域网。Clash Verge 在「设置 → 允许局域网」中开启。
### 所有命令
### CLI 命令
串口 CLI 提供调试和运维命令:
```
mimi> wifi_set <ssid> <pass> # 设置 WiFi
mimi> wifi_status # 连上了吗?
mimi> set_tg_token <token> # 设置 Telegram Bot Token
mimi> set_api_key <key> # 设置 Anthropic API Key
mimi> set_model claude-opus-4-6 # 换个模型
mimi> set_search_key <key> # 设置 Brave Search API Keyweb_search 工具用)
mimi> set_proxy 10.0.0.1 7897 # 通过 HTTP 代理
mimi> clear_proxy # 清除代理,直连
mimi> memory_read # 看看它记住了什么
mimi> memory_write "内容" # 写入 MEMORY.md
mimi> heap_info # 还剩多少内存?
mimi> session_list # 列出所有会话
mimi> session_clear 12345 # 删除一个会话
@@ -173,7 +138,7 @@ MimiClaw 使用 Anthropic 的 tool use 协议 — Claude 在对话中可以调
| `web_search` | 通过 Brave Search API 搜索网页,获取实时信息 |
| `get_current_time` | 通过 HTTP 获取当前日期和时间,并设置系统时钟 |
启用网页搜索需要设置 [Brave Search API key](https://brave.com/search/api/),在配置文件或 CLI`set_search_key`)中设置
启用网页搜索需要`mimi_secrets.h`设置 [Brave Search API key](https://brave.com/search/api/)`MIMI_SECRET_SEARCH_KEY`
## 其他功能

View File

@@ -106,7 +106,7 @@ main/
├── wifi/
│ ├── wifi_manager.h WiFi STA lifecycle API
│ └── wifi_manager.c NVS credentials, event handler, exponential backoff
│ └── wifi_manager.c Event handler, exponential backoff
├── telegram/
│ ├── telegram_bot.h Bot init/start, send_message API
@@ -144,7 +144,7 @@ main/
├── cli/
│ ├── serial_cli.h CLI init API
│ └── serial_cli.c esp_console REPL with 15 commands
│ └── serial_cli.c esp_console REPL with debug/maintenance commands
└── ota/
├── ota_manager.h OTA update API
@@ -190,7 +190,7 @@ Large buffers (32 KB+) are allocated from PSRAM via `heap_caps_calloc(1, size, M
```
Offset Size Name Purpose
─────────────────────────────────────────────
0x009000 24 KB nvs WiFi creds, TG token, API key, model
0x009000 24 KB nvs ESP-IDF internal use (WiFi calibration etc.)
0x00F000 8 KB otadata OTA boot state
0x011000 4 KB phy_init WiFi PHY calibration
0x020000 2 MB ota_0 Firmware slot A
@@ -223,22 +223,22 @@ Session files are JSONL (one JSON object per line):
---
## NVS Configuration
## Configuration
| Namespace | Key | Description |
|-----------------|--------------|-----------------------------------------|
| `wifi_config` | `ssid` | WiFi SSID |
| `wifi_config` | `password` | WiFi password |
| `tg_config` | `bot_token` | Telegram Bot API token |
| `llm_config` | `api_key` | Anthropic API key |
| `llm_config` | `model` | Model ID (default: claude-opus-4-6) |
| `proxy_config` | `host` | HTTP proxy hostname/IP |
| `proxy_config` | `port` | HTTP proxy port |
| `search_config` | `api_key` | Brave Search API key |
All configuration is done exclusively through `mimi_secrets.h` at build time. There is no runtime configuration — changing any setting requires `idf.py fullclean && idf.py build`.
**Configuration priority**: `mimi_secrets.h` (build-time) > NVS (CLI-set) > defaults.
| Define | Description |
|------------------------------|-----------------------------------------|
| `MIMI_SECRET_WIFI_SSID` | WiFi SSID |
| `MIMI_SECRET_WIFI_PASS` | WiFi password |
| `MIMI_SECRET_TG_TOKEN` | Telegram Bot API token |
| `MIMI_SECRET_API_KEY` | Anthropic API key |
| `MIMI_SECRET_MODEL` | Model ID (default: claude-opus-4-6) |
| `MIMI_SECRET_PROXY_HOST` | HTTP proxy hostname/IP (optional) |
| `MIMI_SECRET_PROXY_PORT` | HTTP proxy port (optional) |
| `MIMI_SECRET_SEARCH_KEY` | Brave Search API key (optional) |
All configurable via Serial CLI or build-time config file (`mimi_secrets.h`).
NVS is still initialized (required by ESP-IDF WiFi internals) but is not used for application configuration.
---
@@ -340,14 +340,14 @@ app_main()
├── memory_store_init() Verify SPIFFS paths
├── session_mgr_init()
├── wifi_manager_init() Init WiFi STA mode + event handlers
├── http_proxy_init() Load proxy config (secrets > NVS)
├── telegram_bot_init() Load bot token (secrets > NVS)
├── llm_proxy_init() Load API key + model (secrets > NVS)
├── http_proxy_init() Load proxy config from build-time secrets
├── telegram_bot_init() Load bot token from build-time secrets
├── llm_proxy_init() Load API key + model from build-time secrets
├── tool_registry_init() Register tools, build tools JSON
├── agent_loop_init()
├── serial_cli_init() Start REPL (works without WiFi)
├── wifi_manager_start() Connect (secrets > NVS credentials)
├── wifi_manager_start() Connect using build-time credentials
│ └── wifi_manager_wait_connected(30s)
└── [if WiFi connected]
@@ -357,22 +357,17 @@ app_main()
└── outbound_dispatch task Launch outbound task (Core 0)
```
If WiFi credentials are missing or connection times out, the CLI remains available for configuration.
If WiFi credentials are missing or connection times out, the CLI remains available for diagnostics.
---
## Serial CLI Commands
The CLI provides debug and maintenance commands only. All configuration is done via `mimi_secrets.h`.
| Command | Description |
|--------------------------------|--------------------------------------|
| `wifi_set <SSID> <PASSWORD>` | Save WiFi credentials to NVS |
| `wifi_status` | Show connection status and IP |
| `set_tg_token <TOKEN>` | Save Telegram bot token |
| `set_api_key <KEY>` | Save Anthropic API key |
| `set_model <MODEL_ID>` | Set LLM model identifier |
| `set_search_key <KEY>` | Save Brave Search API key |
| `set_proxy <HOST> <PORT>` | Set HTTP CONNECT proxy |
| `clear_proxy` | Remove proxy, use direct connection |
| `memory_read` | Print MEMORY.md contents |
| `memory_write <CONTENT>` | Overwrite MEMORY.md |
| `session_list` | List all session files |
@@ -381,8 +376,6 @@ If WiFi credentials are missing or connection times out, the CLI remains availab
| `restart` | Reboot the device |
| `help` | List all available commands |
> **Note**: CLI-set values are stored in NVS but are overridden by `mimi_secrets.h` build-time values if set.
---
## Nanobot Reference Mapping
@@ -396,7 +389,7 @@ If WiFi credentials are missing or connection times out, the CLI remains availab
| `channels/telegram.py` | `telegram/telegram_bot.c` | Raw HTTP, no python-telegram-bot |
| `bus/events.py` + `queue.py`| `bus/message_bus.c` | FreeRTOS queues vs asyncio |
| `providers/litellm_provider.py` | `llm/llm_proxy.c` | Direct Anthropic API only |
| `config/schema.py` | `mimi_config.h` + `mimi_secrets.h` + NVS | Build-time secrets > NVS |
| `config/schema.py` | `mimi_config.h` + `mimi_secrets.h` | Build-time secrets only |
| `cli/commands.py` | `cli/serial_cli.c` | esp_console REPL |
| `agent/tools/*` | `tools/tool_registry.c` + `tool_web_search.c` | web_search via Brave API |
| `agent/subagent.py` | *(not yet implemented)* | See TODO.md |

View File

@@ -36,7 +36,7 @@
### [ ] Telegram User Allowlist (allow_from)
- **nanobot**: `channels/base.py` L59-82 — `is_allowed()` checks sender_id against allow_list
- **MimiClaw**: No authentication; anyone can message the bot and consume API credits
- **Recommendation**: Store allow_from list in NVS, filter in `process_updates()`
- **Recommendation**: Store allow_from list in `mimi_secrets.h` as a build-time define, filter in `process_updates()`
### [ ] Telegram Markdown to HTML Conversion
- **nanobot**: `channels/telegram.py` L16-76 — `_markdown_to_telegram_html()` full converter: code blocks, inline code, bold, italic, links, strikethrough, lists
@@ -105,7 +105,7 @@
- **Recommendation**: Requires extra HTTPS request to Whisper API: download Telegram voice -> forward -> get text
### [x] ~~Build-time Config File~~
- Implemented: `mimi_secrets.h`build-time credentials with highest priority over NVS/CLI
- Implemented: `mimi_secrets.h`sole configuration method (build-time only, no NVS/CLI)
- Replaces need for YAML config; suitable for MCU workflow
### [ ] WebSocket Gateway Protocol Enhancement
@@ -124,7 +124,7 @@
- **Recommendation**: Low priority, Telegram is sufficient
### [x] ~~Telegram Proxy Support (HTTP CONNECT)~~
- Implemented: HTTP CONNECT tunnel via `proxy/http_proxy.c`, configurable via NVS + CLI (`set_proxy`/`clear_proxy`)
- Implemented: HTTP CONNECT tunnel via `proxy/http_proxy.c`, configurable via `mimi_secrets.h` (`MIMI_SECRET_PROXY_HOST`/`MIMI_SECRET_PROXY_PORT`)
### [ ] Session Metadata Persistence
- **nanobot**: `session/manager.py` L136-153 — session file includes metadata line (created_at, updated_at)
@@ -144,13 +144,12 @@
- [x] Memory Store (MEMORY.md + daily notes)
- [x] Session Manager (JSONL per chat_id, ring buffer history)
- [x] WebSocket Gateway (port 18789, JSON protocol)
- [x] Serial CLI (esp_console, 15 commands)
- [x] Serial CLI (esp_console, debug/maintenance commands)
- [x] HTTP CONNECT Proxy (Telegram + Claude API + Brave Search via proxy tunnel)
- [x] OTA Update
- [x] WiFi Manager (NVS credentials, exponential backoff)
- [x] WiFi Manager (build-time credentials, exponential backoff)
- [x] SPIFFS storage
- [x] Build-time config (`mimi_secrets.h`, highest priority over NVS)
- [x] NVS configuration (token, API key, model, search key)
- [x] Build-time config (`mimi_secrets.h`, sole configuration method)
---

View File

@@ -1,12 +1,8 @@
#include "serial_cli.h"
#include "mimi_config.h"
#include "wifi/wifi_manager.h"
#include "telegram/telegram_bot.h"
#include "llm/llm_proxy.h"
#include "memory/memory_store.h"
#include "memory/session_mgr.h"
#include "proxy/http_proxy.h"
#include "tools/tool_web_search.h"
#include <string.h>
#include <stdio.h>
@@ -18,26 +14,6 @@
static const char *TAG = "cli";
/* --- wifi_set command --- */
static struct {
struct arg_str *ssid;
struct arg_str *password;
struct arg_end *end;
} wifi_set_args;
static int cmd_wifi_set(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&wifi_set_args);
if (nerrors != 0) {
arg_print_errors(stderr, wifi_set_args.end, argv[0]);
return 1;
}
wifi_manager_set_credentials(wifi_set_args.ssid->sval[0],
wifi_set_args.password->sval[0]);
printf("WiFi credentials saved. Restart to apply.\n");
return 0;
}
/* --- wifi_status command --- */
static int cmd_wifi_status(int argc, char **argv)
{
@@ -46,60 +22,6 @@ static int cmd_wifi_status(int argc, char **argv)
return 0;
}
/* --- set_tg_token command --- */
static struct {
struct arg_str *token;
struct arg_end *end;
} tg_token_args;
static int cmd_set_tg_token(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&tg_token_args);
if (nerrors != 0) {
arg_print_errors(stderr, tg_token_args.end, argv[0]);
return 1;
}
telegram_set_token(tg_token_args.token->sval[0]);
printf("Telegram bot token saved.\n");
return 0;
}
/* --- set_api_key command --- */
static struct {
struct arg_str *key;
struct arg_end *end;
} api_key_args;
static int cmd_set_api_key(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&api_key_args);
if (nerrors != 0) {
arg_print_errors(stderr, api_key_args.end, argv[0]);
return 1;
}
llm_set_api_key(api_key_args.key->sval[0]);
printf("API key saved.\n");
return 0;
}
/* --- set_model command --- */
static struct {
struct arg_str *model;
struct arg_end *end;
} model_args;
static int cmd_set_model(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&model_args);
if (nerrors != 0) {
arg_print_errors(stderr, model_args.end, argv[0]);
return 1;
}
llm_set_model(model_args.model->sval[0]);
printf("Model set.\n");
return 0;
}
/* --- memory_read command --- */
static int cmd_memory_read(int argc, char **argv)
{
@@ -176,51 +98,6 @@ static int cmd_heap_info(int argc, char **argv)
return 0;
}
/* --- set_proxy command --- */
static struct {
struct arg_str *host;
struct arg_int *port;
struct arg_end *end;
} proxy_args;
static int cmd_set_proxy(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&proxy_args);
if (nerrors != 0) {
arg_print_errors(stderr, proxy_args.end, argv[0]);
return 1;
}
http_proxy_set(proxy_args.host->sval[0], (uint16_t)proxy_args.port->ival[0]);
printf("Proxy set. Restart to apply.\n");
return 0;
}
/* --- clear_proxy command --- */
static int cmd_clear_proxy(int argc, char **argv)
{
http_proxy_clear();
printf("Proxy cleared. Restart to apply.\n");
return 0;
}
/* --- set_search_key command --- */
static struct {
struct arg_str *key;
struct arg_end *end;
} search_key_args;
static int cmd_set_search_key(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&search_key_args);
if (nerrors != 0) {
arg_print_errors(stderr, search_key_args.end, argv[0]);
return 1;
}
tool_web_search_set_key(search_key_args.key->sval[0]);
printf("Search API key saved.\n");
return 0;
}
/* --- restart command --- */
static int cmd_restart(int argc, char **argv)
{
@@ -244,18 +121,6 @@ esp_err_t serial_cli_init(void)
/* Register commands */
/* wifi_set */
wifi_set_args.ssid = arg_str1(NULL, NULL, "<ssid>", "WiFi SSID");
wifi_set_args.password = arg_str1(NULL, NULL, "<password>", "WiFi password");
wifi_set_args.end = arg_end(2);
esp_console_cmd_t wifi_set_cmd = {
.command = "wifi_set",
.help = "Set WiFi SSID and password",
.func = &cmd_wifi_set,
.argtable = &wifi_set_args,
};
esp_console_cmd_register(&wifi_set_cmd);
/* wifi_status */
esp_console_cmd_t wifi_status_cmd = {
.command = "wifi_status",
@@ -264,39 +129,6 @@ esp_err_t serial_cli_init(void)
};
esp_console_cmd_register(&wifi_status_cmd);
/* set_tg_token */
tg_token_args.token = arg_str1(NULL, NULL, "<token>", "Telegram bot token");
tg_token_args.end = arg_end(1);
esp_console_cmd_t tg_token_cmd = {
.command = "set_tg_token",
.help = "Set Telegram bot token",
.func = &cmd_set_tg_token,
.argtable = &tg_token_args,
};
esp_console_cmd_register(&tg_token_cmd);
/* set_api_key */
api_key_args.key = arg_str1(NULL, NULL, "<key>", "Anthropic API key");
api_key_args.end = arg_end(1);
esp_console_cmd_t api_key_cmd = {
.command = "set_api_key",
.help = "Set Claude API key",
.func = &cmd_set_api_key,
.argtable = &api_key_args,
};
esp_console_cmd_register(&api_key_cmd);
/* set_model */
model_args.model = arg_str1(NULL, NULL, "<model>", "Model identifier");
model_args.end = arg_end(1);
esp_console_cmd_t model_cmd = {
.command = "set_model",
.help = "Set LLM model (default: " MIMI_LLM_DEFAULT_MODEL ")",
.func = &cmd_set_model,
.argtable = &model_args,
};
esp_console_cmd_register(&model_cmd);
/* memory_read */
esp_console_cmd_t mem_read_cmd = {
.command = "memory_read",
@@ -343,37 +175,6 @@ esp_err_t serial_cli_init(void)
};
esp_console_cmd_register(&heap_cmd);
/* set_search_key */
search_key_args.key = arg_str1(NULL, NULL, "<key>", "Brave Search API key");
search_key_args.end = arg_end(1);
esp_console_cmd_t search_key_cmd = {
.command = "set_search_key",
.help = "Set Brave Search API key for web_search tool",
.func = &cmd_set_search_key,
.argtable = &search_key_args,
};
esp_console_cmd_register(&search_key_cmd);
/* set_proxy */
proxy_args.host = arg_str1(NULL, NULL, "<host>", "Proxy host/IP");
proxy_args.port = arg_int1(NULL, NULL, "<port>", "Proxy port");
proxy_args.end = arg_end(2);
esp_console_cmd_t proxy_cmd = {
.command = "set_proxy",
.help = "Set HTTP proxy (e.g. set_proxy 192.168.1.83 7897)",
.func = &cmd_set_proxy,
.argtable = &proxy_args,
};
esp_console_cmd_register(&proxy_cmd);
/* clear_proxy */
esp_console_cmd_t clear_proxy_cmd = {
.command = "clear_proxy",
.help = "Remove proxy configuration",
.func = &cmd_clear_proxy,
};
esp_console_cmd_register(&clear_proxy_cmd);
/* restart */
esp_console_cmd_t restart_cmd = {
.command = "restart",

View File

@@ -8,7 +8,6 @@
#include "esp_http_client.h"
#include "esp_crt_bundle.h"
#include "esp_heap_caps.h"
#include "nvs.h"
#include "cJSON.h"
static const char *TAG = "llm";
@@ -71,7 +70,6 @@ static esp_err_t http_event_handler(esp_http_client_event_t *evt)
esp_err_t llm_proxy_init(void)
{
/* Build-time secrets take highest priority */
if (MIMI_SECRET_API_KEY[0] != '\0') {
strncpy(s_api_key, MIMI_SECRET_API_KEY, sizeof(s_api_key) - 1);
}
@@ -79,27 +77,10 @@ esp_err_t llm_proxy_init(void)
strncpy(s_model, MIMI_SECRET_MODEL, sizeof(s_model) - 1);
}
/* Fall back to NVS for values not set at build time */
if (s_api_key[0] == '\0' || s_model[0] == '\0') {
nvs_handle_t nvs;
esp_err_t err = nvs_open(MIMI_NVS_LLM, NVS_READONLY, &nvs);
if (err == ESP_OK) {
if (s_api_key[0] == '\0') {
size_t len = sizeof(s_api_key);
nvs_get_str(nvs, MIMI_NVS_KEY_API_KEY, s_api_key, &len);
}
if (strcmp(s_model, MIMI_LLM_DEFAULT_MODEL) == 0) {
size_t len = sizeof(s_model);
nvs_get_str(nvs, MIMI_NVS_KEY_MODEL, s_model, &len);
}
nvs_close(nvs);
}
}
if (s_api_key[0]) {
ESP_LOGI(TAG, "LLM proxy initialized (model: %s)", s_model);
} else {
ESP_LOGW(TAG, "No API key. Use CLI: set_api_key <KEY>");
ESP_LOGW(TAG, "No API key. Set MIMI_SECRET_API_KEY in mimi_secrets.h");
}
return ESP_OK;
}
@@ -467,30 +448,3 @@ esp_err_t llm_chat_tools(const char *system_prompt,
return ESP_OK;
}
/* ── NVS helpers ──────────────────────────────────────────────── */
esp_err_t llm_set_api_key(const char *api_key)
{
nvs_handle_t nvs;
ESP_ERROR_CHECK(nvs_open(MIMI_NVS_LLM, NVS_READWRITE, &nvs));
ESP_ERROR_CHECK(nvs_set_str(nvs, MIMI_NVS_KEY_API_KEY, api_key));
ESP_ERROR_CHECK(nvs_commit(nvs));
nvs_close(nvs);
strncpy(s_api_key, api_key, sizeof(s_api_key) - 1);
ESP_LOGI(TAG, "API key saved");
return ESP_OK;
}
esp_err_t llm_set_model(const char *model)
{
nvs_handle_t nvs;
ESP_ERROR_CHECK(nvs_open(MIMI_NVS_LLM, NVS_READWRITE, &nvs));
ESP_ERROR_CHECK(nvs_set_str(nvs, MIMI_NVS_KEY_MODEL, model));
ESP_ERROR_CHECK(nvs_commit(nvs));
nvs_close(nvs);
strncpy(s_model, model, sizeof(s_model) - 1);
ESP_LOGI(TAG, "Model set to: %s", s_model);
return ESP_OK;
}

View File

@@ -8,7 +8,7 @@
#include "mimi_config.h"
/**
* Initialize the LLM proxy. Reads API key and model from NVS.
* Initialize the LLM proxy.
*/
esp_err_t llm_proxy_init(void);
@@ -24,16 +24,6 @@ esp_err_t llm_proxy_init(void);
esp_err_t llm_chat(const char *system_prompt, const char *messages_json,
char *response_buf, size_t buf_size);
/**
* Save the Anthropic API key to NVS.
*/
esp_err_t llm_set_api_key(const char *api_key);
/**
* Save the model identifier to NVS.
*/
esp_err_t llm_set_model(const char *model);
/* ── Tool Use Support ──────────────────────────────────────────── */
typedef struct {

View File

@@ -134,10 +134,10 @@ void app_main(void)
ESP_LOGI(TAG, "All services started!");
} else {
ESP_LOGW(TAG, "WiFi connection timeout. Configure via CLI: wifi_set <SSID> <PASS>");
ESP_LOGW(TAG, "WiFi connection timeout. Check MIMI_SECRET_WIFI_SSID in mimi_secrets.h");
}
} else {
ESP_LOGW(TAG, "No WiFi credentials. Configure via CLI: wifi_set <SSID> <PASS>");
ESP_LOGW(TAG, "No WiFi credentials. Set MIMI_SECRET_WIFI_SSID in mimi_secrets.h");
}
ESP_LOGI(TAG, "MimiClaw ready. Type 'help' for CLI commands.");

View File

@@ -2,7 +2,7 @@
/* MimiClaw Global Configuration */
/* Build-time secrets (highest priority, override NVS) */
/* Build-time secrets (sole configuration method) */
#if __has_include("mimi_secrets.h")
#include "mimi_secrets.h"
#endif
@@ -88,18 +88,3 @@
#define MIMI_CLI_PRIO 3
#define MIMI_CLI_CORE 0
/* NVS Namespaces */
#define MIMI_NVS_WIFI "wifi_config"
#define MIMI_NVS_TG "tg_config"
#define MIMI_NVS_LLM "llm_config"
#define MIMI_NVS_PROXY "proxy_config"
#define MIMI_NVS_SEARCH "search_config"
/* NVS Keys */
#define MIMI_NVS_KEY_SSID "ssid"
#define MIMI_NVS_KEY_PASS "password"
#define MIMI_NVS_KEY_TG_TOKEN "bot_token"
#define MIMI_NVS_KEY_API_KEY "api_key"
#define MIMI_NVS_KEY_MODEL "model"
#define MIMI_NVS_KEY_PROXY_HOST "host"
#define MIMI_NVS_KEY_PROXY_PORT "port"

View File

@@ -1,9 +1,9 @@
/*
* MimiClaw Build-time Secrets
*
* This is the ONLY way to configure MimiClaw.
* Copy this file to mimi_secrets.h and fill in your values.
* Non-empty values here take HIGHEST priority (override NVS/CLI).
* Leave empty ("") to use NVS values set via CLI.
* After any change, rebuild: idf.py fullclean && idf.py build
*
* cp mimi_secrets.h.example mimi_secrets.h
*/

View File

@@ -9,7 +9,6 @@
#include <unistd.h>
#include "esp_log.h"
#include "nvs.h"
#include "esp_tls.h"
#include "esp_crt_bundle.h"
@@ -21,26 +20,14 @@ __attribute__((constructor)) static void proxy_log_level(void)
esp_log_level_set(TAG, ESP_LOG_WARN);
}
/* ── Config (cached from NVS) ─────────────────────────────────── */
static char s_proxy_host[64] = {0};
static uint16_t s_proxy_port = 0;
esp_err_t http_proxy_init(void)
{
/* Build-time secrets take highest priority */
if (MIMI_SECRET_PROXY_HOST[0] != '\0' && MIMI_SECRET_PROXY_PORT[0] != '\0') {
strncpy(s_proxy_host, MIMI_SECRET_PROXY_HOST, sizeof(s_proxy_host) - 1);
s_proxy_port = (uint16_t)atoi(MIMI_SECRET_PROXY_PORT);
} else {
nvs_handle_t nvs;
esp_err_t err = nvs_open(MIMI_NVS_PROXY, NVS_READONLY, &nvs);
if (err == ESP_OK) {
size_t len = sizeof(s_proxy_host);
nvs_get_str(nvs, MIMI_NVS_KEY_PROXY_HOST, s_proxy_host, &len);
nvs_get_u16(nvs, MIMI_NVS_KEY_PROXY_PORT, &s_proxy_port);
nvs_close(nvs);
}
}
if (s_proxy_host[0] && s_proxy_port) {
@@ -56,36 +43,6 @@ bool http_proxy_is_enabled(void)
return s_proxy_host[0] != '\0' && s_proxy_port != 0;
}
esp_err_t http_proxy_set(const char *host, uint16_t port)
{
nvs_handle_t nvs;
ESP_ERROR_CHECK(nvs_open(MIMI_NVS_PROXY, NVS_READWRITE, &nvs));
ESP_ERROR_CHECK(nvs_set_str(nvs, MIMI_NVS_KEY_PROXY_HOST, host));
ESP_ERROR_CHECK(nvs_set_u16(nvs, MIMI_NVS_KEY_PROXY_PORT, port));
ESP_ERROR_CHECK(nvs_commit(nvs));
nvs_close(nvs);
strncpy(s_proxy_host, host, sizeof(s_proxy_host) - 1);
s_proxy_port = port;
ESP_LOGI(TAG, "Proxy set to %s:%d", s_proxy_host, s_proxy_port);
return ESP_OK;
}
esp_err_t http_proxy_clear(void)
{
nvs_handle_t nvs;
ESP_ERROR_CHECK(nvs_open(MIMI_NVS_PROXY, NVS_READWRITE, &nvs));
nvs_erase_key(nvs, MIMI_NVS_KEY_PROXY_HOST);
nvs_erase_key(nvs, MIMI_NVS_KEY_PROXY_PORT);
nvs_commit(nvs);
nvs_close(nvs);
s_proxy_host[0] = '\0';
s_proxy_port = 0;
ESP_LOGI(TAG, "Proxy cleared");
return ESP_OK;
}
/* ── Proxied TLS connection ───────────────────────────────────── */
struct proxy_conn {

View File

@@ -5,7 +5,7 @@
#include <stdbool.h>
/**
* Initialize proxy module — loads config from NVS.
* Initialize proxy module.
*/
esp_err_t http_proxy_init(void);
@@ -14,16 +14,6 @@ esp_err_t http_proxy_init(void);
*/
bool http_proxy_is_enabled(void);
/**
* Save proxy host and port to NVS.
*/
esp_err_t http_proxy_set(const char *host, uint16_t port);
/**
* Remove proxy config from NVS.
*/
esp_err_t http_proxy_clear(void);
/* ── Proxied HTTPS connection ─────────────────────────────────── */
typedef struct proxy_conn proxy_conn_t;

View File

@@ -8,7 +8,6 @@
#include "esp_log.h"
#include "esp_http_client.h"
#include "esp_crt_bundle.h"
#include "nvs.h"
#include "cJSON.h"
static const char *TAG = "telegram";
@@ -257,21 +256,10 @@ static void telegram_poll_task(void *arg)
esp_err_t telegram_bot_init(void)
{
/* Build-time secret takes highest priority */
if (s_bot_token[0] == '\0') {
nvs_handle_t nvs;
esp_err_t err = nvs_open(MIMI_NVS_TG, NVS_READONLY, &nvs);
if (err == ESP_OK) {
size_t len = sizeof(s_bot_token);
nvs_get_str(nvs, MIMI_NVS_KEY_TG_TOKEN, s_bot_token, &len);
nvs_close(nvs);
}
}
if (s_bot_token[0]) {
ESP_LOGI(TAG, "Telegram bot token loaded (len=%d)", (int)strlen(s_bot_token));
} else {
ESP_LOGW(TAG, "No Telegram bot token. Use CLI: set_tg_token <TOKEN>");
ESP_LOGW(TAG, "No Telegram bot token. Set MIMI_SECRET_TG_TOKEN in mimi_secrets.h");
}
return ESP_OK;
}
@@ -369,15 +357,3 @@ esp_err_t telegram_send_message(const char *chat_id, const char *text)
return ESP_OK;
}
esp_err_t telegram_set_token(const char *token)
{
nvs_handle_t nvs;
ESP_ERROR_CHECK(nvs_open(MIMI_NVS_TG, NVS_READWRITE, &nvs));
ESP_ERROR_CHECK(nvs_set_str(nvs, MIMI_NVS_KEY_TG_TOKEN, token));
ESP_ERROR_CHECK(nvs_commit(nvs));
nvs_close(nvs);
strncpy(s_bot_token, token, sizeof(s_bot_token) - 1);
ESP_LOGI(TAG, "Telegram bot token saved");
return ESP_OK;
}

View File

@@ -4,7 +4,6 @@
/**
* Initialize the Telegram bot.
* Reads bot token from NVS.
*/
esp_err_t telegram_bot_init(void);
@@ -21,7 +20,3 @@ esp_err_t telegram_bot_start(void);
*/
esp_err_t telegram_send_message(const char *chat_id, const char *text);
/**
* Save the Telegram bot token to NVS.
*/
esp_err_t telegram_set_token(const char *token);

View File

@@ -8,7 +8,6 @@
#include "esp_http_client.h"
#include "esp_crt_bundle.h"
#include "esp_heap_caps.h"
#include "nvs.h"
#include "cJSON.h"
static const char *TAG = "web_search";
@@ -44,40 +43,18 @@ static esp_err_t http_event_handler(esp_http_client_event_t *evt)
esp_err_t tool_web_search_init(void)
{
/* Build-time secret takes highest priority */
if (MIMI_SECRET_SEARCH_KEY[0] != '\0') {
strncpy(s_search_key, MIMI_SECRET_SEARCH_KEY, sizeof(s_search_key) - 1);
} else {
nvs_handle_t nvs;
esp_err_t err = nvs_open(MIMI_NVS_SEARCH, NVS_READONLY, &nvs);
if (err == ESP_OK) {
size_t len = sizeof(s_search_key);
nvs_get_str(nvs, MIMI_NVS_KEY_API_KEY, s_search_key, &len);
nvs_close(nvs);
}
}
if (s_search_key[0]) {
ESP_LOGI(TAG, "Web search initialized (key configured)");
} else {
ESP_LOGW(TAG, "No search API key. Use CLI: set_search_key <KEY>");
ESP_LOGW(TAG, "No search API key. Set MIMI_SECRET_SEARCH_KEY in mimi_secrets.h");
}
return ESP_OK;
}
esp_err_t tool_web_search_set_key(const char *api_key)
{
nvs_handle_t nvs;
ESP_ERROR_CHECK(nvs_open(MIMI_NVS_SEARCH, NVS_READWRITE, &nvs));
ESP_ERROR_CHECK(nvs_set_str(nvs, MIMI_NVS_KEY_API_KEY, api_key));
ESP_ERROR_CHECK(nvs_commit(nvs));
nvs_close(nvs);
strncpy(s_search_key, api_key, sizeof(s_search_key) - 1);
ESP_LOGI(TAG, "Search API key saved");
return ESP_OK;
}
/* ── URL-encode a query string ────────────────────────────────── */
static size_t url_encode(const char *src, char *dst, size_t dst_size)
@@ -237,7 +214,7 @@ static esp_err_t search_via_proxy(const char *path, search_buf_t *sb)
esp_err_t tool_web_search_execute(const char *input_json, char *output, size_t output_size)
{
if (s_search_key[0] == '\0') {
snprintf(output, output_size, "Error: No search API key configured. Use set_search_key command.");
snprintf(output, output_size, "Error: No search API key configured. Set MIMI_SECRET_SEARCH_KEY in mimi_secrets.h");
return ESP_ERR_INVALID_STATE;
}

View File

@@ -4,7 +4,7 @@
#include <stddef.h>
/**
* Initialize web search tool — loads API key from NVS.
* Initialize web search tool.
*/
esp_err_t tool_web_search_init(void);
@@ -18,7 +18,3 @@ esp_err_t tool_web_search_init(void);
*/
esp_err_t tool_web_search_execute(const char *input_json, char *output, size_t output_size);
/**
* Save Brave Search API key to NVS.
*/
esp_err_t tool_web_search_set_key(const char *api_key);

View File

@@ -6,8 +6,6 @@
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_netif.h"
#include "nvs_flash.h"
#include "nvs.h"
static const char *TAG = "wifi";
@@ -72,34 +70,15 @@ esp_err_t wifi_manager_init(void)
esp_err_t wifi_manager_start(void)
{
wifi_config_t wifi_cfg = {0};
/* Build-time secrets take highest priority */
if (MIMI_SECRET_WIFI_SSID[0] != '\0') {
strncpy((char *)wifi_cfg.sta.ssid, MIMI_SECRET_WIFI_SSID, sizeof(wifi_cfg.sta.ssid) - 1);
strncpy((char *)wifi_cfg.sta.password, MIMI_SECRET_WIFI_PASS, sizeof(wifi_cfg.sta.password) - 1);
} else {
/* Fall back to NVS */
nvs_handle_t nvs;
esp_err_t err = nvs_open(MIMI_NVS_WIFI, NVS_READONLY, &nvs);
if (err != ESP_OK) {
ESP_LOGW(TAG, "No WiFi credentials. Use CLI: wifi_set <SSID> <PASS>");
return ESP_ERR_NOT_FOUND;
}
size_t len = sizeof(wifi_cfg.sta.ssid);
err = nvs_get_str(nvs, MIMI_NVS_KEY_SSID, (char *)wifi_cfg.sta.ssid, &len);
if (err != ESP_OK) {
nvs_close(nvs);
ESP_LOGW(TAG, "SSID not found in NVS");
return ESP_ERR_NOT_FOUND;
}
len = sizeof(wifi_cfg.sta.password);
nvs_get_str(nvs, MIMI_NVS_KEY_PASS, (char *)wifi_cfg.sta.password, &len);
nvs_close(nvs);
if (MIMI_SECRET_WIFI_SSID[0] == '\0') {
ESP_LOGW(TAG, "No WiFi credentials. Set MIMI_SECRET_WIFI_SSID in mimi_secrets.h");
return ESP_ERR_NOT_FOUND;
}
wifi_config_t wifi_cfg = {0};
strncpy((char *)wifi_cfg.sta.ssid, MIMI_SECRET_WIFI_SSID, sizeof(wifi_cfg.sta.ssid) - 1);
strncpy((char *)wifi_cfg.sta.password, MIMI_SECRET_WIFI_PASS, sizeof(wifi_cfg.sta.password) - 1);
ESP_LOGI(TAG, "Connecting to SSID: %s", wifi_cfg.sta.ssid);
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg));
@@ -126,18 +105,6 @@ bool wifi_manager_is_connected(void)
return s_connected;
}
esp_err_t wifi_manager_set_credentials(const char *ssid, const char *password)
{
nvs_handle_t nvs;
ESP_ERROR_CHECK(nvs_open(MIMI_NVS_WIFI, NVS_READWRITE, &nvs));
ESP_ERROR_CHECK(nvs_set_str(nvs, MIMI_NVS_KEY_SSID, ssid));
ESP_ERROR_CHECK(nvs_set_str(nvs, MIMI_NVS_KEY_PASS, password));
ESP_ERROR_CHECK(nvs_commit(nvs));
nvs_close(nvs);
ESP_LOGI(TAG, "WiFi credentials saved for SSID: %s", ssid);
return ESP_OK;
}
const char *wifi_manager_get_ip(void)
{
return s_ip_str;

View File

@@ -10,7 +10,6 @@
/**
* Initialize WiFi subsystem (STA mode).
* Reads SSID/password from NVS. If not set, waits for serial configuration.
*/
esp_err_t wifi_manager_init(void);
@@ -31,11 +30,6 @@ esp_err_t wifi_manager_wait_connected(uint32_t timeout_ms);
*/
bool wifi_manager_is_connected(void);
/**
* Save WiFi credentials to NVS.
*/
esp_err_t wifi_manager_set_credentials(const char *ssid, const char *password);
/**
* Get the current IP address string (or "0.0.0.0" if not connected).
*/