mirror of
https://github.com/qwibitai/nanoclaw.git
synced 2026-06-04 10:14:47 +08:00
Merge branch 'main' into docs/pr-hygiene-check
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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 <chosen>` 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=<their-token>' >> .env
|
||||
```
|
||||
|
||||
For API key: add to `.env`:
|
||||
```bash
|
||||
echo 'ANTHROPIC_API_KEY=<their-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`.
|
||||
|
||||
|
||||
@@ -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.
|
||||
Generated
+2
-2
@@ -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",
|
||||
|
||||
+1
-1
@@ -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",
|
||||
|
||||
@@ -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]');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user