- Remove unused claude-agent-sdk from host deps (only used in container) - Remove dead scheduler MCP config (built into IPC) - Remove unused eslint script - Add clear error message when Apple Container fails to start - Auto-generate launchd plist with real paths in setup skill - Standardize Node.js version to 20+ everywhere Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
410 lines
11 KiB
Markdown
410 lines
11 KiB
Markdown
---
|
|
name: setup
|
|
description: Run initial NanoClaw setup. Use when user wants to install dependencies, authenticate WhatsApp/Gmail, register their main channel, or start the background services. Triggers on "setup", "install", "configure nanoclaw", or first-time setup requests.
|
|
---
|
|
|
|
# NanoClaw Setup
|
|
|
|
Run all commands automatically. Only pause when user action is required (scanning QR codes).
|
|
|
|
## 1. Install Dependencies
|
|
|
|
```bash
|
|
npm install
|
|
```
|
|
|
|
## 2. Install Apple Container
|
|
|
|
Check if Apple Container is installed:
|
|
|
|
```bash
|
|
which container && container --version || echo "Not installed"
|
|
```
|
|
|
|
If not installed, tell the user:
|
|
> Apple Container is required for running agents in isolated environments.
|
|
>
|
|
> 1. Download the latest `.pkg` from https://github.com/apple/container/releases
|
|
> 2. Double-click to install
|
|
> 3. Run `container system start` to start the service
|
|
>
|
|
> Let me know when you've completed these steps.
|
|
|
|
Wait for user confirmation, then verify:
|
|
|
|
```bash
|
|
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.
|
|
|
|
## 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)
|
|
|
|
Ask the user:
|
|
> Want me to grab the OAuth token from your current Claude session?
|
|
|
|
If yes:
|
|
```bash
|
|
TOKEN=$(cat ~/.claude/.credentials.json 2>/dev/null | jq -r '.claudeAiOauth.accessToken // empty')
|
|
if [ -n "$TOKEN" ]; then
|
|
echo "CLAUDE_CODE_OAUTH_TOKEN=$TOKEN" > .env
|
|
echo "Token configured: ${TOKEN:0:20}...${TOKEN: -4}"
|
|
else
|
|
echo "No token found - are you logged in to Claude Code?"
|
|
fi
|
|
```
|
|
|
|
If the token wasn't found, tell the user:
|
|
> Run `claude` in another terminal and log in first, then come back here.
|
|
|
|
### Option 2: API Key
|
|
|
|
Ask if they have an existing key to copy or need to create one.
|
|
|
|
**Copy existing:**
|
|
```bash
|
|
grep "^ANTHROPIC_API_KEY=" /path/to/source/.env > .env
|
|
```
|
|
|
|
**Create new:**
|
|
```bash
|
|
echo 'ANTHROPIC_API_KEY=' > .env
|
|
```
|
|
|
|
Tell the user to add their key from https://console.anthropic.com/
|
|
|
|
**Verify:**
|
|
```bash
|
|
KEY=$(grep "^ANTHROPIC_API_KEY=" .env | cut -d= -f2)
|
|
[ -n "$KEY" ] && echo "API key configured: ${KEY:0:10}...${KEY: -4}" || echo "Missing"
|
|
```
|
|
|
|
## 4. Build Container Image
|
|
|
|
Build the NanoClaw agent container:
|
|
|
|
```bash
|
|
./container/build.sh
|
|
```
|
|
|
|
This creates the `nanoclaw-agent:latest` image with Node.js, Chromium, Claude Code CLI, and agent-browser.
|
|
|
|
Verify the build succeeded (the `container images` command may not work due to a plugin issue, so we verify by running a simple test):
|
|
|
|
```bash
|
|
echo '{}' | container run -i --entrypoint /bin/echo nanoclaw-agent:latest "Container OK" || echo "Container build failed"
|
|
```
|
|
|
|
## 5. WhatsApp Authentication
|
|
|
|
**USER ACTION REQUIRED**
|
|
|
|
Run the authentication script:
|
|
|
|
```bash
|
|
npm run auth
|
|
```
|
|
|
|
Tell the user:
|
|
> A QR code will appear. On your phone:
|
|
> 1. Open WhatsApp
|
|
> 2. Tap **Settings → Linked Devices → Link a Device**
|
|
> 3. Scan the QR code
|
|
|
|
Wait for the script to output "Successfully authenticated" then continue.
|
|
|
|
If it says "Already authenticated", skip to the next step.
|
|
|
|
## 6. 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:
|
|
1. `groups/CLAUDE.md` - Change "# Andy" and "You are Andy" to the new name
|
|
2. `groups/main/CLAUDE.md` - Same changes at the top
|
|
3. `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.
|
|
|
|
## 7. 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:
|
|
|
|
```bash
|
|
timeout 10 npm run dev || true
|
|
```
|
|
|
|
Then find the JID from the database:
|
|
|
|
```bash
|
|
# 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:
|
|
```json
|
|
{
|
|
"JID_HERE": {
|
|
"name": "main",
|
|
"folder": "main",
|
|
"trigger": "@ASSISTANT_NAME",
|
|
"added_at": "CURRENT_ISO_TIMESTAMP"
|
|
}
|
|
}
|
|
```
|
|
|
|
Ensure the groups folder exists:
|
|
```bash
|
|
mkdir -p groups/main/logs
|
|
```
|
|
|
|
## 8. 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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
### 8a. 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
|
|
|
|
### 8b. 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.
|
|
|
|
### 8c. Create the Allowlist
|
|
|
|
Create the allowlist file based on their answers:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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`:
|
|
> ```json
|
|
> "containerConfig": {
|
|
> "additionalMounts": [
|
|
> { "hostPath": "~/projects/my-app", "containerPath": "my-app", "readonly": false }
|
|
> ]
|
|
> }
|
|
> ```
|
|
|
|
## 9. Gmail Authentication (Optional)
|
|
|
|
Ask the user:
|
|
> Do you want to enable Gmail integration for reading/sending emails?
|
|
>
|
|
> **Note:** This requires setting up Google Cloud Platform OAuth credentials, which involves:
|
|
> 1. Creating a GCP project
|
|
> 2. Enabling the Gmail API
|
|
> 3. Creating OAuth 2.0 credentials
|
|
> 4. Downloading a credentials file
|
|
>
|
|
> This takes about 5-10 minutes. Skip if you don't need email integration.
|
|
|
|
If yes, guide them through the prerequisites:
|
|
1. Go to https://console.cloud.google.com
|
|
2. Create a new project (or use an existing one)
|
|
3. Enable the Gmail API (APIs & Services → Enable APIs → search "Gmail API")
|
|
4. Create OAuth 2.0 credentials (APIs & Services → Credentials → Create Credentials → OAuth client ID → Desktop app)
|
|
5. Download the JSON file and save to `~/.gmail-mcp/gcp-oauth.keys.json`
|
|
|
|
Then run:
|
|
```bash
|
|
npx -y @gongrzhe/server-gmail-autoauth-mcp
|
|
```
|
|
|
|
This will open a browser for OAuth consent. After authorization, credentials are cached.
|
|
|
|
## 10. Configure launchd Service
|
|
|
|
Generate the plist file with correct paths automatically:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
npm run build
|
|
mkdir -p logs
|
|
launchctl load ~/Library/LaunchAgents/com.nanoclaw.plist
|
|
```
|
|
|
|
Verify it's running:
|
|
```bash
|
|
launchctl list | grep nanoclaw
|
|
```
|
|
|
|
## 11. Test
|
|
|
|
Tell the user (using the assistant name they configured):
|
|
> Send `@ASSISTANT_NAME hello` in your registered chat.
|
|
|
|
Check the logs:
|
|
```bash
|
|
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 Apple Container is running: `container system start`
|
|
- 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**:
|
|
```bash
|
|
launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist
|
|
```
|