Compare commits

..

20 Commits

Author SHA1 Message Date
gavrielc 0c67fbf456 Merge branch 'main' into skill/wiki 2026-04-05 11:29:01 +03:00
gavrielc 15e356a572 chore: revert unrelated db.ts formatting change
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 11:27:01 +03:00
gavrielc 33b5627f42 chore: rename skill to add-karpathy-llm-wiki
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 11:25:57 +03:00
gavrielc f69979fb9e fix: simplify source handling step and fix typo in wiki skill
Remove hardcoded file path checks. Step 4 now discusses source types
with the user and helps install needed skills dynamically. Fix "use use"
typo and change curl example to file download.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 11:24:51 +03:00
gavrielc 54bf4543f2 refactor: rework wiki skill to use Karpathy's original text as reference
Remove pre-written container skill. Instead, include llm-wiki.md
(Karpathy's gist) as the reference material and have the setup skill
guide the user through collaboratively building their own wiki schema,
container skill, and directory structure based on the pattern.

Add NanoClaw-specific notes: image vision, PDF reader, voice
transcription, curl for full document fetch, file attachment handling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 10:07:48 +03:00
gavrielc 36943fbcfd feat: add /add-wiki skill for persistent LLM Wiki knowledge bases
Container skill teaches the agent to maintain a structured, interlinked
wiki from ingested sources. Feature skill bootstraps the setup — directory
structure, group CLAUDE.md, optional scheduled lint.

Based on Karpathy's LLM Wiki pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:58:40 +03:00
gavrielc 391b729623 Merge pull request #1634 from qwibitai/skill/migrate-nanoclaw
Skill/migrate nanoclaw
2026-04-05 00:29:42 +03:00
gavrielc 3703c9decb feat: suggest /migrate-nanoclaw when user is far behind upstream
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 00:28:14 +03:00
gavrielc c5cb97b761 Merge pull request #1633 from qwibitai/skill/migrate-from-openclaw
Skill/migrate from openclaw
2026-04-05 00:23:20 +03:00
github-actions[bot] cbb4da19c7 docs: update token count to 43.7k tokens · 22% of context window 2026-04-04 21:11:05 +00:00
github-actions[bot] b752e5cd34 chore: bump version to 1.2.49 2026-04-04 21:11:00 +00:00
gavrielc a74be06956 Merge pull request #1632 from qwibitai/feat/session-cleanup-pr
feat: auto-prune stale session artifacts
2026-04-05 00:10:49 +03:00
Gavriel Cohen d4a6b4a3b5 fix: portable stat and subshell variable mutation in cleanup script
- Replace macOS-only `stat -f%z` with portable `wc -c` for Linux compat
- Replace `find | while` pipes with process substitution so TOTAL_FREED
  counter survives the loop (pipe runs in subshell, losing mutations)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 00:09:28 +03:00
Gavriel Cohen 67020f9fbf feat: auto-prune stale session artifacts on startup + daily
Session files (JSONLs, debug logs, todos, telemetry, group logs) accumulate
unboundedly — especially from daily cron tasks. This adds a cleanup script
that prunes old artifacts while protecting active sessions (read from DB),
and wires it into the main process on a 24h interval.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 00:03:00 +03:00
github-actions[bot] 9019e4e3b8 docs: update token count to 43.5k tokens · 22% of context window 2026-04-04 20:47:43 +00:00
github-actions[bot] 8a02170b21 chore: bump version to 1.2.48 2026-04-04 20:47:36 +00:00
gavrielc db3440f662 feat: upgrade agent SDK to 0.2.92 with 1M context and 200k auto-compact
Use sonnet[1m] for full 1M context window and set auto-compact at 200k
tokens to keep costs down while preserving access to extended context.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 23:47:17 +03:00
gavrielc 426ae0285e feat: add diagnostics telemetry to migrate-nanoclaw skill
Matches the pattern used by /setup and /update-nanoclaw. Captures
migration-specific properties (tier, phase, customization count,
skill interactions). Opt-out permanently disables across all skills.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:13:22 +03:00
gavrielc 7ef1c4f5e0 fix: apply lessons from real-world migration test run
Based on analysis of a live migration (v1.2.42 -> v1.2.47):

1. Absolute worktree paths: Bash tool resets cwd between calls,
   so relative cd .upgrade-worktree fails. Store PROJECT_ROOT and
   WORKTREE as absolute paths, use them throughout.

2. Smarter tier assessment: discount files from skill merges when
   counting — a fork with 3 skills and no other changes is Tier 2,
   not Tier 3 just because 24 files changed.

3. Inter-skill conflict analysis: new "Skill Interactions" section
   in the migration guide captures conflicts between applied skills
   (duplicate declarations, conflicting env var handling).

4. Cleaner swap recipe: use git reset --hard to the upgrade commit
   instead of git checkout -B intermediate branch. Backup tag
   preserves rollback. Copy guide to /tmp before worktree removal.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 00:20:13 +03:00
gavrielc f60bb3c3d5 feat: add /migrate-nanoclaw skill for intent-based upgrades
Replaces merge-based upgrades with a two-phase approach:
1. Extract: analyzes user's fork, captures customizations as a
   migration guide (intent + implementation details in markdown)
2. Upgrade: checks out clean upstream in a worktree, reapplies
   customizations from the guide, validates, and swaps in

Key features:
- Tiered complexity (lightweight/standard/complex)
- Sub-agent exploration with haiku for efficient analysis
- Incremental guide updates instead of full re-extraction
- Live e2e testing via worktree symlinks before swapping
- New-changes guard prevents losing unrecorded work

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 22:06:35 +03:00
14 changed files with 942 additions and 16 deletions
@@ -0,0 +1,80 @@
---
name: add-karpathy-llm-wiki
description: Add a persistent wiki knowledge base to a NanoClaw group. Based on Karpathy's LLM Wiki pattern. Triggers on "add wiki", "wiki", "knowledge base", "llm wiki", "karpathy wiki".
---
# Add Karpathy LLM Wiki
Set up a persistent wiki knowledge base on NanoClaw, based on Karpathy's LLM Wiki pattern.
## Step 1: Read the pattern
Read `${CLAUDE_SKILL_DIR}/llm-wiki.md` — this is the full LLM Wiki idea as written by Karpathy. Understand it thoroughly before proceeding. Summarize the core idea to the user briefly, then discuss what they want to build.
## Step 2: Choose a group
AskUserQuestion: "Which group should have the wiki?"
1. **Main group** — add to your existing main chat
2. **Dedicated group** — create a new group just for the wiki
3. **Other** — pick an existing group
If dedicated: ask which channel and chat, then register with `npx tsx setup/index.ts --step register`.
## Step 3: Design collaboratively
Discuss with the user based on the pattern:
- What's the wiki's domain or topic?
- What kinds of sources will they add? (URLs, PDFs, images, voice notes, books, transcripts)
- Do they want the full three-layer architecture or a lighter version?
- Any specific conventions they care about? (The pattern intentionally leaves this open.)
Based on this discussion, create three things:
### 3a. Directory structure
Create `wiki/` and `sources/` directories in the group folder. Create initial `index.md` and `log.md` per the pattern's Indexing and Logging section. Adapt to the user's domain.
### 3b. Container skill
Create a `container/skills/wiki/SKILL.md` tailored to this user's wiki. This is the schema layer from the pattern — it tells the agent how to maintain the wiki. Base it on the pattern's Operations section (ingest, query, lint) and the conventions you agreed on with the user. Don't over-prescribe — the pattern says "your LLM figures out the rest."
### 3c. Group CLAUDE.md
Add a wiki section to the group's CLAUDE.md that activates the wiki behavior and points to the container skill. It should concisely explain the system and have an index of the key files and folders.
## Step 4: Source handling capabilities
Based on the source types the user plans to ingest (discussed in Step 3), check whether the agent can already handle those formats — some are supported natively, others need a skill (e.g. `/add-image-vision`, `/add-pdf-reader`, `/add-voice-transcription`). If a needed capability isn't installed, check if there's an available skill for it and help the user get it set up.
### URL handling note
claude has built-in `WebFetch`, but it returns a summary, not the full document. For wiki ingestion of a URL where the full text matters, the container skill and CLAUDE.md should instruct claude to use bash commands to download full files instead. For example:
```bash
curl -sLo sources/filename.pdf "<url>"
```
If the document is a webpage, then claude can use fetch or `agent-browser` to open the page and extract full text if available. The container skill and CLAUDE.md should note this so claude gets full content for sources rather than summaries.
## Step 5: Optional lint schedule
AskUserQuestion: "Want periodic wiki health checks?"
1. **Weekly**
2. **Monthly**
3. **Skip** — lint manually
If yes, schedule via `mcp__nanoclaw__schedule_task` with a prompt based on the pattern's Lint operation.
## Step 6: Build and restart
```bash
npm run build
./container/build.sh
launchctl kickstart -k gui/$(id -u)/com.nanoclaw # macOS
# Linux: systemctl --user restart nanoclaw
```
Tell the user to test by sending a source to the wiki group.
@@ -0,0 +1,75 @@
# LLM Wiki
> Source: [karpathy/llm-wiki.md](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f)
A pattern for building personal knowledge bases using LLMs.
This is an idea file, designed to be copied to your own LLM Agent (e.g. OpenAI Codex, Claude Code, OpenCode / Pi, etc.). Its goal is to communicate the high-level idea, with your agent building out specifics through collaboration with you.
## The Core Idea
Most interactions with LLMs and documents follow RAG patterns: upload files, retrieve relevant chunks at query time, generate answers. The knowledge is re-derived on each question with no accumulation.
The concept here differs fundamentally. Rather than just retrieving from raw documents, the LLM incrementally builds and maintains a persistent wiki — a structured, interlinked markdown collection sitting between you and raw sources. When adding new material, the LLM reads it, extracts key information, and integrates it into existing wiki pages—updating entities, revising summaries, flagging contradictions, strengthening synthesis. Knowledge compiles once and stays current rather than re-deriving on every query.
The wiki becomes a persistent, compounding artifact. Cross-references already exist. Contradictions are flagged. Synthesis reflects everything read. The wiki enriches with every source added and question asked.
You source material and ask questions; the LLM maintains everything—summarizing, cross-referencing, filing, and organizing. The LLM acts as programmer; Obsidian serves as IDE; the wiki functions as codebase.
**Applications include:**
- Personal: tracking goals, health, self-improvement
- Research: deep dives over weeks/months
- Reading: building companion wikis while progressing through books
- Business/teams: internal wikis fed by Slack, transcripts, documents
- Analysis: competitive research, due diligence, trip planning, hobby deep-dives
## Architecture
Three layers comprise the system:
**Raw sources** — immutable curated documents (articles, papers, images, data). The LLM reads but never modifies these.
**The wiki** — LLM-generated markdown directories containing summaries, entity pages, concept pages, comparisons, syntheses. The LLM owns this entirely, creating and updating pages while maintaining cross-references and consistency.
**The schema** — configuration document (e.g., CLAUDE.md) explaining wiki structure, conventions, and workflows for ingestion, querying, and maintenance. This key file transforms the LLM into disciplined wiki maintainer rather than generic chatbot.
## Operations
**Ingest:** Drop new sources into the raw collection; the LLM processes them. The agent reads sources, discusses takeaways, writes summaries, updates indexes, refreshes entity and concept pages, logs entries. Single sources might touch 10-15 wiki pages. Prefer ingesting individually while staying involved, though batch ingestion with less oversight is possible.
**Query:** Ask questions against the wiki. The LLM searches relevant pages, synthesizes answers with citations. Answers take various forms—markdown pages, comparison tables, slide decks, charts, canvas. Good answers can be filed back into the wiki as new pages—explorations compound in the knowledge base rather than disappearing into chat history.
**Lint:** Periodically health-check the wiki. Look for contradictions, stale claims superseded by newer sources, orphan pages lacking inbound links, important concepts lacking dedicated pages, missing cross-references, data gaps. The LLM suggests investigations and sources to pursue, keeping the wiki healthy as it grows.
## Indexing and Logging
Two special files help navigate the growing wiki:
**index.md** — content-oriented catalog of everything (each page with link, one-line summary, optional metadata like dates or source counts), organized by category. The LLM updates it on every ingest. When answering queries, read the index first to locate relevant pages before drilling deeper. This approach works surprisingly well at moderate scale (~100 sources, ~hundreds of pages) while avoiding embedding-based RAG infrastructure needs.
**log.md** — append-only chronological record of what happened and when (ingests, queries, lint passes). Each entry beginning with consistent prefix (e.g., `## [2026-04-02] ingest | Article Title`) becomes parseable with simple tools—`grep "^## \[" log.md | tail -5` yields last 5 entries. The log shows wiki evolution timeline and helps the LLM understand recent activity.
## Optional: CLI Tools
At scale, small tools help the LLM operate more efficiently. Search engine over wiki pages is most obvious—at small scale the index suffices, but as the wiki grows, proper search becomes necessary. qmd (https://github.com/tobi/qmd) offers local search with hybrid BM25/vector search and LLM re-ranking, entirely on-device. It includes both CLI (so LLMs can shell out) and MCP server (native tool integration). Build simpler custom search scripts as needs arise.
## Tips and Tricks
- **Obsidian Web Clipper** converts web articles to markdown for quick source collection
- **Download images locally:** Set attachment folder in Obsidian Settings, bind download hotkey. All images store locally; LLM views and references directly instead of relying on potentially broken URLs
- **Obsidian's graph view** visualizes wiki connectivity—what connects to what, hub pages, orphans
- **Marp** provides markdown-based slide deck format with Obsidian plugin integration
- **Dataview** plugin queries page frontmatter, generating dynamic tables/lists when LLM adds YAML frontmatter
- The wiki is simply a git-backed markdown directory—version history, branching, collaboration included
## Why This Works
Knowledge base maintenance's tedious part is bookkeeping, not reading/thinking: updating cross-references, keeping summaries current, noting data contradictions, maintaining consistency across pages. Humans abandon wikis as maintenance burden outpaces value. LLMs don't bore, don't forget updates, can touch 15 files in one pass. Wiki maintenance becomes nearly free.
Humans curate sources, direct analysis, ask good questions, think about meaning. LLMs handle everything else.
This relates in spirit to Vannevar Bush's 1945 Memex—personal curated knowledge stores with associative document trails. Bush's vision resembled this more than what the web became: private, actively curated, with connections between documents as valuable as documents themselves. Bush couldn't solve maintenance; LLMs handle that.
## Note
This document intentionally remains abstract, describing the idea rather than specific implementation. Directory structure, schema conventions, page formats, tooling—all depend on domain, preferences, and LLM choice. Everything is optional and modular. Pick what's useful; ignore what isn't. Your sources might be text-only (no image handling needed). Your wiki might stay small enough that index files suffice (no search engine required). You might want different output formats entirely. Share this with your LLM agent and work collaboratively to instantiate a version fitting your needs. This document's sole purpose is communicating the pattern; your LLM figures out the rest.
+484
View File
@@ -0,0 +1,484 @@
---
name: migrate-nanoclaw
description: Extracts user customizations from a fork, generates a replayable migration guide, and upgrades to upstream by reapplying customizations on a clean base. Replaces merge-based upgrades with intent-based migration.
---
# Context
NanoClaw users fork the repo and customize it — changing config values, editing source files, modifying personas, adding skills. When upstream ships updates or refactors, `git merge` produces painful conflicts because the same core files were changed on both sides.
This skill extracts the user's customizations into a migration guide — capturing both the intent (what they want) and the implementation details (how they did it, with code snippets, API calls, and specific configurations). On upgrade, it checks out clean upstream in a worktree, then reapplies customizations using the guide. No merge conflicts because there's nothing to merge.
The migration guide is markdown, not structured data. It needs to capture the full range of what a user might customize, with enough implementation detail that a fresh Claude session can reapply it without having seen the original code. Standard changes (config values, simple logic) can be described briefly. Non-standard changes (specific APIs, custom integrations, unusual patterns) need code snippets and precise instructions.
Two phases: **Extract** (build the migration guide) and **Upgrade** (use it). If a guide already exists, offer to skip to Upgrade.
# Principles
- Never proceed with a dirty working tree.
- Always create a rollback point (backup branch + tag) before touching anything.
- The migration guide is the source of truth, not diffs.
- Use a worktree to validate before affecting the live install.
- Data directories (`groups/`, `store/`, `data/`, `.env`) are never touched — only code.
- Be helpful: offer to do things (stash, commit, stop services) rather than telling the user to do them.
- **Use sub-agents for exploration.** Spawn haiku sub-agents to explore the codebase, trace skill merges, diff files, and identify customizations. This keeps the main context focused on the user conversation and decision-making.
- **Always use absolute paths in worktrees.** The Bash tool resets the working directory between calls. Never use relative `cd .upgrade-worktree` — always use the full absolute path: `cd /absolute/path/.upgrade-worktree && <command>`. Store the worktree absolute path in a variable at creation time and reference it throughout.
- **Balance exploration and asking.** Don't bombard the user with questions when you can figure things out from the code. Don't burn endless tokens exploring when the user could clarify in one sentence. Use sub-agents to explore first, then ask the user targeted questions about things that are ambiguous or where intent isn't clear from the code alone.
- **Scale effort to complexity.** Not every migration needs the full process. Assess the scope early and take the lightest path that fits.
---
# Phase 1: Extract
## 1.0 Preflight
Run `git status --porcelain`. If non-empty, offer to stash or commit for them (AskUserQuestion: "Stash changes" / "Commit changes" / "I'll handle it"). If they want to commit, stage and commit with a descriptive message. If they want to stash, run `git stash push -m "pre-migration stash"`.
Check remotes with `git remote -v`. If `upstream` is missing, ask for the URL (default: `https://github.com/qwibitai/nanoclaw.git`), add it, then `git fetch upstream --prune`.
Detect upstream branch: check `git branch -r | grep upstream/` for `main` or `master`. Store as UPSTREAM_BRANCH.
## 1.1 Assess scope and determine path
Quickly assess the scale of divergence, check for an existing guide, and determine the right approach — all before asking the user anything.
```bash
BASE=$(git merge-base HEAD upstream/$UPSTREAM_BRANCH)
# Divergence stats
git rev-list --count $BASE..upstream/$UPSTREAM_BRANCH # upstream commits
git rev-list --count $BASE..HEAD # user commits
git diff --name-only $BASE..HEAD | wc -l # user changed files
git diff --stat $BASE..HEAD | tail -1 # insertions/deletions
git diff --name-only $BASE..upstream/$UPSTREAM_BRANCH | wc -l # upstream changed files
```
Check for existing guide: `.nanoclaw-migrations/guide.md` or `.nanoclaw-migrations/index.md`.
**Determine the tier based on the total diff from base:**
### Tier 1: Lightweight — suggest `/update-nanoclaw` instead
Conditions (any of):
- Very few upstream changes (< ~5 commits) AND few user changes (< ~3 changed files)
- User recently updated/migrated (merge-base is close to upstream HEAD)
Tell the user the scope is small and suggest `/update-nanoclaw` might be simpler. Let them choose.
### Tier 2: Standard
Conditions:
- Moderate total diff (3-15 changed files, no large number of new files)
- Manageable scope that fits in a single guide file
### Tier 3: Complex
Conditions (any of):
- Many new files added (indicates many skills applied) — discount files that come purely from skill merges when assessing complexity; a fork with 3 skills and no other changes is simpler than it looks by file count alone
- Deep source changes to core files (`src/index.ts`, `src/container-runner.ts`, etc.) beyond what skills introduced
- Lots of insertions/deletions in user-authored code (not skill-merged code)
- Many skills applied (3+) AND the user confirms or sub-agents find customizations on top of them
Use the full process: multiple sub-agents in parallel, directory-based guide, migration plan.
**Now combine the scope assessment with initial user input in one interaction.** Present the scope summary (how many commits, files, which tier) and ask (AskUserQuestion):
For Tier 1:
- **Use /update-nanoclaw** — simpler merge-based approach
- **Proceed with full migration** — continue
For Tier 2/3 (with or without existing guide):
- If guide exists and is current: **Skip to upgrade** / **Update guide** (add new changes) / **Re-extract from scratch**
- If guide exists but is stale: **Update guide** (recommended) / **Re-extract from scratch** / **Skip to upgrade anyway**
- If no guide: **Yes, let me describe my customizations first** / **Just figure it out** / **A bit of both**
This single interaction replaces what were previously separate steps for scope assessment, user input, and existing guide check.
## 1.2 Update existing guide (if applicable)
If the user chose to update an existing guide rather than re-extract:
1. Read the existing guide
2. Find commits made since the guide was generated (compare guide's recorded base hash against current HEAD)
3. Spawn a haiku sub-agent to analyze only the new changes:
> Diff HEAD against `<guide-recorded-hash>`. For each changed file, summarize what changed and why.
4. Present the new changes to the user for confirmation
5. Append new customizations to the existing guide, update the header hashes
6. Skip to Phase 2
## 1.3 Explore the codebase
Spawn a haiku sub-agent (Agent tool, model: haiku) for initial exploration:
> Explore this NanoClaw fork to identify all changes from the upstream base. Run these commands and report back:
>
> 1. `git diff --name-only $BASE..HEAD` — all changed files
> 2. `git log --oneline $BASE..HEAD` — all commits (look for skill branch merges like `Merge branch 'skill/*'`)
> 3. `git branch -r --list 'upstream/skill/*'` — available upstream skill branches
> 4. `ls .claude/skills/` — installed skills
> 5. For each skill merge found, record the merge commit hash
>
> Report: (a) list of applied skills with their merge commit hashes, (b) list of all changed files, (c) any custom skill directories that don't match upstream branches.
From the sub-agent results, identify:
- **Which files came purely from skill merges** — these will be reapplied by re-merging skill branches in Phase 2
- **Everything else** — all remaining changes are customizations to analyze (whether they're on skill-touched files or not)
Don't try to distinguish "user modified a skill file" from "user made their own change" at this stage. The sub-agents in 1.4 will look at all non-skill changes together and surface what matters.
## 1.4 Analyze customizations
For each applied skill, ask the user in a single batched question (AskUserQuestion, multiSelect):
> "I found these applied skills. Select any you customized further after applying:"
Options: one per skill, plus "None — all used as-is".
Then spawn sub-agents to analyze all non-skill changes. For Tier 2, one or two agents. For Tier 3, run in parallel by area:
- **Config + build files** — one sub-agent
- **Source files** (`src/*.ts`) — one sub-agent
- **Skills the user flagged as modified** (or all of them for Tier 3) — one sub-agent per skill, comparing the user's current files against the skill merge commit version:
```
git diff <merge-commit-hash>..HEAD -- <files-touched-by-skill>
```
- **Container files** — one sub-agent (if changes exist)
Each sub-agent task:
> Read these diffs and the current file contents. For each change:
> 1. `git diff $BASE..HEAD -- <file>` (or `git diff <skill-merge-hash>..HEAD -- <file>` for skill-modified files)
> 2. Read the full current file for context
> 3. Summarize: what changed, what the likely intent is
> 4. Assess detail level: could a fresh Claude session reproduce this from intent alone, or does it need specific code snippets, API details, import paths?
> 5. For non-standard changes, extract the key code, imports, API calls, and configurations verbatim.
**Inter-skill conflicts:** If multiple skills are applied, spawn an additional sub-agent to check for interactions between them. Look for:
- Duplicate declarations (same variable/constant defined by two skill branches)
- Conflicting approaches (one skill throws on missing env var, another provides a fallback)
- Shared files modified by multiple skills
Document any findings in the "Skill Interactions" section of the migration guide so they can be resolved after skill branches are re-merged during upgrade.
## 1.5 Confirm with user
After sub-agents report back, compile the findings and present to the user.
For customizations where the intent is clear (config values, simple modifications): present as a batch for confirmation. Use AskUserQuestion with multiSelect to let the user flag any entries that need correction.
For customizations where the intent is ambiguous: ask specific questions. Don't ask "what did you do?" — instead ask "I see you added X in this file. Was this for Y or something else?"
The user can select "Other" on any question to provide their own description.
## 1.6 Migration plan (Tier 3 only)
For complex migrations, before writing the guide, create a migration plan:
- **Order of operations**: which customizations depend on others, which skills must be applied first
- **Staging**: whether the migration should happen in stages (e.g. apply skills first, validate, then apply source customizations)
- **Risk areas**: customizations that touch files heavily changed by upstream — these may need manual review
- **Interactions**: customizations that interact with each other (e.g. a source change that depends on a skill, or two customizations that touch the same file)
Present the plan to the user for review before proceeding to the guide.
## 1.7 Write the migration guide
**Storage:** `.nanoclaw-migrations/guide.md` for Tier 2. `.nanoclaw-migrations/` directory with `index.md` and section files for Tier 3.
**Verification:** After writing the guide, read it back and verify:
- Every referenced file path exists in the current codebase
- Code snippets match what's actually in the files
- No customizations from the analysis were accidentally omitted
The guide is structured markdown that a fresh Claude session can follow to reproduce this user's exact setup on a clean upstream checkout.
Structure:
```markdown
# NanoClaw Migration Guide
Generated: <timestamp>
Base: <BASE hash>
HEAD at generation: <HEAD hash>
Upstream: <upstream HEAD hash>
## Migration Plan
(Tier 3 only — big-picture overview of order, staging, risks)
## Applied Skills
List each skill with its branch name. These are reapplied by merging the upstream skill branch.
- `add-telegram` — branch `skill/telegram`
- `add-voice-transcription` — branch `skill/voice-transcription`
Custom skills (user-created, not from upstream): `.claude/skills/my-custom-skill/` — copy as-is from main tree.
## Skill Interactions
(Document known conflicts or interactions between applied skills.
When two or more skills modify the same file or depend on shared
config, describe the conflict and how to resolve it after merging.
Example: skill A and skill B both add a PROXY_BIND_HOST declaration —
after merging both, deduplicate. Or: skill A throws if ENV_VAR is
missing, but skill B provides a fallback — use the fallback version.)
## Modifications to Applied Skills
### <Skill name>: <what was modified>
**Intent:** ...
**Files:** ...
**How to apply:** (after the skill branch has been merged)
...
## Customizations
### <Descriptive title for customization>
**Intent:** What the user wants and why.
**Files:** Which files to modify.
**How to apply:**
<For standard changes, a brief description is enough.>
<For non-standard changes, include code snippets, API details,
specific values, import paths — everything needed to reproduce
without seeing the original diff.>
### <Next customization...>
```
**Judging detail level:** For each customization, assess whether a fresh Claude session could reproduce it from intent alone:
- **Standard changes** (config values, simple logic, well-known patterns): describe the intent and the target. Example: "Change `POLL_INTERVAL` in `src/config.ts` from 2000 to 1000."
- **Non-standard changes** (specific API usage, custom integrations, unusual patterns, library-specific configurations): include the actual code snippets, import paths, API endpoints, configuration objects — everything needed to reproduce it without guessing.
Example entries at different detail levels:
**Standard (brief):**
```markdown
### Custom trigger word
**Intent:** Use `@Bob` instead of the default `@Andy`.
**Files:** `src/config.ts`
**How to apply:** Change the default value of `ASSISTANT_NAME` from `'Andy'` to `'Bob'`.
```
**Non-standard (detailed):**
```markdown
### Spanish translation for outbound messages
**Intent:** All outbound messages are translated to Spanish before sending. Uses the DeepL API via the `deepl-node` package.
**Files:** `src/router.ts`, `package.json`
**How to apply:**
1. Add dependency: `npm install deepl-node`
2. In `src/router.ts`, add import at top:
```typescript
import * as deepl from 'deepl-node';
const translator = new deepl.Translator(process.env.DEEPL_API_KEY!);
```
3. In the `formatOutbound` function, before the return statement, add:
```typescript
const result = await translator.translateText(text, null, 'es');
text = result.text;
```
Note: the function needs to be made async if it isn't already.
```
After writing, offer to commit for the user:
```bash
git add .nanoclaw-migrations/
git commit -m "chore: save migration guide"
```
Ask (AskUserQuestion): "Migration guide saved. Want to upgrade now or later?"
- **Upgrade now** — continue to Phase 2
- **Later** — stop here
---
# Phase 2: Upgrade
## 2.0 Preflight
Same checks as 1.0 — clean tree (offer to stash/commit if dirty), upstream configured, fetch latest.
Read the migration guide. If missing, tell the user you need to extract customizations first and ask if they want to do that now.
**New-changes guard:** Compare the guide's "HEAD at generation" hash against current HEAD. If there are commits since the guide was generated, warn the user:
> "You've made changes since the migration guide was generated. These changes won't be included in the upgrade."
AskUserQuestion:
- **Update the guide first** — go to step 1.2 to incorporate new changes
- **Proceed anyway** — user accepts that recent changes will be lost
- **Abort** — stop
## 2.1 Safety net
```bash
HASH=$(git rev-parse --short HEAD)
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
git branch backup/pre-migrate-$HASH-$TIMESTAMP
git tag pre-migrate-$HASH-$TIMESTAMP
```
Save the tag name for rollback instructions at the end.
## 2.2 Preview upstream changes
```bash
BASE=$(git merge-base HEAD upstream/$UPSTREAM_BRANCH)
git log --oneline $BASE..upstream/$UPSTREAM_BRANCH
git diff $BASE..upstream/$UPSTREAM_BRANCH -- CHANGELOG.md
```
If there are `[BREAKING]` entries, show them and explain how they interact with the user's customizations from the migration guide.
Ask (AskUserQuestion) to proceed or abort.
## 2.3 Create upgrade worktree
```bash
PROJECT_ROOT=$(pwd)
git worktree add .upgrade-worktree upstream/$UPSTREAM_BRANCH --detach
WORKTREE="$PROJECT_ROOT/.upgrade-worktree"
```
Store `$PROJECT_ROOT` and `$WORKTREE` as absolute paths. Use `$WORKTREE` in all subsequent commands — never `cd .upgrade-worktree` with a relative path.
## 2.4 Reapply skills in worktree
For each skill listed in the migration guide's "Applied Skills" section:
1. Check if branch exists: `git branch -r --list "upstream/$branch"`
2. If yes, merge it in the worktree:
```bash
cd "$WORKTREE" && git merge upstream/skill/<name> --no-edit
```
3. If missing, warn the user (skill may have been removed or renamed upstream).
4. If any skill merge conflicts, stop and tell the user — the skill needs updating for the new upstream.
Copy any custom skills mentioned in the guide from the main tree into the worktree.
## 2.5 Reapply customizations in worktree
Work in `.upgrade-worktree/`. Follow each customization section in the migration guide, including "Modifications to Applied Skills."
For Tier 3 migrations with a migration plan, follow the plan's ordering and staging. If the plan calls for staged validation (e.g. validate after skills, then validate after source changes), do so.
For each customization:
1. Read the "How to apply" instructions from the guide
2. Read the target file(s) in the worktree to understand the current upstream version
3. Apply the changes as described — use the code snippets and specific instructions from the guide
4. If the target file has changed significantly from what the guide expects (function removed, file restructured, API changed), flag it and ask the user what to do
5. Verify the file has no syntax errors or broken imports after each change
For behavior customizations (CLAUDE.md files): copy from the main tree. These are user content, not code.
## 2.6 Validate in worktree
```bash
cd "$WORKTREE" && npm install && npm run build && npm test
```
If build fails, show the error. Fix only issues caused by the migration. If unclear, ask the user.
## 2.7 Live test (optional)
Ask (AskUserQuestion):
- **Test live** — stop service, run from worktree against real data, send a test message
- **Skip** — trust the build, proceed to swap
If testing live:
1. Stop the service (do this directly):
```bash
launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist 2>/dev/null || true
```
2. Symlink data into the worktree:
```bash
ln -s "$PROJECT_ROOT/store" "$WORKTREE/store"
ln -s "$PROJECT_ROOT/data" "$WORKTREE/data"
ln -s "$PROJECT_ROOT/groups" "$WORKTREE/groups"
ln -s "$PROJECT_ROOT/.env" "$WORKTREE/.env"
```
3. Start from worktree: `cd "$WORKTREE" && npm run dev`
4. Ask the user to send a test message from their phone. Wait for them to confirm it works.
5. After confirmation, stop the dev server.
6. Clean up symlinks:
```bash
rm "$WORKTREE/store" "$WORKTREE/data" "$WORKTREE/groups" "$WORKTREE/.env"
```
## 2.8 Swap into main tree
The swap must be done carefully — the worktree has the upgraded code, but main needs to point to it cleanly. Use absolute paths throughout.
```bash
# 1. Capture the worktree HEAD before removing it
WORKTREE_PATH=$(cd "$PROJECT_ROOT/.upgrade-worktree" && pwd)
UPGRADE_COMMIT=$(git -C "$WORKTREE_PATH" rev-parse HEAD)
# 2. Copy the migration guide out of the worktree before removing it
cp -r "$WORKTREE_PATH/.nanoclaw-migrations" /tmp/nanoclaw-migrations-backup 2>/dev/null || true
# 3. Remove the worktree
git worktree remove "$WORKTREE_PATH" --force
# 4. Point the current branch at the upgraded commit
git reset --hard $UPGRADE_COMMIT
# 5. Restore the migration guide and update its hashes
cp -r /tmp/nanoclaw-migrations-backup/* .nanoclaw-migrations/ 2>/dev/null || true
rm -rf /tmp/nanoclaw-migrations-backup
```
Update the guide's header hashes to reflect the new state. Offer to commit:
```bash
git add .nanoclaw-migrations/
git commit -m "chore: upgrade to upstream $(git rev-parse --short upstream/$UPSTREAM_BRANCH)"
```
Do NOT use `git checkout -B` to create an intermediate branch — this caused issues in practice. The `git reset --hard` to the upgrade commit is the cleanest path since the backup tag already preserves the pre-upgrade state.
## 2.9 Post-upgrade
Run `npm install && npm run build` in the main tree to confirm.
Restart the service:
```bash
launchctl load ~/Library/LaunchAgents/com.nanoclaw.plist
```
Show summary:
- Previous version (backup tag)
- New HEAD
- Customizations reapplied (list from guide)
- Skills reapplied
- Rollback: `git reset --hard <backup-tag>`
- Any customizations that needed manual adjustment
Offer to pop the stash if one was created in preflight: `git stash pop`
## Diagnostics
1. Use the Read tool to read `.claude/skills/migrate-nanoclaw/diagnostics.md`.
2. Follow every step in that file before finishing.
@@ -0,0 +1,52 @@
# Diagnostics
Gather system info:
```bash
node -p "require('./package.json').version"
uname -s
uname -m
node -p "process.versions.node.split('.')[0]"
```
Write `/tmp/nanoclaw-diagnostics.json`. No paths, usernames, hostnames, or IP addresses.
```json
{
"api_key": "phc_fx1Hhx9ucz8GuaJC8LVZWO8u03yXZZJJ6ObS4yplnaP",
"event": "migrate_complete",
"distinct_id": "<uuid>",
"properties": {
"success": true,
"nanoclaw_version": "1.2.43",
"os_platform": "darwin",
"arch": "arm64",
"node_major_version": 22,
"migration_phase": "extract|upgrade|both",
"tier": 2,
"customization_count": 3,
"skills_applied_count": 2,
"skill_interaction_count": 0,
"live_test": false,
"breaking_changes_found": false,
"error_count": 0
}
}
```
Show the entire JSON to the user and ask via AskUserQuestion: **Yes** / **No** / **Never ask again**
**Yes**:
```bash
curl -s -X POST https://us.i.posthog.com/capture/ -H 'Content-Type: application/json' -d @/tmp/nanoclaw-diagnostics.json
rm /tmp/nanoclaw-diagnostics.json
```
**No**: `rm /tmp/nanoclaw-diagnostics.json`
**Never ask again**:
1. Replace contents of `.claude/skills/setup/diagnostics.md` with `# Diagnostics — opted out`
2. Replace contents of `.claude/skills/update-nanoclaw/diagnostics.md` with `# Diagnostics — opted out`
3. Replace contents of `.claude/skills/migrate-nanoclaw/diagnostics.md` with `# Diagnostics — opted out`
4. Remove the diagnostics sections from each corresponding SKILL.md
5. `rm /tmp/nanoclaw-diagnostics.json`
+2
View File
@@ -112,6 +112,8 @@ Bucket the upstream changed files:
- **Build/config** (`package.json`, `package-lock.json`, `tsconfig*.json`, `container/`, `launchd/`): review needed
- **Other**: docs, tests, misc
**Large drift check:** If the upstream commit count and age suggest the user has a lot of catching up to do, mention that `/migrate-nanoclaw` might be a better fit — it extracts customizations and reapplies them on clean upstream instead of merging. Offer it as an option but don't push.
Present these buckets to the user and ask them to choose one path using AskUserQuestion:
- A) **Full update**: merge all upstream changes
- B) **Selective update**: cherry-pick specific upstream commits
+59 -7
View File
@@ -8,7 +8,7 @@
"name": "nanoclaw-agent-runner",
"version": "1.0.0",
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.2.76",
"@anthropic-ai/claude-agent-sdk": "^0.2.92",
"@modelcontextprotocol/sdk": "^1.12.1",
"cron-parser": "^5.0.0",
"zod": "^4.0.0"
@@ -19,10 +19,14 @@
}
},
"node_modules/@anthropic-ai/claude-agent-sdk": {
"version": "0.2.76",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.2.76.tgz",
"integrity": "sha512-HZxvnT8ZWkzCnQygaYCA0dl8RSUzuVbxE1YG4ecy6vh4nQbTT36CxUxBy+QVdR12pPQluncC0mCOLhI2918Eaw==",
"version": "0.2.92",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.2.92.tgz",
"integrity": "sha512-loYyxVUC5gBwHjGi9Fv0b84mduJTp9Z3Pum+y/7IVQDb4NynKfVQl6l4VeDKZaW+1QTQtd25tY4hwUznD7Krqw==",
"license": "SEE LICENSE IN README.md",
"dependencies": {
"@anthropic-ai/sdk": "^0.80.0",
"@modelcontextprotocol/sdk": "^1.27.1"
},
"engines": {
"node": ">=18.0.0"
},
@@ -41,6 +45,35 @@
"zod": "^4.0.0"
}
},
"node_modules/@anthropic-ai/sdk": {
"version": "0.80.0",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.80.0.tgz",
"integrity": "sha512-WeXLn7zNVk3yjeshn+xZHvld6AoFUOR3Sep6pSoHho5YbSi6HwcirqgPA5ccFuW8QTVJAAU7N8uQQC6Wa9TG+g==",
"license": "MIT",
"dependencies": {
"json-schema-to-ts": "^3.1.1"
},
"bin": {
"anthropic-ai-sdk": "bin/cli"
},
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
},
"peerDependenciesMeta": {
"zod": {
"optional": true
}
}
},
"node_modules/@babel/runtime": {
"version": "7.29.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@hono/node-server": {
"version": "1.19.9",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz",
@@ -358,9 +391,9 @@
}
},
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.26.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz",
"integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==",
"version": "1.29.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz",
"integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==",
"license": "MIT",
"dependencies": {
"@hono/node-server": "^1.19.9",
@@ -1013,6 +1046,19 @@
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/json-schema-to-ts": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz",
"integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.18.3",
"ts-algebra": "^2.0.0"
},
"engines": {
"node": ">=16"
}
},
"node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
@@ -1428,6 +1474,12 @@
"node": ">=0.6"
}
},
"node_modules/ts-algebra": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz",
"integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==",
"license": "MIT"
},
"node_modules/type-is": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
+1 -1
View File
@@ -9,7 +9,7 @@
"start": "node dist/index.js"
},
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.2.76",
"@anthropic-ai/claude-agent-sdk": "^0.2.92",
"@modelcontextprotocol/sdk": "^1.12.1",
"cron-parser": "^5.0.0",
"zod": "^4.0.0"
+5 -1
View File
@@ -449,6 +449,7 @@ async function runQuery(
append: globalClaudeMd,
}
: undefined,
model: 'sonnet[1m]',
allowedTools: [
'Bash',
'Read',
@@ -623,7 +624,10 @@ async function main(): Promise<void> {
// Credentials are injected by the host's credential proxy via ANTHROPIC_BASE_URL.
// No real secrets exist in the container environment.
const sdkEnv: Record<string, string | undefined> = { ...process.env };
const sdkEnv: Record<string, string | undefined> = {
...process.env,
CLAUDE_CODE_AUTO_COMPACT_WINDOW: '200000',
};
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const mcpServerPath = path.join(__dirname, 'ipc-mcp-stdio.js');
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "nanoclaw",
"version": "1.2.47",
"version": "1.2.49",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "nanoclaw",
"version": "1.2.47",
"version": "1.2.49",
"dependencies": {
"@onecli-sh/sdk": "^0.2.0",
"better-sqlite3": "11.10.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "nanoclaw",
"version": "1.2.47",
"version": "1.2.49",
"description": "Personal Claude assistant. Lightweight, secure, customizable.",
"type": "module",
"main": "dist/index.js",
+4 -4
View File
@@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="97" height="20" role="img" aria-label="43.4k tokens, 22% of context window">
<title>43.4k tokens, 22% of context window</title>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="97" height="20" role="img" aria-label="43.7k tokens, 22% of context window">
<title>43.7k tokens, 22% of context window</title>
<linearGradient id="s" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
@@ -15,8 +15,8 @@
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11">
<text aria-hidden="true" x="26" y="15" fill="#010101" fill-opacity=".3">tokens</text>
<text x="26" y="14">tokens</text>
<text aria-hidden="true" x="74" y="15" fill="#010101" fill-opacity=".3">43.4k</text>
<text x="74" y="14">43.4k</text>
<text aria-hidden="true" x="74" y="15" fill="#010101" fill-opacity=".3">43.7k</text>
<text x="74" y="14">43.7k</text>
</g>
</g>
</a>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

+150
View File
@@ -0,0 +1,150 @@
#!/bin/bash
#
# Prune stale session artifacts (JSONLs, debug logs, todos, telemetry, group logs).
# Safe to run while NanoClaw is live — active sessions are read from the DB.
#
# Usage: ./scripts/cleanup-sessions.sh [--dry-run]
#
# Retention:
# Session JSONLs + tool-results: 7 days (active session always kept)
# Debug logs: 3 days
# Todo files: 3 days
# Telemetry: 7 days
# Group logs: 7 days
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
STORE_DB="$PROJECT_ROOT/store/messages.db"
SESSIONS_DIR="$PROJECT_ROOT/data/sessions"
GROUPS_DIR="$PROJECT_ROOT/groups"
DRY_RUN=false
[[ "${1:-}" == "--dry-run" ]] && DRY_RUN=true
TOTAL_FREED=0
log() { echo "[cleanup] $*"; }
remove() {
local target="$1"
if $DRY_RUN; then
if [ -d "$target" ]; then
size=$(du -sk "$target" 2>/dev/null | cut -f1)
else
size=$(wc -c < "$target" 2>/dev/null || echo 0)
size=$((size / 1024))
fi
TOTAL_FREED=$((TOTAL_FREED + size))
log "would remove: $target (${size}K)"
else
if [ -d "$target" ]; then
size=$(du -sk "$target" 2>/dev/null | cut -f1)
rm -rf "$target"
else
size=$(wc -c < "$target" 2>/dev/null || echo 0)
size=$((size / 1024))
rm -f "$target"
fi
TOTAL_FREED=$((TOTAL_FREED + size))
fi
}
# --- Collect active session IDs from the database ---
if [ ! -f "$STORE_DB" ]; then
log "ERROR: database not found at $STORE_DB"
exit 1
fi
ACTIVE_IDS=$(sqlite3 "$STORE_DB" "SELECT session_id FROM sessions;" 2>/dev/null || true)
is_active() {
echo "$ACTIVE_IDS" | grep -qF "$1"
}
# --- Prune session JSONLs and tool-results dirs ---
for group_dir in "$SESSIONS_DIR"/*/; do
[ -d "$group_dir" ] || continue
jsonl_dir="$group_dir/.claude/projects/-workspace-group"
[ -d "$jsonl_dir" ] || continue
for jsonl in "$jsonl_dir"/*.jsonl; do
[ -f "$jsonl" ] || continue
id=$(basename "$jsonl" .jsonl)
# Never delete the active session
if is_active "$id"; then
continue
fi
# Only delete if older than 7 days
if [ -n "$(find "$jsonl" -mtime +7 2>/dev/null)" ]; then
remove "$jsonl"
# Remove matching tool-results directory
[ -d "$jsonl_dir/$id" ] && remove "$jsonl_dir/$id"
fi
done
done
# --- Prune debug logs (>3 days, skip files named after active sessions) ---
for group_dir in "$SESSIONS_DIR"/*/; do
debug_dir="$group_dir/.claude/debug"
[ -d "$debug_dir" ] || continue
while IFS= read -r -d '' f; do
fname=$(basename "$f" .txt)
is_active "$fname" && continue
remove "$f"
done < <(find "$debug_dir" -type f -mtime +3 ! -name "latest" -print0 2>/dev/null)
done
# --- Prune todo files (>3 days, skip files named after active sessions) ---
for group_dir in "$SESSIONS_DIR"/*/; do
todos_dir="$group_dir/.claude/todos"
[ -d "$todos_dir" ] || continue
while IFS= read -r -d '' f; do
fname=$(basename "$f" .json)
# Todo filenames are like {session_id}-agent-{session_id}.json
for aid in $ACTIVE_IDS; do
if [[ "$fname" == *"$aid"* ]]; then
continue 2
fi
done
remove "$f"
done < <(find "$todos_dir" -type f -mtime +3 -print0 2>/dev/null)
done
# --- Prune telemetry (>7 days, skip files named after active sessions) ---
for group_dir in "$SESSIONS_DIR"/*/; do
telem_dir="$group_dir/.claude/telemetry"
[ -d "$telem_dir" ] || continue
while IFS= read -r -d '' f; do
fname=$(basename "$f")
for aid in $ACTIVE_IDS; do
if [[ "$fname" == *"$aid"* ]]; then
continue 2
fi
done
remove "$f"
done < <(find "$telem_dir" -type f -mtime +7 -print0 2>/dev/null)
done
# --- Prune group logs (>7 days) ---
while IFS= read -r -d '' f; do
remove "$f"
done < <(find "$GROUPS_DIR"/*/logs -type f -mtime +7 -print0 2>/dev/null)
# --- Summary ---
if $DRY_RUN; then
log "DRY RUN complete — would free ~${TOTAL_FREED}K"
else
log "Done — freed ~${TOTAL_FREED}K"
fi
+2
View File
@@ -61,6 +61,7 @@ import {
loadSenderAllowlist,
shouldDropMessage,
} from './sender-allowlist.js';
import { startSessionCleanup } from './session-cleanup.js';
import { startSchedulerLoop } from './task-scheduler.js';
import { Channel, NewMessage, RegisteredGroup } from './types.js';
import { logger } from './logger.js';
@@ -746,6 +747,7 @@ async function main(): Promise<void> {
}
},
});
startSessionCleanup();
queue.setProcessMessagesFn(processGroupMessages);
recoverPendingMessages();
startMessageLoop().catch((err) => {
+25
View File
@@ -0,0 +1,25 @@
import { execFile } from 'child_process';
import path from 'path';
import { logger } from './logger.js';
const CLEANUP_INTERVAL = 24 * 60 * 60 * 1000; // 24 hours
const SCRIPT_PATH = path.resolve(process.cwd(), 'scripts/cleanup-sessions.sh');
function runCleanup(): void {
execFile('/bin/bash', [SCRIPT_PATH], { timeout: 60_000 }, (err, stdout) => {
if (err) {
logger.error({ err }, 'Session cleanup failed');
return;
}
const summary = stdout.trim().split('\n').pop();
if (summary) logger.info(summary);
});
}
export function startSessionCleanup(): void {
// Run once at startup (delayed 30s to not compete with init)
setTimeout(runCleanup, 30_000);
// Then every 24 hours
setInterval(runCleanup, CLEANUP_INTERVAL);
}