diff --git a/.claude/settings.json b/.claude/settings.json
index f859a6d..0967ef4 100644
--- a/.claude/settings.json
+++ b/.claude/settings.json
@@ -1,10 +1 @@
-{
- "extraKnownMarketplaces": {
- "nanoclaw-skills": {
- "source": {
- "source": "github",
- "repo": "qwibitai/nanoclaw-skills"
- }
- }
- }
-}
+{}
diff --git a/.claude/skills/add-discord/SKILL.md b/.claude/skills/add-discord/SKILL.md
index f4a3164..e46bd3e 100644
--- a/.claude/skills/add-discord/SKILL.md
+++ b/.claude/skills/add-discord/SKILL.md
@@ -39,7 +39,11 @@ git remote add discord https://github.com/qwibitai/nanoclaw-discord.git
```bash
git fetch discord main
-git merge discord/main
+git merge discord/main || {
+ git checkout --theirs package-lock.json
+ git add package-lock.json
+ git merge --continue
+}
```
This merges in:
@@ -126,31 +130,18 @@ Wait for the user to provide the channel ID (format: `dc:1234567890123456`).
### Register the channel
-Use the IPC register flow or register directly. The channel ID, name, and folder name are needed.
+The channel ID, name, and folder name are needed. Use `npx tsx setup/index.ts --step register` with the appropriate flags.
For a main channel (responds to all messages):
-```typescript
-registerGroup("dc:", {
- name: " #",
- folder: "discord_main",
- trigger: `@${ASSISTANT_NAME}`,
- added_at: new Date().toISOString(),
- requiresTrigger: false,
- isMain: true,
-});
+```bash
+npx tsx setup/index.ts --step register -- --jid "dc:" --name " #" --folder "discord_main" --trigger "@${ASSISTANT_NAME}" --channel discord --no-trigger-required --is-main
```
For additional channels (trigger-only):
-```typescript
-registerGroup("dc:", {
- name: " #",
- folder: "discord_",
- trigger: `@${ASSISTANT_NAME}`,
- added_at: new Date().toISOString(),
- requiresTrigger: true,
-});
+```bash
+npx tsx setup/index.ts --step register -- --jid "dc:" --name " #" --folder "discord_" --trigger "@${ASSISTANT_NAME}" --channel discord
```
## Phase 5: Verify
diff --git a/.claude/skills/add-gmail/SKILL.md b/.claude/skills/add-gmail/SKILL.md
index f77bbf7..781a0eb 100644
--- a/.claude/skills/add-gmail/SKILL.md
+++ b/.claude/skills/add-gmail/SKILL.md
@@ -40,7 +40,11 @@ git remote add gmail https://github.com/qwibitai/nanoclaw-gmail.git
```bash
git fetch gmail main
-git merge gmail/main
+git merge gmail/main || {
+ git checkout --theirs package-lock.json
+ git add package-lock.json
+ git merge --continue
+}
```
This merges in:
diff --git a/.claude/skills/add-image-vision/SKILL.md b/.claude/skills/add-image-vision/SKILL.md
index 53ef471..072bf7b 100644
--- a/.claude/skills/add-image-vision/SKILL.md
+++ b/.claude/skills/add-image-vision/SKILL.md
@@ -32,7 +32,11 @@ git remote add whatsapp https://github.com/qwibitai/nanoclaw-whatsapp.git
```bash
git fetch whatsapp skill/image-vision
-git merge whatsapp/skill/image-vision
+git merge whatsapp/skill/image-vision || {
+ git checkout --theirs package-lock.json
+ git add package-lock.json
+ git merge --continue
+}
```
This merges in:
diff --git a/.claude/skills/add-pdf-reader/SKILL.md b/.claude/skills/add-pdf-reader/SKILL.md
index cd3736b..a01e530 100644
--- a/.claude/skills/add-pdf-reader/SKILL.md
+++ b/.claude/skills/add-pdf-reader/SKILL.md
@@ -30,7 +30,11 @@ git remote add whatsapp https://github.com/qwibitai/nanoclaw-whatsapp.git
```bash
git fetch whatsapp skill/pdf-reader
-git merge whatsapp/skill/pdf-reader
+git merge whatsapp/skill/pdf-reader || {
+ git checkout --theirs package-lock.json
+ git add package-lock.json
+ git merge --continue
+}
```
This merges in:
diff --git a/.claude/skills/add-reactions/SKILL.md b/.claude/skills/add-reactions/SKILL.md
index be725c3..de86768 100644
--- a/.claude/skills/add-reactions/SKILL.md
+++ b/.claude/skills/add-reactions/SKILL.md
@@ -37,7 +37,11 @@ git remote add whatsapp https://github.com/qwibitai/nanoclaw-whatsapp.git
```bash
git fetch whatsapp skill/reactions
-git merge whatsapp/skill/reactions
+git merge whatsapp/skill/reactions || {
+ git checkout --theirs package-lock.json
+ git add package-lock.json
+ git merge --continue
+}
```
This adds:
diff --git a/.claude/skills/add-slack/SKILL.md b/.claude/skills/add-slack/SKILL.md
index 4eb9225..4c86e19 100644
--- a/.claude/skills/add-slack/SKILL.md
+++ b/.claude/skills/add-slack/SKILL.md
@@ -35,7 +35,11 @@ git remote add slack https://github.com/qwibitai/nanoclaw-slack.git
```bash
git fetch slack main
-git merge slack/main
+git merge slack/main || {
+ git checkout --theirs package-lock.json
+ git add package-lock.json
+ git merge --continue
+}
```
This merges in:
@@ -114,31 +118,18 @@ Wait for the user to provide the channel ID.
### Register the channel
-Use the IPC register flow or register directly. The channel ID, name, and folder name are needed.
+The channel ID, name, and folder name are needed. Use `npx tsx setup/index.ts --step register` with the appropriate flags.
For a main channel (responds to all messages):
-```typescript
-registerGroup("slack:", {
- name: "",
- folder: "slack_main",
- trigger: `@${ASSISTANT_NAME}`,
- added_at: new Date().toISOString(),
- requiresTrigger: false,
- isMain: true,
-});
+```bash
+npx tsx setup/index.ts --step register -- --jid "slack:" --name "" --folder "slack_main" --trigger "@${ASSISTANT_NAME}" --channel slack --no-trigger-required --is-main
```
For additional channels (trigger-only):
-```typescript
-registerGroup("slack:", {
- name: "",
- folder: "slack_",
- trigger: `@${ASSISTANT_NAME}`,
- added_at: new Date().toISOString(),
- requiresTrigger: true,
-});
+```bash
+npx tsx setup/index.ts --step register -- --jid "slack:" --name "" --folder "slack_" --trigger "@${ASSISTANT_NAME}" --channel slack
```
## Phase 5: Verify
diff --git a/.claude/skills/add-telegram/SKILL.md b/.claude/skills/add-telegram/SKILL.md
index a2e29d7..10f25ab 100644
--- a/.claude/skills/add-telegram/SKILL.md
+++ b/.claude/skills/add-telegram/SKILL.md
@@ -39,7 +39,11 @@ git remote add telegram https://github.com/qwibitai/nanoclaw-telegram.git
```bash
git fetch telegram main
-git merge telegram/main
+git merge telegram/main || {
+ git checkout --theirs package-lock.json
+ git add package-lock.json
+ git merge --continue
+}
```
This merges in:
@@ -129,31 +133,18 @@ Wait for the user to provide the chat ID (format: `tg:123456789` or `tg:-1001234
### Register the chat
-Use the IPC register flow or register directly. The chat ID, name, and folder name are needed.
+The chat ID, name, and folder name are needed. Use `npx tsx setup/index.ts --step register` with the appropriate flags.
For a main chat (responds to all messages):
-```typescript
-registerGroup("tg:", {
- name: "",
- folder: "telegram_main",
- trigger: `@${ASSISTANT_NAME}`,
- added_at: new Date().toISOString(),
- requiresTrigger: false,
- isMain: true,
-});
+```bash
+npx tsx setup/index.ts --step register -- --jid "tg:" --name "" --folder "telegram_main" --trigger "@${ASSISTANT_NAME}" --channel telegram --no-trigger-required --is-main
```
For additional chats (trigger-only):
-```typescript
-registerGroup("tg:", {
- name: "",
- folder: "telegram_",
- trigger: `@${ASSISTANT_NAME}`,
- added_at: new Date().toISOString(),
- requiresTrigger: true,
-});
+```bash
+npx tsx setup/index.ts --step register -- --jid "tg:" --name "" --folder "telegram_" --trigger "@${ASSISTANT_NAME}" --channel telegram
```
## Phase 5: Verify
diff --git a/.claude/skills/add-voice-transcription/SKILL.md b/.claude/skills/add-voice-transcription/SKILL.md
index c3c0043..8ccec32 100644
--- a/.claude/skills/add-voice-transcription/SKILL.md
+++ b/.claude/skills/add-voice-transcription/SKILL.md
@@ -41,7 +41,11 @@ git remote add whatsapp https://github.com/qwibitai/nanoclaw-whatsapp.git
```bash
git fetch whatsapp skill/voice-transcription
-git merge whatsapp/skill/voice-transcription
+git merge whatsapp/skill/voice-transcription || {
+ git checkout --theirs package-lock.json
+ git add package-lock.json
+ git merge --continue
+}
```
This merges in:
diff --git a/.claude/skills/add-whatsapp/SKILL.md b/.claude/skills/add-whatsapp/SKILL.md
index 8ce68be..0774799 100644
--- a/.claude/skills/add-whatsapp/SKILL.md
+++ b/.claude/skills/add-whatsapp/SKILL.md
@@ -62,7 +62,11 @@ git remote add whatsapp https://github.com/qwibitai/nanoclaw-whatsapp.git
```bash
git fetch whatsapp main
-git merge whatsapp/main
+git merge whatsapp/main || {
+ git checkout --theirs package-lock.json
+ git add package-lock.json
+ git merge --continue
+}
```
This merges in:
diff --git a/.claude/skills/customize/SKILL.md b/.claude/skills/customize/SKILL.md
index 13b5b89..614a979 100644
--- a/.claude/skills/customize/SKILL.md
+++ b/.claude/skills/customize/SKILL.md
@@ -9,12 +9,7 @@ This skill helps users add capabilities or modify behavior. Use AskUserQuestion
## Workflow
-1. **Install marketplace** - If feature skills aren't available yet, install the marketplace plugin:
- ```bash
- claude plugin install nanoclaw-skills@nanoclaw-skills --scope project
- ```
- This is hot-loaded — all feature skills become immediately available.
-2. **Understand the request** - Ask clarifying questions
+1. **Understand the request** - Ask clarifying questions
3. **Plan the changes** - Identify files to modify. If a skill exists for the request (e.g., `/add-telegram` for adding Telegram), invoke it instead of implementing manually.
4. **Implement** - Make changes directly to the code
5. **Test guidance** - Tell user how to verify
diff --git a/.claude/skills/setup/SKILL.md b/.claude/skills/setup/SKILL.md
index 544ee1d..d173927 100644
--- a/.claude/skills/setup/SKILL.md
+++ b/.claude/skills/setup/SKILL.md
@@ -58,7 +58,7 @@ Run `bash setup.sh` and parse the status block.
- macOS: `brew install node@22` (if brew available) or install nvm then `nvm install 22`
- Linux: `curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt-get install -y nodejs`, or nvm
- After installing Node, re-run `bash setup.sh`
-- If DEPS_OK=false → Read `logs/setup.log`. Try: delete `node_modules` and `package-lock.json`, re-run `bash setup.sh`. If native module build fails, install build tools (`xcode-select --install` on macOS, `build-essential` on Linux), then retry.
+- If DEPS_OK=false → Read `logs/setup.log`. Try: delete `node_modules`, re-run `bash setup.sh`. If native module build fails, install build tools (`xcode-select --install` on macOS, `build-essential` on Linux), then retry.
- If NATIVE_OK=false → better-sqlite3 failed to load. Install build tools and re-run.
- Record PLATFORM and IS_WSL for later steps.
@@ -122,19 +122,7 @@ AskUserQuestion: Claude subscription (Pro/Max) vs Anthropic API key?
**API key:** Tell user to add `ANTHROPIC_API_KEY=` to `.env`.
-## 5. Install Skills Marketplace
-
-Register and install the NanoClaw skills marketplace plugin so all feature skills (channel integrations, add-ons) are available:
-
-```bash
-claude plugin marketplace add qwibitai/nanoclaw-skills
-claude plugin marketplace update nanoclaw-skills
-claude plugin install nanoclaw-skills@nanoclaw-skills --scope project
-```
-
-The marketplace update ensures the local cache is fresh before installing. This is hot-loaded — no restart needed. All feature skills become immediately available.
-
-## 6. Set Up Channels
+## 5. Set Up Channels
AskUserQuestion (multiSelect): Which messaging channels do you want to enable?
- WhatsApp (authenticates via QR code or pairing code)
@@ -164,16 +152,16 @@ Each skill will:
npm install && npm run build
```
-If the build fails, read the error output and fix it (usually a missing dependency). Then continue to step 7.
+If the build fails, read the error output and fix it (usually a missing dependency). Then continue to step 6.
-## 7. Mount Allowlist
+## 6. Mount Allowlist
AskUserQuestion: Agent access to external directories?
**No:** `npx tsx setup/index.ts --step mounts -- --empty`
**Yes:** Collect paths/permissions. `npx tsx setup/index.ts --step mounts -- --json '{"allowedRoots":[...],"blockedPatterns":[],"nonMainReadOnly":true}'`
-## 8. Start Service
+## 7. Start Service
If service already running: unload first.
- macOS: `launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist`
@@ -203,23 +191,23 @@ Replace `USERNAME` with the actual username (from `whoami`). Run the two `sudo`
- Linux: check `systemctl --user status nanoclaw`.
- Re-run the service step after fixing.
-## 9. Verify
+## 8. Verify
Run `npx tsx setup/index.ts --step verify` and parse the status block.
**If STATUS=failed, fix each:**
- SERVICE=stopped → `npm run build`, then restart: `launchctl kickstart -k gui/$(id -u)/com.nanoclaw` (macOS) or `systemctl --user restart nanoclaw` (Linux) or `bash start-nanoclaw.sh` (WSL nohup)
-- SERVICE=not_found → re-run step 8
+- SERVICE=not_found → re-run step 7
- CREDENTIALS=missing → re-run step 4
- CHANNEL_AUTH shows `not_found` for any channel → re-invoke that channel's skill (e.g. `/add-telegram`)
-- REGISTERED_GROUPS=0 → re-invoke the channel skills from step 6
+- REGISTERED_GROUPS=0 → re-invoke the channel skills from step 5
- MOUNT_ALLOWLIST=missing → `npx tsx setup/index.ts --step mounts -- --empty`
Tell user to test: send a message in their registered chat. Show: `tail -f logs/nanoclaw.log`
## Troubleshooting
-**Service not starting:** Check `logs/nanoclaw.error.log`. Common: wrong Node path (re-run step 8), missing `.env` (step 4), missing channel credentials (re-invoke channel skill).
+**Service not starting:** Check `logs/nanoclaw.error.log`. Common: wrong Node path (re-run step 7), missing `.env` (step 4), missing channel credentials (re-invoke channel skill).
**Container agent fails ("Claude Code process exited with code 1"):** Ensure the container runtime is running — `open -a Docker` (macOS Docker), `container system start` (Apple Container), or `sudo systemctl start docker` (Linux). Check container logs in `groups/main/logs/container-*.log`.
diff --git a/.claude/skills/use-local-whisper/SKILL.md b/.claude/skills/use-local-whisper/SKILL.md
index 76851f3..ec18a09 100644
--- a/.claude/skills/use-local-whisper/SKILL.md
+++ b/.claude/skills/use-local-whisper/SKILL.md
@@ -75,7 +75,11 @@ git remote add whatsapp https://github.com/qwibitai/nanoclaw-whatsapp.git
```bash
git fetch whatsapp skill/local-whisper
-git merge whatsapp/skill/local-whisper
+git merge whatsapp/skill/local-whisper || {
+ git checkout --theirs package-lock.json
+ git add package-lock.json
+ git merge --continue
+}
```
This modifies `src/transcription.ts` to use the `whisper-cli` binary instead of the OpenAI API.
diff --git a/.github/workflows/merge-forward-skills.yml b/.github/workflows/merge-forward-skills.yml
index 20dada3..093130a 100644
--- a/.github/workflows/merge-forward-skills.yml
+++ b/.github/workflows/merge-forward-skills.yml
@@ -142,6 +142,7 @@ jobs:
'nanoclaw-discord',
'nanoclaw-slack',
'nanoclaw-gmail',
+ 'nanoclaw-docker-sandboxes',
];
const sha = context.sha.substring(0, 7);
for (const repo of forks) {
diff --git a/CLAUDE.md b/CLAUDE.md
index 90c8910..318d6dd 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -57,7 +57,7 @@ systemctl --user restart nanoclaw
## Troubleshooting
-**WhatsApp not connecting after upgrade:** WhatsApp is now a separate channel fork, not bundled in core. Run `/add-whatsapp` (or `git remote add whatsapp https://github.com/qwibitai/nanoclaw-whatsapp.git && git fetch whatsapp main && git merge whatsapp/main && npm run build`) to install it. Existing auth credentials and groups are preserved.
+**WhatsApp not connecting after upgrade:** WhatsApp is now a separate channel fork, not bundled in core. Run `/add-whatsapp` (or `git remote add whatsapp https://github.com/qwibitai/nanoclaw-whatsapp.git && git fetch whatsapp main && (git merge whatsapp/main || { git checkout --theirs package-lock.json && git add package-lock.json && git merge --continue; }) && npm run build`) to install it. Existing auth credentials and groups are preserved.
## Container Build Cache
diff --git a/README.md b/README.md
index e0e167d..56d9331 100644
--- a/README.md
+++ b/README.md
@@ -12,9 +12,27 @@
•
-Using Claude Code, NanoClaw can dynamically rewrite its code to customize its feature set for your needs.
-**New:** First AI assistant to support [Agent Swarms](https://code.claude.com/docs/en/agent-teams). Spin up teams of agents that collaborate in your chat.
+---
+
+
🐳 Now Runs in Docker Sandboxes
+
Every agent gets its own isolated container inside a micro VM. Hypervisor-level isolation. Millisecond startup. No complex setup.
+
+**macOS (Apple Silicon)**
+```bash
+curl -fsSL https://nanoclaw.dev/install-docker-sandboxes.sh | bash
+```
+
+**Windows (WSL)**
+```bash
+curl -fsSL https://nanoclaw.dev/install-docker-sandboxes-windows.sh | bash
+```
+
+> Currently supported on macOS (Apple Silicon) and Windows (x86). Linux support coming soon.
+
+
+
+---
## Why I Built NanoClaw
@@ -70,8 +88,8 @@ Then run `/setup`. Claude Code handles everything: dependencies, authentication,
- **Main channel** - Your private channel (self-chat) for admin control; every group is completely isolated
- **Scheduled tasks** - Recurring jobs that run Claude and can message you back
- **Web access** - Search and fetch content from the Web
-- **Container isolation** - Agents are sandboxed in Apple Container (macOS) or Docker (macOS/Linux)
-- **Agent Swarms** - Spin up teams of specialized agents that collaborate on complex tasks. NanoClaw is the first personal AI assistant to support agent swarms.
+- **Container isolation** - Agents are sandboxed in [Docker Sandboxes](https://nanoclaw.dev/blog/nanoclaw-docker-sandboxes) (micro VM isolation), Apple Container (macOS), or Docker (macOS/Linux)
+- **Agent Swarms** - Spin up teams of specialized agents that collaborate on complex tasks
- **Optional integrations** - Add Gmail (`/add-gmail`) and more via skills
## Usage
diff --git a/container/agent-runner/package-lock.json b/container/agent-runner/package-lock.json
index 89cee2c..9ae119b 100644
--- a/container/agent-runner/package-lock.json
+++ b/container/agent-runner/package-lock.json
@@ -8,7 +8,7 @@
"name": "nanoclaw-agent-runner",
"version": "1.0.0",
"dependencies": {
- "@anthropic-ai/claude-agent-sdk": "^0.2.34",
+ "@anthropic-ai/claude-agent-sdk": "^0.2.76",
"@modelcontextprotocol/sdk": "^1.12.1",
"cron-parser": "^5.0.0",
"zod": "^4.0.0"
@@ -19,9 +19,9 @@
}
},
"node_modules/@anthropic-ai/claude-agent-sdk": {
- "version": "0.2.68",
- "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.2.68.tgz",
- "integrity": "sha512-y4n6hTTgAqmiV/pqy1G4OgIdg6gDiAKPJaEgO1NOh7/rdsrXyc/HQoUmUy0ty4HkBq1hasm7hB92wtX3W1UMEw==",
+ "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==",
"license": "SEE LICENSE IN README.md",
"engines": {
"node": ">=18.0.0"
diff --git a/container/agent-runner/package.json b/container/agent-runner/package.json
index bf13328..42a994e 100644
--- a/container/agent-runner/package.json
+++ b/container/agent-runner/package.json
@@ -9,7 +9,7 @@
"start": "node dist/index.js"
},
"dependencies": {
- "@anthropic-ai/claude-agent-sdk": "^0.2.34",
+ "@anthropic-ai/claude-agent-sdk": "^0.2.76",
"@modelcontextprotocol/sdk": "^1.12.1",
"cron-parser": "^5.0.0",
"zod": "^4.0.0"
diff --git a/docs/SPEC.md b/docs/SPEC.md
index d2b4723..598f34e 100644
--- a/docs/SPEC.md
+++ b/docs/SPEC.md
@@ -358,7 +358,7 @@ export const TRIGGER_PATTERN = new RegExp(`^@${ASSISTANT_NAME}\\b`, 'i');
Groups can have additional directories mounted via `containerConfig` in the SQLite `registered_groups` table (stored as JSON in the `container_config` column). Example registration:
```typescript
-registerGroup("1234567890@g.us", {
+setRegisteredGroup("1234567890@g.us", {
name: "Dev Team",
folder: "whatsapp_dev-team",
trigger: "@Andy",
diff --git a/docs/docker-sandboxes.md b/docs/docker-sandboxes.md
new file mode 100644
index 0000000..e887bad
--- /dev/null
+++ b/docs/docker-sandboxes.md
@@ -0,0 +1,359 @@
+# Running NanoClaw in Docker Sandboxes (Manual Setup)
+
+This guide walks through setting up NanoClaw inside a [Docker Sandbox](https://docs.docker.com/ai/sandboxes/) from scratch — no install script, no pre-built fork. You'll clone the upstream repo, apply the necessary patches, and have agents running in full hypervisor-level isolation.
+
+## Architecture
+
+```
+Host (macOS / Windows WSL)
+└── Docker Sandbox (micro VM with isolated kernel)
+ ├── NanoClaw process (Node.js)
+ │ ├── Channel adapters (WhatsApp, Telegram, etc.)
+ │ └── Container spawner → nested Docker daemon
+ └── Docker-in-Docker
+ └── nanoclaw-agent containers
+ └── Claude Agent SDK
+```
+
+Each agent runs in its own container, inside a micro VM that is fully isolated from your host. Two layers of isolation: per-agent containers + the VM boundary.
+
+The sandbox provides a MITM proxy at `host.docker.internal:3128` that handles network access and injects your Anthropic API key automatically.
+
+> **Note:** This guide is based on a validated setup running on macOS (Apple Silicon) with WhatsApp. Other channels (Telegram, Slack, etc.) and environments (Windows WSL) may require additional proxy patches for their specific HTTP/WebSocket clients. The core patches (container runner, credential proxy, Dockerfile) apply universally — channel-specific proxy configuration varies.
+
+## Prerequisites
+
+- **Docker Desktop v4.40+** with Sandbox support
+- **Anthropic API key** (the sandbox proxy manages injection)
+- For **Telegram**: a bot token from [@BotFather](https://t.me/BotFather) and your chat ID
+- For **WhatsApp**: a phone with WhatsApp installed
+
+Verify sandbox support:
+```bash
+docker sandbox version
+```
+
+## Step 1: Create the Sandbox
+
+On your host machine:
+
+```bash
+# Create a workspace directory
+mkdir -p ~/nanoclaw-workspace
+
+# Create a shell sandbox with the workspace mounted
+docker sandbox create shell ~/nanoclaw-workspace
+```
+
+If you're using WhatsApp, configure proxy bypass so WhatsApp's Noise protocol isn't MITM-inspected:
+
+```bash
+docker sandbox network proxy shell-nanoclaw-workspace \
+ --bypass-host web.whatsapp.com \
+ --bypass-host "*.whatsapp.com" \
+ --bypass-host "*.whatsapp.net"
+```
+
+Telegram does not need proxy bypass.
+
+Enter the sandbox:
+```bash
+docker sandbox run shell-nanoclaw-workspace
+```
+
+## Step 2: Install Prerequisites
+
+Inside the sandbox:
+
+```bash
+sudo apt-get update && sudo apt-get install -y build-essential python3
+npm config set strict-ssl false
+```
+
+## Step 3: Clone and Install NanoClaw
+
+NanoClaw must live inside the workspace directory — Docker-in-Docker can only bind-mount from the shared workspace path.
+
+```bash
+# Clone to home first (virtiofs can corrupt git pack files during clone)
+cd ~
+git clone https://github.com/qwibitai/nanoclaw.git
+
+# Replace with YOUR workspace path (the host path you passed to `docker sandbox create`)
+WORKSPACE=/Users/you/nanoclaw-workspace
+
+# Move into workspace so DinD mounts work
+mv nanoclaw "$WORKSPACE/nanoclaw"
+cd "$WORKSPACE/nanoclaw"
+
+# Install dependencies
+npm install
+npm install https-proxy-agent
+```
+
+## Step 4: Apply Proxy and Sandbox Patches
+
+NanoClaw needs several patches to work inside a Docker Sandbox. These handle proxy routing, CA certificates, and Docker-in-Docker mount restrictions.
+
+### 4a. Dockerfile — proxy args for container image build
+
+`npm install` inside `docker build` fails with `SELF_SIGNED_CERT_IN_CHAIN` because the sandbox's MITM proxy presents its own certificate. Add proxy build args to `container/Dockerfile`:
+
+Add these lines after the `FROM` line:
+
+```dockerfile
+# Accept proxy build args
+ARG http_proxy
+ARG https_proxy
+ARG no_proxy
+ARG NODE_EXTRA_CA_CERTS
+ARG npm_config_strict_ssl=true
+RUN npm config set strict-ssl ${npm_config_strict_ssl}
+```
+
+And after the `RUN npm install` line:
+
+```dockerfile
+RUN npm config set strict-ssl true
+```
+
+### 4b. Build script — forward proxy args
+
+Patch `container/build.sh` to pass proxy env vars to `docker build`:
+
+Add these `--build-arg` flags to the `docker build` command:
+
+```bash
+--build-arg http_proxy="${http_proxy:-$HTTP_PROXY}" \
+--build-arg https_proxy="${https_proxy:-$HTTPS_PROXY}" \
+--build-arg no_proxy="${no_proxy:-$NO_PROXY}" \
+--build-arg npm_config_strict_ssl=false \
+```
+
+### 4c. Container runner — proxy forwarding, CA cert mount, /dev/null fix
+
+Three changes to `src/container-runner.ts`:
+
+**Replace `/dev/null` shadow mount.** The sandbox rejects `/dev/null` bind mounts. Find where `.env` is shadow-mounted to `/dev/null` and replace it with an empty file:
+
+```typescript
+// Create an empty file to shadow .env (Docker Sandbox rejects /dev/null mounts)
+const emptyEnvPath = path.join(DATA_DIR, 'empty-env');
+if (!fs.existsSync(emptyEnvPath)) fs.writeFileSync(emptyEnvPath, '');
+// Use emptyEnvPath instead of '/dev/null' in the mount
+```
+
+**Forward proxy env vars** to spawned agent containers. Add `-e` flags for `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` and their lowercase variants.
+
+**Mount CA certificate.** If `NODE_EXTRA_CA_CERTS` or `SSL_CERT_FILE` is set, copy the cert into the project directory and mount it into agent containers:
+
+```typescript
+const caCertSrc = process.env.NODE_EXTRA_CA_CERTS || process.env.SSL_CERT_FILE;
+if (caCertSrc) {
+ const certDir = path.join(DATA_DIR, 'ca-cert');
+ fs.mkdirSync(certDir, { recursive: true });
+ fs.copyFileSync(caCertSrc, path.join(certDir, 'proxy-ca.crt'));
+ // Mount: certDir -> /workspace/ca-cert (read-only)
+ // Set NODE_EXTRA_CA_CERTS=/workspace/ca-cert/proxy-ca.crt in the container
+}
+```
+
+### 4d. Container runtime — prevent self-termination
+
+In `src/container-runtime.ts`, the `cleanupOrphans()` function matches containers by the `nanoclaw-` prefix. Inside a sandbox, the sandbox container itself may match (e.g., `nanoclaw-docker-sandbox`). Filter out the current hostname:
+
+```typescript
+// In cleanupOrphans(), filter out os.hostname() from the list of containers to stop
+```
+
+### 4e. Credential proxy — route through MITM proxy
+
+In `src/credential-proxy.ts`, upstream API requests need to go through the sandbox proxy. Add `HttpsProxyAgent` to outbound requests:
+
+```typescript
+import { HttpsProxyAgent } from 'https-proxy-agent';
+
+const proxyUrl = process.env.HTTPS_PROXY || process.env.https_proxy;
+const upstreamAgent = proxyUrl ? new HttpsProxyAgent(proxyUrl) : undefined;
+// Pass upstreamAgent to https.request() options
+```
+
+### 4f. Setup script — proxy build args
+
+Patch `setup/container.ts` to pass the same proxy `--build-arg` flags as `build.sh` (Step 4b).
+
+## Step 5: Build
+
+```bash
+npm run build
+bash container/build.sh
+```
+
+## Step 6: Add a Channel
+
+### Telegram
+
+```bash
+# Apply the Telegram skill
+npx tsx scripts/apply-skill.ts .claude/skills/add-telegram
+
+# Rebuild after applying the skill
+npm run build
+
+# Configure .env
+cat > .env << EOF
+TELEGRAM_BOT_TOKEN=
+ASSISTANT_NAME=nanoclaw
+ANTHROPIC_API_KEY=proxy-managed
+EOF
+mkdir -p data/env && cp .env data/env/env
+
+# Register your chat
+npx tsx setup/index.ts --step register \
+ --jid "tg:" \
+ --name "My Chat" \
+ --trigger "@nanoclaw" \
+ --folder "telegram_main" \
+ --channel telegram \
+ --assistant-name "nanoclaw" \
+ --is-main \
+ --no-trigger-required
+```
+
+**To find your chat ID:** Send any message to your bot, then:
+```bash
+curl -s --proxy $HTTPS_PROXY "https://api.telegram.org/bot/getUpdates" | python3 -m json.tool
+```
+
+**Telegram in groups:** Disable Group Privacy in @BotFather (`/mybots` > Bot Settings > Group Privacy > Turn off), then remove and re-add the bot.
+
+**Important:** If the Telegram skill creates `src/channels/telegram.ts`, you'll need to patch it for proxy support. Add an `HttpsProxyAgent` and pass it to grammy's `Bot` constructor via `baseFetchConfig.agent`. Then rebuild.
+
+### WhatsApp
+
+Make sure you configured proxy bypass in [Step 1](#step-1-create-the-sandbox) first.
+
+```bash
+# Apply the WhatsApp skill
+npx tsx scripts/apply-skill.ts .claude/skills/add-whatsapp
+
+# Rebuild
+npm run build
+
+# Configure .env
+cat > .env << EOF
+ASSISTANT_NAME=nanoclaw
+ANTHROPIC_API_KEY=proxy-managed
+EOF
+mkdir -p data/env && cp .env data/env/env
+
+# Authenticate (choose one):
+
+# QR code — scan with WhatsApp camera:
+npx tsx src/whatsapp-auth.ts
+
+# OR pairing code — enter code in WhatsApp > Linked Devices > Link with phone number:
+npx tsx src/whatsapp-auth.ts --pairing-code --phone
+
+# Register your chat (JID = your phone number + @s.whatsapp.net)
+npx tsx setup/index.ts --step register \
+ --jid "@s.whatsapp.net" \
+ --name "My Chat" \
+ --trigger "@nanoclaw" \
+ --folder "whatsapp_main" \
+ --channel whatsapp \
+ --assistant-name "nanoclaw" \
+ --is-main \
+ --no-trigger-required
+```
+
+**Important:** The WhatsApp skill files (`src/channels/whatsapp.ts` and `src/whatsapp-auth.ts`) also need proxy patches — add `HttpsProxyAgent` for WebSocket connections and a proxy-aware version fetch. Then rebuild.
+
+### Both Channels
+
+Apply both skills, patch both for proxy support, combine the `.env` variables, and register each chat separately.
+
+## Step 7: Run
+
+```bash
+npm start
+```
+
+You don't need to set `ANTHROPIC_API_KEY` manually. The sandbox proxy intercepts requests and replaces `proxy-managed` with your real key automatically.
+
+## Networking Details
+
+### How the proxy works
+
+All traffic from the sandbox routes through the host proxy at `host.docker.internal:3128`:
+
+```
+Agent container → DinD bridge → Sandbox VM → host.docker.internal:3128 → Host proxy → api.anthropic.com
+```
+
+**"Bypass" does not mean traffic skips the proxy.** It means the proxy passes traffic through without MITM inspection. Node.js doesn't automatically use `HTTP_PROXY` env vars — you need explicit `HttpsProxyAgent` configuration in every HTTP/WebSocket client.
+
+### Shared paths for DinD mounts
+
+Only the workspace directory is available for Docker-in-Docker bind mounts. Paths outside the workspace fail with "path not shared":
+- `/dev/null` → replace with an empty file in the project dir
+- `/usr/local/share/ca-certificates/` → copy cert to project dir
+- `/home/agent/` → clone to workspace instead
+
+### Git clone and virtiofs
+
+The workspace is mounted via virtiofs. Git's pack file handling can corrupt over virtiofs during clone. Workaround: clone to `/home/agent` first, then `mv` into the workspace.
+
+## Troubleshooting
+
+### npm install fails with SELF_SIGNED_CERT_IN_CHAIN
+```bash
+npm config set strict-ssl false
+```
+
+### Container build fails with proxy errors
+```bash
+docker build \
+ --build-arg http_proxy=$http_proxy \
+ --build-arg https_proxy=$https_proxy \
+ -t nanoclaw-agent:latest container/
+```
+
+### Agent containers fail with "path not shared"
+All bind-mounted paths must be under the workspace directory. Check:
+- Is NanoClaw cloned into the workspace? (not `/home/agent/`)
+- Is the CA cert copied to the project root?
+- Has the empty `.env` shadow file been created?
+
+### Agent containers can't reach Anthropic API
+Verify proxy env vars are forwarded to agent containers. Check container logs for `HTTP_PROXY=http://host.docker.internal:3128`.
+
+### WhatsApp error 405
+The version fetch is returning a stale version. Make sure the proxy-aware `fetchWaVersionViaProxy` patch is applied — it fetches `sw.js` through `HttpsProxyAgent` and parses `client_revision`.
+
+### WhatsApp "Connection failed" immediately
+Proxy bypass not configured. From the **host**, run:
+```bash
+docker sandbox network proxy \
+ --bypass-host web.whatsapp.com \
+ --bypass-host "*.whatsapp.com" \
+ --bypass-host "*.whatsapp.net"
+```
+
+### Telegram bot doesn't receive messages
+1. Check the grammy proxy patch is applied (look for `HttpsProxyAgent` in `src/channels/telegram.ts`)
+2. Check Group Privacy is disabled in @BotFather if using in groups
+
+### Git clone fails with "inflate: data stream error"
+Clone to a non-workspace path first, then move:
+```bash
+cd ~ && git clone https://github.com/qwibitai/nanoclaw.git && mv nanoclaw /path/to/workspace/nanoclaw
+```
+
+### WhatsApp QR code doesn't display
+Run the auth command interactively inside the sandbox (not piped through `docker sandbox exec`):
+```bash
+docker sandbox run shell-nanoclaw-workspace
+# Then inside:
+npx tsx src/whatsapp-auth.ts
+```
diff --git a/package-lock.json b/package-lock.json
index ef19a6c..ee97d7c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "nanoclaw",
- "version": "1.2.12",
+ "version": "1.2.15",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "nanoclaw",
- "version": "1.2.12",
+ "version": "1.2.15",
"dependencies": {
"better-sqlite3": "^11.8.1",
"cron-parser": "^5.5.0",
@@ -1740,7 +1740,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -2295,7 +2294,6 @@
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "~0.27.0",
"get-tsconfig": "^4.7.5"
@@ -2355,7 +2353,6 @@
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@@ -2431,7 +2428,6 @@
"integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@vitest/expect": "4.0.18",
"@vitest/mocker": "4.0.18",
@@ -2532,7 +2528,6 @@
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
"license": "ISC",
- "peer": true,
"bin": {
"yaml": "bin.mjs"
},
diff --git a/package.json b/package.json
index 5fae6f4..97c3a6f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "nanoclaw",
- "version": "1.2.12",
+ "version": "1.2.15",
"description": "Personal Claude assistant. Lightweight, secure, customizable.",
"type": "module",
"main": "dist/index.js",
diff --git a/repo-tokens/badge.svg b/repo-tokens/badge.svg
index c3810cb..480cd9f 100644
--- a/repo-tokens/badge.svg
+++ b/repo-tokens/badge.svg
@@ -1,5 +1,5 @@
-