fix(skills): replace sqlite3 CLI with in-tree better-sqlite3 wrapper

Setup deliberately avoids the sqlite3 CLI (`setup/verify.ts:5` calls
this out: "Uses better-sqlite3 directly (no sqlite3 CLI)") and never
installs or probes for the binary. Despite that, 13 skills shelled out
to `sqlite3 ...` directly, breaking on hosts where the CLI isn't
preinstalled — the same root cause as #2191 but spread across the
skill surface.

Add `scripts/q.ts`, a ~30-LOC wrapper over the `better-sqlite3` dep
that setup already installs and verifies. Default output matches
`sqlite3 -list` (pipe-separated, no header) so existing skill text
reads identically — only the binary changes. SELECT/WITH queries go
through `db.prepare().all()`; everything else (INSERT/UPDATE/DELETE,
including compound statements) goes through `db.exec()`.

Migrate every in-tree caller:

- 17 hardcoded invocations across 8 SKILL.md files (init-first-agent,
  add-deltachat, add-signal, add-emacs, add-whatsapp, add-ollama-provider,
  debug, add-parallel) plus add-deltachat/VERIFY.md.
- `manage-channels/SKILL.md` shows canonical SQL but never prescribed
  a tool, so the assistant defaulted to `sqlite3` and silently failed.
  Add a one-line wrapper hint above the SQL block.
- `migrate-v2.sh` schema/count probes (was the original #2191 case).
  Replace `.tables` with `SELECT name FROM sqlite_master`.
- Document the wrapper convention in root `CLAUDE.md` under "Central DB".

Add `scripts/q.test.ts` with 6 vitest cases covering both modes,
NULL rendering, empty-result, compound mutations, and arg validation.
Wire `scripts/**/*.test.ts` into `vitest.config.ts`.

Out of scope (flagged for follow-up):
- `debug` and `add-parallel` still reference the v1-only path
  `store/messages.db`. Routing through the wrapper now produces a
  cleaner "no such file" error, but the surrounding sections are
  v1-era throughout — a v1-content cleanup is its own PR.
- `cleanup-sessions.sh` is being addressed in #1889 (different style,
  hard-fail rather than wrap); left untouched here to avoid stepping
  on that author's work.

Closes #2191.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
NanoClaw bot user
2026-05-06 19:38:33 +02:00
parent f2d2ce9aed
commit 0d7458c6f3
15 changed files with 178 additions and 25 deletions

View File

@@ -140,7 +140,7 @@ After accepting, DeltaChat exchanges keys and creates the chat automatically.
Once the first message arrives the router auto-creates a `messaging_groups` row. Look up the chat ID:
```bash
sqlite3 data/v2.db \
pnpm exec tsx scripts/q.ts data/v2.db \
"SELECT platform_id, name FROM messaging_groups WHERE channel_type='deltachat' AND is_group=0 ORDER BY created_at DESC LIMIT 5"
```
@@ -226,7 +226,7 @@ Set `DC_SMTP_SECURITY=1` and `DC_SMTP_PORT=465` in `.env`, then restart.
1. Check the service is running and the adapter started: `grep "Channel adapter started.*deltachat" logs/nanoclaw.log`
2. Check connectivity: `grep "DeltaChat: IO started" logs/nanoclaw.log`
3. Check the sender has been granted access — run `/init-first-agent` to create their user record and wire the chat
4. Verify the messaging group is wired: `sqlite3 data/v2.db "SELECT mg.platform_id, mga.agent_group_id FROM messaging_groups mg JOIN messaging_group_agents mga ON mg.id = mga.messaging_group_id WHERE mg.channel_type='deltachat'"`
4. Verify the messaging group is wired: `pnpm exec tsx scripts/q.ts data/v2.db "SELECT mg.platform_id, mga.agent_group_id FROM messaging_groups mg JOIN messaging_group_agents mga ON mg.id = mga.messaging_group_id WHERE mg.channel_type='deltachat'"`
### Stale lock file after crash
@@ -248,7 +248,7 @@ grep "DeltaChat" logs/nanoclaw.error.log | tail -20
The messaging group exists but may not be wired to an agent group. Run:
```bash
sqlite3 data/v2.db "SELECT id, platform_id, name FROM messaging_groups WHERE channel_type='deltachat'"
pnpm exec tsx scripts/q.ts data/v2.db "SELECT id, platform_id, name FROM messaging_groups WHERE channel_type='deltachat'"
```
If the group has no entry in `messaging_group_agents`, wire it with `/manage-channels`.

