Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
bbb6c5f
feat: add Docker Sandbox setup script
gabi-simons Mar 9, 2026
ac6f389
feat: add multi-arch Docker Sandbox image and simplified setup
gabi-simons Mar 9, 2026
c0b2396
feat: use Docker Sandbox plugin for network bypass, remove code patches
gabi-simons Mar 10, 2026
e411701
feat: simplify Docker Sandbox to clone-on-host approach
gabi-simons Mar 10, 2026
310a913
Merge branch 'qwibitai:main' into feature/sandbox-setup-script
gabi-simons Mar 10, 2026
408d4f3
feat: use Docker claude agent type, add sandbox detection to /setup
gabi-simons Mar 10, 2026
171cc72
fix: clone repo as workspace root so Claude starts in correct path
gabi-simons Mar 10, 2026
3f6f625
feat: auto-run sandbox init from /setup, simplify user flow to 2 steps
gabi-simons Mar 10, 2026
9782404
fix: force LF line endings for shell scripts, configure npm proxy
gabi-simons Mar 10, 2026
ff13fda
fix: virtiofs compatibility for sed and line endings
gabi-simons Mar 10, 2026
dd92577
feat: guide user through Docker Desktop API key setup and verify
gabi-simons Mar 10, 2026
ef7afc4
feat: add detailed API key setup guide for sandbox authentication
gabi-simons Mar 10, 2026
1d5247f
feat: add API key guidance to non-sandbox setup path too
gabi-simons Mar 10, 2026
61fd784
simplify: remove API key verification step from sandbox setup
gabi-simons Mar 10, 2026
1245afa
simplify: unified auth flow, remove proxy-managed option
gabi-simons Mar 10, 2026
8b2e8d5
feat: sandbox DinD runtime — tmpfs mounts, tar pipe, single-turn cont…
gabi-simons Mar 10, 2026
482cc36
refactor: remove /setup-docker-sandbox — /setup auto-detects sandbox
gabi-simons Mar 10, 2026
910476f
docs: simplify sandbox README — /setup handles everything
gabi-simons Mar 10, 2026
e1509a3
docs: simplify Docker Sandbox section in README
gabi-simons Mar 10, 2026
68ee027
style: fix prettier formatting in sandbox files
gabi-simons Mar 10, 2026
031a886
fix: add IS_SANDBOX to config mock in container-runner tests
gabi-simons Mar 10, 2026
2f7a964
fix: sandbox E2E issues — merge conflicts, verify, telegram proxy
gabi-simons Mar 10, 2026
e2a6b1a
feat: auto-launch sandbox after creation in setup script
gabi-simons Mar 11, 2026
3d4b314
fix: prevent stdin consumption in curl|bash setup script
gabi-simons Mar 11, 2026
a2f89bf
fix: auto-confirm workspace creation in sandbox setup
gabi-simons Mar 11, 2026
c659d59
fix: use Windows-accessible path for workspace on WSL
gabi-simons Mar 11, 2026
286a019
fix: prevent cmd.exe from consuming stdin in curl|bash on WSL
gabi-simons Mar 11, 2026
8709f6c
fix: pass Windows-native path to docker sandbox create on WSL
gabi-simons Mar 11, 2026
f980ed9
fix: pull latest code when workspace already exists
gabi-simons Mar 11, 2026
825e1e6
fix: reset workspace before pull to handle dirty/conflicted state
gabi-simons Mar 11, 2026
9481bf5
revert: skip update on existing workspace, just continue
gabi-simons Mar 11, 2026
afefd63
fix: attach TTY for docker sandbox run in curl|bash context
gabi-simons Mar 11, 2026
a034541
fix: don't auto-launch sandbox, print run command instead
gabi-simons Mar 11, 2026
5013499
fix: launch sandbox in new terminal window on WSL
gabi-simons Mar 11, 2026
0bd1d3b
fix: launch sandbox via cmd window to avoid start parsing issues
gabi-simons Mar 11, 2026
8d69756
refactor: pre-apply sandbox proxy patches, remove runtime patching
gabi-simons Mar 11, 2026
208380a
fix: remove API key prompt from setup script, keep in /setup flow
gabi-simons Mar 11, 2026
543c94c
cleanup: remove dead sandbox.ts, add https-proxy-agent install
gabi-simons Mar 11, 2026
a261942
Merge remote-tracking branch 'origin/main' into feature/sandbox-setup…
gabi-simons Mar 11, 2026
eba78fb
feat: global proxy bootstrap for sandbox, remove per-library proxy hacks
gabi-simons Mar 11, 2026
ee1793f
fix: prevent UTF-16 .env and badge merge conflicts in sandbox setup
gabi-simons Mar 11, 2026
7b6c716
fix: skip bootstrap step in sandbox when init.sh already ran
gabi-simons Mar 11, 2026
103be78
fix: revert monkey-patch, keep simple globalAgent approach
gabi-simons Mar 11, 2026
9489dbe
fix: commit sandbox proxy deps in init.sh to prevent merge conflicts
gabi-simons Mar 11, 2026
4cbfe11
refactor: strip init.sh to sandbox-only prep, let /setup handle build
gabi-simons Mar 11, 2026
58be075
perf: pre-compile TypeScript in Docker build, not at container start
gabi-simons Mar 11, 2026
0832847
fix: virtiofs symlink workaround in init.sh, hardcode IS_SANDBOX
gabi-simons Mar 12, 2026
da36e11
fix: sandbox proxy compatibility and orphan cleanup safety
gabi-simons Mar 12, 2026
7d93f74
fix: esbuild binary copy, orphan cleanup, proxy forwarding
gabi-simons Mar 12, 2026
89ffc19
fix: commit all tracked changes in init.sh for clean channel merges
gabi-simons Mar 12, 2026
d284045
fix: update add-whatsapp skill remote URL to docker-sandbox repo
Gabisimons Mar 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/skills/add-whatsapp/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ git remote -v
If `whatsapp` is missing, add it:

```bash
git remote add whatsapp https://github.com/qwibitai/nanoclaw-whatsapp.git
git remote add whatsapp https://github.com/qwibitai/nanoclaw-docker-sandbox.git
```

### Merge the skill branch
Expand Down
64 changes: 55 additions & 9 deletions .claude/skills/setup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,27 @@ Run setup steps automatically. Only pause when user action is required (channel

**UX Note:** Use `AskUserQuestion` for all user-facing questions.

## 0. Git & Fork Setup
## 0. Sandbox Bootstrap (conditional)

Check if running inside a Docker Sandbox by looking for `sandbox/init.sh` AND the file `$HOME/.nanoclaw-workspace` does NOT exist (meaning init hasn't run yet):

```bash
test -f sandbox/init.sh && ! test -f "$HOME/.nanoclaw-initialized"
```

**If both conditions are true** (sandbox init script exists and hasn't been run yet):

Tell the user: "Detected Docker Sandbox environment. Running initial setup..." Then run:

```bash
bash sandbox/init.sh
```

This configures the sandbox proxy, fixes CRLF line endings, and installs npm packages (using a /tmp workaround for virtiofs symlink limitations). It takes 2-5 minutes. Run it in the background with a long timeout and wait for completion, then continue to step 1.

**If the conditions are false** (not a sandbox, or already initialized): skip this step.

## 0b. Git & Fork Setup

Check the git remote configuration to ensure the user has a fork and upstream is configured.

Expand Down Expand Up @@ -52,7 +72,9 @@ Already configured. Continue.

## 1. Bootstrap (Node.js + Dependencies)

Run `bash setup.sh` and parse the status block.
**If sandbox init already ran** (`test -f "$HOME/.nanoclaw-initialized"`): skip this step entirely — `init.sh` already installed Node dependencies, build tools, and native modules. Jump to step 2.

**Otherwise:** Run `bash setup.sh` and parse the status block.

- If NODE_OK=false → Node.js is missing or too old. Use `AskUserQuestion: Would you like me to install Node.js 22?` If confirmed:
- macOS: `brew install node@22` (if brew available) or install nvm then `nvm install 22`
Expand All @@ -74,7 +96,9 @@ Run `npx tsx setup/index.ts --step environment` and parse the status block.

### 3a. Choose runtime

Check the preflight results for `APPLE_CONTAINER` and `DOCKER`, and the PLATFORM from step 1.
**If IS_SANDBOX=true:** Docker is the only runtime (DinD inside sandbox). Skip the runtime question and go directly to 3a-docker.

**If IS_SANDBOX=false:** Check the preflight results for `APPLE_CONTAINER` and `DOCKER`, and the PLATFORM from step 1.

- PLATFORM=linux → Docker (only option)
- PLATFORM=macos + APPLE_CONTAINER=installed → Use `AskUserQuestion: Docker (cross-platform) or Apple Container (native macOS)?` If Apple Container, run `/convert-to-apple-container` now, then skip to 3c.
Expand Down Expand Up @@ -116,11 +140,25 @@ Run `npx tsx setup/index.ts --step container -- --runtime <chosen>` and parse th

If HAS_ENV=true from step 2, read `.env` and check for `CLAUDE_CODE_OAUTH_TOKEN` or `ANTHROPIC_API_KEY`. If present, confirm with user: keep or reconfigure?

AskUserQuestion: Claude subscription (Pro/Max) vs Anthropic API key?
AskUserQuestion: "How do you want to authenticate with Claude?"
- **Claude subscription (Pro/Max) — recommended:** Use your existing Claude subscription. No separate API key or billing needed.
- **Anthropic API key:** Use an API key from console.anthropic.com (separate billing).

**Subscription:** Tell user to run `claude setup-token` in another terminal (or in the sandbox terminal), copy the token, then paste it when asked. Use `AskUserQuestion` to collect the token, then write it to `.env` yourself using `printf` (NOT `echo`, which may produce UTF-16 on Windows):

```bash
printf 'CLAUDE_CODE_OAUTH_TOKEN=%s\n' '<token>' > .env
```

After writing, verify: `grep -q 'CLAUDE_CODE_OAUTH_TOKEN' .env && echo OK`. If verification fails, the file may have wrong encoding — re-write it with `printf`.

**Subscription:** Tell user to run `claude setup-token` in another terminal, copy the token, add `CLAUDE_CODE_OAUTH_TOKEN=<token>` to `.env`. Do NOT collect the token in chat.
**API key:** Tell the user: "Go to https://console.anthropic.com → API Keys → Create Key. The key starts with `sk-ant-...`". Collect the key via `AskUserQuestion`, then write it yourself:

**API key:** Tell user to add `ANTHROPIC_API_KEY=<key>` to `.env`.
```bash
printf 'ANTHROPIC_API_KEY=%s\n' '<key>' > .env
```

**If IS_SANDBOX=true:** Also copy `.env` to `data/env/env` (`mkdir -p data/env && cp .env data/env/env`).

## 5. Set Up Channels

Expand All @@ -146,12 +184,15 @@ Each skill will:
4. Register the chat with the correct JID format
5. Build and verify

**After all channel skills complete**, install dependencies and rebuild — channel merges may introduce new packages:
**After all channel skills complete**, install dependencies and rebuild:

```bash
npm install && npm run build
npm install --no-bin-links
npm run build
```

The `--no-bin-links` flag prevents symlink errors on virtiofs (Docker sandbox). It is harmless on normal filesystems.

If the build fails, read the error output and fix it (usually a missing dependency). Then continue to step 6.

## 6. Mount Allowlist
Expand All @@ -163,6 +204,10 @@ AskUserQuestion: Agent access to external directories?

## 7. Start Service

**If IS_SANDBOX=true:** Skip the service step. After all setup steps complete, auto-start NanoClaw by running `npm start` in the foreground. Tell the user: "Starting NanoClaw... Send a message to your bot to test." Then skip to step 9.

**If IS_SANDBOX=false (normal setup):**

If service already running: unload first.
- macOS: `launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist`
- Linux: `systemctl --user stop nanoclaw` (or `systemctl stop nanoclaw` if root)
Expand Down Expand Up @@ -196,8 +241,9 @@ Replace `USERNAME` with the actual username (from `whoami`). Run the two `sudo`
Run `npx tsx setup/index.ts --step verify` and parse the status block.

**If STATUS=failed, fix each:**
- SERVICE=not_found and IS_SANDBOX=true → This is expected (sandbox runs in foreground, no service). Not an error.
- 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 7
- SERVICE=not_found (non-sandbox) → 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 5
Expand Down
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Force LF line endings for shell scripts (critical for Docker sandboxes)
*.sh text eol=lf

# Auto-generated badge — always accept incoming version on merge
repo-tokens/badge.svg merge=theirs
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Single Node.js process with skill-based channel system. Channels (WhatsApp, Tele

| Skill | When to Use |
|-------|-------------|
| `/setup` | First-time installation, authentication, service configuration |
| `/setup` | First-time installation, authentication, service configuration (auto-detects Docker Sandbox) |
| `/customize` | Adding channels, integrations, changing behavior |
| `/debug` | Container issues, logs, troubleshooting |
| `/update-nanoclaw` | Bring upstream NanoClaw updates into a customized install |
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ Then run `/setup`. Claude Code handles everything: dependencies, authentication,

> **Note:** Commands prefixed with `/` (like `/setup`, `/add-whatsapp`) are [Claude Code skills](https://code.claude.com/docs/en/skills). Type them inside the `claude` CLI prompt, not in your regular terminal. If you don't have Claude Code installed, get it at [claude.com/product/claude-code](https://claude.com/product/claude-code).

### Docker Sandbox (optional)

Requires [Docker Desktop](https://www.docker.com/products/docker-desktop/) 4.40+ with sandbox support.

```bash
curl -fsSL https://raw.githubusercontent.com/qwibitai/nanoclaw/main/sandbox/setup-sandbox.sh | bash
docker sandbox run claude-nanoclaw-workspace
# then type: /setup
```

## Philosophy

**Small enough to understand.** One process, a few source files and no microservices. If you want to understand the full NanoClaw codebase, just ask Claude Code to walk you through it.
Expand Down
11 changes: 8 additions & 3 deletions container/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ RUN apt-get update && apt-get install -y \
ENV AGENT_BROWSER_EXECUTABLE_PATH=/usr/bin/chromium
ENV PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium

# Sandbox: proxy build args for npm through MITM proxy
ARG http_proxy
ARG https_proxy
RUN npm config set strict-ssl false

# Install agent-browser and claude-code globally
RUN npm install -g agent-browser @anthropic-ai/claude-code

Expand All @@ -45,8 +50,8 @@ RUN npm install
# Copy source code
COPY agent-runner/ ./

# Build TypeScript
RUN npm run build
# Build TypeScript to /app/dist (pre-compiled, not at runtime)
RUN npx tsc --outDir /app/dist && ln -s /app/node_modules /app/dist/node_modules

# Create workspace directories
RUN mkdir -p /workspace/group /workspace/global /workspace/extra /workspace/ipc/messages /workspace/ipc/tasks /workspace/ipc/input
Expand All @@ -55,7 +60,7 @@ RUN mkdir -p /workspace/group /workspace/global /workspace/extra /workspace/ipc/
# Container input (prompt, group info) is passed via stdin JSON.
# Credentials are injected by the host's credential proxy — never passed here.
# Follow-up messages arrive via IPC files in /workspace/ipc/input/
RUN printf '#!/bin/bash\nset -e\ncd /app && npx tsc --outDir /tmp/dist 2>&1 >&2\nln -s /app/node_modules /tmp/dist/node_modules\nchmod -R a-w /tmp/dist\ncat > /tmp/input.json\nnode /tmp/dist/index.js < /tmp/input.json\n' > /app/entrypoint.sh && chmod +x /app/entrypoint.sh
RUN printf '#!/bin/bash\nset -e\ncat > /tmp/input.json\nnode /app/dist/index.js < /tmp/input.json\n' > /app/entrypoint.sh && chmod +x /app/entrypoint.sh

# Set ownership to node user (non-root) for writable directories
RUN chown -R node:node /workspace && chmod 777 /home/node
Expand Down
8 changes: 8 additions & 0 deletions container/agent-runner/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,14 @@ async function main(): Promise<void> {
break;
}

// Single-turn mode: exit after first query (sandbox — IPC doesn't work with tmpfs).
// Must use process.exit() because the Claude SDK keeps background subprocesses
// alive that prevent the Node.js event loop from draining naturally.
if (process.env.NANOCLAW_SINGLE_TURN === '1') {
log('Single-turn mode: exiting after first query');
process.exit(0);
}

// Emit session update so host can track it
writeOutput({ status: 'success', result: null, newSessionId: sessionId });

Expand Down
7 changes: 6 additions & 1 deletion container/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ CONTAINER_RUNTIME="${CONTAINER_RUNTIME:-docker}"
echo "Building NanoClaw agent container image..."
echo "Image: ${IMAGE_NAME}:${TAG}"

${CONTAINER_RUNTIME} build -t "${IMAGE_NAME}:${TAG}" .
# Forward proxy env vars for sandbox builds
BUILD_ARGS=""
[ -n "${http_proxy:-}" ] && BUILD_ARGS="$BUILD_ARGS --build-arg http_proxy=$http_proxy"
[ -n "${https_proxy:-}" ] && BUILD_ARGS="$BUILD_ARGS --build-arg https_proxy=$https_proxy"

${CONTAINER_RUNTIME} build ${BUILD_ARGS} -t "${IMAGE_NAME}:${TAG}" .

echo ""
echo "Build complete!"
Expand Down
46 changes: 46 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"dependencies": {
"better-sqlite3": "^11.8.1",
"cron-parser": "^5.5.0",
"https-proxy-agent": "^8.0.0",
"pino": "^9.6.0",
"pino-pretty": "^13.0.0",
"yaml": "^2.8.2",
Expand Down
Loading
Loading