diff --git a/.claude/skills/setup/SKILL.md b/.claude/skills/setup/SKILL.md index 03811a1..90f25e2 100644 --- a/.claude/skills/setup/SKILL.md +++ b/.claude/skills/setup/SKILL.md @@ -5,7 +5,7 @@ description: Run initial NanoClaw setup. Use when user wants to install dependen # NanoClaw Setup -Run setup steps automatically. Only pause when user action is required (WhatsApp authentication, configuration choices). Setup uses `bash setup.sh` for bootstrap, then `npx tsx src/setup/index.ts --step ` for all other steps. Steps emit structured status blocks to stdout. Verbose logs go to `logs/setup.log`. +Run setup steps automatically. Only pause when user action is required (WhatsApp authentication, configuration choices). Setup uses `bash setup.sh` for bootstrap, then `npx tsx setup/index.ts --step ` for all other steps. Steps emit structured status blocks to stdout. Verbose logs go to `logs/setup.log`. **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. scanning a QR code, 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. @@ -25,7 +25,7 @@ Run `bash setup.sh` and parse the status block. ## 2. Check Environment -Run `npx tsx src/setup/index.ts --step environment` and parse the status block. +Run `npx tsx setup/index.ts --step environment` and parse the status block. - If HAS_AUTH=true → note that WhatsApp auth exists, offer to skip step 5 - If HAS_REGISTERED_GROUPS=true → note existing config, offer to skip or reconfigure @@ -65,7 +65,7 @@ grep -q "CONTAINER_RUNTIME_BIN = 'container'" src/container-runtime.ts && echo " ### 3c. Build and test -Run `npx tsx src/setup/index.ts --step container -- --runtime ` and parse the status block. +Run `npx tsx setup/index.ts --step container -- --runtime ` and parse the status block. **If BUILD_OK=false:** Read `logs/setup.log` tail for the build error. - Cache issue (stale layers): `docker builder prune -f` (Docker) or `container builder stop && container builder rm && container builder start` (Apple Container). Retry. @@ -92,9 +92,9 @@ If HAS_AUTH=true, confirm: keep or re-authenticate? If IS_HEADLESS=true AND IS_WSL=false → AskUserQuestion: Pairing code (recommended) vs QR code in terminal? Otherwise (macOS, desktop Linux, or WSL) → AskUserQuestion: QR code in browser (recommended) vs pairing code vs QR code in terminal? -- **QR browser:** `npx tsx src/setup/index.ts --step whatsapp-auth -- --method qr-browser` (Bash timeout: 150000ms) -- **Pairing code:** Ask for phone number first. `npx tsx src/setup/index.ts --step whatsapp-auth -- --method pairing-code --phone NUMBER` (Bash timeout: 150000ms). Display PAIRING_CODE. -- **QR terminal:** `npx tsx src/setup/index.ts --step whatsapp-auth -- --method qr-terminal`. Tell user to run `npm run auth` in another terminal. +- **QR browser:** `npx tsx setup/index.ts --step whatsapp-auth -- --method qr-browser` (Bash timeout: 150000ms) +- **Pairing code:** Ask for phone number first. `npx tsx setup/index.ts --step whatsapp-auth -- --method pairing-code --phone NUMBER` (Bash timeout: 150000ms). Display PAIRING_CODE. +- **QR terminal:** `npx tsx setup/index.ts --step whatsapp-auth -- --method qr-terminal`. Tell user to run `npm run auth` in another terminal. **If failed:** qr_timeout → re-run. logged_out → delete `store/auth/` and re-run. 515 → re-run. timeout → ask user, offer retry. @@ -113,21 +113,21 @@ AskUserQuestion: Shared number or dedicated? → AskUserQuestion: Trigger word? **DM with bot:** Ask for bot's number, JID = `NUMBER@s.whatsapp.net` **Group:** -1. `npx tsx src/setup/index.ts --step groups` (Bash timeout: 60000ms) +1. `npx tsx setup/index.ts --step groups` (Bash timeout: 60000ms) 2. BUILD=failed → fix TypeScript, re-run. GROUPS_IN_DB=0 → check logs. -3. `npx tsx src/setup/index.ts --step groups -- --list` for pipe-separated JID|name lines. +3. `npx tsx setup/index.ts --step groups -- --list` for pipe-separated JID|name lines. 4. Present candidates as AskUserQuestion (names only, not JIDs). ## 8. Register Channel -Run `npx tsx src/setup/index.ts --step register -- --jid "JID" --name "main" --trigger "@TriggerWord" --folder "main"` plus `--no-trigger-required` if personal/DM/solo, `--assistant-name "Name"` if not Andy. +Run `npx tsx setup/index.ts --step register -- --jid "JID" --name "main" --trigger "@TriggerWord" --folder "main"` plus `--no-trigger-required` if personal/DM/solo, `--assistant-name "Name"` if not Andy. ## 9. Mount Allowlist AskUserQuestion: Agent access to external directories? -**No:** `npx tsx src/setup/index.ts --step mounts -- --empty` -**Yes:** Collect paths/permissions. `npx tsx src/setup/index.ts --step mounts -- --json '{"allowedRoots":[...],"blockedPatterns":[],"nonMainReadOnly":true}'` +**No:** `npx tsx setup/index.ts --step mounts -- --empty` +**Yes:** Collect paths/permissions. `npx tsx setup/index.ts --step mounts -- --json '{"allowedRoots":[...],"blockedPatterns":[],"nonMainReadOnly":true}'` ## 10. Start Service @@ -135,7 +135,7 @@ If service already running: unload first. - macOS: `launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist` - Linux: `systemctl --user stop nanoclaw` (or `systemctl stop nanoclaw` if root) -Run `npx tsx src/setup/index.ts --step service` and parse the status block. +Run `npx tsx setup/index.ts --step service` and parse the status block. **If FALLBACK=wsl_no_systemd:** WSL without systemd detected. Tell user they can either enable systemd in WSL (`echo -e "[boot]\nsystemd=true" | sudo tee /etc/wsl.conf` then restart WSL) or use the generated `start-nanoclaw.sh` wrapper. @@ -161,7 +161,7 @@ Replace `USERNAME` with the actual username (from `whoami`). Run the two `sudo` ## 11. Verify -Run `npx tsx src/setup/index.ts --step verify` and parse the status block. +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) @@ -169,7 +169,7 @@ Run `npx tsx src/setup/index.ts --step verify` and parse the status block. - CREDENTIALS=missing → re-run step 4 - WHATSAPP_AUTH=not_found → re-run step 5 - REGISTERED_GROUPS=0 → re-run steps 7-8 -- MOUNT_ALLOWLIST=missing → `npx tsx src/setup/index.ts --step mounts -- --empty` +- MOUNT_ALLOWLIST=missing → `npx tsx setup/index.ts --step mounts -- --empty` Tell user to test: send a message in their registered chat. Show: `tail -f logs/nanoclaw.log` @@ -179,7 +179,7 @@ Tell user to test: send a message in their registered chat. Show: `tail -f logs/ **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`. -**No response to messages:** Check trigger pattern. Main channel doesn't need prefix. Check DB: `npx tsx src/setup/index.ts --step verify`. Check `logs/nanoclaw.log`. +**No response to messages:** Check trigger pattern. Main channel doesn't need prefix. Check DB: `npx tsx setup/index.ts --step verify`. Check `logs/nanoclaw.log`. **WhatsApp disconnected:** `npm run auth` then rebuild and restart: `npm run build && launchctl kickstart -k gui/$(id -u)/com.nanoclaw` (macOS) or `systemctl --user restart nanoclaw` (Linux). diff --git a/package.json b/package.json index 0b0835f..4180347 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "typecheck": "tsc --noEmit", "format": "prettier --write \"src/**/*.ts\"", "format:check": "prettier --check \"src/**/*.ts\"", - "setup": "tsx src/setup/index.ts", + "setup": "tsx setup/index.ts", "test": "vitest run", "test:watch": "vitest" }, diff --git a/src/setup/container.ts b/setup/container.ts similarity index 98% rename from src/setup/container.ts rename to setup/container.ts index e6b3d64..7122898 100644 --- a/src/setup/container.ts +++ b/setup/container.ts @@ -5,7 +5,7 @@ import { execSync } from 'child_process'; import path from 'path'; -import { logger } from '../logger.js'; +import { logger } from '../src/logger.js'; import { commandExists } from './platform.js'; import { emitStatus } from './status.js'; diff --git a/src/setup/environment.test.ts b/setup/environment.test.ts similarity index 100% rename from src/setup/environment.test.ts rename to setup/environment.test.ts diff --git a/src/setup/environment.ts b/setup/environment.ts similarity index 96% rename from src/setup/environment.ts rename to setup/environment.ts index cafbe23..a7f436f 100644 --- a/src/setup/environment.ts +++ b/setup/environment.ts @@ -7,8 +7,8 @@ import path from 'path'; import Database from 'better-sqlite3'; -import { STORE_DIR } from '../config.js'; -import { logger } from '../logger.js'; +import { STORE_DIR } from '../src/config.js'; +import { logger } from '../src/logger.js'; import { commandExists, getPlatform, isHeadless, isWSL } from './platform.js'; import { emitStatus } from './status.js'; diff --git a/src/setup/groups.ts b/setup/groups.ts similarity index 98% rename from src/setup/groups.ts rename to setup/groups.ts index a6f6a79..6d18998 100644 --- a/src/setup/groups.ts +++ b/setup/groups.ts @@ -8,8 +8,8 @@ import path from 'path'; import Database from 'better-sqlite3'; -import { STORE_DIR } from '../config.js'; -import { logger } from '../logger.js'; +import { STORE_DIR } from '../src/config.js'; +import { logger } from '../src/logger.js'; import { emitStatus } from './status.js'; function parseArgs(args: string[]): { list: boolean; limit: number } { diff --git a/src/setup/index.ts b/setup/index.ts similarity index 87% rename from src/setup/index.ts rename to setup/index.ts index b39afb7..2d96f81 100644 --- a/src/setup/index.ts +++ b/setup/index.ts @@ -1,8 +1,8 @@ /** * Setup CLI entry point. - * Usage: npx tsx src/setup/index.ts --step [args...] + * Usage: npx tsx setup/index.ts --step [args...] */ -import { logger } from '../logger.js'; +import { logger } from '../src/logger.js'; import { emitStatus } from './status.js'; const STEPS: Record Promise<{ run: (args: string[]) => Promise }>> = { @@ -21,7 +21,7 @@ async function main(): Promise { const stepIdx = args.indexOf('--step'); if (stepIdx === -1 || !args[stepIdx + 1]) { - console.error(`Usage: npx tsx src/setup/index.ts --step <${Object.keys(STEPS).join('|')}> [args...]`); + console.error(`Usage: npx tsx setup/index.ts --step <${Object.keys(STEPS).join('|')}> [args...]`); process.exit(1); } diff --git a/src/setup/mounts.ts b/setup/mounts.ts similarity index 98% rename from src/setup/mounts.ts rename to setup/mounts.ts index eaca5d1..061b329 100644 --- a/src/setup/mounts.ts +++ b/setup/mounts.ts @@ -6,7 +6,7 @@ import fs from 'fs'; import path from 'path'; import os from 'os'; -import { logger } from '../logger.js'; +import { logger } from '../src/logger.js'; import { isRoot } from './platform.js'; import { emitStatus } from './status.js'; diff --git a/src/setup/platform.test.ts b/setup/platform.test.ts similarity index 89% rename from src/setup/platform.test.ts rename to setup/platform.test.ts index 653ca0e..266370a 100644 --- a/src/setup/platform.test.ts +++ b/setup/platform.test.ts @@ -20,10 +20,6 @@ describe('getPlatform', () => { expect(['macos', 'linux', 'unknown']).toContain(result); }); - it('returns linux on this system', () => { - // This test runs on Linux - expect(getPlatform()).toBe('linux'); - }); }); // --- isWSL --- @@ -81,10 +77,14 @@ describe('getServiceManager', () => { expect(['launchd', 'systemd', 'none']).toContain(result); }); - it('returns systemd or none on Linux', () => { + it('matches the detected platform', () => { + const platform = getPlatform(); const result = getServiceManager(); - // On Linux, should be systemd if available, else none - expect(['systemd', 'none']).toContain(result); + if (platform === 'macos') { + expect(result).toBe('launchd'); + } else { + expect(['systemd', 'none']).toContain(result); + } }); }); diff --git a/src/setup/platform.ts b/setup/platform.ts similarity index 100% rename from src/setup/platform.ts rename to setup/platform.ts diff --git a/src/setup/register.test.ts b/setup/register.test.ts similarity index 100% rename from src/setup/register.test.ts rename to setup/register.test.ts diff --git a/src/setup/register.ts b/setup/register.ts similarity index 98% rename from src/setup/register.ts rename to setup/register.ts index 58aa68e..8c00685 100644 --- a/src/setup/register.ts +++ b/setup/register.ts @@ -9,8 +9,8 @@ import path from 'path'; import Database from 'better-sqlite3'; -import { STORE_DIR } from '../config.js'; -import { logger } from '../logger.js'; +import { STORE_DIR } from '../src/config.js'; +import { logger } from '../src/logger.js'; import { emitStatus } from './status.js'; interface RegisterArgs { diff --git a/src/setup/service.test.ts b/setup/service.test.ts similarity index 100% rename from src/setup/service.test.ts rename to setup/service.test.ts diff --git a/src/setup/service.ts b/setup/service.ts similarity index 99% rename from src/setup/service.ts rename to setup/service.ts index 95fc851..7026331 100644 --- a/src/setup/service.ts +++ b/setup/service.ts @@ -9,7 +9,7 @@ import fs from 'fs'; import os from 'os'; import path from 'path'; -import { logger } from '../logger.js'; +import { logger } from '../src/logger.js'; import { getPlatform, getNodePath, diff --git a/src/setup/status.ts b/setup/status.ts similarity index 100% rename from src/setup/status.ts rename to setup/status.ts diff --git a/src/setup/verify.ts b/setup/verify.ts similarity index 97% rename from src/setup/verify.ts rename to setup/verify.ts index 1a49887..0e563f3 100644 --- a/src/setup/verify.ts +++ b/setup/verify.ts @@ -11,8 +11,8 @@ import path from 'path'; import Database from 'better-sqlite3'; -import { STORE_DIR } from '../config.js'; -import { logger } from '../logger.js'; +import { STORE_DIR } from '../src/config.js'; +import { logger } from '../src/logger.js'; import { getPlatform, getServiceManager, hasSystemd, isRoot } from './platform.js'; import { emitStatus } from './status.js'; diff --git a/src/setup/whatsapp-auth.ts b/setup/whatsapp-auth.ts similarity index 99% rename from src/setup/whatsapp-auth.ts rename to setup/whatsapp-auth.ts index bc33af4..7028f08 100644 --- a/src/setup/whatsapp-auth.ts +++ b/setup/whatsapp-auth.ts @@ -6,7 +6,7 @@ import { execSync, spawn } from 'child_process'; import fs from 'fs'; import path from 'path'; -import { logger } from '../logger.js'; +import { logger } from '../src/logger.js'; import { openBrowser, isHeadless } from './platform.js'; import { emitStatus } from './status.js'; diff --git a/vitest.config.ts b/vitest.config.ts index 4433f3e..354e6a5 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,6 +2,6 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { - include: ['src/**/*.test.ts', 'skills-engine/**/*.test.ts'], + include: ['src/**/*.test.ts', 'setup/**/*.test.ts', 'skills-engine/**/*.test.ts'], }, });