Initial commit: NanoClaw - Personal Claude assistant via WhatsApp

A minimal Node.js application that connects Claude Agent SDK to WhatsApp
using baileys. Features per-group memory via CLAUDE.md files, session
continuity, scheduled tasks, and Gmail integration via MCP.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-01-31 18:54:24 +02:00
commit c17823a732
16 changed files with 4375 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
---
name: customize
description: Add new capabilities or modify NanoClaw behavior. Use when user wants to add channels (Telegram, Slack, email input), change triggers, add integrations, modify the router, or make any other customizations. This is an interactive skill that asks questions to understand what the user wants.
---
# NanoClaw Customization
This skill helps users add capabilities or modify behavior. Use AskUserQuestion to understand what they want before making changes.
## Workflow
1. **Understand the request** - Ask clarifying questions
2. **Plan the changes** - Identify files to modify
3. **Implement** - Make changes directly to the code
4. **Test guidance** - Tell user how to verify
## Key Files
| File | Purpose |
|------|---------|
| `src/config.py` | Assistant name, trigger pattern, settings |
| `src/router.py` | Message routing, polling, agent invocation |
| `src/scheduler_worker.py` | Scheduled task execution |
| `src/commands.py` | Command handlers |
| `.mcp.json` | MCP server configuration |
| `groups/CLAUDE.md` | Global memory/persona |
## Common Customization Patterns
### Adding a New Input Channel (e.g., Telegram, Slack, Email)
Questions to ask:
- Which channel? (Telegram, Slack, Discord, email, SMS, etc.)
- Same trigger word or different?
- Same memory hierarchy or separate?
- Should messages from this channel go to existing groups or new ones?
Implementation pattern:
1. Find/add MCP server for the channel to `.mcp.json`
2. Add polling function in `router.py` (similar to `get_new_messages()`)
3. Add to main loop to poll both sources
4. Ensure responses route back to correct channel
### Adding a New MCP Integration
Questions to ask:
- What service? (Calendar, Notion, database, etc.)
- What operations needed? (read, write, both)
- Which groups should have access?
Implementation:
1. Add MCP server config to `.mcp.json`
2. Add tools to `allowed_tools` in `router.py`
3. Document in `groups/CLAUDE.md`
### Changing Assistant Behavior
Questions to ask:
- What aspect? (name, trigger, persona, response style)
- Apply to all groups or specific ones?
Simple changes → edit `src/config.py`
Persona changes → edit `groups/CLAUDE.md`
Per-group behavior → edit specific group's `CLAUDE.md`
### Adding New Commands
Questions to ask:
- What should the command do?
- Available in all groups or main only?
- Does it need new MCP tools?
Implementation:
1. Add handler function in `src/commands.py`
2. Claude will recognize natural language and call the function
### Changing Deployment
Questions to ask:
- Target platform? (Linux server, Docker, different Mac)
- Service manager? (systemd, Docker, supervisord)
Implementation:
1. Create appropriate service files
2. Update paths in config
3. Provide setup instructions
## After Changes
Always tell the user:
```bash
# Restart to apply changes
launchctl unload ~/Library/LaunchAgents/com.nanoclaw.router.plist
launchctl load ~/Library/LaunchAgents/com.nanoclaw.router.plist
```
## Example Interaction
User: "Add Telegram as an input channel"
1. Ask: "Should Telegram use the same @Andy trigger, or a different one?"
2. Ask: "Should Telegram messages create separate conversation contexts, or share with WhatsApp groups?"
3. Find Telegram MCP (e.g., telegram-mcp)
4. Add polling for Telegram in router.py
5. Update .mcp.json
6. Tell user how to authenticate and test

View File