View File

@@ -37,7 +37,7 @@ grep "DeltaChat" logs/nanoclaw.error.log | tail -10
## 4. Check messaging group was created
```bash
sqlite3 data/v2.db \
pnpm exec tsx scripts/q.ts data/v2.db \
"SELECT id, platform_id, name FROM messaging_groups WHERE channel_type='deltachat' ORDER BY created_at DESC LIMIT 5"
```
@@ -48,7 +48,7 @@ If a row appears, the inbound routing is working. If not, the adapter isn't rece
If the message arrived but the agent didn't respond, the sender may not have access:
```bash
sqlite3 data/v2.db "SELECT id, display_name FROM users WHERE id LIKE 'deltachat:%'"
pnpm exec tsx scripts/q.ts data/v2.db "SELECT id, display_name FROM users WHERE id LIKE 'deltachat:%'"
```
Grant access as shown in the SKILL.md "Grant user access" section.

View File

@@ -241,7 +241,7 @@ grep -q "import './emacs.js'" src/channels/index.ts && echo "imported" || echo "
### No response from agent
1. NanoClaw running: `launchctl list | grep nanoclaw` (macOS) / `systemctl --user status nanoclaw` (Linux)
2. Messaging group wired: `sqlite3 data/v2.db "SELECT mg.platform_id, ag.folder FROM messaging_groups mg JOIN messaging_group_agents mga ON mg.id = mga.messaging_group_id JOIN agent_groups ag ON ag.id = mga.agent_group_id WHERE mg.channel_type = 'emacs'"`
2. Messaging group wired: `pnpm exec tsx scripts/q.ts data/v2.db "SELECT mg.platform_id, ag.folder FROM messaging_groups mg JOIN messaging_group_agents mga ON mg.id = mga.messaging_group_id JOIN agent_groups ag ON ag.id = mga.agent_group_id WHERE mg.channel_type = 'emacs'"`
3. Logs show inbound: `grep 'channel_type=emacs\|Emacs' logs/nanoclaw.log | tail -20`
If no messaging group row exists, run the `register` command above.
@@ -292,5 +292,5 @@ launchctl kickstart -k gui/$(id -u)/com.nanoclaw # macOS
# Remove the NanoClaw block from your Emacs config
# Optionally clean up the messaging group:
sqlite3 data/v2.db "DELETE FROM messaging_group_agents WHERE messaging_group_id IN (SELECT id FROM messaging_groups WHERE channel_type='emacs'); DELETE FROM messaging_groups WHERE channel_type='emacs';"
pnpm exec tsx scripts/q.ts data/v2.db "DELETE FROM messaging_group_agents WHERE messaging_group_id IN (SELECT id FROM messaging_groups WHERE channel_type='emacs'); DELETE FROM messaging_groups WHERE channel_type='emacs';"
```

View File

@@ -76,7 +76,7 @@ Then rebuild the container image: `./container/build.sh`
Ask the user (plain text, not AskUserQuestion):
1. **Which agent group?** List available groups: `sqlite3 data/v2.db "SELECT folder, name FROM agent_groups;"`
1. **Which agent group?** List available groups: `pnpm exec tsx scripts/q.ts data/v2.db "SELECT folder, name FROM agent_groups;"`
2. **Which Ollama model?** List available: `curl -s http://localhost:11434/api/tags | grep '"name"'`
3. **Block Anthropic API?** Recommended yes — prevents accidental spend if config drifts.
@@ -111,7 +111,7 @@ Read the agent group's shared Claude settings:
```bash
# Find the agent group ID
AG_ID=$(sqlite3 data/v2.db "SELECT id FROM agent_groups WHERE folder='<FOLDER>';")
AG_ID=$(pnpm exec tsx scripts/q.ts data/v2.db "SELECT id FROM agent_groups WHERE folder='<FOLDER>';")
SETTINGS=data/v2-sessions/$AG_ID/.claude-shared/settings.json
```

View File

