* feat: add Telegram channel with agent swarm support Add Telegram as a messaging channel that can run alongside WhatsApp or standalone (TELEGRAM_ONLY mode). Includes bot pool support for agent swarms where each subagent appears as a different bot identity in the group. - Add grammy dependency for Telegram Bot API - Route messages through tg: JID prefix convention - Add storeMessageDirect for non-Baileys channels - Add sender field to IPC send_message for swarm identity - Support TELEGRAM_BOT_TOKEN, TELEGRAM_ONLY, TELEGRAM_BOT_POOL config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add index.ts refactor plan Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: extract channel abstraction, IPC, and router from index.ts Break the 1088-line monolith into focused modules: - src/channels/whatsapp.ts: WhatsAppChannel class implementing Channel interface - src/ipc.ts: IPC watcher and task processing with dependency injection - src/router.ts: message formatting, outbound routing, channel lookup - src/types.ts: Channel interface, OnInboundMessage, OnChatMetadata types Also adds regression test suite (98 tests), updates all documentation and skill files to reflect the new architecture. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * ci: add test workflow for PRs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: remove accidentally committed pool-bot assets Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ci): remove grammy from base dependencies Grammy is installed by the /add-telegram skill, not a base dependency. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
628 lines
26 KiB
Markdown
628 lines
26 KiB
Markdown
# NanoClaw Specification
|
|
|
|
A personal Claude assistant accessible via WhatsApp, with persistent memory per conversation, scheduled tasks, and email integration.
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Architecture](#architecture)
|
|
2. [Folder Structure](#folder-structure)
|
|
3. [Configuration](#configuration)
|
|
4. [Memory System](#memory-system)
|
|
5. [Session Management](#session-management)
|
|
6. [Message Flow](#message-flow)
|
|
7. [Commands](#commands)
|
|
8. [Scheduled Tasks](#scheduled-tasks)
|
|
9. [MCP Servers](#mcp-servers)
|
|
10. [Deployment](#deployment)
|
|
11. [Security Considerations](#security-considerations)
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
│ HOST (macOS) │
|
|
│ (Main Node.js Process) │
|
|
├─────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────┐ ┌────────────────────┐ │
|
|
│ │ WhatsApp │────────────────────▶│ SQLite Database │ │
|
|
│ │ (baileys) │◀────────────────────│ (messages.db) │ │
|
|
│ └──────────────┘ store/send └─────────┬──────────┘ │
|
|
│ │ │
|
|
│ ┌────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌──────────────────┐ ┌──────────────────┐ ┌───────────────┐ │
|
|
│ │ Message Loop │ │ Scheduler Loop │ │ IPC Watcher │ │
|
|
│ │ (polls SQLite) │ │ (checks tasks) │ │ (file-based) │ │
|
|
│ └────────┬─────────┘ └────────┬─────────┘ └───────────────┘ │
|
|
│ │ │ │
|
|
│ └───────────┬───────────┘ │
|
|
│ │ spawns container │
|
|
│ ▼ │
|
|
├─────────────────────────────────────────────────────────────────────┤
|
|
│ APPLE CONTAINER (Linux VM) │
|
|
├─────────────────────────────────────────────────────────────────────┤
|
|
│ ┌──────────────────────────────────────────────────────────────┐ │
|
|
│ │ AGENT RUNNER │ │
|
|
│ │ │ │
|
|
│ │ Working directory: /workspace/group (mounted from host) │ │
|
|
│ │ Volume mounts: │ │
|
|
│ │ • groups/{name}/ → /workspace/group │ │
|
|
│ │ • groups/global/ → /workspace/global/ (non-main only) │ │
|
|
│ │ • data/sessions/{group}/.claude/ → /home/node/.claude/ │ │
|
|
│ │ • Additional dirs → /workspace/extra/* │ │
|
|
│ │ │ │
|
|
│ │ Tools (all groups): │ │
|
|
│ │ • Bash (safe - sandboxed in container!) │ │
|
|
│ │ • Read, Write, Edit, Glob, Grep (file operations) │ │
|
|
│ │ • WebSearch, WebFetch (internet access) │ │
|
|
│ │ • agent-browser (browser automation) │ │
|
|
│ │ • mcp__nanoclaw__* (scheduler tools via IPC) │ │
|
|
│ │ │ │
|
|
│ └──────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└──────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Technology Stack
|
|
|
|
| Component | Technology | Purpose |
|
|
|-----------|------------|---------|
|
|
| WhatsApp Connection | Node.js (@whiskeysockets/baileys) | Connect to WhatsApp, send/receive messages |
|
|
| Message Storage | SQLite (better-sqlite3) | Store messages for polling |
|
|
| Container Runtime | Apple Container | Isolated Linux VMs for agent execution |
|
|
| Agent | @anthropic-ai/claude-agent-sdk (0.2.29) | Run Claude with tools and MCP servers |
|
|
| Browser Automation | agent-browser + Chromium | Web interaction and screenshots |
|
|
| Runtime | Node.js 20+ | Host process for routing and scheduling |
|
|
|
|
---
|
|
|
|
## Folder Structure
|
|
|
|
```
|
|
nanoclaw/
|
|
├── CLAUDE.md # Project context for Claude Code
|
|
├── docs/
|
|
│ ├── SPEC.md # This specification document
|
|
│ ├── REQUIREMENTS.md # Architecture decisions
|
|
│ └── SECURITY.md # Security model
|
|
├── README.md # User documentation
|
|
├── package.json # Node.js dependencies
|
|
├── tsconfig.json # TypeScript configuration
|
|
├── .mcp.json # MCP server configuration (reference)
|
|
├── .gitignore
|
|
│
|
|
├── src/
|
|
│ ├── index.ts # Orchestrator: state, message loop, agent invocation
|
|
│ ├── channels/
|
|
│ │ └── whatsapp.ts # WhatsApp connection, auth, send/receive
|
|
│ ├── ipc.ts # IPC watcher and task processing
|
|
│ ├── router.ts # Message formatting and outbound routing
|
|
│ ├── config.ts # Configuration constants
|
|
│ ├── types.ts # TypeScript interfaces (includes Channel)
|
|
│ ├── logger.ts # Pino logger setup
|
|
│ ├── db.ts # SQLite database initialization and queries
|
|
│ ├── group-queue.ts # Per-group queue with global concurrency limit
|
|
│ ├── mount-security.ts # Mount allowlist validation for containers
|
|
│ ├── whatsapp-auth.ts # Standalone WhatsApp authentication
|
|
│ ├── task-scheduler.ts # Runs scheduled tasks when due
|
|
│ └── container-runner.ts # Spawns agents in Apple Containers
|
|
│
|
|
├── container/
|
|
│ ├── Dockerfile # Container image (runs as 'node' user, includes Claude Code CLI)
|
|
│ ├── build.sh # Build script for container image
|
|
│ ├── agent-runner/ # Code that runs inside the container
|
|
│ │ ├── package.json
|
|
│ │ ├── tsconfig.json
|
|
│ │ └── src/
|
|
│ │ ├── index.ts # Entry point (query loop, IPC polling, session resume)
|
|
│ │ └── ipc-mcp-stdio.ts # Stdio-based MCP server for host communication
|
|
│ └── skills/
|
|
│ └── agent-browser.md # Browser automation skill
|
|
│
|
|
├── dist/ # Compiled JavaScript (gitignored)
|
|
│
|
|
├── .claude/
|
|
│ └── skills/
|
|
│ ├── setup/SKILL.md # /setup - First-time installation
|
|
│ ├── customize/SKILL.md # /customize - Add capabilities
|
|
│ ├── debug/SKILL.md # /debug - Container debugging
|
|
│ ├── add-telegram/SKILL.md # /add-telegram - Telegram channel
|
|
│ ├── add-gmail/SKILL.md # /add-gmail - Gmail integration
|
|
│ ├── add-voice-transcription/ # /add-voice-transcription - Whisper
|
|
│ ├── x-integration/SKILL.md # /x-integration - X/Twitter
|
|
│ ├── convert-to-docker/SKILL.md # /convert-to-docker - Docker runtime
|
|
│ └── add-parallel/SKILL.md # /add-parallel - Parallel agents
|
|
│
|
|
├── groups/
|
|
│ ├── CLAUDE.md # Global memory (all groups read this)
|
|
│ ├── main/ # Self-chat (main control channel)
|
|
│ │ ├── CLAUDE.md # Main channel memory
|
|
│ │ └── logs/ # Task execution logs
|
|
│ └── {Group Name}/ # Per-group folders (created on registration)
|
|
│ ├── CLAUDE.md # Group-specific memory
|
|
│ ├── logs/ # Task logs for this group
|
|
│ └── *.md # Files created by the agent
|
|
│
|
|
├── store/ # Local data (gitignored)
|
|
│ ├── auth/ # WhatsApp authentication state
|
|
│ └── messages.db # SQLite database (messages, chats, scheduled_tasks, task_run_logs, registered_groups, sessions, router_state)
|
|
│
|
|
├── data/ # Application state (gitignored)
|
|
│ ├── sessions/ # Per-group session data (.claude/ dirs with JSONL transcripts)
|
|
│ ├── env/env # Copy of .env for container mounting
|
|
│ └── ipc/ # Container IPC (messages/, tasks/)
|
|
│
|
|
├── logs/ # Runtime logs (gitignored)
|
|
│ ├── nanoclaw.log # Host stdout
|
|
│ └── nanoclaw.error.log # Host stderr
|
|
│ # Note: Per-container logs are in groups/{folder}/logs/container-*.log
|
|
│
|
|
└── launchd/
|
|
└── com.nanoclaw.plist # macOS service configuration
|
|
```
|
|
|
|
---
|
|
|
|
## Configuration
|
|
|
|
Configuration constants are in `src/config.ts`:
|
|
|
|
```typescript
|
|
import path from 'path';
|
|
|
|
export const ASSISTANT_NAME = process.env.ASSISTANT_NAME || 'Andy';
|
|
export const POLL_INTERVAL = 2000;
|
|
export const SCHEDULER_POLL_INTERVAL = 60000;
|
|
|
|
// Paths are absolute (required for container mounts)
|
|
const PROJECT_ROOT = process.cwd();
|
|
export const STORE_DIR = path.resolve(PROJECT_ROOT, 'store');
|
|
export const GROUPS_DIR = path.resolve(PROJECT_ROOT, 'groups');
|
|
export const DATA_DIR = path.resolve(PROJECT_ROOT, 'data');
|
|
|
|
// Container configuration
|
|
export const CONTAINER_IMAGE = process.env.CONTAINER_IMAGE || 'nanoclaw-agent:latest';
|
|
export const CONTAINER_TIMEOUT = parseInt(process.env.CONTAINER_TIMEOUT || '1800000', 10); // 30min default
|
|
export const IPC_POLL_INTERVAL = 1000;
|
|
export const IDLE_TIMEOUT = parseInt(process.env.IDLE_TIMEOUT || '1800000', 10); // 30min — keep container alive after last result
|
|
export const MAX_CONCURRENT_CONTAINERS = Math.max(1, parseInt(process.env.MAX_CONCURRENT_CONTAINERS || '5', 10) || 5);
|
|
|
|
export const TRIGGER_PATTERN = new RegExp(`^@${ASSISTANT_NAME}\\b`, 'i');
|
|
```
|
|
|
|
**Note:** Paths must be absolute for Apple Container volume mounts to work correctly.
|
|
|
|
### Container Configuration
|
|
|
|
Groups can have additional directories mounted via `containerConfig` in the SQLite `registered_groups` table (stored as JSON in the `container_config` column). Example registration:
|
|
|
|
```typescript
|
|
registerGroup("1234567890@g.us", {
|
|
name: "Dev Team",
|
|
folder: "dev-team",
|
|
trigger: "@Andy",
|
|
added_at: new Date().toISOString(),
|
|
containerConfig: {
|
|
additionalMounts: [
|
|
{
|
|
hostPath: "~/projects/webapp",
|
|
containerPath: "webapp",
|
|
readonly: false,
|
|
},
|
|
],
|
|
timeout: 600000,
|
|
},
|
|
});
|
|
```
|
|
|
|
Additional mounts appear at `/workspace/extra/{containerPath}` inside the container.
|
|
|
|
**Apple Container mount syntax note:** Read-write mounts use `-v host:container`, but readonly mounts require `--mount "type=bind,source=...,target=...,readonly"` (the `:ro` suffix doesn't work).
|
|
|
|
### Claude Authentication
|
|
|
|
Configure authentication in a `.env` file in the project root. Two options:
|
|
|
|
**Option 1: Claude Subscription (OAuth token)**
|
|
```bash
|
|
CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-...
|
|
```
|
|
The token can be extracted from `~/.claude/.credentials.json` if you're logged in to Claude Code.
|
|
|
|
**Option 2: Pay-per-use API Key**
|
|
```bash
|
|
ANTHROPIC_API_KEY=sk-ant-api03-...
|
|
```
|
|
|
|
Only the authentication variables (`CLAUDE_CODE_OAUTH_TOKEN` and `ANTHROPIC_API_KEY`) are extracted from `.env` and written to `data/env/env`, then mounted into the container at `/workspace/env-dir/env` and sourced by the entrypoint script. This ensures other environment variables in `.env` are not exposed to the agent. This workaround is needed because Apple Container loses `-e` environment variables when using `-i` (interactive mode with piped stdin).
|
|
|
|
### Changing the Assistant Name
|
|
|
|
Set the `ASSISTANT_NAME` environment variable:
|
|
|
|
```bash
|
|
ASSISTANT_NAME=Bot npm start
|
|
```
|
|
|
|
Or edit the default in `src/config.ts`. This changes:
|
|
- The trigger pattern (messages must start with `@YourName`)
|
|
- The response prefix (`YourName:` added automatically)
|
|
|
|
### Placeholder Values in launchd
|
|
|
|
Files with `{{PLACEHOLDER}}` values need to be configured:
|
|
- `{{PROJECT_ROOT}}` - Absolute path to your nanoclaw installation
|
|
- `{{NODE_PATH}}` - Path to node binary (detected via `which node`)
|
|
- `{{HOME}}` - User's home directory
|
|
|
|
---
|
|
|
|
## Memory System
|
|
|
|
NanoClaw uses a hierarchical memory system based on CLAUDE.md files.
|
|
|
|
### Memory Hierarchy
|
|
|
|
| Level | Location | Read By | Written By | Purpose |
|
|
|-------|----------|---------|------------|---------|
|
|
| **Global** | `groups/CLAUDE.md` | All groups | Main only | Preferences, facts, context shared across all conversations |
|
|
| **Group** | `groups/{name}/CLAUDE.md` | That group | That group | Group-specific context, conversation memory |
|
|
| **Files** | `groups/{name}/*.md` | That group | That group | Notes, research, documents created during conversation |
|
|
|
|
### How Memory Works
|
|
|
|
1. **Agent Context Loading**
|
|
- Agent runs with `cwd` set to `groups/{group-name}/`
|
|
- Claude Agent SDK with `settingSources: ['project']` automatically loads:
|
|
- `../CLAUDE.md` (parent directory = global memory)
|
|
- `./CLAUDE.md` (current directory = group memory)
|
|
|
|
2. **Writing Memory**
|
|
- When user says "remember this", agent writes to `./CLAUDE.md`
|
|
- When user says "remember this globally" (main channel only), agent writes to `../CLAUDE.md`
|
|
- Agent can create files like `notes.md`, `research.md` in the group folder
|
|
|
|
3. **Main Channel Privileges**
|
|
- Only the "main" group (self-chat) can write to global memory
|
|
- Main can manage registered groups and schedule tasks for any group
|
|
- Main can configure additional directory mounts for any group
|
|
- All groups have Bash access (safe because it runs inside container)
|
|
|
|
---
|
|
|
|
## Session Management
|
|
|
|
Sessions enable conversation continuity - Claude remembers what you talked about.
|
|
|
|
### How Sessions Work
|
|
|
|
1. Each group has a session ID stored in SQLite (`sessions` table, keyed by `group_folder`)
|
|
2. Session ID is passed to Claude Agent SDK's `resume` option
|
|
3. Claude continues the conversation with full context
|
|
4. Session transcripts are stored as JSONL files in `data/sessions/{group}/.claude/`
|
|
|
|
---
|
|
|
|
## Message Flow
|
|
|
|
### Incoming Message Flow
|
|
|
|
```
|
|
1. User sends WhatsApp message
|
|
│
|
|
▼
|
|
2. Baileys receives message via WhatsApp Web protocol
|
|
│
|
|
▼
|
|
3. Message stored in SQLite (store/messages.db)
|
|
│
|
|
▼
|
|
4. Message loop polls SQLite (every 2 seconds)
|
|
│
|
|
▼
|
|
5. Router checks:
|
|
├── Is chat_jid in registered groups (SQLite)? → No: ignore
|
|
└── Does message match trigger pattern? → No: store but don't process
|
|
│
|
|
▼
|
|
6. Router catches up conversation:
|
|
├── Fetch all messages since last agent interaction
|
|
├── Format with timestamp and sender name
|
|
└── Build prompt with full conversation context
|
|
│
|
|
▼
|
|
7. Router invokes Claude Agent SDK:
|
|
├── cwd: groups/{group-name}/
|
|
├── prompt: conversation history + current message
|
|
├── resume: session_id (for continuity)
|
|
└── mcpServers: nanoclaw (scheduler)
|
|
│
|
|
▼
|
|
8. Claude processes message:
|
|
├── Reads CLAUDE.md files for context
|
|
└── Uses tools as needed (search, email, etc.)
|
|
│
|
|
▼
|
|
9. Router prefixes response with assistant name and sends via WhatsApp
|
|
│
|
|
▼
|
|
10. Router updates last agent timestamp and saves session ID
|
|
```
|
|
|
|
### Trigger Word Matching
|
|
|
|
Messages must start with the trigger pattern (default: `@Andy`):
|
|
- `@Andy what's the weather?` → ✅ Triggers Claude
|
|
- `@andy help me` → ✅ Triggers (case insensitive)
|
|
- `Hey @Andy` → ❌ Ignored (trigger not at start)
|
|
- `What's up?` → ❌ Ignored (no trigger)
|
|
|
|
### Conversation Catch-Up
|
|
|
|
When a triggered message arrives, the agent receives all messages since its last interaction in that chat. Each message is formatted with timestamp and sender name:
|
|
|
|
```
|
|
[Jan 31 2:32 PM] John: hey everyone, should we do pizza tonight?
|
|
[Jan 31 2:33 PM] Sarah: sounds good to me
|
|
[Jan 31 2:35 PM] John: @Andy what toppings do you recommend?
|
|
```
|
|
|
|
This allows the agent to understand the conversation context even if it wasn't mentioned in every message.
|
|
|
|
---
|
|
|
|
## Commands
|
|
|
|
### Commands Available in Any Group
|
|
|
|
| Command | Example | Effect |
|
|
|---------|---------|--------|
|
|
| `@Assistant [message]` | `@Andy what's the weather?` | Talk to Claude |
|
|
|
|
### Commands Available in Main Channel Only
|
|
|
|
| Command | Example | Effect |
|
|
|---------|---------|--------|
|
|
| `@Assistant add group "Name"` | `@Andy add group "Family Chat"` | Register a new group |
|
|
| `@Assistant remove group "Name"` | `@Andy remove group "Work Team"` | Unregister a group |
|
|
| `@Assistant list groups` | `@Andy list groups` | Show registered groups |
|
|
| `@Assistant remember [fact]` | `@Andy remember I prefer dark mode` | Add to global memory |
|
|
|
|
---
|
|
|
|
## Scheduled Tasks
|
|
|
|
NanoClaw has a built-in scheduler that runs tasks as full agents in their group's context.
|
|
|
|
### How Scheduling Works
|
|
|
|
1. **Group Context**: Tasks created in a group run with that group's working directory and memory
|
|
2. **Full Agent Capabilities**: Scheduled tasks have access to all tools (WebSearch, file operations, etc.)
|
|
3. **Optional Messaging**: Tasks can send messages to their group using the `send_message` tool, or complete silently
|
|
4. **Main Channel Privileges**: The main channel can schedule tasks for any group and view all tasks
|
|
|
|
### Schedule Types
|
|
|
|
| Type | Value Format | Example |
|
|
|------|--------------|---------|
|
|
| `cron` | Cron expression | `0 9 * * 1` (Mondays at 9am) |
|
|
| `interval` | Milliseconds | `3600000` (every hour) |
|
|
| `once` | ISO timestamp | `2024-12-25T09:00:00Z` |
|
|
|
|
### Creating a Task
|
|
|
|
```
|
|
User: @Andy remind me every Monday at 9am to review the weekly metrics
|
|
|
|
Claude: [calls mcp__nanoclaw__schedule_task]
|
|
{
|
|
"prompt": "Send a reminder to review weekly metrics. Be encouraging!",
|
|
"schedule_type": "cron",
|
|
"schedule_value": "0 9 * * 1"
|
|
}
|
|
|
|
Claude: Done! I'll remind you every Monday at 9am.
|
|
```
|
|
|
|
### One-Time Tasks
|
|
|
|
```
|
|
User: @Andy at 5pm today, send me a summary of today's emails
|
|
|
|
Claude: [calls mcp__nanoclaw__schedule_task]
|
|
{
|
|
"prompt": "Search for today's emails, summarize the important ones, and send the summary to the group.",
|
|
"schedule_type": "once",
|
|
"schedule_value": "2024-01-31T17:00:00Z"
|
|
}
|
|
```
|
|
|
|
### Managing Tasks
|
|
|
|
From any group:
|
|
- `@Andy list my scheduled tasks` - View tasks for this group
|
|
- `@Andy pause task [id]` - Pause a task
|
|
- `@Andy resume task [id]` - Resume a paused task
|
|
- `@Andy cancel task [id]` - Delete a task
|
|
|
|
From main channel:
|
|
- `@Andy list all tasks` - View tasks from all groups
|
|
- `@Andy schedule task for "Family Chat": [prompt]` - Schedule for another group
|
|
|
|
---
|
|
|
|
## MCP Servers
|
|
|
|
### NanoClaw MCP (built-in)
|
|
|
|
The `nanoclaw` MCP server is created dynamically per agent call with the current group's context.
|
|
|
|
**Available Tools:**
|
|
| Tool | Purpose |
|
|
|------|---------|
|
|
| `schedule_task` | Schedule a recurring or one-time task |
|
|
| `list_tasks` | Show tasks (group's tasks, or all if main) |
|
|
| `get_task` | Get task details and run history |
|
|
| `update_task` | Modify task prompt or schedule |
|
|
| `pause_task` | Pause a task |
|
|
| `resume_task` | Resume a paused task |
|
|
| `cancel_task` | Delete a task |
|
|
| `send_message` | Send a WhatsApp message to the group |
|
|
|
|
---
|
|
|
|
## Deployment
|
|
|
|
NanoClaw runs as a single macOS launchd service.
|
|
|
|
### Startup Sequence
|
|
|
|
When NanoClaw starts, it:
|
|
1. **Ensures Apple Container system is running** - Automatically starts it if needed; kills orphaned NanoClaw containers from previous runs
|
|
2. Initializes the SQLite database (migrates from JSON files if they exist)
|
|
3. Loads state from SQLite (registered groups, sessions, router state)
|
|
4. Connects to WhatsApp (on `connection.open`):
|
|
- Starts the scheduler loop
|
|
- Starts the IPC watcher for container messages
|
|
- Sets up the per-group queue with `processGroupMessages`
|
|
- Recovers any unprocessed messages from before shutdown
|
|
- Starts the message polling loop
|
|
|
|
### Service: com.nanoclaw
|
|
|
|
**launchd/com.nanoclaw.plist:**
|
|
```xml
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "...">
|
|
<plist version="1.0">
|
|
<dict>
|
|
<key>Label</key>
|
|
<string>com.nanoclaw</string>
|
|
<key>ProgramArguments</key>
|
|
<array>
|
|
<string>{{NODE_PATH}}</string>
|
|
<string>{{PROJECT_ROOT}}/dist/index.js</string>
|
|
</array>
|
|
<key>WorkingDirectory</key>
|
|
<string>{{PROJECT_ROOT}}</string>
|
|
<key>RunAtLoad</key>
|
|
<true/>
|
|
<key>KeepAlive</key>
|
|
<true/>
|
|
<key>EnvironmentVariables</key>
|
|
<dict>
|
|
<key>PATH</key>
|
|
<string>{{HOME}}/.local/bin:/usr/local/bin:/usr/bin:/bin</string>
|
|
<key>HOME</key>
|
|
<string>{{HOME}}</string>
|
|
<key>ASSISTANT_NAME</key>
|
|
<string>Andy</string>
|
|
</dict>
|
|
<key>StandardOutPath</key>
|
|
<string>{{PROJECT_ROOT}}/logs/nanoclaw.log</string>
|
|
<key>StandardErrorPath</key>
|
|
<string>{{PROJECT_ROOT}}/logs/nanoclaw.error.log</string>
|
|
</dict>
|
|
</plist>
|
|
```
|
|
|
|
### Managing the Service
|
|
|
|
```bash
|
|
# Install service
|
|
cp launchd/com.nanoclaw.plist ~/Library/LaunchAgents/
|
|
|
|
# Start service
|
|
launchctl load ~/Library/LaunchAgents/com.nanoclaw.plist
|
|
|
|
# Stop service
|
|
launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist
|
|
|
|
# Check status
|
|
launchctl list | grep nanoclaw
|
|
|
|
# View logs
|
|
tail -f logs/nanoclaw.log
|
|
```
|
|
|
|
---
|
|
|
|
## Security Considerations
|
|
|
|
### Container Isolation
|
|
|
|
All agents run inside Apple Container (lightweight Linux VMs), providing:
|
|
- **Filesystem isolation**: Agents can only access mounted directories
|
|
- **Safe Bash access**: Commands run inside the container, not on your Mac
|
|
- **Network isolation**: Can be configured per-container if needed
|
|
- **Process isolation**: Container processes can't affect the host
|
|
- **Non-root user**: Container runs as unprivileged `node` user (uid 1000)
|
|
|
|
### Prompt Injection Risk
|
|
|
|
WhatsApp messages could contain malicious instructions attempting to manipulate Claude's behavior.
|
|
|
|
**Mitigations:**
|
|
- Container isolation limits blast radius
|
|
- Only registered groups are processed
|
|
- Trigger word required (reduces accidental processing)
|
|
- Agents can only access their group's mounted directories
|
|
- Main can configure additional directories per group
|
|
- Claude's built-in safety training
|
|
|
|
**Recommendations:**
|
|
- Only register trusted groups
|
|
- Review additional directory mounts carefully
|
|
- Review scheduled tasks periodically
|
|
- Monitor logs for unusual activity
|
|
|
|
### Credential Storage
|
|
|
|
| Credential | Storage Location | Notes |
|
|
|------------|------------------|-------|
|
|
| Claude CLI Auth | data/sessions/{group}/.claude/ | Per-group isolation, mounted to /home/node/.claude/ |
|
|
| WhatsApp Session | store/auth/ | Auto-created, persists ~20 days |
|
|
|
|
### File Permissions
|
|
|
|
The groups/ folder contains personal memory and should be protected:
|
|
```bash
|
|
chmod 700 groups/
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
| Issue | Cause | Solution |
|
|
|-------|-------|----------|
|
|
| No response to messages | Service not running | Check `launchctl list | grep nanoclaw` |
|
|
| "Claude Code process exited with code 1" | Apple Container failed to start | Check logs; NanoClaw auto-starts container system but may fail |
|
|
| "Claude Code process exited with code 1" | Session mount path wrong | Ensure mount is to `/home/node/.claude/` not `/root/.claude/` |
|
|
| Session not continuing | Session ID not saved | Check SQLite: `sqlite3 store/messages.db "SELECT * FROM sessions"` |
|
|
| Session not continuing | Mount path mismatch | Container user is `node` with HOME=/home/node; sessions must be at `/home/node/.claude/` |
|
|
| "QR code expired" | WhatsApp session expired | Delete store/auth/ and restart |
|
|
| "No groups registered" | Haven't added groups | Use `@Andy add group "Name"` in main |
|
|
|
|
### Log Location
|
|
|
|
- `logs/nanoclaw.log` - stdout
|
|
- `logs/nanoclaw.error.log` - stderr
|
|
|
|
### Debug Mode
|
|
|
|
Run manually for verbose output:
|
|
```bash
|
|
npm run dev
|
|
# or
|
|
node dist/index.js
|
|
```
|