From deb5389077534db268bf2ee900502c61fd57c66b Mon Sep 17 00:00:00 2001 From: Ken Bolton Date: Wed, 25 Mar 2026 16:52:29 -0400 Subject: [PATCH 01/21] fix(skill/channel-formatting): correct Telegram link behaviour in SKILL.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Telegram Markdown v1 renders [text](url) links natively — they are now preserved rather than flattened to "text (url)". Update the skill table to reflect the actual post-fix behaviour. Co-Authored-By: Claude Sonnet 4.6 --- .claude/skills/channel-formatting/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.claude/skills/channel-formatting/SKILL.md b/.claude/skills/channel-formatting/SKILL.md index b995fb8..3e2334c 100644 --- a/.claude/skills/channel-formatting/SKILL.md +++ b/.claude/skills/channel-formatting/SKILL.md @@ -11,8 +11,8 @@ Telegram. | Channel | Transformation | |---------|---------------| -| WhatsApp | `**bold**` → `*bold*`, `*italic*` → `_italic_`, headings → bold, links flattened | -| Telegram | same as WhatsApp | +| WhatsApp | `**bold**` → `*bold*`, `*italic*` → `_italic_`, headings → bold, links → `text (url)` | +| Telegram | same as WhatsApp, but `[text](url)` links are preserved (Markdown v1 renders them natively) | | Slack | same as WhatsApp, but links become `` | | Discord | passthrough (Discord already renders Markdown) | | Signal | passthrough for `parseTextStyles`; `parseSignalStyles` in `src/text-styles.ts` produces plain text + native `textStyle` ranges for use by the Signal skill | From d25b79a5a97a750a09d610e91e355ca2c49abbb7 Mon Sep 17 00:00:00 2001 From: NanoClaw Date: Thu, 26 Mar 2026 13:17:07 +0000 Subject: [PATCH 02/21] docs: add auth credentials guidance to main group CLAUDE.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clarify that only long-lived OAuth tokens (claude setup-token) or API keys should be used — short-lived tokens from the keychain expire within hours and cause recurring 401s. Also update native credential proxy skill to swap the OneCLI reference when applied. Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/skills/use-native-credential-proxy/SKILL.md | 10 ++++++++++ groups/main/CLAUDE.md | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/.claude/skills/use-native-credential-proxy/SKILL.md b/.claude/skills/use-native-credential-proxy/SKILL.md index 4cdda4c..71448b1 100644 --- a/.claude/skills/use-native-credential-proxy/SKILL.md +++ b/.claude/skills/use-native-credential-proxy/SKILL.md @@ -64,6 +64,16 @@ This merges in: If the merge reports conflicts beyond `package-lock.json`, resolve them by reading the conflicted files and understanding the intent of both sides. +### Update main group CLAUDE.md + +Replace the OneCLI auth reference with the native proxy: + +In `groups/main/CLAUDE.md`, replace: +> OneCLI manages credentials (including Anthropic auth) — run `onecli --help`. + +with: +> The native credential proxy manages credentials (including Anthropic auth) via `.env` — see `src/credential-proxy.ts`. + ### Validate code changes ```bash diff --git a/groups/main/CLAUDE.md b/groups/main/CLAUDE.md index 6080427..17b39cb 100644 --- a/groups/main/CLAUDE.md +++ b/groups/main/CLAUDE.md @@ -77,6 +77,10 @@ Standard Markdown: `**bold**`, `*italic*`, `[links](url)`, `# headings`. This is the **main channel**, which has elevated privileges. +## Authentication + +Anthropic credentials must be either an API key from console.anthropic.com (`ANTHROPIC_API_KEY`) or a long-lived OAuth token from `claude setup-token` (`CLAUDE_CODE_OAUTH_TOKEN`). Short-lived tokens from the system keychain or `~/.claude/.credentials.json` expire within hours and can cause recurring container 401s. The `/setup` skill walks through this. OneCLI manages credentials (including Anthropic auth) — run `onecli --help`. + ## Container Mounts Main has read-only access to the project and read-write access to its group folder: From 813e1c6fa4d9c170b1e8f748347cb3bb6e3c97e4 Mon Sep 17 00:00:00 2001 From: NanoClaw User Date: Wed, 25 Mar 2026 22:05:29 +0000 Subject: [PATCH 03/21] fix: improve task scripts agent instructions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reword Task Scripts opening in main template to guide agents toward schedule_task instead of inline bash loops. Add missing Task Scripts section to global template — non-main groups have unrestricted access to schedule_task with script parameter, so omitting instructions just leads to worse patterns. Co-Authored-By: Claude Opus 4.6 (1M context) --- groups/global/CLAUDE.md | 39 +++++++++++++++++++++++++++++++++++++++ groups/main/CLAUDE.md | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/groups/global/CLAUDE.md b/groups/global/CLAUDE.md index c814e39..7018c04 100644 --- a/groups/global/CLAUDE.md +++ b/groups/global/CLAUDE.md @@ -74,3 +74,42 @@ No `##` headings. No `[links](url)`. No `**double stars**`. ### Discord channels (folder starts with `discord_`) Standard Markdown works: `**bold**`, `*italic*`, `[links](url)`, `# headings`. + +--- + +## Task Scripts + +To check or monitor something on a recurring basis, use `schedule_task` — not a bash loop. This way the check survives container restarts and doesn't block other messages. If the user only needs to know when a condition changes, add a `script` to avoid unnecessary wake-ups — the script runs first, and you only wake up when there's something to act on. + +### How it works + +1. You provide a bash `script` alongside the `prompt` when scheduling +2. When the task fires, the script runs first (30-second timeout) +3. Script prints JSON to stdout: `{ "wakeAgent": true/false, "data": {...} }` +4. If `wakeAgent: false` — nothing happens, task waits for next run +5. If `wakeAgent: true` — you wake up and receive the script's data + prompt + +### Always test your script first + +Before scheduling, run the script in your sandbox to verify it works: + +```bash +bash -c 'node --input-type=module -e " + const r = await fetch(\"https://api.github.com/repos/owner/repo/pulls?state=open\"); + const prs = await r.json(); + console.log(JSON.stringify({ wakeAgent: prs.length > 0, data: prs.slice(0, 5) })); +"' +``` + +### When NOT to use scripts + +If a task requires your judgment every time (daily briefings, reminders, reports), skip the script — just use a regular prompt. + +### Frequent task guidance + +If a user wants tasks running more than ~2x daily and a script can't reduce agent wake-ups: + +- Explain that each wake-up uses API credits and risks rate limits +- Suggest restructuring with a script that checks the condition first +- If the user needs an LLM to evaluate data, suggest using an API key with direct Anthropic API calls inside the script +- Help the user find the minimum viable frequency diff --git a/groups/main/CLAUDE.md b/groups/main/CLAUDE.md index 6080427..5e693fa 100644 --- a/groups/main/CLAUDE.md +++ b/groups/main/CLAUDE.md @@ -267,7 +267,7 @@ The task will run in that group's context with access to their files and memory. ## Task Scripts -When scheduling tasks that check a condition before acting (new PRs, website changes, API status), use the `script` parameter. The script runs first — if there's nothing to do, you don't wake up. +To check or monitor something on a recurring basis, use `schedule_task` — not a bash loop. This way the check survives container restarts and doesn't block other messages. If the user only needs to know when a condition changes, add a `script` to avoid unnecessary wake-ups — the script runs first, and you only wake up when there's something to act on. ### How it works From a29ca0835c37ede7ef490e21dda6a6a840bbe4a7 Mon Sep 17 00:00:00 2001 From: NanoClaw User Date: Thu, 26 Mar 2026 11:21:18 +0000 Subject: [PATCH 04/21] fix: rewrite task scripts intro for broader use cases and clarity Broadens the trigger from "check or monitor" to "any recurring task", adds context about API credit usage and account risk for frequent tasks, and prompts the agent to clarify ambiguous requests. Co-Authored-By: Claude Opus 4.6 (1M context) --- groups/global/CLAUDE.md | 2 +- groups/main/CLAUDE.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/groups/global/CLAUDE.md b/groups/global/CLAUDE.md index 7018c04..935578a 100644 --- a/groups/global/CLAUDE.md +++ b/groups/global/CLAUDE.md @@ -79,7 +79,7 @@ Standard Markdown works: `**bold**`, `*italic*`, `[links](url)`, `# headings`. ## Task Scripts -To check or monitor something on a recurring basis, use `schedule_task` — not a bash loop. This way the check survives container restarts and doesn't block other messages. If the user only needs to know when a condition changes, add a `script` to avoid unnecessary wake-ups — the script runs first, and you only wake up when there's something to act on. +For any recurring task, use `schedule_task`. Tasks that wake the agent frequently — especially multiple times a day — consume API credits and can risk account restrictions. If a simple check can determine whether you need to act, add a `script` — it runs first, and you only wake up when the check passes. This keeps agent invocations to a minimum. If it's unclear whether the user wants a response every time or only when something requires attention, ask. ### How it works diff --git a/groups/main/CLAUDE.md b/groups/main/CLAUDE.md index 5e693fa..d3ea5f9 100644 --- a/groups/main/CLAUDE.md +++ b/groups/main/CLAUDE.md @@ -267,7 +267,7 @@ The task will run in that group's context with access to their files and memory. ## Task Scripts -To check or monitor something on a recurring basis, use `schedule_task` — not a bash loop. This way the check survives container restarts and doesn't block other messages. If the user only needs to know when a condition changes, add a `script` to avoid unnecessary wake-ups — the script runs first, and you only wake up when there's something to act on. +For any recurring task, use `schedule_task`. Tasks that wake the agent frequently — especially multiple times a day — consume API credits and can risk account restrictions. If a simple check can determine whether you need to act, add a `script` — it runs first, and you only wake up when the check passes. This keeps agent invocations to a minimum. If it's unclear whether the user wants a response every time or only when something requires attention, ask. ### How it works From eda14f472beaa3e7a94e773bdcafeeacc1612ec6 Mon Sep 17 00:00:00 2001 From: NanoClaw User Date: Thu, 26 Mar 2026 12:37:28 +0000 Subject: [PATCH 05/21] fix: include script field in task snapshot for current_tasks.json The task snapshot mappings in index.ts were omitting the script field, making it appear that scheduled tasks had no script even when one was stored in the database. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/index.ts b/src/index.ts index 60fe910..bf57823 100644 --- a/src/index.ts +++ b/src/index.ts @@ -329,6 +329,7 @@ async function runAgent( id: t.id, groupFolder: t.group_folder, prompt: t.prompt, + script: t.script || undefined, schedule_type: t.schedule_type, schedule_value: t.schedule_value, status: t.status, @@ -685,6 +686,7 @@ async function main(): Promise { id: t.id, groupFolder: t.group_folder, prompt: t.prompt, + script: t.script || undefined, schedule_type: t.schedule_type, schedule_value: t.schedule_value, status: t.status, From 730ea0d713634edf1abcf16defe887b05a5accc0 Mon Sep 17 00:00:00 2001 From: NanoClaw User Date: Thu, 26 Mar 2026 15:05:53 +0000 Subject: [PATCH 06/21] fix: refine task scripts intro wording Use third-person voice and clearer terminology for the task scripts intro paragraph. Co-Authored-By: Claude Opus 4.6 (1M context) --- groups/global/CLAUDE.md | 2 +- groups/main/CLAUDE.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/groups/global/CLAUDE.md b/groups/global/CLAUDE.md index 935578a..11988bc 100644 --- a/groups/global/CLAUDE.md +++ b/groups/global/CLAUDE.md @@ -79,7 +79,7 @@ Standard Markdown works: `**bold**`, `*italic*`, `[links](url)`, `# headings`. ## Task Scripts -For any recurring task, use `schedule_task`. Tasks that wake the agent frequently — especially multiple times a day — consume API credits and can risk account restrictions. If a simple check can determine whether you need to act, add a `script` — it runs first, and you only wake up when the check passes. This keeps agent invocations to a minimum. If it's unclear whether the user wants a response every time or only when something requires attention, ask. +For any recurring task, use `schedule_task`. Frequent agent invocations — especially multiple times a day — consume API credits and can risk account restrictions. If a simple check can determine whether action is needed, add a `script` — it runs first, and the agent is only called when the check passes. This keeps invocations to a minimum. ### How it works diff --git a/groups/main/CLAUDE.md b/groups/main/CLAUDE.md index d3ea5f9..c901813 100644 --- a/groups/main/CLAUDE.md +++ b/groups/main/CLAUDE.md @@ -267,7 +267,7 @@ The task will run in that group's context with access to their files and memory. ## Task Scripts -For any recurring task, use `schedule_task`. Tasks that wake the agent frequently — especially multiple times a day — consume API credits and can risk account restrictions. If a simple check can determine whether you need to act, add a `script` — it runs first, and you only wake up when the check passes. This keeps agent invocations to a minimum. If it's unclear whether the user wants a response every time or only when something requires attention, ask. +For any recurring task, use `schedule_task`. Frequent agent invocations — especially multiple times a day — consume API credits and can risk account restrictions. If a simple check can determine whether action is needed, add a `script` — it runs first, and the agent is only called when the check passes. This keeps invocations to a minimum. ### How it works From 4383e3e61aeaad30ad3cac69ac9e377ac214f89a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 26 Mar 2026 15:39:34 +0000 Subject: [PATCH 07/21] chore: bump version to 1.2.35 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2c69d40..46a8742 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "nanoclaw", - "version": "1.2.34", + "version": "1.2.35", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "nanoclaw", - "version": "1.2.34", + "version": "1.2.35", "dependencies": { "@onecli-sh/sdk": "^0.2.0", "better-sqlite3": "11.10.0", diff --git a/package.json b/package.json index 0d76ee5..fca2280 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nanoclaw", - "version": "1.2.34", + "version": "1.2.35", "description": "Personal Claude assistant. Lightweight, secure, customizable.", "type": "module", "main": "dist/index.js", From a4fd4f2a2f0362612b344eda4178f3d230179f46 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 26 Mar 2026 19:00:28 -0300 Subject: [PATCH 08/21] fix(security): prevent command injection in stopContainer and mount path injection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **stopContainer (container-runtime.ts):** - Validate container name against `^[a-zA-Z0-9][a-zA-Z0-9_.-]*$` before passing to shell command. Rejects names with shell metacharacters (`;`, `$()`, backticks, etc.) that could execute arbitrary commands. - Changed return type from string to void — callers no longer build shell commands from the return value. **mount-security.ts:** - Reject container paths containing `:` to prevent Docker `-v` option injection (e.g., `repo:rw` could override readonly flags). - Don't permanently cache "file not found" for mount allowlist — the file may be created later without requiring a service restart. Only parse/structural errors are permanently cached. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/container-runtime.test.ts | 13 +++++++++++-- src/container-runtime.ts | 11 +++++++---- src/mount-security.ts | 8 +++++++- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/container-runtime.test.ts b/src/container-runtime.test.ts index d111bf6..fd43286 100644 --- a/src/container-runtime.test.ts +++ b/src/container-runtime.test.ts @@ -39,11 +39,20 @@ describe('readonlyMountArgs', () => { }); describe('stopContainer', () => { - it('returns stop command using CONTAINER_RUNTIME_BIN', () => { - expect(stopContainer('nanoclaw-test-123')).toBe( + it('calls docker stop for valid container names', () => { + stopContainer('nanoclaw-test-123'); + expect(mockExecSync).toHaveBeenCalledWith( `${CONTAINER_RUNTIME_BIN} stop -t 1 nanoclaw-test-123`, + { stdio: 'pipe' }, ); }); + + it('rejects names with shell metacharacters', () => { + expect(() => stopContainer('foo; rm -rf /')).toThrow('Invalid container name'); + expect(() => stopContainer('foo$(whoami)')).toThrow('Invalid container name'); + expect(() => stopContainer('foo`id`')).toThrow('Invalid container name'); + expect(mockExecSync).not.toHaveBeenCalled(); + }); }); // --- ensureContainerRuntimeRunning --- diff --git a/src/container-runtime.ts b/src/container-runtime.ts index 6326fde..beaedfa 100644 --- a/src/container-runtime.ts +++ b/src/container-runtime.ts @@ -27,9 +27,12 @@ export function readonlyMountArgs( return ['-v', `${hostPath}:${containerPath}:ro`]; } -/** Returns the shell command to stop a container by name. */ -export function stopContainer(name: string): string { - return `${CONTAINER_RUNTIME_BIN} stop -t 1 ${name}`; +/** Stop a container by name. Uses execFileSync to avoid shell injection. */ +export function stopContainer(name: string): void { + if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(name)) { + throw new Error(`Invalid container name: ${name}`); + } + execSync(`${CONTAINER_RUNTIME_BIN} stop -t 1 ${name}`, { stdio: 'pipe' }); } /** Ensure the container runtime is running, starting it if needed. */ @@ -82,7 +85,7 @@ export function cleanupOrphans(): void { const orphans = output.trim().split('\n').filter(Boolean); for (const name of orphans) { try { - execSync(stopContainer(name), { stdio: 'pipe' }); + stopContainer(name); } catch { /* already stopped */ } diff --git a/src/mount-security.ts b/src/mount-security.ts index 3dceea5..e19c6bf 100644 --- a/src/mount-security.ts +++ b/src/mount-security.ts @@ -63,7 +63,8 @@ export function loadMountAllowlist(): MountAllowlist | null { try { if (!fs.existsSync(MOUNT_ALLOWLIST_PATH)) { - allowlistLoadError = `Mount allowlist not found at ${MOUNT_ALLOWLIST_PATH}`; + // Do NOT cache this as an error — file may be created later without restart. + // Only parse/structural errors are permanently cached. logger.warn( { path: MOUNT_ALLOWLIST_PATH }, 'Mount allowlist not found - additional mounts will be BLOCKED. ' + @@ -215,6 +216,11 @@ function isValidContainerPath(containerPath: string): boolean { return false; } + // Must not contain colons — prevents Docker -v option injection (e.g., "repo:rw") + if (containerPath.includes(':')) { + return false; + } + return true; } From 0f01fe2c07a37d5cf39069839b771735327392bc Mon Sep 17 00:00:00 2001 From: root Date: Thu, 26 Mar 2026 19:01:17 -0300 Subject: [PATCH 09/21] fix(env): prevent crash on single-character .env values A value like `X=a` would pass the startsWith/endsWith quote check (both `"` and `'` are single chars), then slice(1, -1) would produce an empty string, silently dropping the value. Add length >= 2 guard before checking for surrounding quotes. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/env.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/env.ts b/src/env.ts index 988b59e..82cd5c3 100644 --- a/src/env.ts +++ b/src/env.ts @@ -30,8 +30,9 @@ export function readEnvFile(keys: string[]): Record { if (!wanted.has(key)) continue; let value = trimmed.slice(eqIdx + 1).trim(); if ( - (value.startsWith('"') && value.endsWith('"')) || - (value.startsWith("'") && value.endsWith("'")) + value.length >= 2 && + ((value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'"))) ) { value = value.slice(1, -1); } From f5375972c409de17d94d7c5d00ae819f932d4a9d Mon Sep 17 00:00:00 2001 From: snw35 Date: Thu, 26 Mar 2026 23:20:30 +0000 Subject: [PATCH 10/21] Preserve isMain on IPC updates --- src/ipc.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ipc.ts b/src/ipc.ts index 043b07a..a454fdf 100644 --- a/src/ipc.ts +++ b/src/ipc.ts @@ -441,7 +441,10 @@ export async function processTaskIpc( ); break; } - // Defense in depth: agent cannot set isMain via IPC + // Defense in depth: agent cannot set isMain via IPC. + // Preserve isMain from the existing registration so IPC config + // updates (e.g. adding additionalMounts) don't strip the flag. + const existingGroup = registeredGroups[data.jid]; deps.registerGroup(data.jid, { name: data.name, folder: data.folder, @@ -449,6 +452,7 @@ export async function processTaskIpc( added_at: new Date().toISOString(), containerConfig: data.containerConfig, requiresTrigger: data.requiresTrigger, + isMain: existingGroup?.isMain, }); } else { logger.warn( From 8f01a9a05ee6e3adb61566afd5a57e2e014eaff6 Mon Sep 17 00:00:00 2001 From: gavrielc Date: Fri, 27 Mar 2026 14:24:41 +0300 Subject: [PATCH 11/21] chore: remove unused dependencies (yaml, zod, @vitest/coverage-v8) None of these are imported or referenced by the main codebase. yaml had zero imports; zod is only used in container/agent-runner (which has its own package.json); coverage-v8 was never configured. Co-Authored-By: Claude Opus 4.6 (1M context) --- package-lock.json | 222 +--------------------------------------------- package.json | 5 +- 2 files changed, 5 insertions(+), 222 deletions(-) diff --git a/package-lock.json b/package-lock.json index 46a8742..cf59cbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,15 +12,12 @@ "better-sqlite3": "11.10.0", "cron-parser": "5.5.0", "pino": "^9.6.0", - "pino-pretty": "^13.0.0", - "yaml": "^2.8.2", - "zod": "^4.3.6" + "pino-pretty": "^13.0.0" }, "devDependencies": { "@eslint/js": "^9.35.0", "@types/better-sqlite3": "^7.6.12", "@types/node": "^22.10.0", - "@vitest/coverage-v8": "^4.0.18", "eslint": "^9.35.0", "eslint-plugin-no-catch-all": "^1.1.0", "globals": "^15.12.0", @@ -35,66 +32,6 @@ "node": ">=20" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.3", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", @@ -743,16 +680,6 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -760,17 +687,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/@onecli-sh/sdk": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@onecli-sh/sdk/-/sdk-0.2.0.tgz", @@ -1460,37 +1376,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@vitest/coverage-v8": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz", - "integrity": "sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.0.18", - "ast-v8-to-istanbul": "^0.3.10", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.2.0", - "magicast": "^0.5.1", - "obug": "^2.1.1", - "std-env": "^3.10.0", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@vitest/browser": "4.0.18", - "vitest": "4.0.18" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } - } - }, "node_modules/@vitest/expect": { "version": "4.0.18", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", @@ -1670,18 +1555,6 @@ "node": ">=12" } }, - "node_modules/ast-v8-to-istanbul": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.11.tgz", - "integrity": "sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.31", - "estree-walker": "^3.0.3", - "js-tokens": "^10.0.0" - } - }, "node_modules/atomic-sleep": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", @@ -2380,13 +2253,6 @@ "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", "license": "MIT" }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, "node_modules/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -2496,45 +2362,6 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -2544,13 +2371,6 @@ "node": ">=10" } }, - "node_modules/js-tokens": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", - "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", - "dev": true, - "license": "MIT" - }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -2643,34 +2463,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/magicast": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", - "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "source-map-js": "^1.2.1" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/mimic-response": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", @@ -3801,7 +3593,10 @@ "version": "2.8.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, "license": "ISC", + "optional": true, + "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -3823,15 +3618,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } } } } diff --git a/package.json b/package.json index fca2280..d21ccd0 100644 --- a/package.json +++ b/package.json @@ -25,15 +25,12 @@ "better-sqlite3": "11.10.0", "cron-parser": "5.5.0", "pino": "^9.6.0", - "pino-pretty": "^13.0.0", - "yaml": "^2.8.2", - "zod": "^4.3.6" + "pino-pretty": "^13.0.0" }, "devDependencies": { "@eslint/js": "^9.35.0", "@types/better-sqlite3": "^7.6.12", "@types/node": "^22.10.0", - "@vitest/coverage-v8": "^4.0.18", "eslint": "^9.35.0", "eslint-plugin-no-catch-all": "^1.1.0", "globals": "^15.12.0", From 2f472a8600a3f30d76311aac27c2620dd36981c8 Mon Sep 17 00:00:00 2001 From: gavrielc Date: Fri, 27 Mar 2026 14:31:23 +0300 Subject: [PATCH 12/21] feat: add opt-in model management tools to ollama skill setup Update SKILL.md to ask users during setup whether they want model management tools (pull, delete, show, list-running) and set OLLAMA_ADMIN_TOOLS=true in .env accordingly. Core inference tools remain always available. Incorporates #1456 by @bitcryptic-gw. Closes #1331. Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/skills/add-ollama-tool/SKILL.md | 56 +++++++++++++++++++++---- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/.claude/skills/add-ollama-tool/SKILL.md b/.claude/skills/add-ollama-tool/SKILL.md index a347b49..aa69295 100644 --- a/.claude/skills/add-ollama-tool/SKILL.md +++ b/.claude/skills/add-ollama-tool/SKILL.md @@ -1,15 +1,21 @@ --- name: add-ollama-tool -description: Add Ollama MCP server so the container agent can call local models for cheaper/faster tasks like summarization, translation, or general queries. +description: Add Ollama MCP server so the container agent can call local models and optionally manage the Ollama model library. --- # Add Ollama Integration -This skill adds a stdio-based MCP server that exposes local Ollama models as tools for the container agent. Claude remains the orchestrator but can offload work to local models. +This skill adds a stdio-based MCP server that exposes local Ollama models as tools for the container agent. Claude remains the orchestrator but can offload work to local models, and can optionally manage the model library directly. -Tools added: -- `ollama_list_models` — lists installed Ollama models -- `ollama_generate` — sends a prompt to a specified model and returns the response +Core tools (always available): +- `ollama_list_models` — list installed Ollama models with name, size, and family +- `ollama_generate` — send a prompt to a specified model and return the response + +Management tools (opt-in via `OLLAMA_ADMIN_TOOLS=true`): +- `ollama_pull_model` — pull (download) a model from the Ollama registry +- `ollama_delete_model` — delete a locally installed model to free disk space +- `ollama_show_model` — show model details: modelfile, parameters, and architecture info +- `ollama_list_running` — list models currently loaded in memory with memory usage and processor type ## Phase 1: Pre-flight @@ -89,6 +95,23 @@ Build must be clean before proceeding. ## Phase 3: Configure +### Enable model management tools (optional) + +Ask the user: + +> Would you like the agent to be able to **manage Ollama models** (pull, delete, inspect, list running)? +> +> - **Yes** — adds tools to pull new models, delete old ones, show model info, and check what's loaded in memory +> - **No** — the agent can only list installed models and generate responses (you manage models yourself on the host) + +If the user wants management tools, add to `.env`: + +```bash +OLLAMA_ADMIN_TOOLS=true +``` + +If they decline (or don't answer), do not add the variable — management tools will be disabled by default. + ### Set Ollama host (optional) By default, the MCP server connects to `http://host.docker.internal:11434` (Docker Desktop) with a fallback to `localhost`. To use a custom Ollama host, add to `.env`: @@ -106,7 +129,7 @@ launchctl kickstart -k gui/$(id -u)/com.nanoclaw # macOS ## Phase 4: Verify -### Test via WhatsApp +### Test inference Tell the user: @@ -114,6 +137,14 @@ Tell the user: > > The agent should use `ollama_list_models` to find available models, then `ollama_generate` to get a response. +### Test model management (if enabled) + +If `OLLAMA_ADMIN_TOOLS=true` was set, tell the user: + +> Send a message like: "pull the gemma3:1b model" or "which ollama models are currently loaded in memory?" +> +> The agent should call `ollama_pull_model` or `ollama_list_running` respectively. + ### Monitor activity (optional) Run the watcher script for macOS notifications when Ollama is used: @@ -129,9 +160,10 @@ tail -f logs/nanoclaw.log | grep -i ollama ``` Look for: -- `Agent output: ... Ollama ...` — agent used Ollama successfully -- `[OLLAMA] >>> Generating` — generation started (if log surfacing works) +- `[OLLAMA] >>> Generating` — generation started - `[OLLAMA] <<< Done` — generation completed +- `[OLLAMA] Pulling model:` — pull in progress (management tools) +- `[OLLAMA] Deleted:` — model removed (management tools) ## Troubleshooting @@ -151,3 +183,11 @@ The agent is trying to run `ollama` CLI inside the container instead of using th ### Agent doesn't use Ollama tools The agent may not know about the tools. Try being explicit: "use the ollama_generate tool with gemma3:1b to answer: ..." + +### `ollama_pull_model` times out on large models + +Large models (7B+) can take several minutes. The tool uses `stream: false` so it blocks until complete — this is intentional. For very large pulls, use the host CLI directly: `ollama pull ` + +### Management tools not showing up + +Ensure `OLLAMA_ADMIN_TOOLS=true` is set in `.env` and the service was restarted after adding it. From 7b22e23761cb83eba12e3b7b25bfdf468b3ab692 Mon Sep 17 00:00:00 2001 From: gavrielc Date: Fri, 27 Mar 2026 15:13:00 +0300 Subject: [PATCH 13/21] chore: replace pino/pino-pretty with built-in logger Drop 23 transitive dependencies by replacing pino + pino-pretty with a ~70-line logger that matches the same output format and API. All 80+ call sites work unchanged. Production deps now: @onecli-sh/sdk, better-sqlite3, cron-parser. Co-Authored-By: Claude Opus 4.6 (1M context) --- package-lock.json | 235 +----------------------------------------- package.json | 4 +- src/logger.ts | 73 +++++++++++-- src/mount-security.ts | 8 +- 4 files changed, 70 insertions(+), 250 deletions(-) diff --git a/package-lock.json b/package-lock.json index cf59cbb..7888048 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,7 @@ "dependencies": { "@onecli-sh/sdk": "^0.2.0", "better-sqlite3": "11.10.0", - "cron-parser": "5.5.0", - "pino": "^9.6.0", - "pino-pretty": "^13.0.0" + "cron-parser": "5.5.0" }, "devDependencies": { "@eslint/js": "^9.35.0", @@ -695,12 +693,6 @@ "node": ">=20" } }, - "node_modules/@pinojs/redact": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", - "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", - "license": "MIT" - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.57.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", @@ -1555,15 +1547,6 @@ "node": ">=12" } }, - "node_modules/atomic-sleep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1714,12 +1697,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "license": "MIT" - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1752,15 +1729,6 @@ "node": ">= 8" } }, - "node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -2072,12 +2040,6 @@ "node": ">=12.0.0" } }, - "node_modules/fast-copy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.2.tgz", - "integrity": "sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==", - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2096,12 +2058,6 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "license": "MIT" - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -2247,12 +2203,6 @@ "node": ">=8" } }, - "node_modules/help-me": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", - "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", - "license": "MIT" - }, "node_modules/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -2362,15 +2312,6 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "node_modules/joycon": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", - "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -2563,15 +2504,6 @@ ], "license": "MIT" }, - "node_modules/on-exit-leak-free": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", - "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2685,76 +2617,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pino": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", - "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", - "license": "MIT", - "dependencies": { - "@pinojs/redact": "^0.4.0", - "atomic-sleep": "^1.0.0", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^2.0.0", - "pino-std-serializers": "^7.0.0", - "process-warning": "^5.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^4.0.1", - "thread-stream": "^3.0.0" - }, - "bin": { - "pino": "bin.js" - } - }, - "node_modules/pino-abstract-transport": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", - "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", - "license": "MIT", - "dependencies": { - "split2": "^4.0.0" - } - }, - "node_modules/pino-pretty": { - "version": "13.1.3", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz", - "integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==", - "license": "MIT", - "dependencies": { - "colorette": "^2.0.7", - "dateformat": "^4.6.3", - "fast-copy": "^4.0.0", - "fast-safe-stringify": "^2.1.1", - "help-me": "^5.0.0", - "joycon": "^3.1.1", - "minimist": "^1.2.6", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^3.0.0", - "pump": "^3.0.0", - "secure-json-parse": "^4.0.0", - "sonic-boom": "^4.0.1", - "strip-json-comments": "^5.0.2" - }, - "bin": { - "pino-pretty": "bin.js" - } - }, - "node_modules/pino-pretty/node_modules/pino-abstract-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", - "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", - "license": "MIT", - "dependencies": { - "split2": "^4.0.0" - } - }, - "node_modules/pino-std-serializers": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", - "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", - "license": "MIT" - }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -2835,22 +2697,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/process-warning": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", - "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, "node_modules/pump": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", @@ -2870,12 +2716,6 @@ "node": ">=6" } }, - "node_modules/quick-format-unescaped": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", - "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", - "license": "MIT" - }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -2914,15 +2754,6 @@ "node": ">= 6" } }, - "node_modules/real-require": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", - "license": "MIT", - "engines": { - "node": ">= 12.13.0" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3007,31 +2838,6 @@ ], "license": "MIT" }, - "node_modules/safe-stable-stringify": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/secure-json-parse": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", - "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -3117,15 +2923,6 @@ "simple-concat": "^1.0.0" } }, - "node_modules/sonic-boom": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", - "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", - "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3136,15 +2933,6 @@ "node": ">=0.10.0" } }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -3168,18 +2956,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/strip-json-comments": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", - "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -3221,15 +2997,6 @@ "node": ">=6" } }, - "node_modules/thread-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", - "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", - "license": "MIT", - "dependencies": { - "real-require": "^0.2.0" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", diff --git a/package.json b/package.json index d21ccd0..a86e33a 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,7 @@ "dependencies": { "@onecli-sh/sdk": "^0.2.0", "better-sqlite3": "11.10.0", - "cron-parser": "5.5.0", - "pino": "^9.6.0", - "pino-pretty": "^13.0.0" + "cron-parser": "5.5.0" }, "devDependencies": { "@eslint/js": "^9.35.0", diff --git a/src/logger.ts b/src/logger.ts index 273dc0f..80cba30 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,11 +1,72 @@ -import pino from 'pino'; +const LEVELS = { debug: 20, info: 30, warn: 40, error: 50, fatal: 60 } as const; +type Level = keyof typeof LEVELS; -export const logger = pino({ - level: process.env.LOG_LEVEL || 'info', - transport: { target: 'pino-pretty', options: { colorize: true } }, -}); +const COLORS: Record = { + debug: '\x1b[34m', + info: '\x1b[32m', + warn: '\x1b[33m', + error: '\x1b[31m', + fatal: '\x1b[41m\x1b[37m', +}; +const KEY_COLOR = '\x1b[35m'; +const MSG_COLOR = '\x1b[36m'; +const RESET = '\x1b[39m'; +const FULL_RESET = '\x1b[0m'; -// Route uncaught errors through pino so they get timestamps in stderr +const threshold = + LEVELS[(process.env.LOG_LEVEL as Level) || 'info'] ?? LEVELS.info; + +function formatErr(err: unknown): string { + if (err instanceof Error) { + return `{\n "type": "${err.constructor.name}",\n "message": "${err.message}",\n "stack":\n ${err.stack}\n }`; + } + return JSON.stringify(err); +} + +function formatData(data: Record): string { + let out = ''; + for (const [k, v] of Object.entries(data)) { + if (k === 'err') { + out += `\n ${KEY_COLOR}err${RESET}: ${formatErr(v)}`; + } else { + out += `\n ${KEY_COLOR}${k}${RESET}: ${JSON.stringify(v)}`; + } + } + return out; +} + +function ts(): string { + const d = new Date(); + return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}.${String(d.getMilliseconds()).padStart(3, '0')}`; +} + +function log(level: Level, dataOrMsg: Record | string, msg?: string): void { + if (LEVELS[level] < threshold) return; + const tag = `${COLORS[level]}${level.toUpperCase()}${level === 'fatal' ? FULL_RESET : RESET}`; + const stream = LEVELS[level] >= LEVELS.warn ? process.stderr : process.stdout; + if (typeof dataOrMsg === 'string') { + stream.write(`[${ts()}] ${tag} (${process.pid}): ${MSG_COLOR}${dataOrMsg}${RESET}\n`); + } else { + stream.write( + `[${ts()}] ${tag} (${process.pid}): ${MSG_COLOR}${msg}${RESET}${formatData(dataOrMsg)}\n`, + ); + } +} + +export const logger = { + debug: (dataOrMsg: Record | string, msg?: string) => + log('debug', dataOrMsg, msg), + info: (dataOrMsg: Record | string, msg?: string) => + log('info', dataOrMsg, msg), + warn: (dataOrMsg: Record | string, msg?: string) => + log('warn', dataOrMsg, msg), + error: (dataOrMsg: Record | string, msg?: string) => + log('error', dataOrMsg, msg), + fatal: (dataOrMsg: Record | string, msg?: string) => + log('fatal', dataOrMsg, msg), +}; + +// Route uncaught errors through logger so they get timestamps in stderr process.on('uncaughtException', (err) => { logger.fatal({ err }, 'Uncaught exception'); process.exit(1); diff --git a/src/mount-security.ts b/src/mount-security.ts index 3dceea5..a724876 100644 --- a/src/mount-security.ts +++ b/src/mount-security.ts @@ -9,16 +9,10 @@ import fs from 'fs'; import os from 'os'; import path from 'path'; -import pino from 'pino'; - import { MOUNT_ALLOWLIST_PATH } from './config.js'; +import { logger } from './logger.js'; import { AdditionalMount, AllowedRoot, MountAllowlist } from './types.js'; -const logger = pino({ - level: process.env.LOG_LEVEL || 'info', - transport: { target: 'pino-pretty', options: { colorize: true } }, -}); - // Cache the allowlist in memory - only reloads on process restart let cachedAllowlist: MountAllowlist | null = null; let allowlistLoadError: string | null = null; From 7e7492ebba9296d2d669a8982aab9e3432de3752 Mon Sep 17 00:00:00 2001 From: gavrielc Date: Fri, 27 Mar 2026 15:13:39 +0300 Subject: [PATCH 14/21] style: apply prettier formatting to logger Co-Authored-By: Claude Opus 4.6 (1M context) --- src/logger.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index 80cba30..6b18a9b 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -40,12 +40,18 @@ function ts(): string { return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}.${String(d.getMilliseconds()).padStart(3, '0')}`; } -function log(level: Level, dataOrMsg: Record | string, msg?: string): void { +function log( + level: Level, + dataOrMsg: Record | string, + msg?: string, +): void { if (LEVELS[level] < threshold) return; const tag = `${COLORS[level]}${level.toUpperCase()}${level === 'fatal' ? FULL_RESET : RESET}`; const stream = LEVELS[level] >= LEVELS.warn ? process.stderr : process.stdout; if (typeof dataOrMsg === 'string') { - stream.write(`[${ts()}] ${tag} (${process.pid}): ${MSG_COLOR}${dataOrMsg}${RESET}\n`); + stream.write( + `[${ts()}] ${tag} (${process.pid}): ${MSG_COLOR}${dataOrMsg}${RESET}\n`, + ); } else { stream.write( `[${ts()}] ${tag} (${process.pid}): ${MSG_COLOR}${msg}${RESET}${formatData(dataOrMsg)}\n`, From 62fc8c770811066dae83784a286810a076cdb42d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 27 Mar 2026 12:13:53 +0000 Subject: [PATCH 15/21] chore: bump version to 1.2.36 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7888048..b1dd2ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "nanoclaw", - "version": "1.2.35", + "version": "1.2.36", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "nanoclaw", - "version": "1.2.35", + "version": "1.2.36", "dependencies": { "@onecli-sh/sdk": "^0.2.0", "better-sqlite3": "11.10.0", diff --git a/package.json b/package.json index a86e33a..081d2b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nanoclaw", - "version": "1.2.35", + "version": "1.2.36", "description": "Personal Claude assistant. Lightweight, secure, customizable.", "type": "module", "main": "dist/index.js", From f900670aaf91ff6cb219a6f6499475c12d3d5e81 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 27 Mar 2026 12:13:56 +0000 Subject: [PATCH 16/21] =?UTF-8?q?docs:=20update=20token=20count=20to=2042.?= =?UTF-8?q?0k=20tokens=20=C2=B7=2021%=20of=20context=20window?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- repo-tokens/badge.svg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/repo-tokens/badge.svg b/repo-tokens/badge.svg index 58e9bb3..6e1646a 100644 --- a/repo-tokens/badge.svg +++ b/repo-tokens/badge.svg @@ -1,5 +1,5 @@ - - 41.3k tokens, 21% of context window + + 42.0k tokens, 21% of context window @@ -15,8 +15,8 @@ tokens - - 41.3k + + 42.0k From 8935e4f636ced7c94b0a56a28affb4da3581e20f Mon Sep 17 00:00:00 2001 From: James Schindler Date: Fri, 27 Mar 2026 08:28:58 -0400 Subject: [PATCH 17/21] docs: add k8s image GC known issue to debug checklist Kubernetes image garbage collection silently deletes the nanoclaw-agent image when disk usage is high because ephemeral containers don't protect the image from GC. Documents symptoms, cause, fix, and diagnosis. --- docs/DEBUG_CHECKLIST.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/DEBUG_CHECKLIST.md b/docs/DEBUG_CHECKLIST.md index 5597067..a04d88f 100644 --- a/docs/DEBUG_CHECKLIST.md +++ b/docs/DEBUG_CHECKLIST.md @@ -11,6 +11,34 @@ Both timers fire at the same time, so containers always exit via hard SIGKILL (c ### 3. Cursor advanced before agent succeeds `processGroupMessages` advances `lastAgentTimestamp` before the agent runs. If the container times out, retries find no messages (cursor already past them). Messages are permanently lost on timeout. +### 4. Kubernetes image garbage collection deletes nanoclaw-agent image + +**Symptoms**: `Container exited with code 125: pull access denied for nanoclaw-agent` — the container image disappears overnight or after a few hours, even though you just built it. + +**Cause**: If your container runtime has Kubernetes enabled (Rancher Desktop enables it by default), the kubelet runs image garbage collection when disk usage exceeds 85%. NanoClaw containers are ephemeral (run and exit), so `nanoclaw-agent:latest` is never protected by a running container. The kubelet sees it as unused and deletes it — often overnight when no messages are being processed. Other images (docker-compose services) survive because they have long-running containers referencing them. + +**Fix**: Disable Kubernetes if you don't need it: +```bash +# Rancher Desktop +rdctl set --kubernetes-enabled=false + +# Then rebuild the container image +./container/build.sh +``` + +**Diagnosis**: Check the k3s log for image GC activity: +```bash +grep -i "nanoclaw" ~/Library/Logs/rancher-desktop/k3s.log +# Look for: "Removing image to free bytes" with the nanoclaw-agent image ID +``` + +Check NanoClaw logs for image status: +```bash +grep -E "image found|image NOT found|image missing" logs/nanoclaw.log +``` + +If you need Kubernetes enabled, set `CONTAINER_IMAGE` to an image stored in a registry that the kubelet won't GC, or raise the GC thresholds. + ## Quick Status Check ```bash From 877650541ae0eb2eb439b3ac3c5a8279f06ce157 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 27 Mar 2026 14:10:01 +0000 Subject: [PATCH 18/21] chore: bump version to 1.2.37 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b1dd2ea..a602361 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "nanoclaw", - "version": "1.2.36", + "version": "1.2.37", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "nanoclaw", - "version": "1.2.36", + "version": "1.2.37", "dependencies": { "@onecli-sh/sdk": "^0.2.0", "better-sqlite3": "11.10.0", diff --git a/package.json b/package.json index 081d2b4..7216a9d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nanoclaw", - "version": "1.2.36", + "version": "1.2.37", "description": "Personal Claude assistant. Lightweight, secure, customizable.", "type": "module", "main": "dist/index.js", From 6e602a1f5bb92e6277c3ebd98032ad7339ceb820 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 27 Mar 2026 14:10:36 +0000 Subject: [PATCH 19/21] chore: bump version to 1.2.38 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a602361..d123488 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "nanoclaw", - "version": "1.2.37", + "version": "1.2.38", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "nanoclaw", - "version": "1.2.37", + "version": "1.2.38", "dependencies": { "@onecli-sh/sdk": "^0.2.0", "better-sqlite3": "11.10.0", diff --git a/package.json b/package.json index 7216a9d..a864db3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nanoclaw", - "version": "1.2.37", + "version": "1.2.38", "description": "Personal Claude assistant. Lightweight, secure, customizable.", "type": "module", "main": "dist/index.js", From 842ec5fd30b8745158d2aa49485b0103f3cf1606 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 27 Mar 2026 14:15:52 +0000 Subject: [PATCH 20/21] chore: bump version to 1.2.39 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d123488..441e85d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "nanoclaw", - "version": "1.2.38", + "version": "1.2.39", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "nanoclaw", - "version": "1.2.38", + "version": "1.2.39", "dependencies": { "@onecli-sh/sdk": "^0.2.0", "better-sqlite3": "11.10.0", diff --git a/package.json b/package.json index a864db3..f40b899 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nanoclaw", - "version": "1.2.38", + "version": "1.2.39", "description": "Personal Claude assistant. Lightweight, secure, customizable.", "type": "module", "main": "dist/index.js", From 2faf1c6e19e65f471c82e93351df98554fa7ad20 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 27 Mar 2026 14:15:55 +0000 Subject: [PATCH 21/21] =?UTF-8?q?docs:=20update=20token=20count=20to=2042.?= =?UTF-8?q?1k=20tokens=20=C2=B7=2021%=20of=20context=20window?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- repo-tokens/badge.svg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/repo-tokens/badge.svg b/repo-tokens/badge.svg index 6e1646a..f30318f 100644 --- a/repo-tokens/badge.svg +++ b/repo-tokens/badge.svg @@ -1,5 +1,5 @@ - - 42.0k tokens, 21% of context window + + 42.1k tokens, 21% of context window @@ -15,8 +15,8 @@ tokens - - 42.0k + + 42.1k