import { spawn } from 'child_process'; import * as React from 'react'; import { useEffect, useState } from 'react'; import { resolve } from 'path'; import { Box, Text } from '@anthropic/ink'; import { Dialog } from '../../components/design-system/Dialog.js'; import { ListItem } from '../../components/design-system/ListItem.js'; import { useRegisterOverlay } from '../../context/overlayContext.js'; import { useKeybindings } from '../../keybindings/useKeybinding.js'; import { findGitRoot } from '../../utils/git.js'; import { getKairosActive, setKairosActive } from '../../bootstrap/state.js'; import type { LocalJSXCommandContext } from '../../commands.js'; import type { LocalJSXCommandOnDone } from '../../types/command.js'; import type { AppState } from '../../state/AppState.js'; /** * Compute the default directory for assistant daemon installation. * Prefers git root of cwd; falls back to cwd itself. */ export async function computeDefaultInstallDir(): Promise { const cwd = process.cwd(); const gitRoot = findGitRoot(cwd); return gitRoot || resolve(cwd); } interface WizardProps { defaultDir: string; onInstalled: (dir: string) => void; onCancel: () => void; onError: (message: string) => void; } /** * Install wizard for assistant mode. Shown when `claude assistant` finds * zero CCR sessions. Guides the user to start a daemon that registers * a bridge → CCR cloud session. * * After installation, main.tsx tells the user to run `claude assistant` * again in a few seconds (daemon needs time to register the bridge session). */ export function NewInstallWizard({ defaultDir, onInstalled, onCancel, onError }: WizardProps): React.ReactNode { useRegisterOverlay('assistant-install-wizard'); const [focusIndex, setFocusIndex] = useState(0); const [starting, setStarting] = useState(false); useKeybindings( { 'select:next': () => setFocusIndex(i => (i + 1) % 2), 'select:previous': () => setFocusIndex(i => (i - 1 + 2) % 2), 'select:accept': () => { if (focusIndex === 0) { startDaemon(); } else { onCancel(); } }, }, { context: 'Select' }, ); function startDaemon(): void { if (starting) return; setStarting(true); const dir = defaultDir || resolve('.'); try { const execArgs = [...process.execArgv, process.argv[1]!, 'daemon', 'start', `--dir=${dir}`]; const child = spawn(process.execPath, execArgs, { cwd: dir, stdio: 'ignore', detached: true, }); child.unref(); child.on('error', err => { onError(`Failed to start daemon: ${err.message}`); }); // Give the daemon a moment to initialize, then report success. // The daemon still needs several more seconds to register the bridge // and create a CCR session — main.tsx will tell the user to reconnect. setTimeout(() => { onInstalled(dir); }, 1500); } catch (err) { onError(`Failed to start daemon: ${err instanceof Error ? err.message : String(err)}`); } } if (starting) { return ( Starting daemon in {defaultDir}... ); } return ( No active assistant sessions found. Start a daemon in {defaultDir || '.'} to create a cloud session? Start assistant daemon Cancel Enter to select · Esc to cancel ); } /** * /assistant command implementation. * * First invocation activates KAIROS (sets kairosActive, enables brief * and proactive tools). Subsequent invocations toggle the assistant panel. */ export async function call( onDone: LocalJSXCommandOnDone, context: LocalJSXCommandContext, _args: string, ): Promise { const { setAppState, getAppState } = context; // First invocation: activate KAIROS if (!getKairosActive()) { setKairosActive(true); setAppState( (prev: AppState) => ({ ...prev, kairosEnabled: true, assistantPanelVisible: true, }) as AppState, ); onDone('KAIROS assistant mode activated.', { display: 'system' }); return null; } // Subsequent invocations: toggle panel visibility const current = getAppState(); const isVisible = (current as Record).assistantPanelVisible; if (isVisible) { setAppState( (prev: AppState) => ({ ...prev, assistantPanelVisible: false, }) as AppState, ); onDone('Assistant panel hidden.', { display: 'system' }); } else { setAppState( (prev: AppState) => ({ ...prev, assistantPanelVisible: true, }) as AppState, ); onDone('Assistant panel opened.', { display: 'system' }); } return null; }