diff --git a/setup/verify.ts b/setup/verify.ts index 281b243ce..4bfd3d0d7 100644 --- a/setup/verify.ts +++ b/setup/verify.ts @@ -14,14 +14,9 @@ import Database from 'better-sqlite3'; import { DATA_DIR } from '../src/config.js'; import { readEnvFile } from '../src/env.js'; import { log } from '../src/log.js'; -import { pingCliAgent } from './lib/agent-ping.js'; +import { pingCliAgent, type PingResult } from './lib/agent-ping.js'; import { getLaunchdLabel, getSystemdUnit } from '../src/install-slug.js'; -import { - getPlatform, - getServiceManager, - hasSystemd, - isRoot, -} from './platform.js'; +import { getPlatform, getServiceManager, hasSystemd, isRoot } from './platform.js'; import { emitStatus } from './status.js'; export async function run(_args: string[]): Promise { @@ -38,11 +33,7 @@ export async function run(_args: string[]): Promise { // a sibling checkout (common for developers with multiple clones), this // repo's `data/cli.sock` won't exist — AGENT_PING would return a // misleading `socket_error`. Surface the mismatch directly instead. - let service: - | 'not_found' - | 'stopped' - | 'running' - | 'running_other_checkout' = 'not_found'; + let service: 'not_found' | 'stopped' | 'running' | 'running_other_checkout' = 'not_found'; let runningFromPath: string | null = null; const mgr = getServiceManager(); @@ -74,10 +65,7 @@ export async function run(_args: string[]): Promise { execSync(`${prefix} is-active ${systemdUnit}`, { stdio: 'ignore' }); service = 'running'; try { - const pidStr = execSync( - `${prefix} show ${systemdUnit} -p MainPID --value`, - { encoding: 'utf-8' }, - ).trim(); + const pidStr = execSync(`${prefix} show ${systemdUnit} -p MainPID --value`, { encoding: 'utf-8' }).trim(); const pid = Number(pidStr); if (Number.isInteger(pid) && pid > 0) { runningFromPath = resolveBinaryScript(pid); @@ -115,11 +103,7 @@ export async function run(_args: string[]): Promise { } } - if ( - service === 'running' && - runningFromPath && - !isPathInside(runningFromPath, projectRoot) - ) { + if (service === 'running' && runningFromPath && !isPathInside(runningFromPath, projectRoot)) { service = 'running_other_checkout'; } @@ -210,11 +194,7 @@ export async function run(_args: string[]): Promise { // 6. Check mount allowlist let mountAllowlist = 'missing'; - if ( - fs.existsSync( - path.join(homeDir, '.config', 'nanoclaw', 'mount-allowlist.json'), - ) - ) { + if (fs.existsSync(path.join(homeDir, '.config', 'nanoclaw', 'mount-allowlist.json'))) { mountAllowlist = 'configured'; } @@ -227,15 +207,15 @@ export async function run(_args: string[]): Promise { log.info('Agent ping result', { agentPing }); } - // Determine overall status - const status = - service === 'running' && - credentials !== 'missing' && - anyChannelConfigured && - registeredGroups > 0 && - (agentPing === 'ok' || agentPing === 'skipped') - ? 'success' - : 'failed'; + // Determine overall status. A CLI-only install is valid when the local + // agent round-trip succeeds; messaging app credentials are optional. + const status = determineVerifyStatus({ + service, + credentials, + anyChannelConfigured, + registeredGroups, + agentPing, + }); log.info('Verification complete', { status, channelAuth }); @@ -255,6 +235,25 @@ export async function run(_args: string[]): Promise { if (status === 'failed') process.exit(1); } +export function determineVerifyStatus(input: { + service: 'not_found' | 'stopped' | 'running' | 'running_other_checkout'; + credentials: string; + anyChannelConfigured: boolean; + registeredGroups: number; + agentPing: PingResult | 'skipped'; +}): 'success' | 'failed' { + const cliAgentResponds = input.agentPing === 'ok'; + const hasUsableChannel = input.anyChannelConfigured || cliAgentResponds; + + return input.service === 'running' && + input.credentials !== 'missing' && + hasUsableChannel && + input.registeredGroups > 0 && + (cliAgentResponds || input.agentPing === 'skipped') + ? 'success' + : 'failed'; +} + /** * Given a PID, resolve the script path the process is executing (i.e. the * first `.js` / `.ts` / `.mjs` arg after `node`). Returns null on any