@@ -0,0 +1,186 @@
---
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
**IMPORTANT**: Run all commands automatically. Only pause for user action when physical interaction is required (scanning QR codes). Give clear instructions for exactly what the user needs to do.
## 1. Check Prerequisites
Run these checks. Install any that are missing:
```bash
python3 --version # Need 3.10+
node --version # Need 18+
uv --version
```
If missing, install automatically:
- **uv**: `curl -LsSf https://astral.sh/uv/install.sh | sh`
- **node**: `brew install node`
- **python**: `brew install python@3.10`
## 2. Install Dependencies
Run all of these automatically:
```bash
# Python dependencies
uv venv && source .venv/bin/activate && uv pip install -r requirements.txt
```
```bash
# WhatsApp bridge dependencies
cd bridge && npm install
```
```bash
# Create logs directory
mkdir -p logs
```
## 3. WhatsApp Authentication
**USER ACTION REQUIRED**
Run the bridge in background and monitor for connection:
```bash
cd bridge && node bridge.js > /tmp/bridge_output.log 2>&1 &
BRIDGE_PID=$!
```
Tell the user:
> A QR code will appear below. On your phone:
> 1. Open WhatsApp
> 2. Tap **Settings → Linked Devices → Link a Device**
> 3. Scan the QR code
Then poll for either QR code or successful connection (check every 2 seconds for up to 3 minutes):
```bash
cat /tmp/bridge_output.log # Look for QR code or "Connected to WhatsApp!"
```
When you see "Connected to WhatsApp!" in the output, stop the bridge:
```bash
kill $BRIDGE_PID
```
Session persists until logged out from WhatsApp.
## 4. Gmail Authentication (Optional)
**Skip this step** unless user specifically needs Gmail integration. It requires Google Cloud Platform OAuth credentials setup.
If needed, user must first:
1. Create a GCP project
2. Enable Gmail API
3. Create OAuth 2.0 credentials
4. Download credentials to `~/.gmail-mcp/gcp-oauth.keys.json`
Then run:
```bash
npx -y @gongrzhe/server-gmail-autoauth-mcp
```
## 5. Register Main Channel
Ask the user:
> Do you want to use a **personal chat** (message yourself) or a **WhatsApp group** as your main channel?
For personal chat:
> Send a test message to yourself in WhatsApp. Tell me when done.
For group:
> Send a message in the WhatsApp group you want to use. Tell me when done.
After user confirms, find the JID:
```bash
# For personal chat
sqlite3 bridge/store/messages.db "SELECT DISTINCT chat_jid FROM messages WHERE chat_jid NOT LIKE '%@g.us' ORDER BY rowid DESC LIMIT 5"
# For group
sqlite3 bridge/store/messages.db "SELECT DISTINCT chat_jid FROM messages WHERE chat_jid LIKE '%@g.us' ORDER BY rowid DESC LIMIT 5"
```
Read the assistant name from `src/config.py` (look for `ASSISTANT_NAME = "..."`).
Then update `data/registered_groups.json`:
```json
{
"THE_JID_HERE": {
"name": "main",
"folder": "main",
"trigger": "@AssistantName",
"added_at": "CURRENT_TIMESTAMP_ISO"
}
}
```
## 6. Configure launchd
First, detect the actual paths:
```bash
which node # Get actual node path (may be nvm, homebrew, etc.)
```
Create plist files directly in `~/Library/LaunchAgents/` with:
**com.nanoclaw.bridge.plist:**
- ProgramArguments: `[actual_node_path, /Users/.../nanoclaw/bridge/bridge.js]`
- WorkingDirectory: `/Users/.../nanoclaw/bridge`
- StandardOutPath/StandardErrorPath: `/Users/.../nanoclaw/logs/bridge.log` and `bridge.error.log`
**com.nanoclaw.router.plist:**
- ProgramArguments: `[/Users/.../nanoclaw/.venv/bin/python, -u, /Users/.../nanoclaw/src/router.py]`
- The `-u` flag is required for unbuffered output (so logs appear immediately)
- WorkingDirectory: `/Users/.../nanoclaw`
- EnvironmentVariables:
- `PATH`: `/Users/USERNAME/.local/bin:/usr/local/bin:/usr/bin:/bin` (must include path to `claude` CLI)
- `HOME`: `/Users/USERNAME` (required for Claude CLI to find its config)
- StandardOutPath/StandardErrorPath: `/Users/.../nanoclaw/logs/router.log` and `router.error.log`
**NOTE**: Do NOT set ANTHROPIC_API_KEY - the Claude CLI handles its own authentication.
Then load the services:
```bash
launchctl load ~/Library/LaunchAgents/com.nanoclaw.bridge.plist
launchctl load ~/Library/LaunchAgents/com.nanoclaw.router.plist
```
Verify they're running:
```bash
launchctl list | grep nanoclaw
```
## 7. Test
Wait a few seconds for services to start, then tell the user:
> Send `@AssistantName hello` in your registered chat/group.
Check `logs/router.log` for activity:
```bash
tail -f logs/router.log
```
If there are issues, also check:
- `logs/router.error.log`
- `logs/bridge.log`
- `logs/bridge.error.log`
## Troubleshooting
**"Command failed with exit code 1"** - Usually means the Claude CLI isn't in PATH. Verify PATH in the router plist includes the directory containing `claude` (typically `~/.local/bin`).
**Messages received but no WhatsApp response** - Check that the bridge HTTP server is running:
```bash
curl -s http://127.0.0.1:3141/send -X POST -H "Content-Type: application/json" -d '{"jid":"test","message":"test"}'
```
Should return an error about invalid JID (not connection refused).
**Router not processing messages** - Check the trigger pattern matches. Messages must start with the trigger (e.g., `@Andy hello`).