diff --git a/.claude/skills/add-telegram/SKILL.md b/.claude/skills/add-telegram/SKILL.md index 10f25ab..609f394 100644 --- a/.claude/skills/add-telegram/SKILL.md +++ b/.claude/skills/add-telegram/SKILL.md @@ -202,14 +202,6 @@ launchctl load ~/Library/LaunchAgents/com.nanoclaw.plist # systemctl --user start nanoclaw ``` -## Agent Swarms (Teams) - -After completing the Telegram setup, use `AskUserQuestion`: - -AskUserQuestion: Would you like to add Agent Swarm support? Without it, Agent Teams still work — they just operate behind the scenes. With Swarm support, each subagent appears as a different bot in the Telegram group so you can see who's saying what and have interactive team sessions. - -If they say yes, invoke the `/add-telegram-swarm` skill. - ## Removal To remove Telegram integration: diff --git a/.claude/skills/setup/SKILL.md b/.claude/skills/setup/SKILL.md index 54c3d2d..fb13c05 100644 --- a/.claude/skills/setup/SKILL.md +++ b/.claude/skills/setup/SKILL.md @@ -9,7 +9,7 @@ Run setup steps automatically. Only pause when user action is required (channel **Principle:** When something is broken or missing, fix it. Don't tell the user to go fix it themselves unless it genuinely requires their manual action (e.g. authenticating a channel, pasting a secret token). If a dependency is missing, install it. If a service won't start, diagnose and repair. Ask the user for permission when needed, then do the work. -**UX Note:** Use `AskUserQuestion` for all user-facing questions. +**UX Note:** Use `AskUserQuestion` for multiple-choice questions only (e.g. "Docker or Apple Container?", "which channels?"). Do NOT use it when free-text input is needed (e.g. phone numbers, tokens, paths) — just ask the question in plain text and wait for the user's reply. ## 0. Git & Fork Setup @@ -50,7 +50,7 @@ Already configured. Continue. **Verify:** `git remote -v` should show `origin` → user's repo, `upstream` → `qwibitai/nanoclaw.git`. -## 1. Bootstrap (Node.js + Dependencies + OneCLI) +## 1. Bootstrap (Node.js + Dependencies) Run `bash setup.sh` and parse the status block. @@ -62,34 +62,6 @@ Run `bash setup.sh` and parse the status block. - If NATIVE_OK=false → better-sqlite3 failed to load. Install build tools and re-run. - Record PLATFORM and IS_WSL for later steps. -After bootstrap succeeds, install OneCLI and its CLI tool: - -```bash -curl -fsSL onecli.sh/install | sh -curl -fsSL onecli.sh/cli/install | sh -``` - -Verify both installed: `onecli version`. If the command is not found, the CLI was likely installed to `~/.local/bin/`. Add it to PATH for the current session and persist it: - -```bash -export PATH="$HOME/.local/bin:$PATH" -# Persist for future sessions (append to shell profile if not already present) -grep -q '.local/bin' ~/.bashrc 2>/dev/null || echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc -grep -q '.local/bin' ~/.zshrc 2>/dev/null || echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc -``` - -Then re-verify with `onecli version`. - -Point the CLI at the local OneCLI instance (it defaults to the cloud service otherwise): -```bash -onecli config set api-host http://127.0.0.1:10254 -``` - -Ensure `.env` has the OneCLI URL (create the file if it doesn't exist): -```bash -grep -q 'ONECLI_URL' .env 2>/dev/null || echo 'ONECLI_URL=http://127.0.0.1:10254' >> .env -``` - ## 2. Check Environment Run `npx tsx setup/index.ts --step environment` and parse the status block. @@ -112,7 +84,10 @@ Run `npx tsx setup/index.ts --step timezone` and parse the status block. Check the preflight results for `APPLE_CONTAINER` and `DOCKER`, and the PLATFORM from step 1. - PLATFORM=linux → Docker (only option) -- PLATFORM=macos + APPLE_CONTAINER=installed → Use `AskUserQuestion: Docker (cross-platform) or Apple Container (native macOS)?` If Apple Container, run `/convert-to-apple-container` now, then skip to 3c. +- PLATFORM=macos + APPLE_CONTAINER=installed → AskUserQuestion with two options: + 1. **Docker (recommended)** — description: "Cross-platform, better credential management, well-tested." + 2. **Apple Container (experimental)** — description: "Native macOS runtime. Requires advanced setup." + If Apple Container, run `/convert-to-apple-container` now, then skip to 3c. - PLATFORM=macos + APPLE_CONTAINER=not_found → Docker ### 3a-docker. Install Docker @@ -147,9 +122,39 @@ Run `npx tsx setup/index.ts --step container -- --runtime ` and parse th **If TEST_OK=false but BUILD_OK=true:** The image built but won't run. Check logs — common cause is runtime not fully started. Wait a moment and retry the test. -## 4. Anthropic Credentials via OneCLI +## 4. Credential System -NanoClaw uses OneCLI to manage credentials — API keys are never stored in `.env` or exposed to containers. The OneCLI gateway injects them at request time. +The credential system depends on the container runtime chosen in step 3. + +### 4a. Docker → OneCLI + +Install OneCLI and its CLI tool: + +```bash +curl -fsSL onecli.sh/install | sh +curl -fsSL onecli.sh/cli/install | sh +``` + +Verify both installed: `onecli version`. If the command is not found, the CLI was likely installed to `~/.local/bin/`. Add it to PATH for the current session and persist it: + +```bash +export PATH="$HOME/.local/bin:$PATH" +# Persist for future sessions (append to shell profile if not already present) +grep -q '.local/bin' ~/.bashrc 2>/dev/null || echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc +grep -q '.local/bin' ~/.zshrc 2>/dev/null || echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc +``` + +Then re-verify with `onecli version`. + +Point the CLI at the local OneCLI instance (it defaults to the cloud service otherwise): +```bash +onecli config set api-host http://127.0.0.1:10254 +``` + +Ensure `.env` has the OneCLI URL (create the file if it doesn't exist): +```bash +grep -q 'ONECLI_URL' .env 2>/dev/null || echo 'ONECLI_URL=http://127.0.0.1:10254' >> .env +``` Check if a secret already exists: ```bash @@ -163,16 +168,20 @@ AskUserQuestion: Do you want to use your **Claude subscription** (Pro/Max) or an 1. **Claude subscription (Pro/Max)** — description: "Uses your existing Claude Pro or Max subscription. You'll run `claude setup-token` in another terminal to get your token." 2. **Anthropic API key** — description: "Pay-per-use API key from console.anthropic.com." -### Subscription path +#### Subscription path -Tell the user to run `claude setup-token` in another terminal and copy the token it outputs. Do NOT collect the token in chat. +Tell the user: -Once they have the token, they register it with OneCLI. AskUserQuestion with two options: +> Run `claude setup-token` in another terminal. It will output a token — copy it but don't paste it here. + +Then stop and wait for the user to confirm they have the token. Do NOT proceed until they respond. + +Once they confirm, they register it with OneCLI. AskUserQuestion with two options: 1. **Dashboard** — description: "Best if you have a browser on this machine. Open http://127.0.0.1:10254 and add the secret in the UI. Use type 'anthropic' and paste your token as the value." 2. **CLI** — description: "Best for remote/headless servers. Run: `onecli secrets create --name Anthropic --type anthropic --value YOUR_TOKEN --host-pattern api.anthropic.com`" -### API key path +#### API key path Tell the user to get an API key from https://console.anthropic.com/settings/keys if they don't have one. @@ -181,7 +190,7 @@ Then AskUserQuestion with two options: 1. **Dashboard** — description: "Best if you have a browser on this machine. Open http://127.0.0.1:10254 and add the secret in the UI." 2. **CLI** — description: "Best for remote/headless servers. Run: `onecli secrets create --name Anthropic --type anthropic --value YOUR_KEY --host-pattern api.anthropic.com`" -### After either path +#### After either path Ask them to let you know when done. @@ -189,6 +198,31 @@ Ask them to let you know when done. **After user confirms:** verify with `onecli secrets list` that an Anthropic secret exists. If not, ask again. +### 4b. Apple Container → Native Credential Proxy + +Apple Container is not compatible with OneCLI. The credential proxy code is already included in the apple-container branch — do NOT invoke `/use-native-credential-proxy` (it would conflict with already-applied code). + +Instead, just configure the credentials in `.env`: + +AskUserQuestion: Do you want to use your **Claude subscription** (Pro/Max) or an **Anthropic API key**? + +1. **Claude subscription (Pro/Max)** — description: "Uses your existing Claude Pro or Max subscription. Run `claude setup-token` in another terminal to get your token." +2. **Anthropic API key** — description: "Pay-per-use API key from console.anthropic.com." + +For subscription: tell the user to run `claude setup-token` in another terminal. Stop and wait for the user to confirm they have completed this step successfully before proceeding. + +Once confirmed, add the token to `.env`: +```bash +echo 'CLAUDE_CODE_OAUTH_TOKEN=' >> .env +``` + +For API key: add to `.env`: +```bash +echo 'ANTHROPIC_API_KEY=' >> .env +``` + +Verify the proxy starts: `npm run dev` should show "Credential proxy listening" in the logs. + ## 5. Set Up Channels AskUserQuestion (multiSelect): Which messaging channels do you want to enable? @@ -265,7 +299,7 @@ Run `npx tsx setup/index.ts --step verify` and parse the status block. **If STATUS=failed, fix each:** - SERVICE=stopped → `npm run build`, then restart: `launchctl kickstart -k gui/$(id -u)/com.nanoclaw` (macOS) or `systemctl --user restart nanoclaw` (Linux) or `bash start-nanoclaw.sh` (WSL nohup) - SERVICE=not_found → re-run step 7 -- CREDENTIALS=missing → re-run step 4 (check `onecli secrets list` for Anthropic secret) +- CREDENTIALS=missing → re-run step 4 (Docker: check `onecli secrets list`; Apple Container: check `.env` for credentials) - CHANNEL_AUTH shows `not_found` for any channel → re-invoke that channel's skill (e.g. `/add-telegram`) - REGISTERED_GROUPS=0 → re-invoke the channel skills from step 5 - MOUNT_ALLOWLIST=missing → `npx tsx setup/index.ts --step mounts -- --empty` @@ -274,7 +308,7 @@ Tell user to test: send a message in their registered chat. Show: `tail -f logs/ ## Troubleshooting -**Service not starting:** Check `logs/nanoclaw.error.log`. Common: wrong Node path (re-run step 7), OneCLI not running (check `curl http://127.0.0.1:10254/api/health`), missing channel credentials (re-invoke channel skill). +**Service not starting:** Check `logs/nanoclaw.error.log`. Common: wrong Node path (re-run step 7), credential system not running (Docker: check `curl http://127.0.0.1:10254/api/health`; Apple Container: check `.env` credentials), missing channel credentials (re-invoke channel skill). **Container agent fails ("Claude Code process exited with code 1"):** Ensure the container runtime is running — `open -a Docker` (macOS Docker), `container system start` (Apple Container), or `sudo systemctl start docker` (Linux). Check container logs in `groups/main/logs/container-*.log`. diff --git a/docs/BRANCH-FORK-MAINTENANCE.md b/docs/BRANCH-FORK-MAINTENANCE.md new file mode 100644 index 0000000..4891f38 --- /dev/null +++ b/docs/BRANCH-FORK-MAINTENANCE.md @@ -0,0 +1,81 @@ +# Branch & Fork Maintenance Guidelines + +## Structure + +**`qwibitai/nanoclaw`** (upstream) — core engine with skill definitions (`.claude/skills/`). No channel code on `main`. + +**Channel forks** (`nanoclaw-whatsapp`, `nanoclaw-telegram`, `nanoclaw-slack`, etc.) — each fork = upstream + one channel's code applied. Users clone upstream, then merge a fork into their clone to add a channel. + +**`skill/*` and `feat/*` branches on upstream** — add features unrelated to channels (e.g. `skill/compact`, `skill/apple-container`). Users merge these into their clone to add capabilities. Channel-specific skill branches that duplicate the forks (e.g. `skill/whatsapp`, `skill/telegram`) are legacy. + +## How users add capabilities + +``` +user clones upstream main + ├── merges nanoclaw-whatsapp fork → adds WhatsApp + ├── merges skill/compact branch → adds /compact command + └── merges skill/apple-container → switches to Apple Container +``` + +## Merge directions + +``` +upstream main ──→ channel forks (forward merge to keep forks caught up) +upstream main ──→ skill branches (forward merge to keep branches caught up) +``` + +Forks and skill branches carry applied code changes. Users merge them into their own clones/forks to add capabilities. They are never merged back into upstream `main`. + +## Forward merge procedure + +```bash +# In your local nanoclaw checkout +git checkout main && git pull + +# For a fork: +git fetch nanoclaw-whatsapp +git checkout -B whatsapp-merge nanoclaw-whatsapp/main +git merge main +# Resolve conflicts (see below) +# Remove upstream-only workflows (re-added by every merge since main has them): +git rm .github/workflows/bump-version.yml .github/workflows/update-tokens.yml 2>/dev/null +git push nanoclaw-whatsapp HEAD:main +git checkout main && git branch -D whatsapp-merge + +# For a skill branch: +git checkout -B skill/compact origin/skill/compact +git merge main +# Resolve conflicts (see below) +git push origin skill/compact +git checkout main && git branch -D skill/compact +``` + +## Conflict resolution + +The same files conflict every time: + +| File | Resolution | +|------|------------| +| `package.json` | Take main's version + keep fork/branch-specific deps | +| `package-lock.json` | `git checkout main -- package-lock.json && npm install` | +| `.env.example` | Combine: main's entries + fork/branch-specific entries | +| `repo-tokens/badge.svg` | Take main's version (auto-generated) | + +Source code changes (e.g. `src/types.ts`, `src/index.ts`) usually auto-merge cleanly, but can conflict if both sides modify the same lines. **Always build and test after every forward merge** — auto-merged code can be silently wrong (e.g. referencing a renamed function or using a removed parameter) even when git reports no conflicts. + +## When to merge forward + +After any main change that touches shared files (`package.json`, `src/index.ts`, `CLAUDE.md`, etc.). Small frequent merges = trivial conflicts. Large infrequent merges = painful. + +## Fork setup + +When creating a new channel fork: + +1. Fork `nanoclaw` to `nanoclaw-{channel}` +2. Remove upstream-only workflows: `bump-version.yml`, `update-tokens.yml` +3. Add channel code, deps, env vars +4. Forward-merge main immediately to establish a clean baseline + +## Dependencies + +Forks and branches add their own deps on top of upstream's. When upstream adds or removes a dependency, verify that forks/branches still build after the next forward merge — transitive dependency changes can break downstream code. diff --git a/package-lock.json b/package-lock.json index ffb6812..be7152c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "nanoclaw", - "version": "1.2.41", + "version": "1.2.42", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "nanoclaw", - "version": "1.2.41", + "version": "1.2.42", "dependencies": { "@onecli-sh/sdk": "^0.2.0", "better-sqlite3": "11.10.0", diff --git a/package.json b/package.json index 2034dd3..a8dd43a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nanoclaw", - "version": "1.2.41", + "version": "1.2.42", "description": "Personal Claude assistant. Lightweight, secure, customizable.", "type": "module", "main": "dist/index.js", diff --git a/src/claw-skill.test.ts b/src/claw-skill.test.ts deleted file mode 100644 index 2d86c8e..0000000 --- a/src/claw-skill.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import fs from 'fs'; -import os from 'os'; -import path from 'path'; -import { spawnSync } from 'child_process'; - -import { describe, expect, it } from 'vitest'; - -describe('claw skill script', () => { - it('exits zero after successful structured output even if the runtime is terminated', { timeout: 20000 }, () => { - const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'claw-skill-test-')); - const binDir = path.join(tempDir, 'bin'); - fs.mkdirSync(binDir, { recursive: true }); - - const runtimePath = path.join(binDir, 'container'); - fs.writeFileSync( - runtimePath, - `#!/bin/sh -cat >/dev/null -printf '%s\n' '---NANOCLAW_OUTPUT_START---' '{"status":"success","result":"4","newSessionId":"sess-1"}' '---NANOCLAW_OUTPUT_END---' -sleep 30 -`, - ); - fs.chmodSync(runtimePath, 0o755); - - const result = spawnSync( - 'python3', - ['.claude/skills/claw/scripts/claw', '-j', 'tg:123', 'What is 2+2?'], - { - cwd: process.cwd(), - encoding: 'utf8', - env: { - ...process.env, - NANOCLAW_DIR: tempDir, - PATH: `${binDir}:${process.env.PATH || ''}`, - }, - timeout: 15000, - }, - ); - - expect(result.status).toBe(0); - expect(result.signal).toBeNull(); - expect(result.stdout).toContain('4'); - expect(result.stderr).toContain('[session: sess-1]'); - }); -});