@@ -275,7 +275,7 @@ Look for: `Parallel AI MCP servers configured`
- Check agent-runner logs for "Parallel AI MCP servers configured" message
**Task polling not working:**
- Verify scheduled task was created: `sqlite3 store/messages.db "SELECT * FROM scheduled_tasks"`
- Verify scheduled task was created: `pnpm exec tsx scripts/q.ts store/messages.db "SELECT * FROM scheduled_tasks"`
- Check task runs: `tail -f logs/nanoclaw.log | grep "scheduled task"`
- Ensure task prompt includes proper Parallel MCP tool names

View File

@@ -200,7 +200,7 @@ systemctl --user restart nanoclaw
After the service starts, send any message to the Signal number from your personal Signal app. The router auto-creates a `messaging_groups` row. Then:
```bash
sqlite3 data/v2.db \
pnpm exec tsx scripts/q.ts data/v2.db \
"SELECT id, platform_id FROM messaging_groups WHERE channel_type='signal' ORDER BY created_at DESC LIMIT 5"
```
@@ -212,7 +212,7 @@ Add the Signal number to a group from your phone, send any message, then wire th
```bash
NOW=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
sqlite3 data/v2.db "
pnpm exec tsx scripts/q.ts data/v2.db "
INSERT OR IGNORE INTO messaging_group_agents
(id, messaging_group_id, agent_group_id, session_mode, priority, created_at)
VALUES
@@ -226,7 +226,7 @@ New Signal users (including the owner's Signal identity) are silently dropped wi
```bash
NOW=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
sqlite3 data/v2.db "
pnpm exec tsx scripts/q.ts data/v2.db "
INSERT OR REPLACE INTO user_roles (user_id, role, agent_group_id, granted_by, granted_at)
VALUES ('signal:UUID', 'owner', NULL, 'system', '$NOW');
INSERT OR IGNORE INTO agent_group_members (user_id, agent_group_id, added_by, added_at)
@@ -282,7 +282,7 @@ If you see `Signal daemon not reachable at 127.0.0.1:7583` and `SIGNAL_MANAGE_DA
### Bot not responding
1. Channel initialized: `grep "Signal channel connected" logs/nanoclaw.log | tail -1`
2. Channel wired: `sqlite3 data/v2.db "SELECT mg.platform_id, mg.name FROM messaging_groups mg JOIN messaging_group_agents mga ON mg.id = mga.messaging_group_id WHERE mg.channel_type='signal'"`
2. Channel wired: `pnpm exec tsx scripts/q.ts data/v2.db "SELECT mg.platform_id, mg.name FROM messaging_groups mg JOIN messaging_group_agents mga ON mg.id = mga.messaging_group_id WHERE mg.channel_type='signal'"`
3. Service running: `launchctl print gui/$(id -u)/com.nanoclaw` (macOS) / `systemctl --user status nanoclaw` (Linux)
### Lost connection mid-session

View File

@@ -200,7 +200,7 @@ Otherwise, run `/manage-channels` to wire this channel to an agent group.
- **type**: `whatsapp`
- **terminology**: WhatsApp calls them "groups" and "chats." A "chat" is a 1:1 DM; a "group" has multiple members.
- **how-to-find-id**: DMs use `<phone>@s.whatsapp.net` (e.g. `14155551234@s.whatsapp.net`). Groups use `<id>@g.us`. To find your number: `node -e "const c=JSON.parse(require('fs').readFileSync('store/auth/creds.json','utf-8'));console.log(c.me?.id?.split(':')[0]+'@s.whatsapp.net')"`. Groups are auto-discovered — check `sqlite3 data/v2.db "SELECT platform_id, name FROM messaging_groups WHERE channel_type='whatsapp' AND is_group=1"`.
- **how-to-find-id**: DMs use `<phone>@s.whatsapp.net` (e.g. `14155551234@s.whatsapp.net`). Groups use `<id>@g.us`. To find your number: `node -e "const c=JSON.parse(require('fs').readFileSync('store/auth/creds.json','utf-8'));console.log(c.me?.id?.split(':')[0]+'@s.whatsapp.net')"`. Groups are auto-discovered — check `pnpm exec tsx scripts/q.ts data/v2.db "SELECT platform_id, name FROM messaging_groups WHERE channel_type='whatsapp' AND is_group=1"`.
- **supports-threads**: no
- **typical-use**: Interactive chat — direct messages or small groups
- **default-isolation**: Same agent group if you're the only participant across multiple chats. Separate agent group if different people are in different groups.
@@ -256,7 +256,7 @@ systemctl --user start nanoclaw
1. Auth exists: `test -f store/auth/creds.json`
2. Connected: `grep "Connected to WhatsApp" logs/nanoclaw.log | tail -1`
3. Channel wired: `sqlite3 data/v2.db "SELECT mg.platform_id, mg.name FROM messaging_groups mg JOIN messaging_group_agents mga ON mg.id=mga.messaging_group_id WHERE mg.channel_type='whatsapp'"`
3. Channel wired: `pnpm exec tsx scripts/q.ts data/v2.db "SELECT mg.platform_id, mg.name FROM messaging_groups mg JOIN messaging_group_agents mga ON mg.id=mga.messaging_group_id WHERE mg.channel_type='whatsapp'"`
4. Service running: `systemctl --user status nanoclaw`
### "conflict" disconnection

View File

@@ -279,7 +279,7 @@ rm -rf data/sessions/
rm -rf data/sessions/{groupFolder}/.claude/
# Also clear the session ID from NanoClaw's tracking (stored in SQLite)
sqlite3 store/messages.db "DELETE FROM sessions WHERE group_folder = '{groupFolder}'"
pnpm exec tsx scripts/q.ts store/messages.db "DELETE FROM sessions WHERE group_folder = '{groupFolder}'"
```
To verify session resumption is working, check the logs for the same session ID across messages:

View File

@@ -54,7 +54,7 @@ Tell the user:
Wait for the user's confirmation. Then look up the most recent DM messaging groups:
```bash
sqlite3 data/v2.db "SELECT id, platform_id, name, created_at FROM messaging_groups WHERE channel_type='${CHANNEL}' AND is_group=0 ORDER BY created_at DESC LIMIT 5"
pnpm exec tsx scripts/q.ts data/v2.db "SELECT id, platform_id, name, created_at FROM messaging_groups WHERE channel_type='${CHANNEL}' AND is_group=0 ORDER BY created_at DESC LIMIT 5"
```
Show the top rows to the user and confirm which `platform_id` is theirs (usually the most recent). Record as `PLATFORM_ID`. If none appeared, check `logs/nanoclaw.log` for `unknown_sender` drops — the adapter might be rejecting inbound due to connection or permission issues.
@@ -103,7 +103,7 @@ Wait for the user's reply. If they confirm receipt, the skill is done.
If they say it didn't arrive, then diagnose using the DB directly (no waiting loops required — the message either delivered or it didn't):
- `sqlite3 data/v2-sessions/<agent-group-id>/sessions/<session-id>/outbound.db "SELECT id, status, created_at FROM messages_out ORDER BY created_at DESC LIMIT 5"` — check for stuck `pending` rows. Replace `<agent-group-id>` and `<session-id>` with the values from the script's output.
- `pnpm exec tsx scripts/q.ts data/v2-sessions/<agent-group-id>/sessions/<session-id>/outbound.db "SELECT id, status, created_at FROM messages_out ORDER BY created_at DESC LIMIT 5"` — check for stuck `pending` rows. Replace `<agent-group-id>` and `<session-id>` with the values from the script's output.
- `grep -E 'Unauthorized channel destination|container.*exited|error' logs/nanoclaw.log | tail -20` — look for ACL rejections or container crashes.
- `ls data/v2-sessions/<agent-group-id>/sessions/*/outbound.db` — confirm the session exists.

View File

@@ -11,7 +11,13 @@ Privilege is a **user-level** concept, not a channel-level one (see `src/db/user
## Assess Current State
Read the central DB (`data/v2.db`) using these canonical queries (column names match the schema, not the CLI flags — the `register` command's `--assistant-name` is stored in `agent_groups.name`):
Read the central DB (`data/v2.db`) using these canonical queries (column names match the schema, not the CLI flags — the `register` command's `--assistant-name` is stored in `agent_groups.name`).
Run each via the in-tree wrapper — the host setup deliberately ships no `sqlite3` CLI:
```bash
pnpm exec tsx scripts/q.ts data/v2.db "<query>"
```
```sql
SELECT id, name AS assistant_name, folder, agent_provider FROM agent_groups;