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 ~226k 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 comes from the repo-tokens/ action. The update-tokens.yml workflow that regenerated it on every push has been removed, so the number is refreshed by running that action rather than automatically. Don’t hand-edit the badge.
Dev loop
NanoClaw uses pnpm for the host and Bun for the agent-runner inside the container — two separate package trees.main runs format:check, typechecks for both trees, host tests under vitest and agent-runner tests (bun run test) on Node 22 and 24, plus a check that the skill-pin mirror is current (pnpm run skill-pins:check) and the Go tests for the Iron Proxy front proxy.
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,container/*.test.ts, and.claude/skills/*/scripts/**/*.test.ts(a gateway skill’s own scripts). 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.
Where channel and provider code lives
Trunk (main) ships no specific channel adapter and no non-default provider. Channel adapters live on a long-lived registry branch, kept in sync with main; provider code ships inside each provider skill:
channels— Discord, Slack, Telegram, WhatsApp, Teams, and the other channel adapters, plus their tests and setup steps. Installed via/add-<channel>skills.- Provider skill payloads — non-default agent providers like OpenCode, Codex and Cursor keep their code in the skill’s own
payload/folder onmain. Installed via/add-opencode,/add-codexand/add-cursor. The oldprovidersbranch is locked; it stays for older releases and for the upstream CI jobs that test refreshing their installs.
git show origin/channels:<path> > <path> for a channel, a plain file copy from payload/ for a provider), never a git merge — Skills: how NanoClaw extends itself explains why (upstream source: docs/skills-model.md).
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 Writing skills (upstream bar:docs/skill-guidelines.md; self-registering module, one appended barrel import, a registration test against the real barrel), add a SKILL.md with the copy steps and a REMOVE.md that reverses every change, then open a PR againstmain. A provider’s code and tests go in the skill’spayload/in that PR. For a channel, maintainers land the code on thechannelsbranch from your work — you don’t push tochannelsdirectly. - 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, published through a gated release workflow with a protected environment and required approvers: a vX.Y.Z tag, a CHANGELOG.md entry, and a release body that mirrors it. A version with an -rc.N suffix is published as a GitHub pre-release through a separate protected prerelease environment and never becomes the latest release; every other version is a stable release. Published tags are annotated, immutable and 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