NanoClaw Setup
Run all commands automatically. Only pause when user action is required (creating bot tokens, sending a test message).
1. Install Dependencies
2. Install Container Runtime
First, detect the platform and check what's available:
bash
1echo "Platform: $(uname -s)"
2which container && echo "Apple Container: installed" || echo "Apple Container: not installed"
3which 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:
bash
1container system start
2container --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.
3. Configure Codex Authentication
Ask the user if they want to use Codex OAuth (preferred) or an API key.
Option A: Codex OAuth (recommended)
Verify the user is logged in with Codex CLI and copy the auth file into the main session:
bash
1codex login status || true
2mkdir -p data/sessions/main/.codex
3cp ~/.codex/auth.json data/sessions/main/.codex/auth.json
4chmod 600 data/sessions/main/.codex/auth.json
Option B: API key
Create .env and set the key:
bash
1echo 'CODEX_API_KEY=' > .env
Then verify:
bash
1KEY=$(grep "^CODEX_API_KEY=" .env | cut -d= -f2)
2[ -n "$KEY" ] && echo "API key configured: ${KEY:0:10}...${KEY: -4}" || echo "Missing"
4. Build Container Image
Build the NanoClaw agent container:
bash
1./container/build.sh
This creates the nanoclaw-agent:latest image with Node.js, Chromium, Codex CLI, and agent-browser.
Verify the build succeeded by running a simple test (this auto-detects which runtime you're using):
bash
1if which docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then
2 echo '{}' | docker run -i --entrypoint /bin/echo nanoclaw-agent:latest "Container OK" || echo "Container build failed"
3else
4 echo '{}' | container run -i --entrypoint /bin/echo nanoclaw-agent:latest "Container OK" || echo "Container build failed"
5fi
5. Telegram Bot Setup
USER ACTION REQUIRED
Ask the user to create a Telegram bot with @BotFather and provide the bot token.
Store the token in .env:
bash
1echo "TELEGRAM_BOT_TOKEN=${TOKEN}" >> .env
Validate the token:
bash
1TOKEN=$(grep "^TELEGRAM_BOT_TOKEN=" .env | cut -d= -f2)
2curl -s "https://api.telegram.org/bot${TOKEN}/getMe"
Optional: If the user wants to restrict access, set allowlists:
bash
1echo "TELEGRAM_ALLOWED_USER_IDS=123456789" >> .env
2echo "TELEGRAM_ALLOWED_USERNAMES=yourusername" >> .env
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 Codex.
If they choose something other than Andy, update it in these places:
groups/MEMORY.md - Change "# Andy" and "You are Andy" to the new name
groups/main/MEMORY.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.
7. Register Main Channel
For Telegram, the first private DM can auto-register as the main channel.
Start the app briefly and ask the user to send a DM to the bot while it's running:
bash
1timeout 15 npm run dev || true
If auto-registration is disabled (TELEGRAM_AUTO_REGISTER=false), manually register the chat by querying the DB and writing data/registered_groups.json:
bash
1sqlite3 store/messages.db "SELECT DISTINCT chat_jid FROM messages WHERE chat_jid LIKE 'telegram:%' ORDER BY timestamp DESC LIMIT 5"
Then update data/registered_groups.json:
json
1{
2 "telegram:CHAT_ID": {
3 "name": "main",
4 "folder": "main",
5 "trigger": "@ASSISTANT_NAME",
6 "added_at": "CURRENT_ISO_TIMESTAMP"
7 }
8}
Ensure the groups folder exists:
bash
1mkdir -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 Codex 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
1mkdir -p ~/.config/nanoclaw
2cat > ~/.config/nanoclaw/mount-allowlist.json << 'EOF'
3{
4 "allowedRoots": [],
5 "blockedPatterns": [],
6 "nonMainReadOnly": true
7}
8EOF
9echo "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 Telegram 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
1mkdir -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
1cat > ~/.config/nanoclaw/mount-allowlist.json << 'EOF'
2{
3 "allowedRoots": [
4 {
5 "path": "~/projects",
6 "allowReadWrite": true,
7 "description": "Development projects"
8 },
9 {
10 "path": "~/docs",
11 "allowReadWrite": false,
12 "description": "Reference documents"
13 }
14 ],
15 "blockedPatterns": [],
16 "nonMainReadOnly": true
17}
18EOF
Verify the file:
bash
1cat ~/.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
1"containerConfig": {
2 "additionalMounts": [
3 { "hostPath": "~/projects/my-app", "containerPath": "my-app", "readonly": false }
4 ]
5}
9. Configure launchd Service
Generate the plist file with correct paths automatically:
bash
1NODE_PATH=$(which node)
2PROJECT_PATH=$(pwd)
3HOME_PATH=$HOME
4
5cat > ~/Library/LaunchAgents/com.nanoclaw.plist << EOF
6<?xml version="1.0" encoding="UTF-8"?>
7<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
8<plist version="1.0">
9<dict>
10 <key>Label</key>
11 <string>com.nanoclaw</string>
12 <key>ProgramArguments</key>
13 <array>
14 <string>${NODE_PATH}</string>
15 <string>${PROJECT_PATH}/dist/index.js</string>
16 </array>
17 <key>WorkingDirectory</key>
18 <string>${PROJECT_PATH}</string>
19 <key>RunAtLoad</key>
20 <true/>
21 <key>KeepAlive</key>
22 <true/>
23 <key>EnvironmentVariables</key>
24 <dict>
25 <key>PATH</key>
26 <string>/usr/local/bin:/usr/bin:/bin:${HOME_PATH}/.local/bin</string>
27 <key>HOME</key>
28 <string>${HOME_PATH}</string>
29 </dict>
30 <key>StandardOutPath</key>
31 <string>${PROJECT_PATH}/logs/nanoclaw.log</string>
32 <key>StandardErrorPath</key>
33 <string>${PROJECT_PATH}/logs/nanoclaw.error.log</string>
34</dict>
35</plist>
36EOF
37
38echo "Created launchd plist with:"
39echo " Node: ${NODE_PATH}"
40echo " Project: ${PROJECT_PATH}"
Build and start the service:
bash
1npm run build
2mkdir -p logs
3launchctl load ~/Library/LaunchAgents/com.nanoclaw.plist
Verify it's running:
bash
1launchctl 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
1tail -f logs/nanoclaw.log
The user should receive a response in Telegram.
Troubleshooting
Service not starting: Check logs/nanoclaw.error.log
Container agent fails with "Codex CLI 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
Telegram bot not responding:
- Verify
TELEGRAM_BOT_TOKEN is correct
- Check
logs/nanoclaw.log for errors
- Restart the service:
launchctl kickstart -k gui/$(id -u)/com.nanoclaw
Unload service:
bash
1launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist