refactor: move setup scripts out of src/ to reduce build token count

Setup scripts are standalone CLI tools run via tsx with no runtime
imports from the main app. Moving them out of src/ excludes them from
the tsc build output and reduces the compiled bundle size.

- git mv src/setup/ setup/
- Fix imports to use ../src/logger.js and ../src/config.js
- Update package.json, vitest.config.ts, SKILL.md references
- Fix platform tests to be cross-platform (macOS + Linux)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-02-22 18:43:22 +02:00
parent c1a2491e77
commit 92d14405c5
18 changed files with 39 additions and 39 deletions

View File

@@ -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 <name>` 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 <name>` 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 <chosen>` and parse the status block.
Run `npx tsx setup/index.ts --step container -- --runtime <chosen>` 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).

View File

@@ -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"
},

View File

@@ -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';

View File

@@ -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';

View File

@@ -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 } {

View File

@@ -1,8 +1,8 @@
/**
* Setup CLI entry point.
* Usage: npx tsx src/setup/index.ts --step <name> [args...]
* Usage: npx tsx setup/index.ts --step <name> [args...]
*/
import { logger } from '../logger.js';
import { logger } from '../src/logger.js';
import { emitStatus } from './status.js';
const STEPS: Record<string, () => Promise<{ run: (args: string[]) => Promise<void> }>> = {
@@ -21,7 +21,7 @@ async function main(): Promise<void> {
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);
}

View File

@@ -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';

View File

@@ -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);
}
});
});

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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';

View File

@@ -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';

View File

@@ -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'],
},
});