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:
@@ -5,7 +5,7 @@ description: Run initial NanoClaw setup. Use when user wants to install dependen
|
|||||||
|
|
||||||
# NanoClaw Setup
|
# 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.
|
**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
|
## 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_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
|
- 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
|
### 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.
|
**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.
|
- 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?
|
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?
|
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)
|
- **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 src/setup/index.ts --step whatsapp-auth -- --method pairing-code --phone NUMBER` (Bash timeout: 150000ms). Display PAIRING_CODE.
|
- **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 src/setup/index.ts --step whatsapp-auth -- --method qr-terminal`. Tell user to run `npm run auth` in another terminal.
|
- **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.
|
**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`
|
**DM with bot:** Ask for bot's number, JID = `NUMBER@s.whatsapp.net`
|
||||||
|
|
||||||
**Group:**
|
**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.
|
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).
|
4. Present candidates as AskUserQuestion (names only, not JIDs).
|
||||||
|
|
||||||
## 8. Register Channel
|
## 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
|
## 9. Mount Allowlist
|
||||||
|
|
||||||
AskUserQuestion: Agent access to external directories?
|
AskUserQuestion: Agent access to external directories?
|
||||||
|
|
||||||
**No:** `npx tsx src/setup/index.ts --step mounts -- --empty`
|
**No:** `npx tsx setup/index.ts --step mounts -- --empty`
|
||||||
**Yes:** Collect paths/permissions. `npx tsx src/setup/index.ts --step mounts -- --json '{"allowedRoots":[...],"blockedPatterns":[],"nonMainReadOnly":true}'`
|
**Yes:** Collect paths/permissions. `npx tsx setup/index.ts --step mounts -- --json '{"allowedRoots":[...],"blockedPatterns":[],"nonMainReadOnly":true}'`
|
||||||
|
|
||||||
## 10. Start Service
|
## 10. Start Service
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ If service already running: unload first.
|
|||||||
- macOS: `launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist`
|
- macOS: `launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist`
|
||||||
- Linux: `systemctl --user stop nanoclaw` (or `systemctl stop nanoclaw` if root)
|
- 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.
|
**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
|
## 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:**
|
**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=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
|
- CREDENTIALS=missing → re-run step 4
|
||||||
- WHATSAPP_AUTH=not_found → re-run step 5
|
- WHATSAPP_AUTH=not_found → re-run step 5
|
||||||
- REGISTERED_GROUPS=0 → re-run steps 7-8
|
- 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`
|
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`.
|
**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).
|
**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).
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"format": "prettier --write \"src/**/*.ts\"",
|
"format": "prettier --write \"src/**/*.ts\"",
|
||||||
"format:check": "prettier --check \"src/**/*.ts\"",
|
"format:check": "prettier --check \"src/**/*.ts\"",
|
||||||
"setup": "tsx src/setup/index.ts",
|
"setup": "tsx setup/index.ts",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"test:watch": "vitest"
|
"test:watch": "vitest"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { logger } from '../logger.js';
|
import { logger } from '../src/logger.js';
|
||||||
import { commandExists } from './platform.js';
|
import { commandExists } from './platform.js';
|
||||||
import { emitStatus } from './status.js';
|
import { emitStatus } from './status.js';
|
||||||
|
|
||||||
@@ -7,8 +7,8 @@ import path from 'path';
|
|||||||
|
|
||||||
import Database from 'better-sqlite3';
|
import Database from 'better-sqlite3';
|
||||||
|
|
||||||
import { STORE_DIR } from '../config.js';
|
import { STORE_DIR } from '../src/config.js';
|
||||||
import { logger } from '../logger.js';
|
import { logger } from '../src/logger.js';
|
||||||
import { commandExists, getPlatform, isHeadless, isWSL } from './platform.js';
|
import { commandExists, getPlatform, isHeadless, isWSL } from './platform.js';
|
||||||
import { emitStatus } from './status.js';
|
import { emitStatus } from './status.js';
|
||||||
|
|
||||||
@@ -8,8 +8,8 @@ import path from 'path';
|
|||||||
|
|
||||||
import Database from 'better-sqlite3';
|
import Database from 'better-sqlite3';
|
||||||
|
|
||||||
import { STORE_DIR } from '../config.js';
|
import { STORE_DIR } from '../src/config.js';
|
||||||
import { logger } from '../logger.js';
|
import { logger } from '../src/logger.js';
|
||||||
import { emitStatus } from './status.js';
|
import { emitStatus } from './status.js';
|
||||||
|
|
||||||
function parseArgs(args: string[]): { list: boolean; limit: number } {
|
function parseArgs(args: string[]): { list: boolean; limit: number } {
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* Setup CLI entry point.
|
* 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';
|
import { emitStatus } from './status.js';
|
||||||
|
|
||||||
const STEPS: Record<string, () => Promise<{ run: (args: string[]) => Promise<void> }>> = {
|
const STEPS: Record<string, () => Promise<{ run: (args: string[]) => Promise<void> }>> = {
|
||||||
@@ -21,7 +21,7 @@ async function main(): Promise<void> {
|
|||||||
const stepIdx = args.indexOf('--step');
|
const stepIdx = args.indexOf('--step');
|
||||||
|
|
||||||
if (stepIdx === -1 || !args[stepIdx + 1]) {
|
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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@ import fs from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
|
||||||
import { logger } from '../logger.js';
|
import { logger } from '../src/logger.js';
|
||||||
import { isRoot } from './platform.js';
|
import { isRoot } from './platform.js';
|
||||||
import { emitStatus } from './status.js';
|
import { emitStatus } from './status.js';
|
||||||
|
|
||||||
@@ -20,10 +20,6 @@ describe('getPlatform', () => {
|
|||||||
expect(['macos', 'linux', 'unknown']).toContain(result);
|
expect(['macos', 'linux', 'unknown']).toContain(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns linux on this system', () => {
|
|
||||||
// This test runs on Linux
|
|
||||||
expect(getPlatform()).toBe('linux');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- isWSL ---
|
// --- isWSL ---
|
||||||
@@ -81,10 +77,14 @@ describe('getServiceManager', () => {
|
|||||||
expect(['launchd', 'systemd', 'none']).toContain(result);
|
expect(['launchd', 'systemd', 'none']).toContain(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns systemd or none on Linux', () => {
|
it('matches the detected platform', () => {
|
||||||
|
const platform = getPlatform();
|
||||||
const result = getServiceManager();
|
const result = getServiceManager();
|
||||||
// On Linux, should be systemd if available, else none
|
if (platform === 'macos') {
|
||||||
|
expect(result).toBe('launchd');
|
||||||
|
} else {
|
||||||
expect(['systemd', 'none']).toContain(result);
|
expect(['systemd', 'none']).toContain(result);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -9,8 +9,8 @@ import path from 'path';
|
|||||||
|
|
||||||
import Database from 'better-sqlite3';
|
import Database from 'better-sqlite3';
|
||||||
|
|
||||||
import { STORE_DIR } from '../config.js';
|
import { STORE_DIR } from '../src/config.js';
|
||||||
import { logger } from '../logger.js';
|
import { logger } from '../src/logger.js';
|
||||||
import { emitStatus } from './status.js';
|
import { emitStatus } from './status.js';
|
||||||
|
|
||||||
interface RegisterArgs {
|
interface RegisterArgs {
|
||||||
@@ -9,7 +9,7 @@ import fs from 'fs';
|
|||||||
import os from 'os';
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { logger } from '../logger.js';
|
import { logger } from '../src/logger.js';
|
||||||
import {
|
import {
|
||||||
getPlatform,
|
getPlatform,
|
||||||
getNodePath,
|
getNodePath,
|
||||||
@@ -11,8 +11,8 @@ import path from 'path';
|
|||||||
|
|
||||||
import Database from 'better-sqlite3';
|
import Database from 'better-sqlite3';
|
||||||
|
|
||||||
import { STORE_DIR } from '../config.js';
|
import { STORE_DIR } from '../src/config.js';
|
||||||
import { logger } from '../logger.js';
|
import { logger } from '../src/logger.js';
|
||||||
import { getPlatform, getServiceManager, hasSystemd, isRoot } from './platform.js';
|
import { getPlatform, getServiceManager, hasSystemd, isRoot } from './platform.js';
|
||||||
import { emitStatus } from './status.js';
|
import { emitStatus } from './status.js';
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@ import { execSync, spawn } from 'child_process';
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { logger } from '../logger.js';
|
import { logger } from '../src/logger.js';
|
||||||
import { openBrowser, isHeadless } from './platform.js';
|
import { openBrowser, isHeadless } from './platform.js';
|
||||||
import { emitStatus } from './status.js';
|
import { emitStatus } from './status.js';
|
||||||
|
|
||||||
@@ -2,6 +2,6 @@ import { defineConfig } from 'vitest/config';
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
test: {
|
test: {
|
||||||
include: ['src/**/*.test.ts', 'skills-engine/**/*.test.ts'],
|
include: ['src/**/*.test.ts', 'setup/**/*.test.ts', 'skills-engine/**/*.test.ts'],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user