The Problem
Running a personal AI assistant that can read WhatsApp, schedule jobs, and keep per‑group memory normally requires a large, tangled codebase (e.g., OpenClaw) and a single host process with broad permissions. That makes security audits hard and customisation painful for a single user.
What This Does
nanoclaw delivers the same core features in a repository that can be read in ≈ 8 minutes. The core runtime lives in src/ (src/index.ts exports the main CLI) and the skills engine (skills‑engine/) provides the plug‑in model used by the Claude Agent SDK. Container isolation is defined in container/Dockerfile and the runner (container/agent‑runner/src/index.ts). All channel adapters (WhatsApp, Discord, Telegram, Gmail, etc.) are implemented as self‑contained Claude skills under .claude/skills/….
How It Is Wired
Execution begins in the skill that boots the Discord integration:
- Entry point –
.claude/skills/add-discord/modify/src/index.ts→ functionmain(line 460). mainloads the persisted state (loadState→skills‑engine/state.readState), registers groups, then starts the message loop (startMessageLoop).
From there the call chain is:
main → loadState → readState (skills-engine/state.ts)
→ registerGroup → getAvailableGroups
→ startMessageLoop → connect → on (Discord client)
→ processGroupMessages → saveState → writeState (skills-engine/state.ts)
→ sendMessage → findChannel → client.channels.fetch (network)
Key functions with the widest blast radius:
| Function | Callers | Callees | Why it matters |
|---|---|---|---|
readState | 15 places | – | Centralised file‑based persistence; any change ripples to all skills. |
writeState | 10 places | – | Writes the same JSON file; errors affect every channel. |
isGitRepo | 9 places | – | Used by the merge helper; a change may break version‑control handling. |
sendMessage | 6 places | findChannel | Direct network call; bugs affect outbound traffic. |
on (event dispatcher) | 6 places | – | Event routing hub; mis‑wiring can drop messages. |
The hub modules identified by the import graph are skills‑engine/constants.ts, skills‑engine/state.ts, and skills‑engine/types.ts. They are imported by 14–17 other files, so they should stay stable and lightweight.
External effects are limited to:
- Network – 4 functions (e.g.,
client.channels.fetchin Discord, WhatsApp API). - Filesystem – 54 functions (state files, DB files, container snapshots).
- Database – 15 functions (SQLite via
src/db.ts). - Model inference – 1 function (Claude Agent SDK call inside the agent runner).
No circular imports were detected, and the internal call graph contains 485 resolved edges, indicating a fairly flat call hierarchy.
How To Use It
# Clone the repository
git clone https://github.com/moses-y/nanoclaw
cd nanoclaw
# Install Node dependencies (root package.json)
npm ci
# Build the container image used for sandboxed agents
docker build -t nanoclaw-agent -f container/Dockerfile .
# Initialise the assistant (first‑run wizard)
npx ts-node src/index.ts # or use the provided `claude` wrapper if installed globally
# Follow the on‑screen prompts to authenticate WhatsApp and register groups
Configuration – the project ships .env.example; copy it to .env and fill the required tokens (WhatsApp, Discord, etc.). The skill manifest files (e.g., .claude/skills/add-discord/manifest.yaml) describe each integration’s metadata and are read by the Claude Agent SDK at runtime.
Running a skill – after setup, invoke a skill via the Claude chat interface, e.g., /add-telegram. The skill’s code lives in .claude/skills/add-telegram/modify/src/index.ts and follows the same load‑state → register → loop pattern as the Discord entry point.
Real‑World Use
A user wants a private WhatsApp‑backed assistant that can run a nightly “summarise today’s email” job. They:
- Run the quick‑start steps above.
- In the Claude chat, execute
/add-gmail(skill located at.claude/skills/add-gmail/). - Add a scheduled task via
/schedulethat calls a custom script stored underscripts/. - The task runs inside an isolated Docker container (
container/agent‑runner), reads the Gmail token from the mounted secret, writes a summary to the group’sCLAUDE.mdfile, and sends the result back through WhatsApp.
All actions are confined to the per‑group filesystem; a compromise in one group cannot affect another.
Code Health & Issues
- High – Pushes go directly to the default branch (
.github/workflows/update-tokens.yml). Fix: use a bot branch and PR workflow. - Medium – GitHub token permissions are not scoped (
.github/workflows/skill-tests.yml). Fix: addpermissions:block. - Medium – No Dependabot/Renovate configured. Fix: add
.github/dependabot.yml. - Medium – Base image not pinned (
container/Dockerfileusesnode:22-slim). Fix: pin by digest. - Medium – No dependency‑vulnerability scan in CI. Fix: add
dependency-review-actionorosv-scanner. - Medium – Large binary (
assets/nanoclaw-profile.jpeg5.6 MiB) stored in Git. Fix: use Git LFS or external storage. - Medium – Checkout step keeps credentials (
persist-credentialsdefault). Fix: setpersist-credentials: false. - Low – Workflow jobs lack
timeout‑minutes. Fix: add reasonable timeouts.
Additional observations: tests exist (43 files) and run via GitHub Actions; a LICENSE file and npm lockfiles are present; no committed secrets were detected.
The Bottom Line
nanoclaw provides a minimal, container‑isolated Claude assistant that is easy to audit and extend for a single user. The codebase is small and well‑structured, but several hub modules carry a high change impact, and the repo would benefit from tighter CI controls and dependency management. It is suitable for engineers who need a personal AI chat‑assistant and are comfortable modifying TypeScript source directly.