Repo layout
nanoclaw
src — host process: routing, containers, channel registry, SQLite state (Node)
container — agent image: Dockerfile, entrypoint, agent-runner (Bun), container skills
setup — setup wizard: environment checks, channel installers, service registration
.claude/skills — host-side skills: /setup, /debug, /add-telegram, and the rest
scripts — dev and maintenance scripts: chat.ts, migrations, e2e harnesses
docs — developer docs: architecture, isolation model, skills model, skill guidelines
repo-tokens — GitHub Action that keeps the token-count badge updated
The whole repo fits in context
The token badge in the README currently reads ~185k tokens for the runtime surface (mainlysrc/, container/agent-runner/src/, the Dockerfile, and CLAUDE.md — host tests excluded). That’s deliberate: the codebase is kept small enough for a coding agent to hold it in context, which is also why feature PRs are redirected to skills. The badge is auto-generated — the update-tokens.yml workflow runs the repo-tokens/ action on every push to main that touches src/, container/, launchd/, or CLAUDE.md and commits the new badge. Don’t update it by hand.
Dev loop
NanoClaw uses pnpm for the host and Bun for the agent-runner inside the container — two separate package trees.format:check, typechecks for both trees, host tests under vitest, and agent-runner tests under bun test.
Test layout
Tests are split across three configs because the two runtimes can’t share one:vitest.config.ts— host tests:src/**/*.test.ts,setup/**/*.test.ts,scripts/**/*.test.ts. Run withpnpm test.vitest.skills.config.ts— skill registration tests:.claude/skills/**/tests/*.test.ts. Run withpnpm exec vitest run --config vitest.skills.config.ts.container/agent-runner— Bun tests, because they depend onbun:sqlite, which vitest (Node) can’t load. Import frombun:test, notvitest.
The three-branch model
Trunk (main) ships no specific channel adapter and no non-default provider. The adapters live on two long-lived registry branches, kept in sync with main:
channels— Discord, Slack, Telegram, WhatsApp, Teams, and the other channel adapters, plus their tests and setup steps. Installed via/add-<channel>skills.providers— non-default agent providers like OpenCode. Installed via/add-opencode.
git show origin/channels:<path> > <path>), never a git merge — docs/skills-model.md explains why.
Where your PR goes depends on what you’re contributing:
- Bug fix or simplification → ordinary PR against
main. - Channel or provider adapter → fork, branch from
main, build the adapter per docs/skill-guidelines.md (self-registering module, one appended barrel import, a registration test against the real barrel), add a SKILL.md with the fetch-and-copy steps and a REMOVE.md that reverses every change, then open a PR againstmain. Maintainers land the code on the registry branch from your work — you don’t push tochannelsorprovidersdirectly. - Utility, operational, or container skill → PR against
main; the skill lives in.claude/skills/(orcontainer/skills/for container skills). CONTRIBUTING.md describes all four skill types.
Releases
Maintainers cut a GitHub Release forpackage.json version bumps that land on main: a vX.Y.Z tag, a CHANGELOG.md entry, and a release body that mirrors it. Every release is stable — there’s no pre-release channel — and published tags are never moved, so pinning a tag is the reproducible choice for forks. Details in RELEASING.md.
Where to go
- Issues and PRs: nanocoai/nanoclaw — link related issues with
Closes #N, test on a fresh clone, keep descriptions short. - Process: CONTRIBUTING.md is the source of truth, including the PR-template checkboxes that auto-apply labels.
- Discussion: the NanoClaw Discord — good for checking alignment before you build.
Related pages
- Extending NanoClaw — customizing your own install instead of upstream
- Architecture — how the pieces in
src/andcontainer/fit together - Security model — boundaries your contribution must preserve