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:
106
.claude/skills/customize/SKILL.md
Normal file
106
.claude/skills/customize/SKILL.md
Normal 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
|
||||
186
.claude/skills/setup/SKILL.md
Normal file
186
.claude/skills/setup/SKILL.md
Normal 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`).
|
||||
Reference in New Issue
Block a user