NanoClaw Setup
Run all commands automatically. Only pause when user action is required (scanning QR codes).
UX Note: When asking the user questions, prefer using the AskUserQuestion tool instead of just outputting text. This integrates with Claude's built-in question/answer system for a better experience.
- Install Dependencies
npm install
- Install Container Runtime
First, detect the platform and check what's available:
echo "Platform: $(uname -s)" which container && echo "Apple Container: installed" || echo "Apple Container: not installed" which docker && docker info >/dev/null 2>&1 && echo "Docker: installed and running" || echo "Docker: not installed or not running"
If NOT on macOS (Linux, etc.)
Apple Container is macOS-only. Use Docker instead.
Tell the user:
You're on Linux, so we'll use Docker for container isolation. Let me set that up now.
Use the /convert-to-docker skill to convert the codebase to Docker, then continue to Section 3.
If on macOS
If Apple Container is already installed: Continue to Section 3.
If Apple Container is NOT installed: Ask the user:
NanoClaw needs a container runtime for isolated agent execution. You have two options:
-
Apple Container (default) - macOS-native, lightweight, designed for Apple silicon
-
Docker - Cross-platform, widely used, works on macOS and Linux
Which would you prefer?
Option A: Apple Container
Tell the user:
Apple Container is required for running agents in isolated environments.
-
Download the latest .pkg from https://github.com/apple/container/releases
-
Double-click to install
-
Run container system start to start the service
Let me know when you've completed these steps.
Wait for user confirmation, then verify:
container system start container --version
Note: NanoClaw automatically starts the Apple Container system when it launches, so you don't need to start it manually after reboots.
Option B: Docker
Tell the user:
You've chosen Docker. Let me set that up now.
Use the /convert-to-docker skill to convert the codebase to Docker, then continue to Section 3.
- Configure Claude Authentication
Ask the user:
Do you want to use your Claude subscription (Pro/Max) or an Anthropic API key?
Option 1: Claude Subscription (Recommended)
Tell the user:
Open another terminal window and run:
claude setup-token
A browser window will open for you to log in. Once authenticated, the token will be displayed in your terminal. Either:
-
Paste it here and I'll add it to .env for you, or
-
Add it to .env yourself as CLAUDE_CODE_OAUTH_TOKEN=<your-token>
If they give you the token, add it to .env :
echo "CLAUDE_CODE_OAUTH_TOKEN=<token>" > .env
Option 2: API Key
Ask if they have an existing key to copy or need to create one.
Copy existing:
grep "^ANTHROPIC_API_KEY=" /path/to/source/.env > .env
Create new:
echo 'ANTHROPIC_API_KEY=' > .env
Tell the user to add their key from https://console.anthropic.com/
Verify:
KEY=$(grep "^ANTHROPIC_API_KEY=" .env | cut -d= -f2) [ -n "$KEY" ] && echo "API key configured: ${KEY:0:10}...${KEY: -4}" || echo "Missing"
- Build Container Image
Build the NanoClaw agent container:
./container/build.sh
This creates the nanoclaw-agent:latest image with Node.js, Chromium, Claude Code CLI, and agent-browser.
Verify the build succeeded by running a simple test (this auto-detects which runtime you're using):
if which docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then echo '{}' | docker run -i --entrypoint /bin/echo nanoclaw-agent:latest "Container OK" || echo "Container build failed" else echo '{}' | container run -i --entrypoint /bin/echo nanoclaw-agent:latest "Container OK" || echo "Container build failed" fi
- WhatsApp Authentication
USER ACTION REQUIRED
Run the authentication script:
npm run auth
Tell the user:
A QR code will appear. On your phone:
-
Open WhatsApp
-
Tap Settings → Linked Devices → Link a Device
-
Scan the QR code
Wait for the script to output "Successfully authenticated" then continue.
If it says "Already authenticated", skip to the next step.
- Configure Assistant Name
Ask the user:
What trigger word do you want to use? (default: Andy )
Messages starting with @TriggerWord will be sent to Claude.
If they choose something other than Andy , update it in these places:
-
groups/CLAUDE.md
-
Change "# Andy" and "You are Andy" to the new name
-
groups/main/CLAUDE.md
-
Same changes at the top
-
data/registered_groups.json
-
Use @NewName as the trigger when registering groups
Store their choice - you'll use it when creating the registered_groups.json and when telling them how to test.
- Understand the Security Model
Before registering your main channel, you need to understand an important security concept.
Use the AskUserQuestion tool to present this:
Important: Your "main" channel is your admin control portal.
The main channel has elevated privileges:
-
Can see messages from ALL other registered groups
-
Can manage and delete tasks across all groups
-
Can write to global memory that all groups can read
-
Has read-write access to the entire NanoClaw project
Recommendation: Use your personal "Message Yourself" chat or a solo WhatsApp group as your main channel. This ensures only you have admin control.
Question: Which setup will you use for your main channel?
Options:
-
Personal chat (Message Yourself) - Recommended
-
Solo WhatsApp group (just me)
-
Group with other people (I understand the security implications)
If they choose option 3, ask a follow-up:
You've chosen a group with other people. This means everyone in that group will have admin privileges over NanoClaw.
Are you sure you want to proceed? The other members will be able to:
-
Read messages from your other registered chats
-
Schedule and manage tasks
-
Access any directories you've mounted
Options:
-
Yes, I understand and want to proceed
-
No, let me use a personal chat or solo group instead
- Register Main Channel
Ask the user:
Do you want to use your personal chat (message yourself) or a WhatsApp group as your main control channel?
For personal chat:
Send any message to yourself in WhatsApp (the "Message Yourself" chat). Tell me when done.
For group:
Send any message in the WhatsApp group you want to use as your main channel. Tell me when done.
After user confirms, start the app briefly to capture the message:
timeout 10 npm run dev || true
Then find the JID from the database:
For personal chat (ends with @s.whatsapp.net)
sqlite3 store/messages.db "SELECT DISTINCT chat_jid FROM messages WHERE chat_jid LIKE '%@s.whatsapp.net' ORDER BY timestamp DESC LIMIT 5"
For group (ends with @g.us)
sqlite3 store/messages.db "SELECT DISTINCT chat_jid FROM messages WHERE chat_jid LIKE '%@g.us' ORDER BY timestamp DESC LIMIT 5"
Create/update data/registered_groups.json using the JID from above and the assistant name from step 5:
{ "JID_HERE": { "name": "main", "folder": "main", "trigger": "@ASSISTANT_NAME", "added_at": "CURRENT_ISO_TIMESTAMP" } }
Ensure the groups folder exists:
mkdir -p groups/main/logs
- Configure External Directory Access (Mount Allowlist)
Ask the user:
Do you want the agent to be able to access any directories outside the NanoClaw project?
Examples: Git repositories, project folders, documents you want Claude to work on.
Note: This is optional. Without configuration, agents can only access their own group folders.
If no, create an empty allowlist to make this explicit:
mkdir -p ~/.config/nanoclaw cat > ~/.config/nanoclaw/mount-allowlist.json << 'EOF' { "allowedRoots": [], "blockedPatterns": [], "nonMainReadOnly": true } EOF echo "Mount allowlist created - no external directories allowed"
Skip to the next step.
If yes, ask follow-up questions:
9a. Collect Directory Paths
Ask the user:
Which directories do you want to allow access to?
You can specify:
-
A parent folder like ~/projects (allows access to anything inside)
-
Specific paths like ~/repos/my-app
List them one per line, or give me a comma-separated list.
For each directory they provide, ask:
Should [directory] be read-write (agents can modify files) or read-only?
Read-write is needed for: code changes, creating files, git commits Read-only is safer for: reference docs, config examples, templates
9b. Configure Non-Main Group Access
Ask the user:
Should non-main groups (other WhatsApp chats you add later) be restricted to read-only access even if read-write is allowed for the directory?
Recommended: Yes - this prevents other groups from modifying files even if you grant them access to a directory.
9c. Create the Allowlist
Create the allowlist file based on their answers:
mkdir -p ~/.config/nanoclaw
Then write the JSON file. Example for a user who wants ~/projects (read-write) and ~/docs (read-only) with non-main read-only:
cat > /.config/nanoclaw/mount-allowlist.json << 'EOF'
{
"allowedRoots": [
{
"path": "/projects",
"allowReadWrite": true,
"description": "Development projects"
},
{
"path": "~/docs",
"allowReadWrite": false,
"description": "Reference documents"
}
],
"blockedPatterns": [],
"nonMainReadOnly": true
}
EOF
Verify the file:
cat ~/.config/nanoclaw/mount-allowlist.json
Tell the user:
Mount allowlist configured. The following directories are now accessible:
-
~/projects (read-write)
-
~/docs (read-only)
Security notes:
-
Sensitive paths (.ssh , .gnupg , .aws , credentials) are always blocked
-
This config file is stored outside the project, so agents cannot modify it
-
Changes require restarting the NanoClaw service
To grant a group access to a directory, add it to their config in data/registered_groups.json :
"containerConfig": { "additionalMounts": [ { "hostPath": "~/projects/my-app", "containerPath": "my-app", "readonly": false } ] }
- Configure launchd Service
Generate the plist file with correct paths automatically:
NODE_PATH=$(which node) PROJECT_PATH=$(pwd) HOME_PATH=$HOME
cat > ~/Library/LaunchAgents/com.nanoclaw.plist << EOF <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.nanoclaw</string> <key>ProgramArguments</key> <array> <string>${NODE_PATH}</string> <string>${PROJECT_PATH}/dist/index.js</string> </array> <key>WorkingDirectory</key> <string>${PROJECT_PATH}</string> <key>RunAtLoad</key> <true/> <key>KeepAlive</key> <true/> <key>EnvironmentVariables</key> <dict> <key>PATH</key> <string>/usr/local/bin:/usr/bin:/bin:${HOME_PATH}/.local/bin</string> <key>HOME</key> <string>${HOME_PATH}</string> </dict> <key>StandardOutPath</key> <string>${PROJECT_PATH}/logs/nanoclaw.log</string> <key>StandardErrorPath</key> <string>${PROJECT_PATH}/logs/nanoclaw.error.log</string> </dict> </plist> EOF
echo "Created launchd plist with:" echo " Node: ${NODE_PATH}" echo " Project: ${PROJECT_PATH}"
Build and start the service:
npm run build mkdir -p logs launchctl load ~/Library/LaunchAgents/com.nanoclaw.plist
Verify it's running:
launchctl list | grep nanoclaw
- Test
Tell the user (using the assistant name they configured):
Send @ASSISTANT_NAME hello in your registered chat.
Check the logs:
tail -f logs/nanoclaw.log
The user should receive a response in WhatsApp.
Troubleshooting
Service not starting: Check logs/nanoclaw.error.log
Container agent fails with "Claude Code process exited with code 1":
-
Ensure the container runtime is running:
-
Apple Container: container system start
-
Docker: docker info (start Docker Desktop on macOS, or sudo systemctl start docker on Linux)
-
Check container logs: cat groups/main/logs/container-*.log | tail -50
No response to messages:
-
Verify the trigger pattern matches (e.g., @AssistantName at start of message)
-
Check that the chat JID is in data/registered_groups.json
-
Check logs/nanoclaw.log for errors
WhatsApp disconnected:
-
The service will show a macOS notification
-
Run npm run auth to re-authenticate
-
Restart the service: launchctl kickstart -k gui/$(id -u)/com.nanoclaw
Unload service:
launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist