@automagik/genie 4.260330.18 → 4.260330.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/dist/genie.js +4 -4
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/plugins/genie/.claude-plugin/plugin.json +1 -1
- package/plugins/genie/package.json +1 -1
- package/src/genie.ts +6 -2
- package/src/term-commands/serve.ts +7 -50
- package/src/tui/components/Nav.tsx +5 -4
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "genie",
|
|
13
|
-
"version": "4.260330.
|
|
13
|
+
"version": "4.260330.20",
|
|
14
14
|
"source": "./plugins/genie",
|
|
15
15
|
"description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, wish them into plans, make with parallel agents, ship as one team. A coding genie that grows with your project."
|
|
16
16
|
}
|
package/dist/genie.js
CHANGED
|
@@ -1362,14 +1362,14 @@ Define your agent's mission here. What is their primary goal? What do they own?
|
|
|
1362
1362
|
`,failedCount++,deps.log({timestamp:now.toISOString(),level:"warn",event:"orphan_run_failed",run_id:run.id,worker_id:run.worker_id,dead_heartbeats:threshold})}if(failedCount>0)deps.log({timestamp:now.toISOString(),level:"info",event:"orphan_reconciliation_completed",failed_count:failedCount});return failedCount}async function collectMachineSnapshot(deps){let sql=await deps.getConnection(),now=deps.now(),snapshotId=deps.generateId(),workers=await deps.listWorkers(),activeWorkers=workers.filter((w)=>!["done","error","suspended"].includes(w.state)).length,teams=new Set(workers.filter((w)=>w.team).map((w)=>w.team)),tmuxSessions=await deps.countTmuxSessions(),cpuPercent=null,memoryMb=null;try{let mem=process.memoryUsage();memoryMb=Math.round(mem.rss/1024/1024)}catch{}try{let cpus=(await import("os")).cpus();if(cpus.length>0){let total=cpus.reduce((acc,cpu)=>{let t=Object.values(cpu.times).reduce((a,b2)=>a+b2,0);return acc+t-cpu.times.idle},0),totalAll=cpus.reduce((acc,cpu)=>acc+Object.values(cpu.times).reduce((a,b2)=>a+b2,0),0);cpuPercent=totalAll>0?Math.round(total/totalAll*100):null}}catch{}await sql`
|
|
1363
1363
|
INSERT INTO machine_snapshots (id, active_workers, active_teams, tmux_sessions, cpu_percent, memory_mb, created_at)
|
|
1364
1364
|
VALUES (${snapshotId}, ${activeWorkers}, ${teams.size}, ${tmuxSessions}, ${cpuPercent}, ${memoryMb}, ${now})
|
|
1365
|
-
`,deps.log({timestamp:now.toISOString(),level:"debug",event:"machine_snapshot",active_workers:activeWorkers,active_teams:teams.size,tmux_sessions:tmuxSessions,cpu_percent:cpuPercent,memory_mb:memoryMb})}async function emitWorkerEvents(deps){let workers=await deps.listWorkers(),now=deps.now().toISOString(),currentIds=new Set;for(let worker of workers){currentIds.add(worker.id);let prev=previousWorkerStates.get(worker.id),repoPath=worker.repoPath??process.cwd();if(!prev)await deps.publishEvent(`genie.agent.${worker.id}.spawned`,{timestamp:now,kind:"state",agent:worker.id,team:worker.team,text:`Agent ${worker.id} spawned`,data:{state:worker.state},source:"registry"},repoPath);else if(prev.state!==worker.state){if(await deps.publishEvent(`genie.agent.${worker.id}.state`,{timestamp:now,kind:"state",agent:worker.id,team:worker.team,text:`Agent ${worker.id} state: ${prev.state} \u2192 ${worker.state}`,data:{previousState:prev.state,state:worker.state},source:"registry"},repoPath),worker.state==="done"&&worker.wishSlug&&worker.groupNumber!=null)await deps.publishEvent(`genie.wish.${worker.wishSlug}.group.${worker.groupNumber}.done`,{timestamp:now,kind:"system",agent:worker.id,team:worker.team,text:`Wish ${worker.wishSlug} group ${worker.groupNumber} completed by ${worker.id}`,data:{wishSlug:worker.wishSlug,groupNumber:worker.groupNumber},source:"registry"},repoPath)}previousWorkerStates.set(worker.id,{...worker})}for(let[id,prev]of previousWorkerStates)if(!currentIds.has(id))await deps.publishEvent(`genie.agent.${id}.killed`,{timestamp:now,kind:"state",agent:id,team:prev.team,text:`Agent ${id} killed`,data:{lastState:prev.state},source:"registry"},prev.repoPath??process.cwd()),previousWorkerStates.delete(id)}function _resetWorkerStatesForTesting(){previousWorkerStates.clear()}function startInboxWatcherIfEnabled(deps){let pollMs=getInboxPollIntervalMs();if(pollMs===0)return deps.log({timestamp:deps.now().toISOString(),level:"info",event:"inbox_watcher_disabled"}),null;return deps.log({timestamp:deps.now().toISOString(),level:"info",event:"inbox_watcher_started",poll_interval_ms:pollMs}),startInboxWatcher()}function startDaemon(configOverrides,depsOverrides){let config=resolveConfig(configOverrides),deps={...createDefaultDeps(),...depsOverrides},daemonId=deps.generateId(),running2=!0,pollTimeout=null,pollResolve=null,listenConnection=null,heartbeatTimer=null,orphanTimer=null,inboxWatcherHandle=null,captureFallbackTimer=null,eventRouterHandle=null,stop=()=>{if(running2=!1,pollTimeout)clearTimeout(pollTimeout),pollTimeout=null;if(pollResolve)pollResolve(),pollResolve=null;if(heartbeatTimer)clearInterval(heartbeatTimer),heartbeatTimer=null;if(orphanTimer)clearInterval(orphanTimer),orphanTimer=null;if(inboxWatcherHandle)stopInboxWatcher(inboxWatcherHandle),inboxWatcherHandle=null;if(listenConnection)listenConnection.end().catch(()=>{}),listenConnection=null;if(captureFallbackTimer)clearInterval(captureFallbackTimer),captureFallbackTimer=null;eventRouterHandle?.stop().catch(()=>{}),eventRouterHandle=null,Promise.resolve().then(() => (init_session_filewatch(),exports_session_filewatch)).then((m)=>m.stopFilewatch()).catch(()=>{}),Promise.resolve().then(() => (init_session_backfill(),exports_session_backfill)).then((m)=>m.stopBackfill()).catch(()=>{}),Promise.resolve().then(() => (init_db(),exports_db)).then(({getLockfilePath:getLockfilePath2})=>{try{__require("fs").unlinkSync(getLockfilePath2())}catch{}}).catch(()=>{})},processTriggers=async()=>{try{let claimed=await claimDueTriggers(deps,config,daemonId);if(claimed.length===0)return;if(claimed.length>config.jitterThreshold){let jitterMs=deps.jitter(config.maxJitterMs);deps.log({timestamp:deps.now().toISOString(),level:"info",event:"jitter_applied",count:claimed.length,jitter_ms:jitterMs}),await deps.sleep(jitterMs)}for(let trigger of claimed){if(!running2)break;await fireTrigger(deps,trigger,daemonId)}}catch(err){let message=err instanceof Error?err.message:String(err);deps.log({timestamp:deps.now().toISOString(),level:"error",event:"process_cycle_error",error:message})}};async function setupListenNotify(d,onTrigger){try{let sql=await d.getConnection();return await sql.listen("genie_trigger_due",async()=>{if(!running2)return;await onTrigger()}),d.log({timestamp:d.now().toISOString(),level:"info",event:"listen_started",channel:"genie_trigger_due"}),sql}catch(err){let message=err instanceof Error?err.message:String(err);return d.log({timestamp:d.now().toISOString(),level:"warn",event:"listen_failed",error:message}),null}}function startOrphanTimer(d,cfg){return setInterval(async()=>{if(!running2)return;try{await reconcileOrphans(d,cfg)}catch(err){let message=err instanceof Error?err.message:String(err);d.log({timestamp:d.now().toISOString(),level:"error",event:"orphan_reconciliation_error",error:message})}},cfg.orphanCheckIntervalMs)}async function startEventRouterSafe(d){try{let handle=await startEventRouter();return d.log({timestamp:d.now().toISOString(),level:"info",event:"event_router_started"}),handle}catch(err){let message=err instanceof Error?err.message:String(err);return d.log({timestamp:d.now().toISOString(),level:"warn",event:"event_router_start_failed",error:message}),null}}async function initSessionCapture(d,cfg){try{let captureSql=await d.getConnection(),{startFilewatch:startFilewatch2}=await Promise.resolve().then(() => (init_session_filewatch(),exports_session_filewatch)),{startBackfill:startBackfill2}=await Promise.resolve().then(() => (init_session_backfill(),exports_session_backfill));if(!await startFilewatch2(captureSql)){let{ingestFileFull:ingestFileFull2,discoverAllJsonlFiles:discoverAllJsonlFiles2,buildWorkerMap:buildWorkerMap2}=await Promise.resolve().then(() => (init_session_capture(),exports_session_capture));d.log({timestamp:d.now().toISOString(),level:"warn",event:"filewatch_failed_fallback_polling"});let timer2=setInterval(async()=>{if(!running2)return;try{let files=await discoverAllJsonlFiles2(),workerMap=await buildWorkerMap2(captureSql);for(let f of files)await ingestFileFull2(captureSql,f.sessionId,f.jsonlPath,f.projectPath,0,{parentSessionId:f.parentSessionId,isSubagent:f.isSubagent,workerMap})}catch{}},cfg.heartbeatIntervalMs);return startBackfill2(captureSql).catch((err)=>{let msg=err instanceof Error?err.message:String(err);d.log({timestamp:d.now().toISOString(),level:"error",event:"backfill_error",error:msg})}),timer2}return startBackfill2(captureSql).catch((err)=>{let msg=err instanceof Error?err.message:String(err);d.log({timestamp:d.now().toISOString(),level:"error",event:"backfill_error",error:msg})}),null}catch(err){let message=err instanceof Error?err.message:String(err);return d.log({timestamp:d.now().toISOString(),level:"warn",event:"session_capture_init_failed",error:message}),null}}async function runHeartbeat(d){if(!running2)return;try{await collectHeartbeats(d),await collectMachineSnapshot(d),await emitWorkerEvents(d);try{let retSql=await d.getConnection();await retSql`DELETE FROM heartbeats WHERE created_at < now() - interval '7 days'`,await retSql`DELETE FROM machine_snapshots WHERE created_at < now() - interval '30 days'`,await retSql`DELETE FROM audit_events WHERE entity_type LIKE 'otel_%' AND created_at < now() - interval '30 days'`}catch{}}catch(err){let message=err instanceof Error?err.message:String(err);d.log({timestamp:d.now().toISOString(),level:"error",event:"heartbeat_error",error:message})}}let done=(async()=>{deps.log({timestamp:deps.now().toISOString(),level:"info",event:"daemon_started",daemon_id:daemonId,max_concurrent:config.maxConcurrent,poll_interval_ms:config.pollIntervalMs});try{await recoverOnStartup(deps,daemonId,config)}catch(err){let message=err instanceof Error?err.message:String(err);deps.log({timestamp:deps.now().toISOString(),level:"error",event:"recovery_error",error:message})}listenConnection=await setupListenNotify(deps,processTriggers),heartbeatTimer=setInterval(()=>runHeartbeat(deps),config.heartbeatIntervalMs),orphanTimer=startOrphanTimer(deps,config),inboxWatcherHandle=startInboxWatcherIfEnabled(deps),eventRouterHandle=await startEventRouterSafe(deps),captureFallbackTimer=await initSessionCapture(deps,config),await processTriggers();while(running2){if(await new Promise((resolve5)=>{pollResolve=resolve5,pollTimeout=setTimeout(resolve5,config.pollIntervalMs)}),pollResolve=null,!running2)break;await processTriggers()}deps.log({timestamp:deps.now().toISOString(),level:"info",event:"daemon_stopped",daemon_id:daemonId})})();return{stop,done,daemonId}}var RESUME_COOLDOWN_MS=60000,DEFAULT_MAX_RESUME_ATTEMPTS=3,previousWorkerStates;var init_scheduler_daemon=__esm(()=>{init_cron();init_event_router();init_inbox_watcher();init_run_spec();previousWorkerStates=new Map});var exports_serve={};__export(exports_serve,{registerServeCommands:()=>registerServeCommands,isTuiSessionReady:()=>isTuiSessionReady,isServeRunning:()=>isServeRunning,ensureTuiSession:()=>ensureTuiSession,autoStartServe:()=>autoStartServe});import{execSync as execSync6,spawn as spawn3}from"child_process";import{existsSync as existsSync24,mkdirSync as mkdirSync8,readFileSync as readFileSync11,unlinkSync as unlinkSync5,writeFileSync as writeFileSync8}from"fs";import{homedir as homedir20}from"os";import{join as join31}from"path";function genieHome(){return process.env.GENIE_HOME??join31(homedir20(),".genie")}function servePidPath(){return join31(genieHome(),"serve.pid")}function genieTmuxConf(){return[join31(genieHome(),"tmux.conf")].find((p)=>existsSync24(p))??"/dev/null"}function tuiTmuxConf(){return[join31(genieHome(),"tui-tmux.conf")].find((p)=>existsSync24(p))??"/dev/null"}function readServePid(){let path2=servePidPath();if(!existsSync24(path2))return null;let raw=readFileSync11(path2,"utf-8").trim(),pid=Number.parseInt(raw,10);if(Number.isNaN(pid)||pid<=0)return null;return pid}function writeServePid(pid){mkdirSync8(genieHome(),{recursive:!0}),writeFileSync8(servePidPath(),String(pid),"utf-8")}function removeServePid(){let path2=servePidPath();if(existsSync24(path2))try{unlinkSync5(path2)}catch{}}function isProcessAlive(pid){try{return process.kill(pid,0),!0}catch{return!1}}function tmuxCmd(socket,conf,subcmd){return`tmux -L ${socket} -f ${conf} ${subcmd}`}function genieTmux(subcmd){return tmuxCmd(GENIE_SOCKET,genieTmuxConf(),subcmd)}function tuiTmux(subcmd){return tmuxCmd(TUI_SOCKET,tuiTmuxConf(),subcmd)}function isTmuxServerRunning(socket,conf){try{return execSync6(tmuxCmd(socket,conf,"list-sessions"),{stdio:"ignore"}),!0}catch{return!1}}function startAgentTmuxServer(agents){let conf=genieTmuxConf();if(!isTmuxServerRunning(GENIE_SOCKET,conf))execSync6(genieTmux("new-session -d -s _bootstrap"),{stdio:"ignore"});for(let agent of agents)try{execSync6(genieTmux(`has-session -t '${agent}'`),{stdio:"ignore"})}catch{try{execSync6(genieTmux(`new-session -d -s '${agent}'`),{stdio:"ignore"})}catch{}}if(agents.length>0)try{execSync6(genieTmux("kill-session -t _bootstrap"),{stdio:"ignore"})}catch{}}function applyTuiStyle(){let cmds=[`set-option -t ${TUI_SESSION} pane-border-style 'fg=${TUI_STYLE.inactiveBorder}'`,`set-option -t ${TUI_SESSION} pane-active-border-style 'fg=${TUI_STYLE.activeBorder}'`,`set-option -t ${TUI_SESSION} mouse off`,`set-option -t ${TUI_SESSION} status off`,`set-option -t ${TUI_SESSION} pane-border-status off`];for(let cmd of cmds)try{execSync6(tuiTmux(cmd),{stdio:"ignore"})}catch{}}function setupTuiKeybindings(){try{execSync6(tuiTmux(`bind-key -T ${KEY_TABLE} Tab select-pane -t ${TUI_SESSION}:0.1 \\; switch-client -T ${KEY_TABLE}`),{stdio:"ignore"}),execSync6(tuiTmux(`bind-key -T ${KEY_TABLE} C-b if-shell "[ $(tmux -L ${TUI_SOCKET} display-message -p '#\\{pane_width\\}' -t ${TUI_SESSION}:0.0) -gt 5 ]" "resize-pane -t ${TUI_SESSION}:0.0 -x 0" "resize-pane -t ${TUI_SESSION}:0.0 -x ${NAV_WIDTH}" \\; switch-client -T ${KEY_TABLE}`),{stdio:"ignore"}),execSync6(tuiTmux(`bind-key -T ${KEY_TABLE} C-t send-keys -t ${TUI_SESSION}:0.1 C-b c \\; switch-client -T ${KEY_TABLE}`),{stdio:"ignore"}),execSync6(tuiTmux(`bind-key -T ${KEY_TABLE} C-q run-shell "tmux -L ${TUI_SOCKET} kill-session -t ${TUI_SESSION}"`),{stdio:"ignore"}),execSync6(tuiTmux(`bind-key -T ${KEY_TABLE} 'C-\\\\' run-shell "tmux -L ${TUI_SOCKET} kill-session -t ${TUI_SESSION}"`),{stdio:"ignore"}),execSync6(tuiTmux(`set-hook -t ${TUI_SESSION} client-session-changed "switch-client -T ${KEY_TABLE}"`),{stdio:"ignore"})}catch{}}function startTuiTmuxServer(){try{execSync6(tuiTmux(`has-session -t ${TUI_SESSION}`),{stdio:"ignore"});let panes2=execSync6(tuiTmux(`list-panes -t ${TUI_SESSION}:0 -F '#{pane_id}'`),{encoding:"utf-8"}).trim().split(`
|
|
1365
|
+
`,deps.log({timestamp:now.toISOString(),level:"debug",event:"machine_snapshot",active_workers:activeWorkers,active_teams:teams.size,tmux_sessions:tmuxSessions,cpu_percent:cpuPercent,memory_mb:memoryMb})}async function emitWorkerEvents(deps){let workers=await deps.listWorkers(),now=deps.now().toISOString(),currentIds=new Set;for(let worker of workers){currentIds.add(worker.id);let prev=previousWorkerStates.get(worker.id),repoPath=worker.repoPath??process.cwd();if(!prev)await deps.publishEvent(`genie.agent.${worker.id}.spawned`,{timestamp:now,kind:"state",agent:worker.id,team:worker.team,text:`Agent ${worker.id} spawned`,data:{state:worker.state},source:"registry"},repoPath);else if(prev.state!==worker.state){if(await deps.publishEvent(`genie.agent.${worker.id}.state`,{timestamp:now,kind:"state",agent:worker.id,team:worker.team,text:`Agent ${worker.id} state: ${prev.state} \u2192 ${worker.state}`,data:{previousState:prev.state,state:worker.state},source:"registry"},repoPath),worker.state==="done"&&worker.wishSlug&&worker.groupNumber!=null)await deps.publishEvent(`genie.wish.${worker.wishSlug}.group.${worker.groupNumber}.done`,{timestamp:now,kind:"system",agent:worker.id,team:worker.team,text:`Wish ${worker.wishSlug} group ${worker.groupNumber} completed by ${worker.id}`,data:{wishSlug:worker.wishSlug,groupNumber:worker.groupNumber},source:"registry"},repoPath)}previousWorkerStates.set(worker.id,{...worker})}for(let[id,prev]of previousWorkerStates)if(!currentIds.has(id))await deps.publishEvent(`genie.agent.${id}.killed`,{timestamp:now,kind:"state",agent:id,team:prev.team,text:`Agent ${id} killed`,data:{lastState:prev.state},source:"registry"},prev.repoPath??process.cwd()),previousWorkerStates.delete(id)}function _resetWorkerStatesForTesting(){previousWorkerStates.clear()}function startInboxWatcherIfEnabled(deps){let pollMs=getInboxPollIntervalMs();if(pollMs===0)return deps.log({timestamp:deps.now().toISOString(),level:"info",event:"inbox_watcher_disabled"}),null;return deps.log({timestamp:deps.now().toISOString(),level:"info",event:"inbox_watcher_started",poll_interval_ms:pollMs}),startInboxWatcher()}function startDaemon(configOverrides,depsOverrides){let config=resolveConfig(configOverrides),deps={...createDefaultDeps(),...depsOverrides},daemonId=deps.generateId(),running2=!0,pollTimeout=null,pollResolve=null,listenConnection=null,heartbeatTimer=null,orphanTimer=null,inboxWatcherHandle=null,captureFallbackTimer=null,eventRouterHandle=null,stop=()=>{if(running2=!1,pollTimeout)clearTimeout(pollTimeout),pollTimeout=null;if(pollResolve)pollResolve(),pollResolve=null;if(heartbeatTimer)clearInterval(heartbeatTimer),heartbeatTimer=null;if(orphanTimer)clearInterval(orphanTimer),orphanTimer=null;if(inboxWatcherHandle)stopInboxWatcher(inboxWatcherHandle),inboxWatcherHandle=null;if(listenConnection)listenConnection.end().catch(()=>{}),listenConnection=null;if(captureFallbackTimer)clearInterval(captureFallbackTimer),captureFallbackTimer=null;eventRouterHandle?.stop().catch(()=>{}),eventRouterHandle=null,Promise.resolve().then(() => (init_session_filewatch(),exports_session_filewatch)).then((m)=>m.stopFilewatch()).catch(()=>{}),Promise.resolve().then(() => (init_session_backfill(),exports_session_backfill)).then((m)=>m.stopBackfill()).catch(()=>{}),Promise.resolve().then(() => (init_db(),exports_db)).then(({getLockfilePath:getLockfilePath2})=>{try{__require("fs").unlinkSync(getLockfilePath2())}catch{}}).catch(()=>{})},processTriggers=async()=>{try{let claimed=await claimDueTriggers(deps,config,daemonId);if(claimed.length===0)return;if(claimed.length>config.jitterThreshold){let jitterMs=deps.jitter(config.maxJitterMs);deps.log({timestamp:deps.now().toISOString(),level:"info",event:"jitter_applied",count:claimed.length,jitter_ms:jitterMs}),await deps.sleep(jitterMs)}for(let trigger of claimed){if(!running2)break;await fireTrigger(deps,trigger,daemonId)}}catch(err){let message=err instanceof Error?err.message:String(err);deps.log({timestamp:deps.now().toISOString(),level:"error",event:"process_cycle_error",error:message})}};async function setupListenNotify(d,onTrigger){try{let sql=await d.getConnection();return await sql.listen("genie_trigger_due",async()=>{if(!running2)return;await onTrigger()}),d.log({timestamp:d.now().toISOString(),level:"info",event:"listen_started",channel:"genie_trigger_due"}),sql}catch(err){let message=err instanceof Error?err.message:String(err);return d.log({timestamp:d.now().toISOString(),level:"warn",event:"listen_failed",error:message}),null}}function startOrphanTimer(d,cfg){return setInterval(async()=>{if(!running2)return;try{await reconcileOrphans(d,cfg)}catch(err){let message=err instanceof Error?err.message:String(err);d.log({timestamp:d.now().toISOString(),level:"error",event:"orphan_reconciliation_error",error:message})}},cfg.orphanCheckIntervalMs)}async function startEventRouterSafe(d){try{let handle=await startEventRouter();return d.log({timestamp:d.now().toISOString(),level:"info",event:"event_router_started"}),handle}catch(err){let message=err instanceof Error?err.message:String(err);return d.log({timestamp:d.now().toISOString(),level:"warn",event:"event_router_start_failed",error:message}),null}}async function initSessionCapture(d,cfg){try{let captureSql=await d.getConnection(),{startFilewatch:startFilewatch2}=await Promise.resolve().then(() => (init_session_filewatch(),exports_session_filewatch)),{startBackfill:startBackfill2}=await Promise.resolve().then(() => (init_session_backfill(),exports_session_backfill));if(!await startFilewatch2(captureSql)){let{ingestFileFull:ingestFileFull2,discoverAllJsonlFiles:discoverAllJsonlFiles2,buildWorkerMap:buildWorkerMap2}=await Promise.resolve().then(() => (init_session_capture(),exports_session_capture));d.log({timestamp:d.now().toISOString(),level:"warn",event:"filewatch_failed_fallback_polling"});let timer2=setInterval(async()=>{if(!running2)return;try{let files=await discoverAllJsonlFiles2(),workerMap=await buildWorkerMap2(captureSql);for(let f of files)await ingestFileFull2(captureSql,f.sessionId,f.jsonlPath,f.projectPath,0,{parentSessionId:f.parentSessionId,isSubagent:f.isSubagent,workerMap})}catch{}},cfg.heartbeatIntervalMs);return startBackfill2(captureSql).catch((err)=>{let msg=err instanceof Error?err.message:String(err);d.log({timestamp:d.now().toISOString(),level:"error",event:"backfill_error",error:msg})}),timer2}return startBackfill2(captureSql).catch((err)=>{let msg=err instanceof Error?err.message:String(err);d.log({timestamp:d.now().toISOString(),level:"error",event:"backfill_error",error:msg})}),null}catch(err){let message=err instanceof Error?err.message:String(err);return d.log({timestamp:d.now().toISOString(),level:"warn",event:"session_capture_init_failed",error:message}),null}}async function runHeartbeat(d){if(!running2)return;try{await collectHeartbeats(d),await collectMachineSnapshot(d),await emitWorkerEvents(d);try{let retSql=await d.getConnection();await retSql`DELETE FROM heartbeats WHERE created_at < now() - interval '7 days'`,await retSql`DELETE FROM machine_snapshots WHERE created_at < now() - interval '30 days'`,await retSql`DELETE FROM audit_events WHERE entity_type LIKE 'otel_%' AND created_at < now() - interval '30 days'`}catch{}}catch(err){let message=err instanceof Error?err.message:String(err);d.log({timestamp:d.now().toISOString(),level:"error",event:"heartbeat_error",error:message})}}let done=(async()=>{deps.log({timestamp:deps.now().toISOString(),level:"info",event:"daemon_started",daemon_id:daemonId,max_concurrent:config.maxConcurrent,poll_interval_ms:config.pollIntervalMs});try{await recoverOnStartup(deps,daemonId,config)}catch(err){let message=err instanceof Error?err.message:String(err);deps.log({timestamp:deps.now().toISOString(),level:"error",event:"recovery_error",error:message})}listenConnection=await setupListenNotify(deps,processTriggers),heartbeatTimer=setInterval(()=>runHeartbeat(deps),config.heartbeatIntervalMs),orphanTimer=startOrphanTimer(deps,config),inboxWatcherHandle=startInboxWatcherIfEnabled(deps),eventRouterHandle=await startEventRouterSafe(deps),captureFallbackTimer=await initSessionCapture(deps,config),await processTriggers();while(running2){if(await new Promise((resolve5)=>{pollResolve=resolve5,pollTimeout=setTimeout(resolve5,config.pollIntervalMs)}),pollResolve=null,!running2)break;await processTriggers()}deps.log({timestamp:deps.now().toISOString(),level:"info",event:"daemon_stopped",daemon_id:daemonId})})();return{stop,done,daemonId}}var RESUME_COOLDOWN_MS=60000,DEFAULT_MAX_RESUME_ATTEMPTS=3,previousWorkerStates;var init_scheduler_daemon=__esm(()=>{init_cron();init_event_router();init_inbox_watcher();init_run_spec();previousWorkerStates=new Map});var exports_serve={};__export(exports_serve,{registerServeCommands:()=>registerServeCommands,isTuiSessionReady:()=>isTuiSessionReady,isServeRunning:()=>isServeRunning,ensureTuiSession:()=>ensureTuiSession,autoStartServe:()=>autoStartServe});import{execSync as execSync6,spawn as spawn3}from"child_process";import{existsSync as existsSync24,mkdirSync as mkdirSync8,readFileSync as readFileSync11,unlinkSync as unlinkSync5,writeFileSync as writeFileSync8}from"fs";import{homedir as homedir20}from"os";import{join as join31}from"path";function genieHome(){return process.env.GENIE_HOME??join31(homedir20(),".genie")}function servePidPath(){return join31(genieHome(),"serve.pid")}function genieTmuxConf(){return[join31(genieHome(),"tmux.conf")].find((p)=>existsSync24(p))??"/dev/null"}function tuiTmuxConf(){return[join31(genieHome(),"tui-tmux.conf")].find((p)=>existsSync24(p))??"/dev/null"}function readServePid(){let path2=servePidPath();if(!existsSync24(path2))return null;let raw=readFileSync11(path2,"utf-8").trim(),pid=Number.parseInt(raw,10);if(Number.isNaN(pid)||pid<=0)return null;return pid}function writeServePid(pid){mkdirSync8(genieHome(),{recursive:!0}),writeFileSync8(servePidPath(),String(pid),"utf-8")}function removeServePid(){let path2=servePidPath();if(existsSync24(path2))try{unlinkSync5(path2)}catch{}}function isProcessAlive(pid){try{return process.kill(pid,0),!0}catch{return!1}}function tmuxCmd(socket,conf,subcmd){return`tmux -L ${socket} -f ${conf} ${subcmd}`}function genieTmux(subcmd){return tmuxCmd(GENIE_SOCKET,genieTmuxConf(),subcmd)}function tuiTmux(subcmd){return tmuxCmd(TUI_SOCKET,tuiTmuxConf(),subcmd)}function isTmuxServerRunning(socket,conf){try{return execSync6(tmuxCmd(socket,conf,"list-sessions"),{stdio:"ignore"}),!0}catch{return!1}}function applyTuiStyle(){let cmds=[`set-option -t ${TUI_SESSION} pane-border-style 'fg=${TUI_STYLE.inactiveBorder}'`,`set-option -t ${TUI_SESSION} pane-active-border-style 'fg=${TUI_STYLE.activeBorder}'`,`set-option -t ${TUI_SESSION} mouse off`,`set-option -t ${TUI_SESSION} status off`,`set-option -t ${TUI_SESSION} pane-border-status off`];for(let cmd of cmds)try{execSync6(tuiTmux(cmd),{stdio:"ignore"})}catch{}}function setupTuiKeybindings(){try{execSync6(tuiTmux(`bind-key -T ${KEY_TABLE} Tab select-pane -t ${TUI_SESSION}:0.1 \\; switch-client -T ${KEY_TABLE}`),{stdio:"ignore"}),execSync6(tuiTmux(`bind-key -T ${KEY_TABLE} C-b if-shell "[ $(tmux -L ${TUI_SOCKET} display-message -p '#\\{pane_width\\}' -t ${TUI_SESSION}:0.0) -gt 5 ]" "resize-pane -t ${TUI_SESSION}:0.0 -x 0" "resize-pane -t ${TUI_SESSION}:0.0 -x ${NAV_WIDTH}" \\; switch-client -T ${KEY_TABLE}`),{stdio:"ignore"}),execSync6(tuiTmux(`bind-key -T ${KEY_TABLE} C-t send-keys -t ${TUI_SESSION}:0.1 C-b c \\; switch-client -T ${KEY_TABLE}`),{stdio:"ignore"}),execSync6(tuiTmux(`bind-key -T ${KEY_TABLE} C-q run-shell "tmux -L ${TUI_SOCKET} kill-session -t ${TUI_SESSION}"`),{stdio:"ignore"}),execSync6(tuiTmux(`bind-key -T ${KEY_TABLE} 'C-\\\\' run-shell "tmux -L ${TUI_SOCKET} kill-session -t ${TUI_SESSION}"`),{stdio:"ignore"}),execSync6(tuiTmux(`set-hook -t ${TUI_SESSION} client-session-changed "switch-client -T ${KEY_TABLE}"`),{stdio:"ignore"})}catch{}}function startTuiTmuxServer(){try{execSync6(tuiTmux(`has-session -t ${TUI_SESSION}`),{stdio:"ignore"});let panes2=execSync6(tuiTmux(`list-panes -t ${TUI_SESSION}:0 -F '#{pane_id}'`),{encoding:"utf-8"}).trim().split(`
|
|
1366
1366
|
`);return{leftPane:panes2[0],rightPane:panes2[1]||panes2[0]}}catch{}let cols=120;execSync6(tuiTmux(`new-session -d -s ${TUI_SESSION} -x ${cols} -y ${40} -e GENIE_TUI_PANE=left`),{stdio:"ignore"}),execSync6(tuiTmux(`split-window -h -t ${TUI_SESSION}:0 -l ${cols-NAV_WIDTH-1}`),{stdio:"ignore"});let panes=execSync6(tuiTmux(`list-panes -t ${TUI_SESSION}:0 -F '#{pane_id}'`),{encoding:"utf-8"}).trim().split(`
|
|
1367
1367
|
`);applyTuiStyle(),setupTuiKeybindings();try{execSync6(tuiTmux(`select-pane -t ${panes[0]}`),{stdio:"ignore"})}catch{}return{leftPane:panes[0],rightPane:panes[1]||panes[0]}}function sendTuiLaunchScript(leftPane,rightPane,workspaceRoot){let home=genieHome(),bunPath=process.execPath||"bun",genieBin=process.argv[1]||"genie",scriptPath=join31(home,"tui-launch.sh"),envVars=["GENIE_TUI_PANE=left",`GENIE_TUI_RIGHT=${rightPane}`];if(workspaceRoot)envVars.push(`GENIE_TUI_WORKSPACE=${workspaceRoot}`);let content=`#!/bin/sh
|
|
1368
1368
|
export ${envVars.join(`
|
|
1369
1369
|
export `)}
|
|
1370
1370
|
exec ${bunPath} ${genieBin}
|
|
1371
1371
|
`;writeFileSync8(scriptPath,content,{mode:493});try{execSync6(tuiTmux(`send-keys -t '${leftPane}' '${scriptPath}' Enter`),{stdio:"ignore"})}catch{}}function killTmuxServer(socket,conf){try{execSync6(tmuxCmd(socket,conf,"kill-server"),{stdio:"ignore"})}catch{}}function listAgentSessions(){try{return execSync6(genieTmux("list-sessions -F '#{session_name}'"),{encoding:"utf-8"}).trim().split(`
|
|
1372
|
-
`).filter(Boolean)}catch{return[]}}function isServeRunning(){let pid=readServePid();return pid!==null&&isProcessAlive(pid)}async function autoStartServe(){if(isServeRunning())return;let bunPath=process.execPath??"bun",genieBin=process.argv[1]??"genie",{spawn:spawnChild}=await import("child_process");spawnChild(bunPath,[genieBin,"serve","--foreground"],{detached:!0,stdio:"ignore",env:{...process.env,GENIE_IS_DAEMON:"1"}}).unref();let deadline=Date.now()+15000;while(Date.now()<deadline)if(await new Promise((resolve5)=>setTimeout(resolve5,500)),isServeRunning()&&isTuiSessionReady())return;if(!isServeRunning())throw Error("genie serve failed to start within 15s. Run `genie serve` manually.")}function isTuiSessionReady(){try{return execSync6(tmuxCmd(TUI_SOCKET,tuiTmuxConf(),`has-session -t ${TUI_SESSION}`),{stdio:"ignore"}),!0}catch{return!1}}function ensureTuiSession(workspaceRoot){if(isTuiSessionReady())return;let{leftPane,rightPane}=startTuiTmuxServer();sendTuiLaunchScript(leftPane,rightPane,workspaceRoot)}
|
|
1372
|
+
`).filter(Boolean)}catch{return[]}}function isServeRunning(){let pid=readServePid();return pid!==null&&isProcessAlive(pid)}async function autoStartServe(){if(isServeRunning())return;let bunPath=process.execPath??"bun",genieBin=process.argv[1]??"genie",{spawn:spawnChild}=await import("child_process");spawnChild(bunPath,[genieBin,"serve","--foreground"],{detached:!0,stdio:"ignore",env:{...process.env,GENIE_IS_DAEMON:"1"}}).unref();let deadline=Date.now()+15000;while(Date.now()<deadline)if(await new Promise((resolve5)=>setTimeout(resolve5,500)),isServeRunning()&&isTuiSessionReady())return;if(!isServeRunning())throw Error("genie serve failed to start within 15s. Run `genie serve` manually.")}function isTuiSessionReady(){try{return execSync6(tmuxCmd(TUI_SOCKET,tuiTmuxConf(),`has-session -t ${TUI_SESSION}`),{stdio:"ignore"}),!0}catch{return!1}}function ensureTuiSession(workspaceRoot){if(isTuiSessionReady())return;let{leftPane,rightPane}=startTuiTmuxServer();sendTuiLaunchScript(leftPane,rightPane,workspaceRoot)}async function startAgentSync(){try{let{findWorkspace:findWorkspace2}=(init_workspace(),__toCommonJS(exports_workspace)),ws=findWorkspace2();if(!ws)return null;let{syncAgentDirectory:syncAgentDirectory2,watchAgentDirectory:watchAgentDirectory2}=await Promise.resolve().then(() => (init_agent_sync(),exports_agent_sync)),syncResult=await syncAgentDirectory2(ws.root);if(syncResult.registered.length+syncResult.updated.length>0)console.log(` Agent sync: ${syncResult.registered.length} registered, ${syncResult.updated.length} updated`);let watcher2=watchAgentDirectory2(ws.root,{onSync:(name,action)=>{console.log(` [agent-watcher] ${name}: ${action}`)}});if(watcher2)console.log(" Agent watcher started (watching agents/ directory)");return watcher2}catch(err){let msg=err instanceof Error?err.message:String(err);return console.error(` Agent sync failed: ${msg}`),null}}async function startForeground(){let existingPid=readServePid();if(existingPid&&isProcessAlive(existingPid))console.log(`genie serve already running (PID ${existingPid})`),process.exit(0);if(existingPid)removeServePid();process.env.GENIE_IS_DAEMON="1",writeServePid(process.pid),console.log(`genie serve starting (PID ${process.pid})`),console.log(" Starting pgserve...");try{let{ensurePgserve:ensurePgserve2}=await Promise.resolve().then(() => (init_db(),exports_db)),port=await ensurePgserve2();console.log(` pgserve ready on port ${port}`)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(` pgserve failed: ${msg}`)}let sessions=listAgentSessions();if(sessions.length>0)console.log(` Agent server (-L ${GENIE_SOCKET}): ${sessions.length} sessions`);else console.log(` Agent server (-L ${GENIE_SOCKET}): no sessions yet (created on first spawn)`);handles.agentWatcher=await startAgentSync(),console.log(` Starting tmux -L ${TUI_SOCKET} server...`);let{leftPane,rightPane}=startTuiTmuxServer(),ws=(()=>{try{let{findWorkspace:findWorkspace2}=(init_workspace(),__toCommonJS(exports_workspace));return findWorkspace2()}catch{return null}})();sendTuiLaunchScript(leftPane,rightPane,ws?.root),console.log(" TUI server ready (session: genie-tui)"),console.log(" Starting scheduler daemon...");try{let{startDaemon:startDaemon2}=await Promise.resolve().then(() => (init_scheduler_daemon(),exports_scheduler_daemon));handles.schedulerHandle=startDaemon2(),console.log(" Scheduler started (includes event-router + inbox-watcher)")}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(` Scheduler failed: ${msg}`)}console.log(`
|
|
1373
1373
|
genie serve is running. Press Ctrl+C to stop.`);let shutdown2=()=>{console.log(`
|
|
1374
1374
|
Shutting down genie serve...`),handles.agentWatcher?.close(),handles.schedulerHandle?.stop(),killTmuxServer(TUI_SOCKET,tuiTmuxConf()),removeServePid(),console.log("genie serve stopped.")};if(process.on("SIGTERM",()=>{shutdown2(),process.exit(143)}),process.on("SIGINT",()=>{shutdown2(),process.exit(130)}),handles.schedulerHandle)await handles.schedulerHandle.done;else await new Promise(()=>{});removeServePid()}async function startBackground(){let existingPid=readServePid();if(existingPid&&isProcessAlive(existingPid))console.log(`genie serve already running (PID ${existingPid})`),process.exit(0);if(existingPid)removeServePid();let bunPath=process.execPath??"bun",genieBin=process.argv[1]??"genie",child=spawn3(bunPath,[genieBin,"serve","--foreground"],{detached:!0,stdio:"ignore",env:{...process.env,GENIE_IS_DAEMON:"1"}});if(child.unref(),child.pid)if(await new Promise((resolve5)=>setTimeout(resolve5,1000)),isProcessAlive(child.pid))console.log(`genie serve started (PID ${child.pid})`);else console.error("Error: genie serve exited immediately."),process.exit(1);else console.error("Error: failed to spawn genie serve"),process.exit(1)}async function stopServe(){let pid=readServePid();if(!pid){console.log("genie serve is not running (no PID file).");return}if(!isProcessAlive(pid)){console.log(`Stale PID file (PID ${pid} not running). Cleaning up.`),removeServePid(),killTmuxServer(TUI_SOCKET,tuiTmuxConf());return}console.log(`Stopping genie serve (PID ${pid})...`);try{process.kill(-pid,"SIGTERM")}catch{try{process.kill(pid,"SIGTERM")}catch{}}let deadline=Date.now()+1e4;while(Date.now()<deadline&&isProcessAlive(pid))await new Promise((resolve5)=>setTimeout(resolve5,250));if(isProcessAlive(pid)){console.log("Did not stop within 10s. Sending SIGKILL.");try{process.kill(-pid,"SIGKILL")}catch{try{process.kill(pid,"SIGKILL")}catch{}}}killTmuxServer(TUI_SOCKET,tuiTmuxConf()),removeServePid(),console.log("genie serve stopped.")}async function printPgserveStatus(){try{let{isAvailable:isAvailable2,getActivePort:getActivePort2}=await Promise.resolve().then(() => (init_db(),exports_db)),dbOk=await isAvailable2();console.log(` pgserve: ${dbOk?`healthy (port ${getActivePort2()})`:"unreachable"}`)}catch{console.log(" pgserve: unavailable")}}function printTmuxStatus(){let agentRunning=isTmuxServerRunning(GENIE_SOCKET,genieTmuxConf()),sessions=agentRunning?listAgentSessions():[];if(console.log(` tmux -L ${GENIE_SOCKET}: ${agentRunning?`running (${sessions.length} sessions)`:"stopped"}`),sessions.length>0)console.log(` ${sessions.join(", ")}`);let tuiRunning=isTmuxServerRunning(TUI_SOCKET,tuiTmuxConf());console.log(` tmux -L ${TUI_SOCKET}: ${tuiRunning?"running":"stopped"}`)}async function printDaemonStatus(serveRunning){try{let schedulerPidPath=join31(genieHome(),"scheduler.pid");if(existsSync24(schedulerPidPath)){let sPid=Number.parseInt(readFileSync11(schedulerPidPath,"utf-8").trim(),10),sAlive=!Number.isNaN(sPid)&&isProcessAlive(sPid);console.log(` scheduler: ${sAlive?`running (PID ${sPid})`:"stopped"}`)}else if(serveRunning)console.log(" scheduler: integrated (in-process)");else console.log(" scheduler: stopped")}catch{console.log(" scheduler: unknown")}try{let{getInboxPollIntervalMs:getInboxPollIntervalMs2}=await Promise.resolve().then(() => (init_inbox_watcher(),exports_inbox_watcher)),pollMs=getInboxPollIntervalMs2();if(pollMs===0)console.log(" inbox: disabled");else console.log(` inbox: ${serveRunning?"watching":"stopped"} (poll ${pollMs/1000}s)`)}catch{console.log(" inbox: unavailable")}}async function statusServe(){let pid=readServePid(),running2=pid!==null&&isProcessAlive(pid);if(console.log(`
|
|
1375
1375
|
Genie Serve`),console.log("\u2500".repeat(50)),console.log(` Status: ${running2?"running":"stopped"}`),running2&&pid)console.log(` PID: ${pid}`);await printPgserveStatus(),printTmuxStatus(),await printDaemonStatus(running2),console.log(` PID file: ${servePidPath()}`),console.log("")}function registerServeCommands(program2){let serve=program2.command("serve").description("Start all genie infrastructure (pgserve, tmux, scheduler)");serve.command("start",{isDefault:!0}).description("Start genie serve").option("--daemon","Run in background").option("--foreground","Run in foreground (default)").action(async(options)=>{if(options.daemon)await startBackground();else await startForeground()}),serve.command("stop").description("Stop genie serve and all services").action(async()=>{await stopServe()}),serve.command("status").description("Show service health").action(async()=>{await statusServe()})}var GENIE_SOCKET="genie",TUI_SOCKET="genie-tui",TUI_SESSION="genie-tui",NAV_WIDTH=30,KEY_TABLE="genie-tui",TUI_STYLE,handles;var init_serve=__esm(()=>{TUI_STYLE={activeBorder:"#7c3aed",inactiveBorder:"#414868"};handles={schedulerHandle:null,agentWatcher:null}});var exports_tmux2={};__export(exports_tmux2,{attachTuiSession:()=>attachTuiSession,attachProjectWindow:()=>attachProjectWindow});import{execSync as execSync7,spawnSync as spawnSync2}from"child_process";function resolveRightPane(rightPane){try{return execSync7(`${TMUX} display-message -t ${rightPane} -p ''`,{stdio:"ignore"}),rightPane}catch{try{let panes=execSync7(`${TMUX} list-panes -t ${SESSION_NAME}:0 -F '#{pane_id}'`,{encoding:"utf-8"}).trim().split(`
|
|
@@ -2072,7 +2072,7 @@ $ bun add react-devtools-core@7 -d
|
|
|
2072
2072
|
AND a.ended_at IS NULL
|
|
2073
2073
|
ORDER BY a.started_at DESC
|
|
2074
2074
|
`).map(mapAssignment)}function mapExecutor(row){let meta=row.metadata;return{id:String(row.id),agentId:String(row.agent_id),agentName:row.agent_name?String(row.agent_name):null,provider:String(row.provider),transport:String(row.transport),pid:row.pid!=null?Number(row.pid):null,tmuxSession:row.tmux_session?String(row.tmux_session):null,tmuxPaneId:row.tmux_pane_id?String(row.tmux_pane_id):null,state:String(row.state),metadata:typeof meta==="string"?JSON.parse(meta):meta??{},startedAt:row.started_at instanceof Date?row.started_at.toISOString():String(row.started_at),role:row.role?String(row.role):null,team:row.team?String(row.team):null}}function mapAssignment(row){return{id:String(row.id),executorId:String(row.executor_id),taskId:row.task_id?String(row.task_id):null,taskTitle:row.task_title?String(row.task_title):null,wishSlug:row.wish_slug?String(row.wish_slug):null,groupNumber:row.group_number!=null?Number(row.group_number):null,startedAt:row.started_at instanceof Date?row.started_at.toISOString():String(row.started_at)}}import{execSync as execSync16}from"child_process";function execQuiet(cmd){try{return execSync16(cmd,{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return""}}function parsePaneLine(parts){let[sessionName,winIdxStr,winName,winActive,winPanes,paneIdxStr,paneId,panePidStr,paneCmd,paneTitle,paneSize,sessAttached,sessWindows,sessCreated,paneDead]=parts;return{sessionName,winIdxStr,session:{name:sessionName,attached:sessAttached==="1",windowCount:Number.parseInt(sessWindows,10)||0,created:Number.parseInt(sessCreated,10)||0},window:{sessionName,index:Number.parseInt(winIdxStr,10)||0,name:winName,active:winActive==="1",paneCount:Number.parseInt(winPanes,10)||0},pane:{sessionName,windowIndex:Number.parseInt(winIdxStr,10)||0,paneIndex:Number.parseInt(paneIdxStr,10)||0,paneId,pid:Number.parseInt(panePidStr,10)||0,command:paneCmd,title:paneTitle,size:paneSize,isDead:paneDead==="1"}}}function getTmuxInventory(){let paneOutput=execQuiet("tmux -L genie list-panes -a -F '#{session_name}|#{window_index}|#{window_name}|#{window_active}|#{window_panes}|#{pane_index}|#{pane_id}|#{pane_pid}|#{pane_current_command}|#{pane_title}|#{pane_width}x#{pane_height}|#{session_attached}|#{session_windows}|#{session_created}|#{pane_dead}'");if(!paneOutput)return[];let sessionMap=new Map,windowMap=new Map;for(let line of paneOutput.split(`
|
|
2075
|
-
`)){if(!line)continue;let parts=line.split("|");if(parts.length<15)continue;let parsed=parsePaneLine(parts);if(!sessionMap.has(parsed.sessionName))sessionMap.set(parsed.sessionName,{...parsed.session,windows:[]});let winKey=`${parsed.sessionName}:${parsed.winIdxStr}`;if(!windowMap.has(winKey)){let win={...parsed.window,panes:[]};windowMap.set(winKey,win),sessionMap.get(parsed.sessionName)?.windows.push(win)}windowMap.get(winKey)?.panes.push(parsed.pane)}return Array.from(sessionMap.values()).sort((a,b3)=>a.name.localeCompare(b3.name))}function isPidAlive(pid){try{return process.kill(pid,0),!0}catch{return!1}}function allClaudePanes(sessions2){return sessions2.flatMap((s)=>s.windows.flatMap((w2)=>w2.panes)).filter((p)=>p.command==="claude"||p.title.includes("claude"))}function detectGaps(executors,sessions2){let deadPidExecutors=executors.filter((e)=>e.pid!=null&&!isPidAlive(e.pid)),executorPaneIds=new Set(executors.map((e)=>e.tmuxPaneId).filter(Boolean)),claudePanes=allClaudePanes(sessions2),orphanPanes=claudePanes.filter((p)=>!executorPaneIds.has(p.paneId)),linkedCount=executors.filter((e)=>e.tmuxPaneId&&!deadPidExecutors.some((d2)=>d2.id===e.id)).length,deadPaneCount=sessions2.flatMap((s)=>s.windows.flatMap((w2)=>w2.panes)).filter((p)=>p.isDead).length;return{deadPidExecutors,orphanPanes,linkedCount,totalExecutors:executors.length,totalClaudePanes:claudePanes.length,deadPaneCount}}async function collectDiagnostics(){let{loadExecutors:loadExecutors2,loadAssignments:loadAssignments2}=await Promise.resolve().then(() => exports_db2),sessions2=getTmuxInventory(),executors=await loadExecutors2(),executorIds=executors.map((e)=>e.id),assignments=await loadAssignments2(executorIds),gaps=detectGaps(executors,sessions2);return{sessions:sessions2,executors,assignments,gaps,timestamp:Date.now()}}var init_diagnostics=()=>{};function buildSessionTree(snapshot){let executorByPaneId=new Map;for(let exec3 of snapshot.executors)if(exec3.tmuxPaneId)executorByPaneId.set(exec3.tmuxPaneId,exec3);return snapshot.sessions.filter((s)=>s.name!=="genie-tui").map((session)=>sessionToNode(session,executorByPaneId))}function buildWorkspaceTree(input){let{agentNames,sessions:sessions2,executors}=input,sessionByName=new Map;for(let s of sessions2)if(s.name!=="genie-tui")sessionByName.set(s.name,s);let executorByPaneId=new Map;for(let exec3 of executors)if(exec3.tmuxPaneId)executorByPaneId.set(exec3.tmuxPaneId,exec3);let executorsByAgent=new Map;for(let exec3 of executors){let name=exec3.agentName??exec3.metadata?.agentName;if(typeof name==="string"){let list2=executorsByAgent.get(name)??[];list2.push(exec3),executorsByAgent.set(name,list2)}}let nodes=agentNames.map((name)=>buildAgentNode(name,sessionByName.get(name),executorsByAgent.get(name)??[],executorByPaneId)),agentNameSet=new Set(agentNames);for(let[name,session]of sessionByName)if(!agentNameSet.has(name))nodes.push(sessionToNode(session,executorByPaneId));return nodes}function countClaudePanes(session){return session.windows.reduce((sum,w2)=>sum+w2.panes.filter((p)=>p.command==="claude"||p.title.includes("claude")).length,0)}function buildAgentNode(name,session,agentExecutors,executorByPaneId){let wsState=deriveWsAgentState(session,agentExecutors),children=[];if(session)for(let win of session.windows){if(win.index===0)continue;children.push(windowToNode(session.name,win,executorByPaneId))}return{id:`agent:${name}`,type:"agent",label:name,depth:0,expanded:children.length>0,children,data:{sessionName:name,windowCount:session?session.windows.length:0},activePanes:session?countClaudePanes(session):0,agentState:agentExecutors.length>0?deriveExecutorState(agentExecutors):void 0,wsAgentState:wsState}}function deriveWsAgentState(session,agentExecutors){if(!session)return"stopped";for(let exec3 of agentExecutors){if(exec3.state==="error"||exec3.state==="terminated")return"error";if(exec3.state==="spawning")return"spawning"}return"running"}function deriveExecutorState(execs){for(let e of execs)if(e.state==="working")return"working";for(let e of execs)if(e.state==="permission")return"permission";for(let e of execs)if(e.state==="error"||e.state==="terminated")return"error";return"idle"}function sessionToNode(session,executorMap){let claudePanes=session.windows.reduce((sum,w2)=>sum+w2.panes.filter((p)=>p.command==="claude"||p.title.includes("claude")).length,0);return{id:`session:${session.name}`,type:"session",label:session.name,depth:0,expanded:!0,children:session.windows.map((w2)=>windowToNode(session.name,w2,executorMap)),data:{attached:session.attached,windowCount:session.windowCount},activePanes:claudePanes,agentState:void 0,wsAgentState:void 0}}function windowToNode(sessionName,window2,executorMap){let activePanes=window2.panes.filter((p)=>!p.isDead&&(p.command==="claude"||p.title.includes("claude"))).length;return{id:`window:${sessionName}:${window2.index}`,type:"window",label:window2.name,depth:1,expanded:!0,children:window2.panes.map((p)=>paneToNode(sessionName,window2.index,p,executorMap)),data:{active:window2.active,paneCount:window2.paneCount},activePanes,agentState:void 0,wsAgentState:void 0}}function paneToNode(sessionName,windowIndex,pane,executorMap){let executor=executorMap.get(pane.paneId),isClaude=pane.command==="claude"||pane.title.includes("claude");return{id:`pane:${pane.paneId}`,type:"pane",label:derivePaneLabel(pane,executor,isClaude),depth:2,expanded:!1,children:[],data:{command:pane.command,isDead:pane.isDead,pid:pane.pid,size:pane.size,paneId:pane.paneId,sessionName,windowIndex},activePanes:0,agentState:derivePaneState(pane,executor),wsAgentState:void 0}}function derivePaneLabel(pane,executor,isClaude){if(executor?.agentName&&executor?.team)return`${executor.team}/${executor.agentName}`;if(executor?.agentName)return executor.agentName;if(isClaude)return"claude";return pane.command}function derivePaneState(pane,executor){if(pane.isDead)return"error";if(!executor)return;let s=executor.state;if(s==="working")return"working";if(s==="idle"||s==="spawning")return"idle";if(s==="permission")return"permission";if(s==="error"||s==="terminated")return"error";return}function getSessionTarget(node){if(node.type==="agent")return{sessionName:node.data.sessionName};if(node.type==="session")return{sessionName:node.label};if(node.type==="window"){let parts=node.id.split(":");return{sessionName:parts[1],windowIndex:Number(parts[2])}}if(node.type==="pane"){let data=node.data;return{sessionName:data.sessionName,windowIndex:data.windowIndex}}return null}var palette,icons;var init_theme2=__esm(()=>{palette={purple:"#a855f7",violet:"#7c3aed",cyan:"#22d3ee",emerald:"#34d399",bg:"#1a1028",bgLight:"#241838",bgLighter:"#2e2048",text:"#e2e8f0",textDim:"#94a3b8",textMuted:"#64748b",border:"#414868",borderActive:"#7c3aed",scrollTrack:"#414868",scrollThumb:"#7aa2f7",active:"#22d3ee",success:"#34d399",warning:"#fbbf24",error:"#f87171",idle:"#94a3b8"},icons={org:"\u25C6",project:"\u25B8",projectOpen:"\u25BE",board:"\u2261",boardOpen:"\u2261",column:"\u2502",task:"\u25CB",taskActive:"\u25CF",taskDone:"\u2713",agent:"\u25B6",collapsed:"\u25B8",expanded:"\u25BE"}});function flattenTree(nodes){let result=[];function walk(node,depth){if(result.push({node,depth,visible:!0}),node.expanded)for(let child of node.children)walk(child,depth+1)}for(let node of nodes)walk(node,0);return result}function toggleNode(nodes,id){return nodes.map((node)=>{if(node.id===id)return{...node,expanded:!node.expanded};return{...node,children:toggleNode(node.children,id)}})}var import_jsx_dev_runtime2;var init_jsx_dev_runtime=__esm(()=>{import_jsx_dev_runtime2=__toESM(require_react_jsx_dev_runtime_development(),1)});function getNodeIcon(node){if(node.type==="agent")return getAgentIcon(node);switch(node.type){case"session":return node.data.attached?"\u25B6":"\u25B8";case"window":return node.data.active?"\u25A0":"\u25A1";case"pane":return getPaneIcon(node);default:return" "}}function getAgentIcon(node){switch(node.wsAgentState){case"running":return"\u25CF";case"stopped":return"\u25CB";case"error":return"\u2298";case"spawning":return"\u231B";default:return"\u25CB"}}function getPaneIcon(node){if(node.data.isDead)return"\u2718";if(node.agentState==="working")return"\u25CF";if(node.agentState==="idle")return"\u25CB";if(node.agentState==="permission")return"\u26A0";if(node.agentState==="error")return"\u2718";if(node.data.command==="claude")return"\u25C6";return"\u25CB"}function getNodeColor(node){if(node.type==="agent")return getAgentColor(node);switch(node.type){case"session":return node.data.attached?palette.emerald:palette.textDim;case"window":return node.data.active?palette.cyan:palette.text;case"pane":return getPaneColor(node);default:return palette.text}}function getAgentColor(node){switch(node.wsAgentState){case"running":return palette.emerald;case"stopped":return palette.textDim;case"error":return palette.error;case"spawning":return palette.warning;default:return palette.textDim}}function getPaneColor(node){if(node.data.isDead)return palette.error;if(node.agentState==="working")return palette.cyan;if(node.agentState==="permission")return palette.warning;if(node.agentState==="error")return palette.error;if(node.agentState==="idle")return palette.textDim;if(node.data.command==="claude")return palette.cyan;return palette.textDim}function getNodeSuffix(node){if(node.type==="agent"){let wc=node.data.windowCount;if(wc>1)return` (${wc} windows)`;if(wc===1)return" (1 window)";return""}if(node.type==="session"||node.type==="pane"){let count=node.activePanes;if(count>0)return` ${icons.agent}${count}`}return""}function getStateColor(state){switch(state){case"working":return palette.cyan;case"idle":return palette.textDim;case"permission":return palette.warning;case"error":return palette.error;default:return palette.textMuted}}var import_react13,TreeNodeRow;var init_TreeNode=__esm(()=>{init_theme2();init_jsx_dev_runtime();import_react13=__toESM(require_react_development(),1),TreeNodeRow=import_react13.memo(function({node,selected,onSelect,onToggle}){let indent=" ".repeat(node.depth),hasChildren=node.children.length>0,expandIcon=hasChildren?node.expanded?icons.expanded:icons.collapsed:" ",icon=getNodeIcon(node),color=getNodeColor(node),suffix=getNodeSuffix(node);return import_jsx_dev_runtime2.jsxDEV("box",{height:1,width:"100%",backgroundColor:selected?palette.violet:void 0,onMouseDown:()=>{if(onSelect(node.id),hasChildren)onToggle(node.id)},children:import_jsx_dev_runtime2.jsxDEV("text",{children:[import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.textDim,children:[indent,expandIcon," "]},void 0,!0,void 0,this),import_jsx_dev_runtime2.jsxDEV("span",{fg:color,children:[icon," "]},void 0,!0,void 0,this),import_jsx_dev_runtime2.jsxDEV("span",{fg:selected?"#ffffff":palette.text,children:node.label},void 0,!1,void 0,this),suffix?import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.textDim,children:suffix},void 0,!1,void 0,this):null,node.agentState?import_jsx_dev_runtime2.jsxDEV("span",{fg:getStateColor(node.agentState),children:[" ",node.agentState]},void 0,!0,void 0,this):null]},void 0,!0,void 0,this)},void 0,!1,void 0,this)})});function Nav({onTmuxSessionSelect,workspaceRoot,initialAgent}){let[diagnostics,setDiagnostics]=import_react15.useState(null),[sessionTree,setSessionTree]=import_react15.useState([]),[selectedIndex,setSelectedIndex]=import_react15.useState(0),lastTarget=import_react15.useRef(null),genieHome3=import_react15.useRef(process.env.GENIE_HOME??`${process.env.HOME}/.genie`);import_react15.useEffect(()=>{let active=!0;async function refresh(){try{let snap=await collectDiagnostics();if(active)setDiagnostics(snap)}catch(err){console.error("TUI: diagnostics failed:",err)}}refresh();let timer2=setInterval(refresh,2000);return()=>{active=!1,clearInterval(timer2)}},[]),import_react15.useEffect(()=>{if(!diagnostics)return;let newTree;if(workspaceRoot){let agentNames=scanAgents2(workspaceRoot);newTree=buildWorkspaceTree({agentNames,sessions:diagnostics.sessions,executors:diagnostics.executors})}else newTree=buildSessionTree(diagnostics);setSessionTree((prev)=>mergeExpandedState(prev,newTree))},[diagnostics,workspaceRoot]);let flatNodes=import_react15.useMemo(()=>flattenTree(sessionTree),[sessionTree]);import_react15.useEffect(()=>{if(flatNodes.length>0&&selectedIndex>=flatNodes.length)setSelectedIndex(flatNodes.length-1)},[flatNodes.length,selectedIndex]);let[pendingAgent,setPendingAgent]=import_react15.useState(initialAgent);import_react15.useEffect(()=>{if(!diagnostics)return;try{let fs3=__require("fs"),agentFile=`${genieHome3.current}/tui-initial-agent`;if(fs3.existsSync(agentFile)){let agent=fs3.readFileSync(agentFile,"utf-8").trim();if(fs3.unlinkSync(agentFile),agent)setPendingAgent(agent)}}catch{}},[diagnostics]),import_react15.useEffect(()=>{if(!pendingAgent||flatNodes.length===0)return;let idx=flatNodes.findIndex((n)=>n.node.id===`agent:${pendingAgent}`);if(idx>=0)setSelectedIndex(idx),setPendingAgent(void 0)},[pendingAgent,flatNodes]),import_react15.useEffect(()=>{let current=flatNodes[selectedIndex]?.node;if(!current)return;let target=getSessionTarget(current);if(!target)return;let key=`${target.sessionName}:${target.windowIndex??""}`;if(key===lastTarget.current)return;if(lastTarget.current=key,current.type==="agent"&¤t.wsAgentState!=="running")return;onTmuxSessionSelect(target.sessionName,target.windowIndex)},[selectedIndex,flatNodes,onTmuxSessionSelect]);let handleSelect=import_react15.useCallback((id)=>{let idx=flatNodes.findIndex((n)=>n.node.id===id);if(idx>=0)setSelectedIndex(idx)},[flatNodes]),handleToggle=import_react15.useCallback((id)=>{setSessionTree((prev)=>toggleNode(prev,id))},[]),handleVerticalNav=import_react15.useCallback((keyName2)=>{let rowCount=flatNodes.length;if(rowCount===0)return;if(keyName2==="up"||keyName2==="k")setSelectedIndex((prev)=>prev===0?rowCount-1:prev-1);else if(keyName2==="down"||keyName2==="j")setSelectedIndex((prev)=>prev>=rowCount-1?0:prev+1)},[flatNodes.length]),handleExpandCollapse=import_react15.useCallback((keyName2)=>{let node=flatNodes[selectedIndex]?.node;if(!node)return;if((keyName2==="right"||keyName2==="l")&&node.children.length>0&&!node.expanded)handleToggle(node.id);else if((keyName2==="left"||keyName2==="h")&&node.expanded)handleToggle(node.id)},[flatNodes,selectedIndex,handleToggle]),handleEnter=import_react15.useCallback(()=>{let node=flatNodes[selectedIndex]?.node;if(!node)return;if(node.type==="agent"&&node.wsAgentState==="stopped"){spawnAgent(node.label);let target2=getSessionTarget(node);if(target2)onTmuxSessionSelect(target2.sessionName,target2.windowIndex);return}if(node.children.length>0)handleToggle(node.id);let target=getSessionTarget(node);if(target)onTmuxSessionSelect(target.sessionName,target.windowIndex)},[flatNodes,selectedIndex,handleToggle,onTmuxSessionSelect]);useKeyboard((key)=>{if(key.name==="up"||key.name==="k"||key.name==="down"||key.name==="j")handleVerticalNav(key.name);else if(key.name==="right"||key.name==="l"||key.name==="left"||key.name==="h")handleExpandCollapse(key.name);else if(key.name==="enter"||key.name==="return")handleEnter()});let agentCount=workspaceRoot?sessionTree.filter((n)=>n.type==="agent").length:diagnostics?.sessions.length??0,runningCount=workspaceRoot?sessionTree.filter((n)=>n.wsAgentState==="running").length:diagnostics?.sessions.reduce((sum,s)=>sum+s.windows.reduce((ws,w2)=>ws+w2.panes.length,0),0)??0,headerLabel=workspaceRoot?"Agents":"Sessions";return import_jsx_dev_runtime2.jsxDEV("box",{flexDirection:"column",width:"100%",height:"100%",backgroundColor:palette.bg,children:[import_jsx_dev_runtime2.jsxDEV("box",{height:1,paddingX:1,backgroundColor:palette.bgLight,children:import_jsx_dev_runtime2.jsxDEV("text",{children:[import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.purple,children:headerLabel},void 0,!1,void 0,this),diagnostics?import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.textDim,children:[" ",workspaceRoot?`${runningCount}/${agentCount}`:`${agentCount}s ${runningCount}p`]},void 0,!0,void 0,this):null]},void 0,!0,void 0,this)},void 0,!1,void 0,this),diagnostics?import_jsx_dev_runtime2.jsxDEV("scrollbox",{focused:!0,height:"100%",style:{scrollbarOptions:{showArrows:!1,trackOptions:{foregroundColor:palette.scrollThumb,backgroundColor:palette.scrollTrack}}},children:flatNodes.map((flat,i2)=>import_jsx_dev_runtime2.jsxDEV(TreeNodeRow,{node:flat.node,selected:i2===selectedIndex,onSelect:handleSelect,onToggle:handleToggle},flat.node.id,!1,void 0,this))},void 0,!1,void 0,this):import_jsx_dev_runtime2.jsxDEV("box",{flexGrow:1,justifyContent:"center",alignItems:"center",children:import_jsx_dev_runtime2.jsxDEV("text",{fg:palette.textDim,children:"Collecting..."},void 0,!1,void 0,this)},void 0,!1,void 0,this),import_jsx_dev_runtime2.jsxDEV("box",{height:1,paddingX:1,backgroundColor:palette.bgLight,children:import_jsx_dev_runtime2.jsxDEV("text",{children:import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.textMuted,children:["\u2191\u2193",":nav ","\u2190\u2192",":expand Enter:",workspaceRoot?"spawn/attach":"attach"]},void 0,!0,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function spawnAgent(name){try{let{spawn:spawn4}=__require("child_process");spawn4("genie",["spawn",name],{detached:!0,stdio:"ignore"}).unref()}catch{}}function mergeExpandedState(oldTree,newTree){if(oldTree.length===0)return newTree;let oldState=new Map;function collect(nodes){for(let n of nodes)oldState.set(n.id,n.expanded),collect(n.children)}collect(oldTree);function apply(nodes){return nodes.map((n)=>({...n,expanded:oldState.has(n.id)?oldState.get(n.id):n.expanded,children:apply(n.children)}))}return apply(newTree)}var import_react15;var init_Nav=__esm(async()=>{init_workspace();init_diagnostics();init_theme2();init_TreeNode();init_jsx_dev_runtime();await init_react();import_react15=__toESM(require_react_development(),1)});function App({rightPane,workspaceRoot,initialAgent}){let handleTmuxSessionSelect=import_react16.useCallback((sessionName,windowIndex)=>{if(!rightPane)return;attachProjectWindow(rightPane,sessionName,windowIndex)},[rightPane]);return import_jsx_dev_runtime2.jsxDEV(Nav,{onTmuxSessionSelect:handleTmuxSessionSelect,workspaceRoot,initialAgent},void 0,!1,void 0,this)}var import_react16;var init_app=__esm(async()=>{init_tmux2();init_jsx_dev_runtime();await init_Nav();import_react16=__toESM(require_react_development(),1)});var exports_render={};__export(exports_render,{renderNav:()=>renderNav});async function renderNav(){let rightPane=process.env.GENIE_TUI_RIGHT||void 0,workspaceRoot=process.env.GENIE_TUI_WORKSPACE||void 0,initialAgent=process.env.GENIE_TUI_AGENT||void 0,renderer=await createCliRenderer({exitOnCtrlC:!1,useMouse:!0});createRoot(renderer).render(import_jsx_dev_runtime2.jsxDEV(App,{rightPane,workspaceRoot,initialAgent},void 0,!1,void 0,this)),await new Promise((resolve10)=>{renderer.once("destroy",resolve10)})}var init_render=__esm(async()=>{init_jsx_dev_runtime();await __promiseAll([init_core(),init_react(),init_app()])});var exports_tui={};__export(exports_tui,{launchTui:()=>launchTui});async function launchTui(){let{renderNav:renderNav2}=await init_render().then(() => exports_render);await renderNav2()}var import__=__toESM(require_commander(),1),{program,createCommand,createArgument,createOption,CommanderError,InvalidArgumentError,InvalidOptionArgumentError,Command,Argument,Option,Help}=import__.default;import{existsSync as existsSync3,unlinkSync as unlinkSync2}from"fs";import{homedir as homedir3}from"os";import{join as join3}from"path";var{$:$2}=globalThis.Bun;import{existsSync,unlinkSync}from"fs";import{homedir}from"os";import{join}from"path";var CLAUDE_DIR=join(homedir(),".claude"),CLAUDE_HOOKS_DIR=join(CLAUDE_DIR,"hooks"),CLAUDE_SETTINGS_FILE=join(CLAUDE_DIR,"settings.json"),GENIE_HOOK_SCRIPT_NAME="genie-bash-hook.sh";function getClaudeSettingsPath(){return CLAUDE_SETTINGS_FILE}function getGenieHookScriptPath(){return join(CLAUDE_HOOKS_DIR,GENIE_HOOK_SCRIPT_NAME)}function hookScriptExists(){return existsSync(getGenieHookScriptPath())}function removeHookScript(){let scriptPath=getGenieHookScriptPath();if(existsSync(scriptPath))unlinkSync(scriptPath)}function contractClaudePath(path){let home=homedir();if(path.startsWith(`${home}/`))return`~${path.slice(home.length)}`;if(path===home)return"~";return path}init_genie_config2();var{$}=globalThis.Bun;async function checkCommand(cmd){try{let cmdPath=(await $`which ${cmd}`.quiet().text()).trim();if(!cmdPath)return{exists:!1};let version;try{let firstLine=(await $`${cmd} --version`.quiet().text()).split(`
|
|
2075
|
+
`)){if(!line)continue;let parts=line.split("|");if(parts.length<15)continue;let parsed=parsePaneLine(parts);if(!sessionMap.has(parsed.sessionName))sessionMap.set(parsed.sessionName,{...parsed.session,windows:[]});let winKey=`${parsed.sessionName}:${parsed.winIdxStr}`;if(!windowMap.has(winKey)){let win={...parsed.window,panes:[]};windowMap.set(winKey,win),sessionMap.get(parsed.sessionName)?.windows.push(win)}windowMap.get(winKey)?.panes.push(parsed.pane)}return Array.from(sessionMap.values()).sort((a,b3)=>a.name.localeCompare(b3.name))}function isPidAlive(pid){try{return process.kill(pid,0),!0}catch{return!1}}function allClaudePanes(sessions2){return sessions2.flatMap((s)=>s.windows.flatMap((w2)=>w2.panes)).filter((p)=>p.command==="claude"||p.title.includes("claude"))}function detectGaps(executors,sessions2){let deadPidExecutors=executors.filter((e)=>e.pid!=null&&!isPidAlive(e.pid)),executorPaneIds=new Set(executors.map((e)=>e.tmuxPaneId).filter(Boolean)),claudePanes=allClaudePanes(sessions2),orphanPanes=claudePanes.filter((p)=>!executorPaneIds.has(p.paneId)),linkedCount=executors.filter((e)=>e.tmuxPaneId&&!deadPidExecutors.some((d2)=>d2.id===e.id)).length,deadPaneCount=sessions2.flatMap((s)=>s.windows.flatMap((w2)=>w2.panes)).filter((p)=>p.isDead).length;return{deadPidExecutors,orphanPanes,linkedCount,totalExecutors:executors.length,totalClaudePanes:claudePanes.length,deadPaneCount}}async function collectDiagnostics(){let{loadExecutors:loadExecutors2,loadAssignments:loadAssignments2}=await Promise.resolve().then(() => exports_db2),sessions2=getTmuxInventory(),executors=await loadExecutors2(),executorIds=executors.map((e)=>e.id),assignments=await loadAssignments2(executorIds),gaps=detectGaps(executors,sessions2);return{sessions:sessions2,executors,assignments,gaps,timestamp:Date.now()}}var init_diagnostics=()=>{};function buildSessionTree(snapshot){let executorByPaneId=new Map;for(let exec3 of snapshot.executors)if(exec3.tmuxPaneId)executorByPaneId.set(exec3.tmuxPaneId,exec3);return snapshot.sessions.filter((s)=>s.name!=="genie-tui").map((session)=>sessionToNode(session,executorByPaneId))}function buildWorkspaceTree(input){let{agentNames,sessions:sessions2,executors}=input,sessionByName=new Map;for(let s of sessions2)if(s.name!=="genie-tui")sessionByName.set(s.name,s);let executorByPaneId=new Map;for(let exec3 of executors)if(exec3.tmuxPaneId)executorByPaneId.set(exec3.tmuxPaneId,exec3);let executorsByAgent=new Map;for(let exec3 of executors){let name=exec3.agentName??exec3.metadata?.agentName;if(typeof name==="string"){let list2=executorsByAgent.get(name)??[];list2.push(exec3),executorsByAgent.set(name,list2)}}let nodes=agentNames.map((name)=>buildAgentNode(name,sessionByName.get(name),executorsByAgent.get(name)??[],executorByPaneId)),agentNameSet=new Set(agentNames);for(let[name,session]of sessionByName)if(!agentNameSet.has(name))nodes.push(sessionToNode(session,executorByPaneId));return nodes}function countClaudePanes(session){return session.windows.reduce((sum,w2)=>sum+w2.panes.filter((p)=>p.command==="claude"||p.title.includes("claude")).length,0)}function buildAgentNode(name,session,agentExecutors,executorByPaneId){let wsState=deriveWsAgentState(session,agentExecutors),children=[];if(session)for(let win of session.windows){if(win.index===0)continue;children.push(windowToNode(session.name,win,executorByPaneId))}return{id:`agent:${name}`,type:"agent",label:name,depth:0,expanded:children.length>0,children,data:{sessionName:name,windowCount:session?session.windows.length:0},activePanes:session?countClaudePanes(session):0,agentState:agentExecutors.length>0?deriveExecutorState(agentExecutors):void 0,wsAgentState:wsState}}function deriveWsAgentState(session,agentExecutors){if(!session)return"stopped";for(let exec3 of agentExecutors){if(exec3.state==="error"||exec3.state==="terminated")return"error";if(exec3.state==="spawning")return"spawning"}return"running"}function deriveExecutorState(execs){for(let e of execs)if(e.state==="working")return"working";for(let e of execs)if(e.state==="permission")return"permission";for(let e of execs)if(e.state==="error"||e.state==="terminated")return"error";return"idle"}function sessionToNode(session,executorMap){let claudePanes=session.windows.reduce((sum,w2)=>sum+w2.panes.filter((p)=>p.command==="claude"||p.title.includes("claude")).length,0);return{id:`session:${session.name}`,type:"session",label:session.name,depth:0,expanded:!0,children:session.windows.map((w2)=>windowToNode(session.name,w2,executorMap)),data:{attached:session.attached,windowCount:session.windowCount},activePanes:claudePanes,agentState:void 0,wsAgentState:void 0}}function windowToNode(sessionName,window2,executorMap){let activePanes=window2.panes.filter((p)=>!p.isDead&&(p.command==="claude"||p.title.includes("claude"))).length;return{id:`window:${sessionName}:${window2.index}`,type:"window",label:window2.name,depth:1,expanded:!0,children:window2.panes.map((p)=>paneToNode(sessionName,window2.index,p,executorMap)),data:{active:window2.active,paneCount:window2.paneCount},activePanes,agentState:void 0,wsAgentState:void 0}}function paneToNode(sessionName,windowIndex,pane,executorMap){let executor=executorMap.get(pane.paneId),isClaude=pane.command==="claude"||pane.title.includes("claude");return{id:`pane:${pane.paneId}`,type:"pane",label:derivePaneLabel(pane,executor,isClaude),depth:2,expanded:!1,children:[],data:{command:pane.command,isDead:pane.isDead,pid:pane.pid,size:pane.size,paneId:pane.paneId,sessionName,windowIndex},activePanes:0,agentState:derivePaneState(pane,executor),wsAgentState:void 0}}function derivePaneLabel(pane,executor,isClaude){if(executor?.agentName&&executor?.team)return`${executor.team}/${executor.agentName}`;if(executor?.agentName)return executor.agentName;if(isClaude)return"claude";return pane.command}function derivePaneState(pane,executor){if(pane.isDead)return"error";if(!executor)return;let s=executor.state;if(s==="working")return"working";if(s==="idle"||s==="spawning")return"idle";if(s==="permission")return"permission";if(s==="error"||s==="terminated")return"error";return}function getSessionTarget(node){if(node.type==="agent")return{sessionName:node.data.sessionName};if(node.type==="session")return{sessionName:node.label};if(node.type==="window"){let parts=node.id.split(":");return{sessionName:parts[1],windowIndex:Number(parts[2])}}if(node.type==="pane"){let data=node.data;return{sessionName:data.sessionName,windowIndex:data.windowIndex}}return null}var palette,icons;var init_theme2=__esm(()=>{palette={purple:"#a855f7",violet:"#7c3aed",cyan:"#22d3ee",emerald:"#34d399",bg:"#1a1028",bgLight:"#241838",bgLighter:"#2e2048",text:"#e2e8f0",textDim:"#94a3b8",textMuted:"#64748b",border:"#414868",borderActive:"#7c3aed",scrollTrack:"#414868",scrollThumb:"#7aa2f7",active:"#22d3ee",success:"#34d399",warning:"#fbbf24",error:"#f87171",idle:"#94a3b8"},icons={org:"\u25C6",project:"\u25B8",projectOpen:"\u25BE",board:"\u2261",boardOpen:"\u2261",column:"\u2502",task:"\u25CB",taskActive:"\u25CF",taskDone:"\u2713",agent:"\u25B6",collapsed:"\u25B8",expanded:"\u25BE"}});function flattenTree(nodes){let result=[];function walk(node,depth){if(result.push({node,depth,visible:!0}),node.expanded)for(let child of node.children)walk(child,depth+1)}for(let node of nodes)walk(node,0);return result}function toggleNode(nodes,id){return nodes.map((node)=>{if(node.id===id)return{...node,expanded:!node.expanded};return{...node,children:toggleNode(node.children,id)}})}var import_jsx_dev_runtime2;var init_jsx_dev_runtime=__esm(()=>{import_jsx_dev_runtime2=__toESM(require_react_jsx_dev_runtime_development(),1)});function getNodeIcon(node){if(node.type==="agent")return getAgentIcon(node);switch(node.type){case"session":return node.data.attached?"\u25B6":"\u25B8";case"window":return node.data.active?"\u25A0":"\u25A1";case"pane":return getPaneIcon(node);default:return" "}}function getAgentIcon(node){switch(node.wsAgentState){case"running":return"\u25CF";case"stopped":return"\u25CB";case"error":return"\u2298";case"spawning":return"\u231B";default:return"\u25CB"}}function getPaneIcon(node){if(node.data.isDead)return"\u2718";if(node.agentState==="working")return"\u25CF";if(node.agentState==="idle")return"\u25CB";if(node.agentState==="permission")return"\u26A0";if(node.agentState==="error")return"\u2718";if(node.data.command==="claude")return"\u25C6";return"\u25CB"}function getNodeColor(node){if(node.type==="agent")return getAgentColor(node);switch(node.type){case"session":return node.data.attached?palette.emerald:palette.textDim;case"window":return node.data.active?palette.cyan:palette.text;case"pane":return getPaneColor(node);default:return palette.text}}function getAgentColor(node){switch(node.wsAgentState){case"running":return palette.emerald;case"stopped":return palette.textDim;case"error":return palette.error;case"spawning":return palette.warning;default:return palette.textDim}}function getPaneColor(node){if(node.data.isDead)return palette.error;if(node.agentState==="working")return palette.cyan;if(node.agentState==="permission")return palette.warning;if(node.agentState==="error")return palette.error;if(node.agentState==="idle")return palette.textDim;if(node.data.command==="claude")return palette.cyan;return palette.textDim}function getNodeSuffix(node){if(node.type==="agent"){let wc=node.data.windowCount;if(wc>1)return` (${wc} windows)`;if(wc===1)return" (1 window)";return""}if(node.type==="session"||node.type==="pane"){let count=node.activePanes;if(count>0)return` ${icons.agent}${count}`}return""}function getStateColor(state){switch(state){case"working":return palette.cyan;case"idle":return palette.textDim;case"permission":return palette.warning;case"error":return palette.error;default:return palette.textMuted}}var import_react13,TreeNodeRow;var init_TreeNode=__esm(()=>{init_theme2();init_jsx_dev_runtime();import_react13=__toESM(require_react_development(),1),TreeNodeRow=import_react13.memo(function({node,selected,onSelect,onToggle}){let indent=" ".repeat(node.depth),hasChildren=node.children.length>0,expandIcon=hasChildren?node.expanded?icons.expanded:icons.collapsed:" ",icon=getNodeIcon(node),color=getNodeColor(node),suffix=getNodeSuffix(node);return import_jsx_dev_runtime2.jsxDEV("box",{height:1,width:"100%",backgroundColor:selected?palette.violet:void 0,onMouseDown:()=>{if(onSelect(node.id),hasChildren)onToggle(node.id)},children:import_jsx_dev_runtime2.jsxDEV("text",{children:[import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.textDim,children:[indent,expandIcon," "]},void 0,!0,void 0,this),import_jsx_dev_runtime2.jsxDEV("span",{fg:color,children:[icon," "]},void 0,!0,void 0,this),import_jsx_dev_runtime2.jsxDEV("span",{fg:selected?"#ffffff":palette.text,children:node.label},void 0,!1,void 0,this),suffix?import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.textDim,children:suffix},void 0,!1,void 0,this):null,node.agentState?import_jsx_dev_runtime2.jsxDEV("span",{fg:getStateColor(node.agentState),children:[" ",node.agentState]},void 0,!0,void 0,this):null]},void 0,!0,void 0,this)},void 0,!1,void 0,this)})});function Nav({onTmuxSessionSelect,workspaceRoot,initialAgent}){let[diagnostics,setDiagnostics]=import_react15.useState(null),[sessionTree,setSessionTree]=import_react15.useState([]),[selectedIndex,setSelectedIndex]=import_react15.useState(0),lastTarget=import_react15.useRef(null),genieHome3=import_react15.useRef(process.env.GENIE_HOME??`${process.env.HOME}/.genie`);import_react15.useEffect(()=>{let active=!0;async function refresh(){try{let snap=await collectDiagnostics();if(active)setDiagnostics(snap)}catch(err){console.error("TUI: diagnostics failed:",err)}}refresh();let timer2=setInterval(refresh,2000);return()=>{active=!1,clearInterval(timer2)}},[]),import_react15.useEffect(()=>{if(!diagnostics)return;let newTree;if(workspaceRoot){let agentNames=scanAgents2(workspaceRoot);newTree=buildWorkspaceTree({agentNames,sessions:diagnostics.sessions,executors:diagnostics.executors})}else newTree=buildSessionTree(diagnostics);setSessionTree((prev)=>mergeExpandedState(prev,newTree))},[diagnostics,workspaceRoot]);let flatNodes=import_react15.useMemo(()=>flattenTree(sessionTree),[sessionTree]);import_react15.useEffect(()=>{if(flatNodes.length>0&&selectedIndex>=flatNodes.length)setSelectedIndex(flatNodes.length-1)},[flatNodes.length,selectedIndex]);let[pendingAgent,setPendingAgent]=import_react15.useState(initialAgent);import_react15.useEffect(()=>{if(!diagnostics)return;try{let fs3=__require("fs"),agentFile=`${genieHome3.current}/tui-initial-agent`;if(fs3.existsSync(agentFile)){let agent=fs3.readFileSync(agentFile,"utf-8").trim();if(fs3.unlinkSync(agentFile),agent)setPendingAgent(agent)}}catch{}},[diagnostics]),import_react15.useEffect(()=>{if(!pendingAgent||flatNodes.length===0)return;let idx=flatNodes.findIndex((n)=>n.node.id===`agent:${pendingAgent}`);if(idx>=0)setSelectedIndex(idx),setPendingAgent(void 0)},[pendingAgent,flatNodes]),import_react15.useEffect(()=>{let current=flatNodes[selectedIndex]?.node;if(!current)return;let target=getSessionTarget(current);if(!target)return;let key=`${target.sessionName}:${target.windowIndex??""}`;if(key===lastTarget.current)return;if(lastTarget.current=key,current.type==="agent"&¤t.wsAgentState!=="running")return;onTmuxSessionSelect(target.sessionName,target.windowIndex)},[selectedIndex,flatNodes,onTmuxSessionSelect]);let handleSelect=import_react15.useCallback((id)=>{let idx=flatNodes.findIndex((n)=>n.node.id===id);if(idx>=0)setSelectedIndex(idx)},[flatNodes]),handleToggle=import_react15.useCallback((id)=>{setSessionTree((prev)=>toggleNode(prev,id))},[]),handleVerticalNav=import_react15.useCallback((keyName2)=>{let rowCount=flatNodes.length;if(rowCount===0)return;if(keyName2==="up"||keyName2==="k")setSelectedIndex((prev)=>prev===0?rowCount-1:prev-1);else if(keyName2==="down"||keyName2==="j")setSelectedIndex((prev)=>prev>=rowCount-1?0:prev+1)},[flatNodes.length]),handleExpandCollapse=import_react15.useCallback((keyName2)=>{let node=flatNodes[selectedIndex]?.node;if(!node)return;if((keyName2==="right"||keyName2==="l")&&node.children.length>0&&!node.expanded)handleToggle(node.id);else if((keyName2==="left"||keyName2==="h")&&node.expanded)handleToggle(node.id)},[flatNodes,selectedIndex,handleToggle]),handleEnter=import_react15.useCallback(()=>{let node=flatNodes[selectedIndex]?.node;if(!node)return;if(node.type==="agent"){if(node.wsAgentState!=="running")spawnAgent(node.label);let target2=getSessionTarget(node);if(target2)onTmuxSessionSelect(target2.sessionName,target2.windowIndex);return}if(node.children.length>0)handleToggle(node.id);let target=getSessionTarget(node);if(target)onTmuxSessionSelect(target.sessionName,target.windowIndex)},[flatNodes,selectedIndex,handleToggle,onTmuxSessionSelect]);useKeyboard((key)=>{if(key.name==="up"||key.name==="k"||key.name==="down"||key.name==="j")handleVerticalNav(key.name);else if(key.name==="right"||key.name==="l"||key.name==="left"||key.name==="h")handleExpandCollapse(key.name);else if(key.name==="enter"||key.name==="return")handleEnter()});let agentCount=workspaceRoot?sessionTree.filter((n)=>n.type==="agent").length:diagnostics?.sessions.length??0,runningCount=workspaceRoot?sessionTree.filter((n)=>n.wsAgentState==="running").length:diagnostics?.sessions.reduce((sum,s)=>sum+s.windows.reduce((ws,w2)=>ws+w2.panes.length,0),0)??0,headerLabel=workspaceRoot?"Agents":"Sessions";return import_jsx_dev_runtime2.jsxDEV("box",{flexDirection:"column",width:"100%",height:"100%",backgroundColor:palette.bg,children:[import_jsx_dev_runtime2.jsxDEV("box",{height:1,paddingX:1,backgroundColor:palette.bgLight,children:import_jsx_dev_runtime2.jsxDEV("text",{children:[import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.purple,children:headerLabel},void 0,!1,void 0,this),diagnostics?import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.textDim,children:[" ",workspaceRoot?`${runningCount}/${agentCount}`:`${agentCount}s ${runningCount}p`]},void 0,!0,void 0,this):null]},void 0,!0,void 0,this)},void 0,!1,void 0,this),diagnostics?import_jsx_dev_runtime2.jsxDEV("scrollbox",{focused:!0,height:"100%",style:{scrollbarOptions:{showArrows:!1,trackOptions:{foregroundColor:palette.scrollThumb,backgroundColor:palette.scrollTrack}}},children:flatNodes.map((flat,i2)=>import_jsx_dev_runtime2.jsxDEV(TreeNodeRow,{node:flat.node,selected:i2===selectedIndex,onSelect:handleSelect,onToggle:handleToggle},flat.node.id,!1,void 0,this))},void 0,!1,void 0,this):import_jsx_dev_runtime2.jsxDEV("box",{flexGrow:1,justifyContent:"center",alignItems:"center",children:import_jsx_dev_runtime2.jsxDEV("text",{fg:palette.textDim,children:"Collecting..."},void 0,!1,void 0,this)},void 0,!1,void 0,this),import_jsx_dev_runtime2.jsxDEV("box",{height:1,paddingX:1,backgroundColor:palette.bgLight,children:import_jsx_dev_runtime2.jsxDEV("text",{children:import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.textMuted,children:["\u2191\u2193",":nav ","\u2190\u2192",":expand Enter:",workspaceRoot?"spawn/attach":"attach"]},void 0,!0,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function spawnAgent(name){try{let{spawn:spawn4}=__require("child_process");spawn4("genie",["spawn",name],{detached:!0,stdio:"ignore"}).unref()}catch{}}function mergeExpandedState(oldTree,newTree){if(oldTree.length===0)return newTree;let oldState=new Map;function collect(nodes){for(let n of nodes)oldState.set(n.id,n.expanded),collect(n.children)}collect(oldTree);function apply(nodes){return nodes.map((n)=>({...n,expanded:oldState.has(n.id)?oldState.get(n.id):n.expanded,children:apply(n.children)}))}return apply(newTree)}var import_react15;var init_Nav=__esm(async()=>{init_workspace();init_diagnostics();init_theme2();init_TreeNode();init_jsx_dev_runtime();await init_react();import_react15=__toESM(require_react_development(),1)});function App({rightPane,workspaceRoot,initialAgent}){let handleTmuxSessionSelect=import_react16.useCallback((sessionName,windowIndex)=>{if(!rightPane)return;attachProjectWindow(rightPane,sessionName,windowIndex)},[rightPane]);return import_jsx_dev_runtime2.jsxDEV(Nav,{onTmuxSessionSelect:handleTmuxSessionSelect,workspaceRoot,initialAgent},void 0,!1,void 0,this)}var import_react16;var init_app=__esm(async()=>{init_tmux2();init_jsx_dev_runtime();await init_Nav();import_react16=__toESM(require_react_development(),1)});var exports_render={};__export(exports_render,{renderNav:()=>renderNav});async function renderNav(){let rightPane=process.env.GENIE_TUI_RIGHT||void 0,workspaceRoot=process.env.GENIE_TUI_WORKSPACE||void 0,initialAgent=process.env.GENIE_TUI_AGENT||void 0,renderer=await createCliRenderer({exitOnCtrlC:!1,useMouse:!0});createRoot(renderer).render(import_jsx_dev_runtime2.jsxDEV(App,{rightPane,workspaceRoot,initialAgent},void 0,!1,void 0,this)),await new Promise((resolve10)=>{renderer.once("destroy",resolve10)})}var init_render=__esm(async()=>{init_jsx_dev_runtime();await __promiseAll([init_core(),init_react(),init_app()])});var exports_tui={};__export(exports_tui,{launchTui:()=>launchTui});async function launchTui(){let{renderNav:renderNav2}=await init_render().then(() => exports_render);await renderNav2()}var import__=__toESM(require_commander(),1),{program,createCommand,createArgument,createOption,CommanderError,InvalidArgumentError,InvalidOptionArgumentError,Command,Argument,Option,Help}=import__.default;import{existsSync as existsSync3,unlinkSync as unlinkSync2}from"fs";import{homedir as homedir3}from"os";import{join as join3}from"path";var{$:$2}=globalThis.Bun;import{existsSync,unlinkSync}from"fs";import{homedir}from"os";import{join}from"path";var CLAUDE_DIR=join(homedir(),".claude"),CLAUDE_HOOKS_DIR=join(CLAUDE_DIR,"hooks"),CLAUDE_SETTINGS_FILE=join(CLAUDE_DIR,"settings.json"),GENIE_HOOK_SCRIPT_NAME="genie-bash-hook.sh";function getClaudeSettingsPath(){return CLAUDE_SETTINGS_FILE}function getGenieHookScriptPath(){return join(CLAUDE_HOOKS_DIR,GENIE_HOOK_SCRIPT_NAME)}function hookScriptExists(){return existsSync(getGenieHookScriptPath())}function removeHookScript(){let scriptPath=getGenieHookScriptPath();if(existsSync(scriptPath))unlinkSync(scriptPath)}function contractClaudePath(path){let home=homedir();if(path.startsWith(`${home}/`))return`~${path.slice(home.length)}`;if(path===home)return"~";return path}init_genie_config2();var{$}=globalThis.Bun;async function checkCommand(cmd){try{let cmdPath=(await $`which ${cmd}`.quiet().text()).trim();if(!cmdPath)return{exists:!1};let version;try{let firstLine=(await $`${cmd} --version`.quiet().text()).split(`
|
|
2076
2076
|
`)[0].trim(),versionMatch=firstLine.match(/(\d+\.[\d.]+[a-z0-9-]*)/i);version=versionMatch?versionMatch[1]:firstLine.slice(0,50)}catch{try{let firstLine=(await $`${cmd} -v`.quiet().text()).split(`
|
|
2077
2077
|
`)[0].trim(),versionMatch=firstLine.match(/(\d+\.[\d.]+[a-z0-9-]*)/i);version=versionMatch?versionMatch[1]:firstLine.slice(0,50)}catch{}}return{exists:!0,version,path:cmdPath}}catch{return{exists:!1}}}function printSectionHeader(title){console.log(),console.log(`\x1B[1m${title}:\x1B[0m`)}function printCheckResult(result){let icon={pass:"\x1B[32m\u2713\x1B[0m",fail:"\x1B[31m\u2717\x1B[0m",warn:"\x1B[33m!\x1B[0m"}[result.status],message=result.message?` ${result.message}`:"";if(console.log(` ${icon} ${result.name}${message}`),result.suggestion)console.log(` \x1B[2m${result.suggestion}\x1B[0m`)}async function checkPrerequisites(){let results=[],tmuxCheck=await checkCommand("tmux");if(tmuxCheck.exists)results.push({name:"tmux",status:"pass",message:tmuxCheck.version||""});else results.push({name:"tmux",status:"fail",suggestion:"Install with: brew install tmux (or apt install tmux)"});let jqCheck=await checkCommand("jq");if(jqCheck.exists)results.push({name:"jq",status:"pass",message:jqCheck.version||""});else results.push({name:"jq",status:"fail",suggestion:"Install with: brew install jq (or apt install jq)"});let bunCheck=await checkCommand("bun");if(bunCheck.exists)results.push({name:"bun",status:"pass",message:bunCheck.version||""});else results.push({name:"bun",status:"fail",suggestion:"Install with: curl -fsSL https://bun.sh/install | bash"});let claudeCheck=await checkCommand("claude");if(claudeCheck.exists)results.push({name:"Claude Code",status:"pass",message:claudeCheck.version||""});else results.push({name:"Claude Code",status:"warn",suggestion:"Install with: npm install -g @anthropic-ai/claude-code"});return results}async function checkConfiguration(){let results=[];if(genieConfigExists())results.push({name:"Genie config exists",status:"pass",message:contractClaudePath(getGenieConfigPath())});else results.push({name:"Genie config exists",status:"warn",message:"not found",suggestion:"Run: genie setup"});if(isSetupComplete())results.push({name:"Setup complete",status:"pass"});else results.push({name:"Setup complete",status:"warn",message:"not completed",suggestion:"Run: genie setup"});let claudeSettingsPath=getClaudeSettingsPath();if(existsSync3(claudeSettingsPath))results.push({name:"Claude settings exists",status:"pass",message:contractClaudePath(claudeSettingsPath)});else results.push({name:"Claude settings exists",status:"warn",message:"not found",suggestion:"Claude Code creates this on first run"});return results}async function checkTmux(){let results=[];try{if((await $2`tmux -L genie list-sessions 2>/dev/null`.quiet()).exitCode===0)results.push({name:"Server running",status:"pass"});else return results.push({name:"Server running",status:"warn",message:"no sessions",suggestion:"Start with: tmux new-session -d -s genie"}),results}catch{return results.push({name:"Server running",status:"warn",message:"could not check"}),results}let sessionName=(await loadGenieConfig()).session.name;try{if((await $2`tmux -L genie has-session -t ${sessionName} 2>/dev/null`.quiet()).exitCode===0)results.push({name:`Session '${sessionName}' exists`,status:"pass"});else results.push({name:`Session '${sessionName}' exists`,status:"warn",suggestion:`Start with: tmux new-session -d -s ${sessionName}`})}catch{results.push({name:`Session '${sessionName}' exists`,status:"warn",message:"could not check"})}return results}async function checkWorkerProfiles(){let results=[];if(!genieConfigExists())return results.push({name:"Worker profiles",status:"warn",message:"no genie config",suggestion:"Run: genie setup"}),results;let config=await loadGenieConfig(),profiles=config.workerProfiles;if(!profiles||Object.keys(profiles).length===0)return results.push({name:"Worker profiles",status:"pass",message:"none configured (using defaults)"}),results;let totalProfiles=Object.keys(profiles).length;results.push({name:"Profiles configured",status:"pass",message:`${totalProfiles} profile${totalProfiles===1?"":"s"}`});for(let name of Object.keys(profiles))results.push({name:`Profile '${name}'`,status:"pass",message:"claude (direct)"});if(config.defaultWorkerProfile)if(profiles[config.defaultWorkerProfile])results.push({name:"Default profile",status:"pass",message:config.defaultWorkerProfile});else results.push({name:"Default profile",status:"warn",message:`'${config.defaultWorkerProfile}' not found`,suggestion:"Run: genie profiles default <profile>"});return results}function runCheckSection(label,results,counts){printSectionHeader(label);for(let result of results){if(printCheckResult(result),result.status==="fail")counts.errors=!0;if(result.status==="warn")counts.warnings=!0}}async function doctorCommand(options){if(options?.fix){await doctorFix();return}console.log(),console.log("\x1B[1mGenie Doctor\x1B[0m"),console.log(`\x1B[2m${"\u2500".repeat(40)}\x1B[0m`);let counts={errors:!1,warnings:!1};if(runCheckSection("Prerequisites",await checkPrerequisites(),counts),runCheckSection("Configuration",await checkConfiguration(),counts),runCheckSection("Tmux",await checkTmux(),counts),runCheckSection("Worker Profiles",await checkWorkerProfiles(),counts),console.log(),console.log(`\x1B[2m${"\u2500".repeat(40)}\x1B[0m`),counts.errors)console.log("\x1B[31mSome checks failed.\x1B[0m Run \x1B[36mgenie setup\x1B[0m to fix.");else if(counts.warnings)console.log("\x1B[33mSome warnings detected.\x1B[0m Everything should still work.");else console.log("\x1B[32mAll checks passed!\x1B[0m");if(console.log(),counts.errors)process.exit(1)}async function killStalePostgres(){console.log(" Killing stale postgres processes...");try{let{execSync}=await import("child_process");execSync('pkill -9 -f "postgres.*pgserve" 2>/dev/null || true',{stdio:"ignore",timeout:5000}),console.log(" \x1B[32m\u2713\x1B[0m Stale postgres processes killed")}catch{console.log(" \x1B[33m!\x1B[0m Could not kill stale postgres processes")}}async function cleanSharedMemory(){console.log(" Cleaning shared memory...");try{let{execSync}=await import("child_process");execSync("ipcs -m 2>/dev/null | awk '$6 == 0 {print $2}' | xargs -I{} ipcrm -m {} 2>/dev/null || true",{stdio:"ignore",timeout:5000}),console.log(" \x1B[32m\u2713\x1B[0m Shared memory cleaned")}catch{console.log(" \x1B[32m\u2713\x1B[0m No stale shared memory")}}async function stopExistingDaemon(pidFile){try{let{readFileSync:readFileSync2}=await import("fs"),pid=Number.parseInt(readFileSync2(pidFile,"utf-8").trim(),10);if(!Number.isNaN(pid)&&pid>0)try{process.kill(pid,"SIGTERM"),console.log(` \x1B[32m\u2713\x1B[0m Stopped existing daemon (PID ${pid})`),await new Promise((r)=>setTimeout(r,1000))}catch{}}catch{}}function removeStaleFiles(genieHome,pidFile){let portFile=join3(genieHome,"pgserve.port"),postmasterPid=join3(genieHome,"data","pgserve","postmaster.pid");for(let file of[portFile,pidFile,postmasterPid])if(existsSync3(file))try{unlinkSync2(file),console.log(` \x1B[32m\u2713\x1B[0m Removed ${file}`)}catch{console.log(` \x1B[33m!\x1B[0m Could not remove ${file}`)}}async function restartDaemon(){console.log(" Restarting daemon...");try{let{spawn}=await import("child_process"),bunPath=process.execPath??"bun",genieBin=process.argv[1]??"genie";spawn(bunPath,[genieBin,"daemon","start"],{detached:!0,stdio:"ignore"}).unref(),await new Promise((resolve)=>setTimeout(resolve,2000)),console.log(" \x1B[32m\u2713\x1B[0m Daemon restart initiated")}catch{console.log(" \x1B[33m!\x1B[0m Could not restart daemon \u2014 run: genie daemon start")}}async function doctorFix(){console.log(`
|
|
2078
2078
|
\x1B[1mGenie Doctor \u2014 Auto Fix\x1B[0m`),console.log(`\x1B[2m${"\u2500".repeat(40)}\x1B[0m
|
|
@@ -2632,4 +2632,4 @@ ${mode}Closed ${result.closed} task${result.closed===1?"":"s"} from ${result.prs
|
|
|
2632
2632
|
Name: ${t.name}`),console.log(`ID: ${t.id}`),console.log(`Builtin: ${t.isBuiltin}`),console.log(`Description: ${t.description??"(none)"}`),t.columns&&t.columns.length>0)console.log(`Columns: ${t.columns.map((c)=>c.name).join(", ")}`);console.log("")}),tmpl.command("delete <name>").description("Delete a template").action(async(name)=>{let{deleteTemplate:deleteTemplate2,getTemplate:getTemplate2}=await Promise.resolve().then(() => (init_template_service(),exports_template_service)),t=await getTemplate2(name);if(!t)console.error(`Template "${name}" not found.`),process.exit(1);let ok=await deleteTemplate2(t.id);console.log(ok?`Deleted template "${t.name}".`:"Delete failed.")})}var _taskService8;async function getTaskService8(){if(!_taskService8)_taskService8=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService8}function printTypeTable(types4){console.log(` ${padRight("ID",20)} ${padRight("NAME",30)} ${padRight("STAGES",8)} BUILTIN`),console.log(` ${"\u2500".repeat(70)}`);for(let t of types4){let stageCount=Array.isArray(t.stages)?t.stages.length:0,builtin=t.isBuiltin?"yes":"no";console.log(` ${padRight(t.id,20)} ${padRight(t.name,30)} ${padRight(String(stageCount),8)} ${builtin}`)}console.log(`
|
|
2633
2633
|
${types4.length} type${types4.length===1?"":"s"}`)}function printTypePipeline(t){if(console.log(`
|
|
2634
2634
|
Type: ${t.name} (${t.id})`),t.description)console.log(`Description: ${t.description}`);if(t.icon)console.log(`Icon: ${t.icon}`);console.log(`Built-in: ${t.isBuiltin?"yes":"no"}`),console.log("\u2500".repeat(60)),console.log(`
|
|
2635
|
-
Stage Pipeline:`);let stages=t.stages;for(let i2=0;i2<stages.length;i2++){let s=stages[i2],arrow=i2<stages.length-1?" \u2192":"",gate=s.gate?` [gate: ${s.gate}]`:"",action=s.action?` (action: ${s.action})`:"",auto=s.auto_advance?" [auto]":"";console.log(` ${i2+1}. ${s.label??s.name}${gate}${action}${auto}${arrow}`)}console.log("")}async function handleTypeList(options){console.warn("Warning: `genie type` is deprecated. Use `genie board` instead.");let types4=await(await getTaskService8()).listTypes();if(options.json){console.log(JSON.stringify(types4,null,2));return}printTypeTable(types4)}async function handleTypeShow(id,options){console.warn("Warning: `genie type` is deprecated. Use `genie board` instead.");let t=await(await getTaskService8()).getType(id);if(!t)console.error(`Error: Type not found: ${id}`),process.exit(1);if(options.json){console.log(JSON.stringify(t,null,2));return}printTypePipeline(t)}async function handleTypeCreate(name,options){console.warn("Warning: `genie type` is deprecated. Use `genie board` instead.");let ts3=await getTaskService8(),stages;try{if(stages=JSON.parse(options.stages),!Array.isArray(stages))throw Error("Stages must be a JSON array")}catch(err){console.error(`Error: Invalid stages JSON. ${err instanceof Error?err.message:String(err)}`),process.exit(1)}for(let s of stages)if(typeof s!=="object"||s===null||!("name"in s))console.error('Error: Each stage must have at least a "name" field.'),process.exit(1);let id=name.toLowerCase().replace(/\s+/g,"-"),t=await ts3.createType({id,name,description:options.description,icon:options.icon,stages});console.log(`Created type "${t.name}" (${t.id}) with ${stages.length} stages.`)}function registerTypeCommands(program2){let type2=program2.command("type").description("Task type management");type2.command("list").description("List all task types").option("--json","Output as JSON").action(async(options)=>{try{await handleTypeList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),type2.command("show <id>").description("Show task type detail with stage pipeline").option("--json","Output as JSON").action(async(id,options)=>{try{await handleTypeShow(id,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),type2.command("create <name>").description("Create a custom task type").requiredOption("--stages <json>","Stages JSON array").option("--description <text>","Type description").option("--icon <icon>","Type icon").action(async(name,options)=>{try{await handleTypeCreate(name,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}try{let{execSync:execSyncStartup}=__require("child_process");if(execSyncStartup("git config core.bare",{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()==="true")execSyncStartup("git config core.bare false",{stdio:["pipe","pipe","pipe"]})}catch{}var program2=new Command;program2.name("genie").description("Genie CLI - AI-assisted development").version(VERSION);async function startNamedSession(name){let{buildTeamLeadCommand:buildTeamLeadCommand2,sessionExists:sessionExists2}=await Promise.resolve().then(() => (init_team_lead_command(),exports_team_lead_command)),{getAgentsFilePath:getAgentsFilePath2}=await Promise.resolve().then(() => (init_session(),exports_session)),systemPromptFile=getAgentsFilePath2(),hasPriorSession=sessionExists2(name),cmd=buildTeamLeadCommand2(name,{systemPromptFile:systemPromptFile??void 0,continueName:hasPriorSession?name:void 0});console.log(hasPriorSession?`Resuming session: ${name}`:`Starting new session: ${name}`);let{spawnSync:spawnSync4}=await import("child_process"),result=spawnSync4("sh",["-c",cmd],{stdio:"inherit"});if(result.status)process.exit(result.status)}program2.command("setup").description("Configure genie settings").option("--quick","Accept all defaults").option("--shortcuts","Only configure keyboard shortcuts").option("--codex","Only configure Codex integration").option("--terminal","Only configure terminal defaults").option("--session","Only configure session settings").option("--reset","Reset configuration to defaults").option("--show","Show current configuration").action(async(options)=>{await setupCommand(options)});program2.command("doctor").description("Run diagnostic checks on genie installation").option("--fix","Auto-fix: kill zombie postgres, clean shared memory, restart daemon").action(doctorCommand);program2.command("update").description("Update Genie CLI to the latest version").option("--next","Switch to dev builds (npm @next tag)").option("--stable","Switch to stable releases (npm @latest tag)").action(updateCommand);program2.command("uninstall").description("Remove Genie CLI and clean up hooks").action(uninstallCommand);var shortcuts=program2.command("shortcuts").description("Manage tmux keyboard shortcuts");shortcuts.action(shortcutsShowCommand);shortcuts.command("show").description("Show available shortcuts and installation status").action(shortcutsShowCommand);shortcuts.command("install").description("Install shortcuts to config files (~/.tmux.conf, shell rc)").action(shortcutsInstallCommand);shortcuts.command("uninstall").description("Remove shortcuts from config files").action(shortcutsUninstallCommand);registerServeCommands(program2);registerAppCommand(program2);registerInitCommands(program2);registerTeamNamespace(program2);registerDirNamespace(program2);registerAgentNamespace(program2);registerSendInboxCommands(program2);registerStateCommands(program2);registerDispatchCommands(program2);registerHookNamespace(program2);registerDbCommands(program2);registerScheduleCommands(program2);registerDaemonCommands(program2);registerTaskCommands(program2);registerTypeCommands(program2);registerBoardCommands(program2);registerTagCommands(program2);registerReleaseCommands(program2);registerProjectCommands(program2);registerNotifyCommands(program2);registerEventsCommands(program2);registerSessionsCommands(program2);registerMetricsCommands(program2);registerExportCommands(program2);registerImportCommands(program2);registerTemplateCommands(program2);registerBriefCommands(program2);registerInstallCommand(program2);registerPublishCommand(program2);var itemCmd=program2.command("item").description("Item registry management");registerItemUninstallCommand(itemCmd);registerItemUpdateCommand(itemCmd);var auditTimers=new Map;program2.hook("preAction",(_thisCommand,actionCommand)=>{let name=actionCommand.name();auditTimers.set(name,Date.now()),Promise.resolve().then(() => (init_db(),exports_db)).then(({isConnected:isConnected2})=>{if(!isConnected2())return;recordAuditEvent("command",name,"command_start",getActor(),{args:actionCommand.args}).catch(()=>{})}).catch(()=>{})});program2.hook("postAction",(_thisCommand,actionCommand)=>{let name=actionCommand.name(),startMs=auditTimers.get(name),durationMs=startMs?Date.now()-startMs:void 0;auditTimers.delete(name),Promise.resolve().then(() => (init_db(),exports_db)).then(({isConnected:isConnected2})=>{if(!isConnected2())return;recordAuditEvent("command",name,"command_success",getActor(),{args:actionCommand.args,duration_ms:durationMs}).catch(()=>{})}).catch(()=>{})});program2.command("spawn <name>").description("Spawn a new agent by name (resolves from directory or built-ins)").option("--provider <provider>","Provider: claude or codex","claude").option("--team <team>","Team name",process.env.GENIE_TEAM??"genie").option("--model <model>","Model override (e.g., sonnet, opus)").option("--skill <skill>","Skill to load (optional)").option("--layout <layout>","Layout mode: mosaic (default) or vertical").option("--color <color>","Teammate pane border color").option("--plan-mode","Start teammate in plan mode").option("--permission-mode <mode>","Permission mode (e.g., acceptEdits)").option("--extra-args <args...>","Extra CLI args forwarded to provider").option("--cwd <path>","Working directory for the agent (overrides directory entry)").option("--session <session>","Tmux session name to spawn into").option("--no-auto-resume","Disable auto-resume on pane death").action(async(name,options)=>{try{await handleWorkerSpawn(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("kill <name>").description("Force kill an agent by name").action(async(name)=>{try{await handleWorkerKill(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("stop <name>").description("Stop an agent (preserves session for resume)").action(async(name)=>{try{await handleWorkerStop(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("resume [name]").description("Resume a suspended/failed agent with its Claude session").option("--all","Resume all eligible agents").action(async(name,options)=>{try{await handleWorkerResume(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("history <name>").description("Show compressed session history for an agent").option("--full","Show full conversation without compression").option("--since <n>","Show last N user/assistant exchanges",Number.parseInt).option("--last <n>","Show last N transcript entries",Number.parseInt).option("--type <role>","Filter by role (user, assistant, tool_call)").option("--after <timestamp>","Only entries after ISO timestamp").option("--json","Output as JSON").option("--ndjson","Output as newline-delimited JSON (pipeable to jq)").option("--raw","Output raw JSONL entries").option("--log-file <path>","Direct path to log file (for testing)").action(async(name,options)=>{await historyCommand(name,options)});program2.command("log [agent]").description("Unified observability feed \u2014 aggregates transcript, DMs, team chat").option("--team <name>","Show interleaved feed for all agents in a team").option("--type <kind>","Filter by event kind (transcript, message, tool_call, state, system)").option("--since <timestamp>","Only events after ISO timestamp").option("--last <n>","Show last N events",Number.parseInt).option("--ndjson","Output as newline-delimited JSON (pipeable to jq)").option("--json","Output as pretty JSON").option("-f, --follow","Follow mode \u2014 real-time streaming").action(async(agent,options)=>{await logCommand(agent,options)});var qaCmd=program2.command("qa").description("QA \u2014 self-testing system for genie CLI");qaCmd.command("run [target]",{isDefault:!0}).description("Run QA specs (all, a domain, or a single spec)").option("--timeout <seconds>","Max seconds per spec",(v2)=>Number(v2),3600).option("--parallel <n>","Max specs to run in parallel",(v2)=>Number(v2),5).option("--verbose","Show all collected events").option("--ndjson","Machine-readable NDJSON output").action(async(target,options)=>{await qaCommand(target,options)});qaCmd.command("status").description("Show QA dashboard with last results per spec").option("--json","Output as JSON").action(async(options)=>{await qaStatusCommand(options)});qaCmd.command("history").description("Show recent QA runs").action(async()=>{await qaHistoryCommand()});qaCmd.command("check <specFile>").description("Evaluate a QA spec against current team logs and publish qa-report").option("--team <name>","Team name (defaults to GENIE_TEAM)").option("--since <timestamp>","Only consider events after this ISO timestamp").option("--since-file <path>","Read the lower-bound timestamp from a file").action(async(specFile,options)=>{await qaCheckCommand(specFile,options)});program2.command("qa-report <json>").description("Publish QA result to the PG event log (called by QA team-lead)").action(async(json2)=>{let team=process.env.GENIE_TEAM;if(!team)console.error("Error: GENIE_TEAM not set. This command must be run by a QA team-lead agent."),process.exit(1);try{let data=JSON.parse(json2),{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(process.cwd(),`genie.qa.${team}.result`,{kind:"qa",agent:"qa",team,text:`QA result: ${String(data.result??"unknown")}`,data,source:"hook"}),console.log(`QA result published to PG event log as genie.qa.${team}.result`)}catch(err){console.error(`Failed to publish QA result: ${err}`),process.exit(1)}});program2.command("read <name>").description("Read terminal output from an agent pane").option("-n, --lines <number>","Number of lines to read").option("--from <line>","Start line").option("--to <line>","End line").option("--range <range>",'Line range (e.g., "10-20")').option("--search <text>","Search for text").option("--grep <pattern>","Grep for pattern").option("-f, --follow","Follow mode (like tail -f)").option("--all","Show all output").option("-r, --reverse","Reverse order").option("--json","Output as JSON").action(async(name,options)=>{await readSessionLogs2(name,options)});program2.command("answer <name> <choice>").description('Answer a question for an agent (use "text:..." for text input)').action(async(name,choice)=>{await answerQuestion(name,choice)});program2.command("ls").description("List registered agents with runtime status").option("--json","Output as JSON").action(async(options)=>{try{await handleLsCommand(options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});var args=process.argv.slice(2);if(process.env.GENIE_TUI_PANE==="left"){let{launchTui:launchTui2}=await Promise.resolve().then(() => exports_tui);await launchTui2(),process.exit(0)}if(args.length===0){let{findWorkspace:findWorkspace2}=await Promise.resolve().then(() => (init_workspace(),exports_workspace)),ws=findWorkspace2();if(!ws)console.error("Not in a genie workspace. Run `genie init` to create one."),process.exit(1);let{isServeRunning:isServeRunning2,autoStartServe:autoStartServe2,isTuiSessionReady:isTuiSessionReady2,ensureTuiSession:ensureTuiSession2}=await Promise.resolve().then(() => (init_serve(),exports_serve));if(!isServeRunning2())console.log("Starting genie serve..."),await autoStartServe2();else if(!isTuiSessionReady2())ensureTuiSession2(ws.root);if(ws.root)process.env.GENIE_TUI_WORKSPACE=ws.root;if(ws.agent)process.env.GENIE_TUI_AGENT=ws.agent;if(ws.agent){let{writeFileSync:writeFileSync16}=await import("fs"),{join:join53}=await import("path"),home=process.env.GENIE_HOME??join53((await import("os")).homedir(),".genie");try{writeFileSync16(join53(home,"tui-initial-agent"),ws.agent,"utf-8")}catch{}}let{attachTuiSession:attachTuiSession2}=await Promise.resolve().then(() => (init_tmux2(),exports_tmux2));attachTuiSession2(),process.exit(0)}if(args.every((a)=>a==="--reset")){let{sessionCommand:sessionCommand2}=await Promise.resolve().then(() => (init_session(),exports_session));await sessionCommand2({reset:!0}),process.exit(0)}var sessionIdx=args.indexOf("--session");if(sessionIdx!==-1&&sessionIdx+1<args.length){let sessionName=args[sessionIdx+1];if(!args.filter((_2,i2)=>i2!==sessionIdx&&i2!==sessionIdx+1).some((a)=>!a.startsWith("-")))try{await startNamedSession(sessionName),process.exit(0)}catch(err){console.error(`Error: ${err instanceof Error?err.message:err}`),process.exit(1)}else try{await program2.parseAsync(process.argv)}finally{stopOtelReceiver(),await shutdown().catch(()=>{})}}else try{await program2.parseAsync(process.argv)}finally{stopOtelReceiver(),await shutdown().catch(()=>{})}
|
|
2635
|
+
Stage Pipeline:`);let stages=t.stages;for(let i2=0;i2<stages.length;i2++){let s=stages[i2],arrow=i2<stages.length-1?" \u2192":"",gate=s.gate?` [gate: ${s.gate}]`:"",action=s.action?` (action: ${s.action})`:"",auto=s.auto_advance?" [auto]":"";console.log(` ${i2+1}. ${s.label??s.name}${gate}${action}${auto}${arrow}`)}console.log("")}async function handleTypeList(options){console.warn("Warning: `genie type` is deprecated. Use `genie board` instead.");let types4=await(await getTaskService8()).listTypes();if(options.json){console.log(JSON.stringify(types4,null,2));return}printTypeTable(types4)}async function handleTypeShow(id,options){console.warn("Warning: `genie type` is deprecated. Use `genie board` instead.");let t=await(await getTaskService8()).getType(id);if(!t)console.error(`Error: Type not found: ${id}`),process.exit(1);if(options.json){console.log(JSON.stringify(t,null,2));return}printTypePipeline(t)}async function handleTypeCreate(name,options){console.warn("Warning: `genie type` is deprecated. Use `genie board` instead.");let ts3=await getTaskService8(),stages;try{if(stages=JSON.parse(options.stages),!Array.isArray(stages))throw Error("Stages must be a JSON array")}catch(err){console.error(`Error: Invalid stages JSON. ${err instanceof Error?err.message:String(err)}`),process.exit(1)}for(let s of stages)if(typeof s!=="object"||s===null||!("name"in s))console.error('Error: Each stage must have at least a "name" field.'),process.exit(1);let id=name.toLowerCase().replace(/\s+/g,"-"),t=await ts3.createType({id,name,description:options.description,icon:options.icon,stages});console.log(`Created type "${t.name}" (${t.id}) with ${stages.length} stages.`)}function registerTypeCommands(program2){let type2=program2.command("type").description("Task type management");type2.command("list").description("List all task types").option("--json","Output as JSON").action(async(options)=>{try{await handleTypeList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),type2.command("show <id>").description("Show task type detail with stage pipeline").option("--json","Output as JSON").action(async(id,options)=>{try{await handleTypeShow(id,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),type2.command("create <name>").description("Create a custom task type").requiredOption("--stages <json>","Stages JSON array").option("--description <text>","Type description").option("--icon <icon>","Type icon").action(async(name,options)=>{try{await handleTypeCreate(name,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}try{let{execSync:execSyncStartup}=__require("child_process");if(execSyncStartup("git config core.bare",{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()==="true")execSyncStartup("git config core.bare false",{stdio:["pipe","pipe","pipe"]})}catch{}var program2=new Command;program2.name("genie").description("Genie CLI - AI-assisted development").version(VERSION);async function startNamedSession(name){let{buildTeamLeadCommand:buildTeamLeadCommand2,sessionExists:sessionExists2}=await Promise.resolve().then(() => (init_team_lead_command(),exports_team_lead_command)),{getAgentsFilePath:getAgentsFilePath2}=await Promise.resolve().then(() => (init_session(),exports_session)),systemPromptFile=getAgentsFilePath2(),hasPriorSession=sessionExists2(name),cmd=buildTeamLeadCommand2(name,{systemPromptFile:systemPromptFile??void 0,continueName:hasPriorSession?name:void 0});console.log(hasPriorSession?`Resuming session: ${name}`:`Starting new session: ${name}`);let{spawnSync:spawnSync4}=await import("child_process"),result=spawnSync4("sh",["-c",cmd],{stdio:"inherit"});if(result.status)process.exit(result.status)}program2.command("setup").description("Configure genie settings").option("--quick","Accept all defaults").option("--shortcuts","Only configure keyboard shortcuts").option("--codex","Only configure Codex integration").option("--terminal","Only configure terminal defaults").option("--session","Only configure session settings").option("--reset","Reset configuration to defaults").option("--show","Show current configuration").action(async(options)=>{await setupCommand(options)});program2.command("doctor").description("Run diagnostic checks on genie installation").option("--fix","Auto-fix: kill zombie postgres, clean shared memory, restart daemon").action(doctorCommand);program2.command("update").description("Update Genie CLI to the latest version").option("--next","Switch to dev builds (npm @next tag)").option("--stable","Switch to stable releases (npm @latest tag)").action(updateCommand);program2.command("uninstall").description("Remove Genie CLI and clean up hooks").action(uninstallCommand);var shortcuts=program2.command("shortcuts").description("Manage tmux keyboard shortcuts");shortcuts.action(shortcutsShowCommand);shortcuts.command("show").description("Show available shortcuts and installation status").action(shortcutsShowCommand);shortcuts.command("install").description("Install shortcuts to config files (~/.tmux.conf, shell rc)").action(shortcutsInstallCommand);shortcuts.command("uninstall").description("Remove shortcuts from config files").action(shortcutsUninstallCommand);registerServeCommands(program2);registerAppCommand(program2);registerInitCommands(program2);registerTeamNamespace(program2);registerDirNamespace(program2);registerAgentNamespace(program2);registerSendInboxCommands(program2);registerStateCommands(program2);registerDispatchCommands(program2);registerHookNamespace(program2);registerDbCommands(program2);registerScheduleCommands(program2);registerDaemonCommands(program2);registerTaskCommands(program2);registerTypeCommands(program2);registerBoardCommands(program2);registerTagCommands(program2);registerReleaseCommands(program2);registerProjectCommands(program2);registerNotifyCommands(program2);registerEventsCommands(program2);registerSessionsCommands(program2);registerMetricsCommands(program2);registerExportCommands(program2);registerImportCommands(program2);registerTemplateCommands(program2);registerBriefCommands(program2);registerInstallCommand(program2);registerPublishCommand(program2);var itemCmd=program2.command("item").description("Item registry management");registerItemUninstallCommand(itemCmd);registerItemUpdateCommand(itemCmd);var auditTimers=new Map;program2.hook("preAction",(_thisCommand,actionCommand)=>{let name=actionCommand.name();auditTimers.set(name,Date.now()),Promise.resolve().then(() => (init_db(),exports_db)).then(({isConnected:isConnected2})=>{if(!isConnected2())return;recordAuditEvent("command",name,"command_start",getActor(),{args:actionCommand.args}).catch(()=>{})}).catch(()=>{})});program2.hook("postAction",(_thisCommand,actionCommand)=>{let name=actionCommand.name(),startMs=auditTimers.get(name),durationMs=startMs?Date.now()-startMs:void 0;auditTimers.delete(name),Promise.resolve().then(() => (init_db(),exports_db)).then(({isConnected:isConnected2})=>{if(!isConnected2())return;recordAuditEvent("command",name,"command_success",getActor(),{args:actionCommand.args,duration_ms:durationMs}).catch(()=>{})}).catch(()=>{})});program2.command("spawn <name>").description("Spawn a new agent by name (resolves from directory or built-ins)").option("--provider <provider>","Provider: claude or codex","claude").option("--team <team>","Team name",process.env.GENIE_TEAM??"genie").option("--model <model>","Model override (e.g., sonnet, opus)").option("--skill <skill>","Skill to load (optional)").option("--layout <layout>","Layout mode: mosaic (default) or vertical").option("--color <color>","Teammate pane border color").option("--plan-mode","Start teammate in plan mode").option("--permission-mode <mode>","Permission mode (e.g., acceptEdits)").option("--extra-args <args...>","Extra CLI args forwarded to provider").option("--cwd <path>","Working directory for the agent (overrides directory entry)").option("--session <session>","Tmux session name to spawn into").option("--no-auto-resume","Disable auto-resume on pane death").action(async(name,options)=>{try{await handleWorkerSpawn(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("kill <name>").description("Force kill an agent by name").action(async(name)=>{try{await handleWorkerKill(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("stop <name>").description("Stop an agent (preserves session for resume)").action(async(name)=>{try{await handleWorkerStop(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("resume [name]").description("Resume a suspended/failed agent with its Claude session").option("--all","Resume all eligible agents").action(async(name,options)=>{try{await handleWorkerResume(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("history <name>").description("Show compressed session history for an agent").option("--full","Show full conversation without compression").option("--since <n>","Show last N user/assistant exchanges",Number.parseInt).option("--last <n>","Show last N transcript entries",Number.parseInt).option("--type <role>","Filter by role (user, assistant, tool_call)").option("--after <timestamp>","Only entries after ISO timestamp").option("--json","Output as JSON").option("--ndjson","Output as newline-delimited JSON (pipeable to jq)").option("--raw","Output raw JSONL entries").option("--log-file <path>","Direct path to log file (for testing)").action(async(name,options)=>{await historyCommand(name,options)});program2.command("log [agent]").description("Unified observability feed \u2014 aggregates transcript, DMs, team chat").option("--team <name>","Show interleaved feed for all agents in a team").option("--type <kind>","Filter by event kind (transcript, message, tool_call, state, system)").option("--since <timestamp>","Only events after ISO timestamp").option("--last <n>","Show last N events",Number.parseInt).option("--ndjson","Output as newline-delimited JSON (pipeable to jq)").option("--json","Output as pretty JSON").option("-f, --follow","Follow mode \u2014 real-time streaming").action(async(agent,options)=>{await logCommand(agent,options)});var qaCmd=program2.command("qa").description("QA \u2014 self-testing system for genie CLI");qaCmd.command("run [target]",{isDefault:!0}).description("Run QA specs (all, a domain, or a single spec)").option("--timeout <seconds>","Max seconds per spec",(v2)=>Number(v2),3600).option("--parallel <n>","Max specs to run in parallel",(v2)=>Number(v2),5).option("--verbose","Show all collected events").option("--ndjson","Machine-readable NDJSON output").action(async(target,options)=>{await qaCommand(target,options)});qaCmd.command("status").description("Show QA dashboard with last results per spec").option("--json","Output as JSON").action(async(options)=>{await qaStatusCommand(options)});qaCmd.command("history").description("Show recent QA runs").action(async()=>{await qaHistoryCommand()});qaCmd.command("check <specFile>").description("Evaluate a QA spec against current team logs and publish qa-report").option("--team <name>","Team name (defaults to GENIE_TEAM)").option("--since <timestamp>","Only consider events after this ISO timestamp").option("--since-file <path>","Read the lower-bound timestamp from a file").action(async(specFile,options)=>{await qaCheckCommand(specFile,options)});program2.command("qa-report <json>").description("Publish QA result to the PG event log (called by QA team-lead)").action(async(json2)=>{let team=process.env.GENIE_TEAM;if(!team)console.error("Error: GENIE_TEAM not set. This command must be run by a QA team-lead agent."),process.exit(1);try{let data=JSON.parse(json2),{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(process.cwd(),`genie.qa.${team}.result`,{kind:"qa",agent:"qa",team,text:`QA result: ${String(data.result??"unknown")}`,data,source:"hook"}),console.log(`QA result published to PG event log as genie.qa.${team}.result`)}catch(err){console.error(`Failed to publish QA result: ${err}`),process.exit(1)}});program2.command("read <name>").description("Read terminal output from an agent pane").option("-n, --lines <number>","Number of lines to read").option("--from <line>","Start line").option("--to <line>","End line").option("--range <range>",'Line range (e.g., "10-20")').option("--search <text>","Search for text").option("--grep <pattern>","Grep for pattern").option("-f, --follow","Follow mode (like tail -f)").option("--all","Show all output").option("-r, --reverse","Reverse order").option("--json","Output as JSON").action(async(name,options)=>{await readSessionLogs2(name,options)});program2.command("answer <name> <choice>").description('Answer a question for an agent (use "text:..." for text input)').action(async(name,choice)=>{await answerQuestion(name,choice)});program2.command("ls").description("List registered agents with runtime status").option("--json","Output as JSON").action(async(options)=>{try{await handleLsCommand(options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});var args=process.argv.slice(2);if(process.env.GENIE_TUI_PANE==="left"&&args.length===0){process.env.GENIE_TUI_PANE=void 0;let{launchTui:launchTui2}=await Promise.resolve().then(() => exports_tui);await launchTui2(),process.exit(0)}process.env.GENIE_TUI_PANE=void 0;if(args.length===0){let{findWorkspace:findWorkspace2}=await Promise.resolve().then(() => (init_workspace(),exports_workspace)),ws=findWorkspace2();if(!ws)console.error("Not in a genie workspace. Run `genie init` to create one."),process.exit(1);let{isServeRunning:isServeRunning2,autoStartServe:autoStartServe2,isTuiSessionReady:isTuiSessionReady2,ensureTuiSession:ensureTuiSession2}=await Promise.resolve().then(() => (init_serve(),exports_serve));if(!isServeRunning2())console.log("Starting genie serve..."),await autoStartServe2();else if(!isTuiSessionReady2())ensureTuiSession2(ws.root);if(ws.root)process.env.GENIE_TUI_WORKSPACE=ws.root;if(ws.agent)process.env.GENIE_TUI_AGENT=ws.agent;if(ws.agent){let{writeFileSync:writeFileSync16}=await import("fs"),{join:join53}=await import("path"),home=process.env.GENIE_HOME??join53((await import("os")).homedir(),".genie");try{writeFileSync16(join53(home,"tui-initial-agent"),ws.agent,"utf-8")}catch{}}let{attachTuiSession:attachTuiSession2}=await Promise.resolve().then(() => (init_tmux2(),exports_tmux2));attachTuiSession2(),process.exit(0)}if(args.every((a)=>a==="--reset")){let{sessionCommand:sessionCommand2}=await Promise.resolve().then(() => (init_session(),exports_session));await sessionCommand2({reset:!0}),process.exit(0)}var sessionIdx=args.indexOf("--session");if(sessionIdx!==-1&&sessionIdx+1<args.length){let sessionName=args[sessionIdx+1];if(!args.filter((_2,i2)=>i2!==sessionIdx&&i2!==sessionIdx+1).some((a)=>!a.startsWith("-")))try{await startNamedSession(sessionName),process.exit(0)}catch(err){console.error(`Error: ${err instanceof Error?err.message:err}`),process.exit(1)}else try{await program2.parseAsync(process.argv)}finally{stopOtelReceiver(),await shutdown().catch(()=>{})}}else try{await program2.parseAsync(process.argv)}finally{stopOtelReceiver(),await shutdown().catch(()=>{})}
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genie",
|
|
3
|
-
"version": "4.260330.
|
|
3
|
+
"version": "4.260330.20",
|
|
4
4
|
"description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Namastex Labs"
|
package/src/genie.ts
CHANGED
|
@@ -469,12 +469,16 @@ program
|
|
|
469
469
|
|
|
470
470
|
const args = process.argv.slice(2);
|
|
471
471
|
|
|
472
|
-
//
|
|
473
|
-
|
|
472
|
+
// TUI renderer mode: only when GENIE_TUI_PANE=left AND no subcommand given.
|
|
473
|
+
// Any subcommand (work, spawn, team, etc.) runs normally regardless of env.
|
|
474
|
+
// Clear the env var after checking so child processes never inherit it.
|
|
475
|
+
if (process.env.GENIE_TUI_PANE === 'left' && args.length === 0) {
|
|
476
|
+
process.env.GENIE_TUI_PANE = undefined;
|
|
474
477
|
const { launchTui } = await import('./tui/index.js');
|
|
475
478
|
await launchTui();
|
|
476
479
|
process.exit(0);
|
|
477
480
|
}
|
|
481
|
+
process.env.GENIE_TUI_PANE = undefined;
|
|
478
482
|
|
|
479
483
|
// Default command: genie (no args) → thin TUI client (attach to serve).
|
|
480
484
|
if (args.length === 0) {
|
|
@@ -110,39 +110,6 @@ function isTmuxServerRunning(socket: string, conf: string): boolean {
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
/** Start the genie agent tmux server and create sessions for all agents */
|
|
114
|
-
function startAgentTmuxServer(agents: string[]): void {
|
|
115
|
-
const conf = genieTmuxConf();
|
|
116
|
-
|
|
117
|
-
// Start server with a bootstrap session (tmux needs at least one)
|
|
118
|
-
if (!isTmuxServerRunning(GENIE_SOCKET, conf)) {
|
|
119
|
-
execSync(genieTmux('new-session -d -s _bootstrap'), { stdio: 'ignore' });
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Create a session per agent
|
|
123
|
-
for (const agent of agents) {
|
|
124
|
-
try {
|
|
125
|
-
execSync(genieTmux(`has-session -t '${agent}'`), { stdio: 'ignore' });
|
|
126
|
-
} catch {
|
|
127
|
-
// Session doesn't exist — create it
|
|
128
|
-
try {
|
|
129
|
-
execSync(genieTmux(`new-session -d -s '${agent}'`), { stdio: 'ignore' });
|
|
130
|
-
} catch {
|
|
131
|
-
// race or naming conflict
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Kill the bootstrap session if we have real agent sessions
|
|
137
|
-
if (agents.length > 0) {
|
|
138
|
-
try {
|
|
139
|
-
execSync(genieTmux('kill-session -t _bootstrap'), { stdio: 'ignore' });
|
|
140
|
-
} catch {
|
|
141
|
-
// already gone or was the only session
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
113
|
const NAV_WIDTH = 30;
|
|
147
114
|
const KEY_TABLE = 'genie-tui';
|
|
148
115
|
|
|
@@ -343,18 +310,6 @@ export function ensureTuiSession(workspaceRoot?: string): void {
|
|
|
343
310
|
// Workspace agent scanning
|
|
344
311
|
// ============================================================================
|
|
345
312
|
|
|
346
|
-
/** Scan for agents from workspace.json and filesystem */
|
|
347
|
-
function discoverAgents(): string[] {
|
|
348
|
-
try {
|
|
349
|
-
const { findWorkspace, scanAgents } = require('../lib/workspace.js') as typeof import('../lib/workspace.js');
|
|
350
|
-
const ws = findWorkspace();
|
|
351
|
-
if (!ws) return [];
|
|
352
|
-
return scanAgents(ws.root);
|
|
353
|
-
} catch {
|
|
354
|
-
return [];
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
313
|
// ============================================================================
|
|
359
314
|
// Service management
|
|
360
315
|
// ============================================================================
|
|
@@ -421,12 +376,14 @@ async function startForeground(): Promise<void> {
|
|
|
421
376
|
console.error(` pgserve failed: ${msg}`);
|
|
422
377
|
}
|
|
423
378
|
|
|
424
|
-
// 2.
|
|
425
|
-
|
|
426
|
-
console.log(` Starting tmux -L ${GENIE_SOCKET} server...`);
|
|
427
|
-
startAgentTmuxServer(agents);
|
|
379
|
+
// 2. Report agent tmux server state (don't create empty sessions —
|
|
380
|
+
// sessions are created on-demand by `genie spawn`).
|
|
428
381
|
const sessions = listAgentSessions();
|
|
429
|
-
|
|
382
|
+
if (sessions.length > 0) {
|
|
383
|
+
console.log(` Agent server (-L ${GENIE_SOCKET}): ${sessions.length} sessions`);
|
|
384
|
+
} else {
|
|
385
|
+
console.log(` Agent server (-L ${GENIE_SOCKET}): no sessions yet (created on first spawn)`);
|
|
386
|
+
}
|
|
430
387
|
|
|
431
388
|
// 2b. Sync agent directory + start watcher
|
|
432
389
|
handles.agentWatcher = await startAgentSync();
|
|
@@ -162,10 +162,11 @@ export function Nav({ onTmuxSessionSelect, workspaceRoot, initialAgent }: NavPro
|
|
|
162
162
|
const node = flatNodes[selectedIndex]?.node;
|
|
163
163
|
if (!node) return;
|
|
164
164
|
|
|
165
|
-
//
|
|
166
|
-
if (node.type === 'agent'
|
|
167
|
-
|
|
168
|
-
|
|
165
|
+
// Agent node: spawn if not running, then attach
|
|
166
|
+
if (node.type === 'agent') {
|
|
167
|
+
if (node.wsAgentState !== 'running') {
|
|
168
|
+
spawnAgent(node.label);
|
|
169
|
+
}
|
|
169
170
|
const target = getSessionTarget(node);
|
|
170
171
|
if (target) onTmuxSessionSelect(target.sessionName, target.windowIndex);
|
|
171
172
|
return;
|