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.
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
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
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}
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