@automagik/genie 4.260331.19 → 4.260331.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.
@@ -10,7 +10,7 @@
10
10
  "plugins": [
11
11
  {
12
12
  "name": "genie",
13
- "version": "4.260331.19",
13
+ "version": "4.260331.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
@@ -1492,7 +1492,7 @@ Stopped following`),process.exit(0)}),await new Promise(()=>{});return}let conte
1492
1492
  `,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`
1493
1493
  INSERT INTO machine_snapshots (id, active_workers, active_teams, tmux_sessions, cpu_percent, memory_mb, created_at)
1494
1494
  VALUES (${snapshotId}, ${activeWorkers}, ${teams.size}, ${tmuxSessions}, ${cpuPercent}, ${memoryMb}, ${now})
1495
- `,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 execSync7,spawn as spawn3}from"child_process";import{existsSync as existsSync24,mkdirSync as mkdirSync9,readFileSync as readFileSync10,unlinkSync as unlinkSync6,writeFileSync as writeFileSync8}from"fs";import{homedir as homedir22}from"os";import{join as join33}from"path";function genieHome2(){return process.env.GENIE_HOME??join33(homedir22(),".genie")}function servePidPath(){return join33(genieHome2(),"serve.pid")}function genieTmuxConf(){return[join33(genieHome2(),"tmux.conf")].find((p)=>existsSync24(p))??"/dev/null"}function readServePid(){let path2=servePidPath();if(!existsSync24(path2))return null;let raw=readFileSync10(path2,"utf-8").trim(),pid=Number.parseInt(raw,10);if(Number.isNaN(pid)||pid<=0)return null;return pid}function writeServePid(pid){mkdirSync9(genieHome2(),{recursive:!0}),writeFileSync8(servePidPath(),String(pid),"utf-8")}function removeServePid(){let path2=servePidPath();if(existsSync24(path2))try{unlinkSync6(path2)}catch{}}function isProcessAlive(pid){try{return process.kill(pid,0),!0}catch{return!1}}function genieTmux(subcmd){return`${tmuxBin()} -L ${GENIE_SOCKET} -f ${genieTmuxConf()} ${subcmd}`}function tuiTmuxConf(){return[join33(genieHome2(),"tui-tmux.conf")].find((p)=>existsSync24(p))??"/dev/null"}function tuiTmux(subcmd){return`${tmuxBin()} -L genie-tui -f ${tuiTmuxConf()} ${subcmd}`}function isGenieTmuxRunning(){try{return execSync7(genieTmux("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 on`,`set-option -t ${TUI_SESSION} status off`,`set-option -t ${TUI_SESSION} pane-border-status off`];for(let cmd of cmds)try{execSync7(tuiTmux(cmd),{stdio:"ignore"})}catch{}}function setupTuiKeybindings(){let bindings=[`bind-key -T root Tab if-shell "[ '#{pane_index}' = '0' ]" "select-pane -t ${TUI_SESSION}:0.1" "select-pane -t ${TUI_SESSION}:0.0"`,`bind-key -T root C-b if-shell "[ $(tmux 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}"`,`bind-key -T root C-t send-keys -t ${TUI_SESSION}:0.1 C-b c`,"bind-key -T root C-d detach-client",`bind-key -T root C-q select-pane -t ${TUI_SESSION}:0.0 \\; send-keys -t ${TUI_SESSION}:0.0 C-q`];for(let cmd of bindings)try{execSync7(tuiTmux(cmd),{stdio:"ignore"})}catch{}}function startTuiTmuxServer(){try{execSync7(tuiTmux(`has-session -t ${TUI_SESSION}`),{stdio:"ignore"});let panes2=execSync7(tuiTmux(`list-panes -t ${TUI_SESSION}:0 -F '#{pane_id}'`),{encoding:"utf-8"}).trim().split(`
1495
+ `,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,getTuiQuitBindingArgs:()=>getTuiQuitBindingArgs,getTuiKeybindings:()=>getTuiKeybindings,ensureTuiSession:()=>ensureTuiSession,autoStartServe:()=>autoStartServe});import{execSync as execSync7,spawn as spawn3,spawnSync as spawnSync2}from"child_process";import{existsSync as existsSync24,mkdirSync as mkdirSync9,readFileSync as readFileSync10,unlinkSync as unlinkSync6,writeFileSync as writeFileSync8}from"fs";import{homedir as homedir22}from"os";import{join as join33}from"path";function genieHome2(){return process.env.GENIE_HOME??join33(homedir22(),".genie")}function servePidPath(){return join33(genieHome2(),"serve.pid")}function genieTmuxConf(){return[join33(genieHome2(),"tmux.conf")].find((p)=>existsSync24(p))??"/dev/null"}function readServePid(){let path2=servePidPath();if(!existsSync24(path2))return null;let raw=readFileSync10(path2,"utf-8").trim(),pid=Number.parseInt(raw,10);if(Number.isNaN(pid)||pid<=0)return null;return pid}function writeServePid(pid){mkdirSync9(genieHome2(),{recursive:!0}),writeFileSync8(servePidPath(),String(pid),"utf-8")}function removeServePid(){let path2=servePidPath();if(existsSync24(path2))try{unlinkSync6(path2)}catch{}}function isProcessAlive(pid){try{return process.kill(pid,0),!0}catch{return!1}}function genieTmux(subcmd){return`${tmuxBin()} -L ${GENIE_SOCKET} -f ${genieTmuxConf()} ${subcmd}`}function tuiTmuxConf(){return[join33(genieHome2(),"tui-tmux.conf")].find((p)=>existsSync24(p))??"/dev/null"}function tuiTmux(subcmd){return`${tmuxBin()} -L genie-tui -f ${tuiTmuxConf()} ${subcmd}`}function isGenieTmuxRunning(){try{return execSync7(genieTmux("list-sessions"),{stdio:"ignore"}),!0}catch{return!1}}function getTuiKeybindings(sessionName=TUI_SESSION){return[`bind-key -T root Tab if-shell "[ '#{pane_index}' = '0' ]" "select-pane -t ${sessionName}:0.1" "select-pane -t ${sessionName}:0.0"`,`bind-key -T root C-1 select-pane -t ${sessionName}:0.0`,`bind-key -T root C-2 select-pane -t ${sessionName}:0.1`,`bind-key -T root C-b if-shell "[ $(tmux display-message -p '#\\{pane_width\\}' -t ${sessionName}:0.0) -gt 5 ]" "resize-pane -t ${sessionName}:0.0 -x 0" "resize-pane -t ${sessionName}:0.0 -x ${NAV_WIDTH}"`,`bind-key -T root C-t send-keys -t ${sessionName}:0.1 C-b c`,"bind-key -T root C-d detach-client",`bind-key -T root C-q select-pane -t ${sessionName}:0.0 \\; send-keys -t ${sessionName}:0.0 C-q`]}function getTuiQuitBindingArgs(sessionName=TUI_SESSION){return["bind-key","-T","root","C-q","select-pane","-t",`${sessionName}:0.0`,"\\;","send-keys","-t",`${sessionName}:0.0`,"C-q"]}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 on`,`set-option -t ${TUI_SESSION} status off`,`set-option -t ${TUI_SESSION} pane-border-status off`];for(let cmd of cmds)try{execSync7(tuiTmux(cmd),{stdio:"ignore"})}catch{}}function setupTuiKeybindings(){for(let cmd of getTuiKeybindings())try{if(cmd.startsWith("bind-key -T root C-q "))spawnSync2(tmuxBin(),["-L","genie-tui","-f",tuiTmuxConf(),...getTuiQuitBindingArgs()],{stdio:"ignore"});else execSync7(tuiTmux(cmd),{stdio:"ignore"})}catch{}}function startTuiTmuxServer(){try{execSync7(tuiTmux(`has-session -t ${TUI_SESSION}`),{stdio:"ignore"});let panes2=execSync7(tuiTmux(`list-panes -t ${TUI_SESSION}:0 -F '#{pane_id}'`),{encoding:"utf-8"}).trim().split(`
1496
1496
  `);return{leftPane:panes2[0],rightPane:panes2[1]||panes2[0]}}catch{}let cols=120;execSync7(tuiTmux(`new-session -d -s ${TUI_SESSION} -x ${cols} -y ${40}`),{stdio:"ignore"}),execSync7(tuiTmux(`split-window -h -t ${TUI_SESSION}:0 -l ${cols-NAV_WIDTH-1}`),{stdio:"ignore"});let panes=execSync7(tuiTmux(`list-panes -t ${TUI_SESSION}:0 -F '#{pane_id}'`),{encoding:"utf-8"}).trim().split(`
1497
1497
  `);applyTuiStyle(),setupTuiKeybindings();try{execSync7(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=genieHome2(),bunPath=process.execPath||"bun",genieBin=process.argv[1]||"genie",scriptPath=join33(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
1498
1498
  export ${envVars.join(`
@@ -1502,7 +1502,7 @@ exec ${bunPath} ${genieBin}
1502
1502
  `).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 execSync7(tuiTmux(`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})`),await ensureTmux(),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(" Setting up TUI session...");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(`
1503
1503
  genie serve is running. Press Ctrl+C to stop.`);let shutdown2=()=>{console.log(`
1504
1504
  Shutting down genie serve...`),handles.agentWatcher?.close(),handles.schedulerHandle?.stop(),killTuiSession(),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(),killTuiSession();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{}}}killTuiSession(),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=isGenieTmuxRunning(),sessions=agentRunning?listAgentSessions():[];if(console.log(` tmux -L ${GENIE_SOCKET}: ${agentRunning?`running (${sessions.length} sessions)`:"stopped"}`),sessions.length>0)console.log(` ${sessions.join(", ")}`);let tuiReady=isTuiSessionReady();console.log(` tmux -L genie-tui: ${tuiReady?"running":"stopped"}`)}async function printDaemonStatus(serveRunning){try{let schedulerPidPath=join33(genieHome2(),"scheduler.pid");if(existsSync24(schedulerPidPath)){let sPid=Number.parseInt(readFileSync10(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(`
1505
- 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_SESSION="genie-tui",NAV_WIDTH=30,TUI_STYLE,handles;var init_serve=__esm(()=>{init_ensure_tmux();TUI_STYLE={activeBorder:"#7c3aed",inactiveBorder:"#414868"};handles={schedulerHandle:null,agentWatcher:null}});var exports_tmux2={};__export(exports_tmux2,{hasProjectSession:()=>hasProjectSession,attachTuiSession:()=>attachTuiSession,attachProjectWindow:()=>attachProjectWindow});import{spawnSync as spawnSync2}from"child_process";function runTuiTmux(args,stdio="ignore"){return spawnSync2(TMUX_BIN,["-L",TMUX_SOCKET,"-f",TUI_TMUX_CONF,...args],{stdio})}function runTuiTmuxOutput(args){let result=spawnSync2(TMUX_BIN,["-L",TMUX_SOCKET,"-f",TUI_TMUX_CONF,...args],{encoding:"utf-8"});return result.status===0?result.stdout.trim():null}function runAgentTmux(args,stdio="ignore"){return spawnSync2(TMUX_BIN,["-L",GENIE_AGENT_SOCKET,...args],{stdio})}function shellQuote3(value){return`'${value.replace(/'/g,"'\\''")}'`}function buildAttachLoop(targetSession){return`while true; do TMUX='' ${[TMUX_BIN,"-L",GENIE_AGENT_SOCKET,"attach-session","-t",targetSession].map(shellQuote3).join(" ")} 2>/dev/null; sleep 0.3; done`}function resolveRightPane(rightPane){if(runTuiTmux(["display-message","-t",rightPane,"-p",""]).status===0)return rightPane;let panes=runTuiTmuxOutput(["list-panes","-t",`${SESSION_NAME}:0`,"-F","#{pane_id}"])?.split(`
1505
+ 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_SESSION="genie-tui",NAV_WIDTH=30,TUI_STYLE,handles;var init_serve=__esm(()=>{init_ensure_tmux();TUI_STYLE={activeBorder:"#7c3aed",inactiveBorder:"#414868"};handles={schedulerHandle:null,agentWatcher:null}});var exports_tmux2={};__export(exports_tmux2,{hasProjectSession:()=>hasProjectSession,attachTuiSession:()=>attachTuiSession,attachProjectWindow:()=>attachProjectWindow});import{spawnSync as spawnSync3}from"child_process";function runTuiTmux(args,stdio="ignore"){return spawnSync3(TMUX_BIN,["-L",TMUX_SOCKET,"-f",TUI_TMUX_CONF,...args],{stdio})}function runTuiTmuxOutput(args){let result=spawnSync3(TMUX_BIN,["-L",TMUX_SOCKET,"-f",TUI_TMUX_CONF,...args],{encoding:"utf-8"});return result.status===0?result.stdout.trim():null}function runAgentTmux(args,stdio="ignore"){return spawnSync3(TMUX_BIN,["-L",GENIE_AGENT_SOCKET,...args],{stdio})}function shellQuote3(value){return`'${value.replace(/'/g,"'\\''")}'`}function buildAttachLoop(targetSession){return`while true; do TMUX='' ${[TMUX_BIN,"-L",GENIE_AGENT_SOCKET,"attach-session","-t",targetSession].map(shellQuote3).join(" ")} 2>/dev/null; sleep 0.3; done`}function resolveRightPane(rightPane){if(runTuiTmux(["display-message","-t",rightPane,"-p",""]).status===0)return rightPane;let panes=runTuiTmuxOutput(["list-panes","-t",`${SESSION_NAME}:0`,"-F","#{pane_id}"])?.split(`
1506
1506
  `)??[];return panes[1]||panes[0]||rightPane}function hasProjectSession(targetSession){return runAgentTmux(["has-session","-t",targetSession]).status===0}function attachProjectWindow(rightPane,targetSession,windowIndex){if(targetSession===SESSION_NAME)return;let pane=resolveRightPane(rightPane);if(!hasProjectSession(targetSession))return;if(windowIndex!==void 0)runAgentTmux(["select-window","-t",`${targetSession}:${windowIndex}`]);runAgentTmux(["set-option","-t",targetSession,"status","off"]),runTuiTmux(["respawn-pane","-k","-t",pane,"sh","-lc",buildAttachLoop(targetSession)]),runTuiTmux(["select-pane","-t",`${SESSION_NAME}:0.0`])}function attachTuiSession(){runTuiTmux(["attach-session","-t",SESSION_NAME],"inherit")}var SESSION_NAME="genie-tui",TMUX_SOCKET="genie-tui",GENIE_AGENT_SOCKET="genie",TUI_TMUX_CONF,TMUX_BIN;var init_tmux2=__esm(()=>{init_ensure_tmux();TUI_TMUX_CONF=(()=>{let{existsSync:existsSync25}=__require("fs"),tuiConf=`${process.env.GENIE_HOME??`${process.env.HOME}/.genie`}/tui-tmux.conf`;return existsSync25(tuiConf)?tuiConf:"/dev/null"})(),TMUX_BIN=tmuxBin()});function onBridgeEvent(handler){return listeners.add(handler),()=>{listeners.delete(handler)}}function emit2(event){for(let handler of listeners)handler(event)}async function listAgents2(){return(await getConnection())`
1507
1507
  SELECT a.id, a.custom_name, a.role, a.team, a.title, a.state,
1508
1508
  a.reports_to, a.current_executor_id, a.started_at
@@ -2175,7 +2175,7 @@ $ bun add react-devtools-core@7 -d
2175
2175
  AND a.ended_at IS NULL
2176
2176
  ORDER BY a.started_at DESC
2177
2177
  `).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 execSync13}from"child_process";function execQuiet(cmd){try{return execSync13(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(`${tmuxBin()} -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(`
2178
- `)){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=__esm(()=>{init_ensure_tmux()});import{existsSync as existsSync38,readFileSync as readFileSync17,unlinkSync as unlinkSync8}from"fs";import{homedir as homedir27}from"os";import{join as join51}from"path";function getInitialAgentFilePath(){let genieHome4=process.env.GENIE_HOME??join51(homedir27(),".genie");return join51(genieHome4,"tui-initial-agent")}function consumeInitialAgentSignal(){let filePath=getInitialAgentFilePath();if(!existsSync38(filePath))return;try{let agent=readFileSync17(filePath,"utf-8").trim();return unlinkSync8(filePath),agent||void 0}catch{return}}var init_initial_agent=()=>{};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 resolvePreferredWindowIndex(session,agentName){let windows=[...session.windows].sort((a,b3)=>a.index-b3.index),hasClaudePane=(window2)=>window2.panes.some((pane)=>!pane.isDead&&(pane.command==="claude"||pane.title.includes("claude")));return(windows.find((window2)=>window2.active&&hasClaudePane(window2))??(agentName?windows.find((window2)=>window2.name===agentName):void 0)??windows.find((window2)=>hasClaudePane(window2))??windows.find((window2)=>window2.active&&window2.index!==0)??windows.find((window2)=>window2.index!==0))?.index}function hasLiveClaudeWindow(session){return session.windows.some((window2)=>window2.panes.some((pane)=>!pane.isDead&&(pane.command==="claude"||pane.title.includes("claude"))))}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),attachWindowIndex=session?resolvePreferredWindowIndex(session,name):void 0,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,attachWindowIndex},activePanes:session?countClaudePanes(session):0,agentState:agentExecutors.length>0?deriveExecutorState(agentExecutors):void 0,wsAgentState:wsState}}function deriveWsAgentState(session,agentExecutors){if(!session)return"stopped";if(hasLiveClaudeWindow(session))return"running";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"){let sessionName=node.data.sessionName,attachWindowIndex=node.data.attachWindowIndex;if(typeof attachWindowIndex==="number")return{sessionName,windowIndex:attachWindowIndex};let firstWindowChild=node.children.find((child)=>child.type==="window");if(firstWindowChild){let parts=firstWindowChild.id.split(":");return{sessionName,windowIndex:Number(parts[2])}}return{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),[requestedInitialAgent,setRequestedInitialAgent]=import_react15.useState(initialAgent),lastTarget=import_react15.useRef(null);import_react15.useEffect(()=>{let active=!0;async function refresh(){try{let snap=await collectDiagnostics();if(!active)return;setDiagnostics(snap);let signaledAgent=consumeInitialAgentSignal();if(signaledAgent)setRequestedInitialAgent(signaledAgent)}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]),import_react15.useEffect(()=>{if(!requestedInitialAgent||flatNodes.length===0)return;let idx=flatNodes.findIndex((n)=>n.node.id===`agent:${requestedInitialAgent}`);if(idx>=0){setSelectedIndex(idx);let node=flatNodes[idx].node;if(node.type==="agent"&&node.wsAgentState!=="running"&&node.wsAgentState!=="spawning")spawnAgent(node.label,onTmuxSessionSelect);setRequestedInitialAgent(void 0)}},[requestedInitialAgent,flatNodes,onTmuxSessionSelect]),import_react15.useEffect(()=>{let current=flatNodes[selectedIndex]?.node;if(!current)return;let target=getSessionTarget(current);if(!target)return;if(current.type==="agent"&&current.wsAgentState!=="running")return;let key=`${target.sessionName}:${target.windowIndex??""}`;if(key===lastTarget.current)return;lastTarget.current=key,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"&&node.wsAgentState!=="spawning")spawnAgent(node.label,onTmuxSessionSelect);else if(node.wsAgentState==="running"){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,onTmuxSessionSelect){try{let{spawn:spawn4}=__require("child_process"),{join:join52,resolve:resolve10}=__require("path"),{existsSync:existsSync39}=__require("fs"),bunPath=process.execPath||"bun",genieBin=process.argv[1],wsRoot=process.env.GENIE_TUI_WORKSPACE,cwd;if(wsRoot){let agentDir=resolve10(join52(wsRoot,"agents",name));if(existsSync39(agentDir))cwd=agentDir}if((genieBin&&genieBin!=="genie"?spawn4(bunPath,[genieBin,"spawn",name,"--session",name],{detached:!0,stdio:"ignore",cwd}):spawn4("genie",["spawn",name,"--session",name],{detached:!0,stdio:"ignore",cwd})).unref(),onTmuxSessionSelect)attachSpawnedAgentWhenReady(name,onTmuxSessionSelect)}catch{}}function attachSpawnedAgentWhenReady(sessionName,onTmuxSessionSelect,attempt=0){(async()=>{try{let session=(await collectDiagnostics()).sessions.find((candidate)=>candidate.name===sessionName);if(session){let windowIndex=resolvePreferredWindowIndex(session,sessionName);if(windowIndex!==void 0){onTmuxSessionSelect(sessionName,windowIndex);return}}}catch{}if(attempt>=40){onTmuxSessionSelect(sessionName);return}setTimeout(()=>{attachSpawnedAgentWhenReady(sessionName,onTmuxSessionSelect,attempt+1)},250)})()}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,{expanded:n.expanded,childCount:n.children.length}),collect(n.children)}collect(oldTree);function apply(nodes){return nodes.map((n)=>({...n,expanded:(()=>{let previous=oldState.get(n.id);if(!previous)return n.expanded;if(previous.childCount===0&&n.children.length>0)return n.expanded;return previous.expanded})(),children:apply(n.children)}))}return apply(newTree)}var import_react15;var init_Nav=__esm(async()=>{init_workspace();init_diagnostics();init_initial_agent();init_theme2();init_TreeNode();init_jsx_dev_runtime();await init_react();import_react15=__toESM(require_react_development(),1)});function QuitDialog({onConfirm,onCancel}){return useKeyboard((key)=>{if(key.name==="enter"||key.name==="return"||key.name==="y")onConfirm();else if(key.name==="escape"||key.name==="n")onCancel()}),import_jsx_dev_runtime2.jsxDEV("box",{position:"absolute",width:"100%",height:"100%",justifyContent:"center",alignItems:"center",backgroundColor:"#0a0a0a",children:import_jsx_dev_runtime2.jsxDEV("box",{border:!0,borderColor:palette.violet,backgroundColor:"#111111",paddingX:3,paddingY:1,flexDirection:"column",alignItems:"center",gap:1,children:[import_jsx_dev_runtime2.jsxDEV("text",{children:import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.purple,children:"Quit genie?"},void 0,!1,void 0,this)},void 0,!1,void 0,this),import_jsx_dev_runtime2.jsxDEV("text",{children:[import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.text,children:"Enter"},void 0,!1,void 0,this),import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.textDim,children:" to quit "},void 0,!1,void 0,this),import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.textMuted,children:" | "},void 0,!1,void 0,this),import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.text,children:" Esc"},void 0,!1,void 0,this),import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.textDim,children:" to cancel"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),import_jsx_dev_runtime2.jsxDEV("text",{children:import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.textMuted,children:"Hint: Ctrl+D to detach (keep running)"},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}var init_QuitDialog=__esm(async()=>{init_theme2();init_jsx_dev_runtime();await init_react()});import{execSync as execSync14}from"child_process";import{readFileSync as readFileSync18}from"fs";function App({rightPane,workspaceRoot,initialAgent}){let[showQuit,setShowQuit]=import_react18.useState(!1);useKeyboard((key)=>{if(key.ctrl&&key.name==="q")if(showQuit)handleQuit();else setShowQuit(!0)});let handleQuit=import_react18.useCallback(()=>{try{let genieHome4=process.env.GENIE_HOME??`${process.env.HOME}/.genie`,pid=readFileSync18(`${genieHome4}/serve.pid`,"utf-8").trim();process.kill(Number.parseInt(pid,10),"SIGTERM")}catch{try{execSync14("tmux -L genie-tui kill-server",{stdio:"ignore"})}catch{}}},[]),handleTmuxSessionSelect=import_react18.useCallback((sessionName,windowIndex)=>{if(!rightPane)return;attachProjectWindow(rightPane,sessionName,windowIndex)},[rightPane]);return import_jsx_dev_runtime2.jsxDEV("box",{width:"100%",height:"100%",children:[import_jsx_dev_runtime2.jsxDEV(Nav,{onTmuxSessionSelect:handleTmuxSessionSelect,workspaceRoot,initialAgent},void 0,!1,void 0,this),showQuit?import_jsx_dev_runtime2.jsxDEV(QuitDialog,{onConfirm:handleQuit,onCancel:()=>setShowQuit(!1)},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}var import_react18;var init_app=__esm(async()=>{init_tmux2();init_jsx_dev_runtime();await __promiseAll([init_react(),init_Nav(),init_QuitDialog()]);import_react18=__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 existsSync4,unlinkSync as unlinkSync3}from"fs";import{homedir as homedir4}from"os";import{join as join4}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_ensure_tmux();init_genie_config2();init_system_detect();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(existsSync4(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`${tmuxBin()} -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`${tmuxBin()} -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:execSync2}=await import("child_process");execSync2('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:execSync2}=await import("child_process");execSync2("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(genieHome2,pidFile){let portFile=join4(genieHome2,"pgserve.port"),postmasterPid=join4(genieHome2,"data","pgserve","postmaster.pid");for(let file of[portFile,pidFile,postmasterPid])if(existsSync4(file))try{unlinkSync3(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(`
2178
+ `)){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=__esm(()=>{init_ensure_tmux()});import{existsSync as existsSync38,readFileSync as readFileSync17,unlinkSync as unlinkSync8}from"fs";import{homedir as homedir27}from"os";import{join as join51}from"path";function getInitialAgentFilePath(){let genieHome4=process.env.GENIE_HOME??join51(homedir27(),".genie");return join51(genieHome4,"tui-initial-agent")}function consumeInitialAgentSignal(){let filePath=getInitialAgentFilePath();if(!existsSync38(filePath))return;try{let agent=readFileSync17(filePath,"utf-8").trim();return unlinkSync8(filePath),agent||void 0}catch{return}}var init_initial_agent=()=>{};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 resolvePreferredWindowIndex(session,agentName){let windows=[...session.windows].sort((a,b3)=>a.index-b3.index),hasClaudePane=(window2)=>window2.panes.some((pane)=>!pane.isDead&&(pane.command==="claude"||pane.title.includes("claude")));return(windows.find((window2)=>window2.active&&hasClaudePane(window2))??(agentName?windows.find((window2)=>window2.name===agentName):void 0)??windows.find((window2)=>hasClaudePane(window2))??windows.find((window2)=>window2.active&&window2.index!==0)??windows.find((window2)=>window2.index!==0))?.index}function hasLiveClaudeWindow(session){return session.windows.some((window2)=>window2.panes.some((pane)=>!pane.isDead&&(pane.command==="claude"||pane.title.includes("claude"))))}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),attachWindowIndex=session?resolvePreferredWindowIndex(session,name):void 0,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,attachWindowIndex},activePanes:session?countClaudePanes(session):0,agentState:agentExecutors.length>0?deriveExecutorState(agentExecutors):void 0,wsAgentState:wsState}}function deriveWsAgentState(session,agentExecutors){if(!session)return"stopped";if(hasLiveClaudeWindow(session))return"running";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"){let sessionName=node.data.sessionName,attachWindowIndex=node.data.attachWindowIndex;if(typeof attachWindowIndex==="number")return{sessionName,windowIndex:attachWindowIndex};let firstWindowChild=node.children.find((child)=>child.type==="window");if(firstWindowChild){let parts=firstWindowChild.id.split(":");return{sessionName,windowIndex:Number(parts[2])}}return{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,keyboardDisabled=!1}){let[diagnostics,setDiagnostics]=import_react15.useState(null),[sessionTree,setSessionTree]=import_react15.useState([]),[selectedIndex,setSelectedIndex]=import_react15.useState(0),[requestedInitialAgent,setRequestedInitialAgent]=import_react15.useState(initialAgent),lastTarget=import_react15.useRef(null);import_react15.useEffect(()=>{let active=!0;async function refresh(){try{let snap=await collectDiagnostics();if(!active)return;setDiagnostics(snap);let signaledAgent=consumeInitialAgentSignal();if(signaledAgent)setRequestedInitialAgent(signaledAgent)}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]),import_react15.useEffect(()=>{if(!requestedInitialAgent||flatNodes.length===0)return;let idx=flatNodes.findIndex((n)=>n.node.id===`agent:${requestedInitialAgent}`);if(idx>=0){setSelectedIndex(idx);let node=flatNodes[idx].node;if(node.type==="agent"&&node.wsAgentState!=="running"&&node.wsAgentState!=="spawning")spawnAgent(node.label,onTmuxSessionSelect);setRequestedInitialAgent(void 0)}},[requestedInitialAgent,flatNodes,onTmuxSessionSelect]),import_react15.useEffect(()=>{let current=flatNodes[selectedIndex]?.node;if(!current)return;let target=getSessionTarget(current);if(!target)return;if(current.type==="agent"&&current.wsAgentState!=="running")return;let key=`${target.sessionName}:${target.windowIndex??""}`;if(key===lastTarget.current)return;lastTarget.current=key,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"&&node.wsAgentState!=="spawning")spawnAgent(node.label,onTmuxSessionSelect);else if(node.wsAgentState==="running"){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(keyboardDisabled)return;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,onTmuxSessionSelect){try{let{spawn:spawn4}=__require("child_process"),{join:join52,resolve:resolve10}=__require("path"),{existsSync:existsSync39}=__require("fs"),bunPath=process.execPath||"bun",genieBin=process.argv[1],wsRoot=process.env.GENIE_TUI_WORKSPACE,cwd;if(wsRoot){let agentDir=resolve10(join52(wsRoot,"agents",name));if(existsSync39(agentDir))cwd=agentDir}if((genieBin&&genieBin!=="genie"?spawn4(bunPath,[genieBin,"spawn",name,"--session",name],{detached:!0,stdio:"ignore",cwd}):spawn4("genie",["spawn",name,"--session",name],{detached:!0,stdio:"ignore",cwd})).unref(),onTmuxSessionSelect)attachSpawnedAgentWhenReady(name,onTmuxSessionSelect)}catch{}}function attachSpawnedAgentWhenReady(sessionName,onTmuxSessionSelect,attempt=0){(async()=>{try{let session=(await collectDiagnostics()).sessions.find((candidate)=>candidate.name===sessionName);if(session){let windowIndex=resolvePreferredWindowIndex(session,sessionName);if(windowIndex!==void 0){onTmuxSessionSelect(sessionName,windowIndex);return}}}catch{}if(attempt>=40){onTmuxSessionSelect(sessionName);return}setTimeout(()=>{attachSpawnedAgentWhenReady(sessionName,onTmuxSessionSelect,attempt+1)},250)})()}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,{expanded:n.expanded,childCount:n.children.length}),collect(n.children)}collect(oldTree);function apply(nodes){return nodes.map((n)=>({...n,expanded:(()=>{let previous=oldState.get(n.id);if(!previous)return n.expanded;if(previous.childCount===0&&n.children.length>0)return n.expanded;return previous.expanded})(),children:apply(n.children)}))}return apply(newTree)}var import_react15;var init_Nav=__esm(async()=>{init_workspace();init_diagnostics();init_initial_agent();init_theme2();init_TreeNode();init_jsx_dev_runtime();await init_react();import_react15=__toESM(require_react_development(),1)});function QuitDialog({onConfirm,onCancel}){return useKeyboard((key)=>{if(key.name==="enter"||key.name==="return"||key.name==="y")onConfirm();else if(key.name==="escape"||key.name==="n")onCancel()}),import_jsx_dev_runtime2.jsxDEV("box",{position:"absolute",width:"100%",height:"100%",justifyContent:"center",alignItems:"center",backgroundColor:"#0a0a0a",children:import_jsx_dev_runtime2.jsxDEV("box",{border:!0,borderColor:palette.violet,backgroundColor:"#111111",paddingX:3,paddingY:1,flexDirection:"column",alignItems:"center",gap:1,children:[import_jsx_dev_runtime2.jsxDEV("text",{children:import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.purple,children:"Quit genie?"},void 0,!1,void 0,this)},void 0,!1,void 0,this),import_jsx_dev_runtime2.jsxDEV("text",{children:[import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.text,children:"Enter"},void 0,!1,void 0,this),import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.textDim,children:" to quit "},void 0,!1,void 0,this),import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.textMuted,children:" | "},void 0,!1,void 0,this),import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.text,children:" Esc"},void 0,!1,void 0,this),import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.textDim,children:" to cancel"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),import_jsx_dev_runtime2.jsxDEV("text",{children:import_jsx_dev_runtime2.jsxDEV("span",{fg:palette.textMuted,children:"Hint: Ctrl+D to detach (keep running)"},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}var init_QuitDialog=__esm(async()=>{init_theme2();init_jsx_dev_runtime();await init_react()});import{execSync as execSync14}from"child_process";import{readFileSync as readFileSync18}from"fs";function App({rightPane,workspaceRoot,initialAgent}){let[showQuit,setShowQuit]=import_react18.useState(!1);useKeyboard((key)=>{if(key.ctrl&&key.name==="q")if(showQuit)handleQuit();else setShowQuit(!0)});let handleQuit=import_react18.useCallback(()=>{try{let genieHome4=process.env.GENIE_HOME??`${process.env.HOME}/.genie`,pid=readFileSync18(`${genieHome4}/serve.pid`,"utf-8").trim();process.kill(Number.parseInt(pid,10),"SIGTERM")}catch{try{execSync14("tmux -L genie-tui kill-server",{stdio:"ignore"})}catch{}}},[]),handleTmuxSessionSelect=import_react18.useCallback((sessionName,windowIndex)=>{if(!rightPane)return;attachProjectWindow(rightPane,sessionName,windowIndex)},[rightPane]);return import_jsx_dev_runtime2.jsxDEV("box",{width:"100%",height:"100%",children:[import_jsx_dev_runtime2.jsxDEV(Nav,{onTmuxSessionSelect:handleTmuxSessionSelect,workspaceRoot,initialAgent,keyboardDisabled:showQuit},void 0,!1,void 0,this),showQuit?import_jsx_dev_runtime2.jsxDEV(QuitDialog,{onConfirm:handleQuit,onCancel:()=>setShowQuit(!1)},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}var import_react18;var init_app=__esm(async()=>{init_tmux2();init_jsx_dev_runtime();await __promiseAll([init_react(),init_Nav(),init_QuitDialog()]);import_react18=__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 existsSync4,unlinkSync as unlinkSync3}from"fs";import{homedir as homedir4}from"os";import{join as join4}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_ensure_tmux();init_genie_config2();init_system_detect();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(existsSync4(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`${tmuxBin()} -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`${tmuxBin()} -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:execSync2}=await import("child_process");execSync2('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:execSync2}=await import("child_process");execSync2("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(genieHome2,pidFile){let portFile=join4(genieHome2,"pgserve.port"),postmasterPid=join4(genieHome2,"data","pgserve","postmaster.pid");for(let file of[portFile,pidFile,postmasterPid])if(existsSync4(file))try{unlinkSync3(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(`
2179
2179
  \x1B[1mGenie Doctor \u2014 Auto Fix\x1B[0m`),console.log(`\x1B[2m${"\u2500".repeat(40)}\x1B[0m
2180
2180
  `),await killStalePostgres(),await cleanSharedMemory();let genieHome2=process.env.GENIE_HOME??join4(homedir4(),".genie"),pidFile=join4(genieHome2,"scheduler.pid");await stopExistingDaemon(pidFile),removeStaleFiles(genieHome2,pidFile),await restartDaemon(),console.log(`
2181
2181
  \x1B[2m${"\u2500".repeat(40)}\x1B[0m`),console.log(`\x1B[32mFix complete.\x1B[0m Run \x1B[36mgenie doctor\x1B[0m to verify.
@@ -2234,7 +2234,7 @@ Registering in Omni (${omniUrl})...`);let existingId=await findOmniAgent(name);i
2234
2234
  (${patterns2.length} pattern${patterns2.length===1?"":"s"})`)}async function eventsListCommand(options){try{let queryOpts={type:options.type,entity:options.entity,since:options.since??"1h",errorsOnly:options.errorsOnly,limit:options.limit?Number.parseInt(options.limit,10):50},rows=await queryAuditEvents(queryOpts);if(options.json)console.log(JSON.stringify(rows,null,2));else printEventsTable(rows)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying events: ${msg}`),process.exit(1)}}async function eventsErrorsCommand(options){try{let patterns2=await queryErrorPatterns(options.since);if(options.json)console.log(JSON.stringify(patterns2,null,2));else printErrorsTable(patterns2)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying error patterns: ${msg}`),process.exit(1)}}function resolveCostsGroupBy(options){if(options.byWish)return"wish";if(options.byModel)return"model";return"agent"}function printCostsTable(rows,groupBy){if(rows.length===0){console.log("No cost data found.");return}let headers=[groupBy==="agent"?"Agent":groupBy==="wish"?"Wish":"Model","Total Cost","Requests","Avg Cost"],data=rows.map((r)=>[r.group_key,`$${r.total_cost.toFixed(4)}`,String(r.request_count),`$${r.avg_cost.toFixed(4)}`]),widths=headers.map((h,i2)=>{let colVals=data.map((row)=>row[i2]);return Math.min(40,Math.max(h.length,...colVals.map((v)=>v.length)))});console.log(headers.map((h,i2)=>padRight(h,widths[i2])).join(" | ")),console.log(widths.map((w)=>"-".repeat(w)).join("-+-"));for(let row of data)console.log(row.map((v,i2)=>padRight(v.slice(0,widths[i2]),widths[i2])).join(" | "));let totalCost=rows.reduce((sum,r)=>sum+r.total_cost,0),totalReqs=rows.reduce((sum,r)=>sum+r.request_count,0);console.log(`
2235
2235
  Total: $${totalCost.toFixed(4)} across ${totalReqs} requests`)}async function eventsCostsCommand(options){try{let since=options.today?"24h":options.since??"24h",groupBy=resolveCostsGroupBy(options),rows=await queryCostBreakdown(since,groupBy);if(options.json)console.log(JSON.stringify(rows,null,2));else printCostsTable(rows,groupBy),console.log(`
2236
2236
  \u26A0 OTel costs only include genie-spawned sessions. For full server costs: npx ccusage monthly`)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying costs: ${msg}`),process.exit(1)}}function printToolsTable(rows,groupBy){if(rows.length===0){console.log("No tool usage data found.");return}let headers=[groupBy==="tool"?"Tool":"Agent","Calls","Success","Errors","Avg Duration"],data=rows.map((r)=>[r.group_key,String(r.total_calls),String(r.success_count),String(r.error_count),r.avg_duration_ms!=null?`${r.avg_duration_ms.toFixed(0)}ms`:"-"]),widths=headers.map((h,i2)=>{let colVals=data.map((row)=>row[i2]);return Math.min(40,Math.max(h.length,...colVals.map((v)=>v.length)))});console.log(headers.map((h,i2)=>padRight(h,widths[i2])).join(" | ")),console.log(widths.map((w)=>"-".repeat(w)).join("-+-"));for(let row of data)console.log(row.map((v,i2)=>padRight(v.slice(0,widths[i2]),widths[i2])).join(" | "));let totalCalls=rows.reduce((sum,r)=>sum+r.total_calls,0);console.log(`
2237
- (${totalCalls} total tool calls)`)}async function eventsToolsCommand(options){try{let since=options.since??"24h",groupBy=options.byAgent?"agent":"tool",rows=await queryToolUsage(since,groupBy);if(options.json)console.log(JSON.stringify(rows,null,2));else printToolsTable(rows,groupBy)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying tool usage: ${msg}`),process.exit(1)}}async function eventsTimelineCommand(entityId,options){try{let rows=await queryTimeline(entityId);if(options.json)console.log(JSON.stringify(rows,null,2));else printEventsTable(rows)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying timeline: ${msg}`),process.exit(1)}}function printSummary(summary){console.log("Event Summary"),console.log("============="),console.log(`Total events: ${summary.total_events}`),console.log(`Agents spawned: ${summary.agents_spawned}`),console.log(`Tasks moved: ${summary.tasks_moved}`),console.log(`API requests: ${summary.api_requests}`),console.log(`Tool calls: ${summary.tool_calls}`),console.log(`Total cost: $${summary.total_cost.toFixed(4)}`),console.log(`Errors: ${summary.error_count}`)}async function eventsSummaryCommand(options){try{let since=options.today?"24h":options.since??"24h",summary=await querySummary(since);if(options.json)console.log(JSON.stringify(summary,null,2));else printSummary(summary)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying summary: ${msg}`),process.exit(1)}}async function eventsScanCommand(options){let{spawnSync:spawnSync3}=__require("child_process"),args=["ccusage","monthly"];if(options.since)args.push("--since",options.since);if(options.json)args.push("--json");if(options.breakdown)args.push("--breakdown");let result=spawnSync3("npx",args,{stdio:options.json?"pipe":"inherit",timeout:30000,env:{...process.env,NODE_NO_WARNINGS:"1"}});if(result.error)console.error("ccusage not available. Install with: npm install -g ccusage"),console.error("Or run directly: npx ccusage monthly"),process.exit(1);if(options.json&&result.stdout)process.stdout.write(result.stdout);if(result.status!==0)process.exit(result.status??1)}function registerEventsCommands(program2){let events=program2.command("events").description("Audit event log from PG");events.command("list",{isDefault:!0}).description("List recent audit events").option("--type <type>","Filter by event_type").option("--entity <entity>","Filter by entity_type or entity_id").option("--since <duration>","Time window (e.g., 1h, 30m, 2d)","1h").option("--errors-only","Show only error events").option("--limit <n>","Max rows to return","50").option("--json","Output as JSON").action(async(options)=>{await eventsListCommand(options)}),events.command("errors").description("Show aggregated error patterns").option("--since <duration>","Time window (e.g., 1h, 24h, 7d)").option("--json","Output as JSON").action(async(options)=>{await eventsErrorsCommand(options)}),events.command("costs").description("Cost breakdown from OTel API request events").option("--today","Show costs from the last 24h").option("--since <duration>","Time window (e.g., 1h, 7d)","24h").option("--by-agent","Group by agent").option("--by-wish","Group by wish").option("--by-model","Group by model").option("--json","Output as JSON").action(async(options)=>{await eventsCostsCommand(options)}),events.command("tools").description("Tool usage analytics from OTel tool events").option("--since <duration>","Time window (e.g., 1h, 7d)","24h").option("--by-tool","Group by tool name (default)").option("--by-agent","Group by agent").option("--json","Output as JSON").action(async(options)=>{await eventsToolsCommand(options)}),events.command("timeline <entity-id>").description("Full event timeline for a task, agent, wish, or traceId").option("--json","Output as JSON").action(async(entityId,options)=>{await eventsTimelineCommand(entityId,options)}),events.command("summary").description("High-level stats: agents spawned, tasks moved, costs, errors").option("--today","Show summary for the last 24h").option("--since <duration>","Time window (e.g., 1h, 7d)","24h").option("--json","Output as JSON").action(async(options)=>{await eventsSummaryCommand(options)}),events.command("scan").description("Full server cost scan via ccusage (all CC sessions, not just genie-spawned)").option("--since <date>","Start date in YYYYMMDD format").option("--json","Output as JSON").option("--breakdown","Show per-model breakdown").action(async(options)=>{await eventsScanCommand(options)})}import{execSync as execSync8}from"child_process";import{existsSync as existsSync26,mkdirSync as mkdirSync11,readFileSync as readFileSync12,writeFileSync as writeFileSync10}from"fs";import{dirname as dirname6,join as join35}from"path";var _boardService;async function getBoardService(){if(!_boardService)_boardService=await Promise.resolve().then(() => (init_board_service(),exports_board_service));return _boardService}var _taskService3;async function getTaskService3(){if(!_taskService3)_taskService3=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService3}var _templateService;async function getTemplateService(){if(!_templateService)_templateService=await Promise.resolve().then(() => (init_template_service(),exports_template_service));return _templateService}async function resolveProjectId(name){let project=await(await getTaskService3()).getProjectByName(name);if(!project)throw Error(`Project not found: ${name}`);return project.id}async function resolveBoard(name,projectName){let bs=await getBoardService(),projectId;if(projectName)projectId=await resolveProjectId(projectName);let board=await bs.getBoard(name,projectId);if(!board)throw Error(`Board not found: ${name}`);return board}function printBoardTable(boards,projectMap){console.log(` ${padRight("NAME",24)} ${padRight("PROJECT",20)} ${padRight("COLUMNS",10)} CREATED`),console.log(` ${"\u2500".repeat(70)}`);for(let b2 of boards){let projName=b2.projectId?projectMap.get(b2.projectId)??b2.projectId:"-",colCount=String(b2.columns.length),created=formatDate(b2.createdAt);console.log(` ${padRight(truncate2(b2.name,22),24)} ${padRight(truncate2(projName,18),20)} ${padRight(colCount,10)} ${created}`)}console.log(`
2237
+ (${totalCalls} total tool calls)`)}async function eventsToolsCommand(options){try{let since=options.since??"24h",groupBy=options.byAgent?"agent":"tool",rows=await queryToolUsage(since,groupBy);if(options.json)console.log(JSON.stringify(rows,null,2));else printToolsTable(rows,groupBy)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying tool usage: ${msg}`),process.exit(1)}}async function eventsTimelineCommand(entityId,options){try{let rows=await queryTimeline(entityId);if(options.json)console.log(JSON.stringify(rows,null,2));else printEventsTable(rows)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying timeline: ${msg}`),process.exit(1)}}function printSummary(summary){console.log("Event Summary"),console.log("============="),console.log(`Total events: ${summary.total_events}`),console.log(`Agents spawned: ${summary.agents_spawned}`),console.log(`Tasks moved: ${summary.tasks_moved}`),console.log(`API requests: ${summary.api_requests}`),console.log(`Tool calls: ${summary.tool_calls}`),console.log(`Total cost: $${summary.total_cost.toFixed(4)}`),console.log(`Errors: ${summary.error_count}`)}async function eventsSummaryCommand(options){try{let since=options.today?"24h":options.since??"24h",summary=await querySummary(since);if(options.json)console.log(JSON.stringify(summary,null,2));else printSummary(summary)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying summary: ${msg}`),process.exit(1)}}async function eventsScanCommand(options){let{spawnSync:spawnSync4}=__require("child_process"),args=["ccusage","monthly"];if(options.since)args.push("--since",options.since);if(options.json)args.push("--json");if(options.breakdown)args.push("--breakdown");let result=spawnSync4("npx",args,{stdio:options.json?"pipe":"inherit",timeout:30000,env:{...process.env,NODE_NO_WARNINGS:"1"}});if(result.error)console.error("ccusage not available. Install with: npm install -g ccusage"),console.error("Or run directly: npx ccusage monthly"),process.exit(1);if(options.json&&result.stdout)process.stdout.write(result.stdout);if(result.status!==0)process.exit(result.status??1)}function registerEventsCommands(program2){let events=program2.command("events").description("Audit event log from PG");events.command("list",{isDefault:!0}).description("List recent audit events").option("--type <type>","Filter by event_type").option("--entity <entity>","Filter by entity_type or entity_id").option("--since <duration>","Time window (e.g., 1h, 30m, 2d)","1h").option("--errors-only","Show only error events").option("--limit <n>","Max rows to return","50").option("--json","Output as JSON").action(async(options)=>{await eventsListCommand(options)}),events.command("errors").description("Show aggregated error patterns").option("--since <duration>","Time window (e.g., 1h, 24h, 7d)").option("--json","Output as JSON").action(async(options)=>{await eventsErrorsCommand(options)}),events.command("costs").description("Cost breakdown from OTel API request events").option("--today","Show costs from the last 24h").option("--since <duration>","Time window (e.g., 1h, 7d)","24h").option("--by-agent","Group by agent").option("--by-wish","Group by wish").option("--by-model","Group by model").option("--json","Output as JSON").action(async(options)=>{await eventsCostsCommand(options)}),events.command("tools").description("Tool usage analytics from OTel tool events").option("--since <duration>","Time window (e.g., 1h, 7d)","24h").option("--by-tool","Group by tool name (default)").option("--by-agent","Group by agent").option("--json","Output as JSON").action(async(options)=>{await eventsToolsCommand(options)}),events.command("timeline <entity-id>").description("Full event timeline for a task, agent, wish, or traceId").option("--json","Output as JSON").action(async(entityId,options)=>{await eventsTimelineCommand(entityId,options)}),events.command("summary").description("High-level stats: agents spawned, tasks moved, costs, errors").option("--today","Show summary for the last 24h").option("--since <duration>","Time window (e.g., 1h, 7d)","24h").option("--json","Output as JSON").action(async(options)=>{await eventsSummaryCommand(options)}),events.command("scan").description("Full server cost scan via ccusage (all CC sessions, not just genie-spawned)").option("--since <date>","Start date in YYYYMMDD format").option("--json","Output as JSON").option("--breakdown","Show per-model breakdown").action(async(options)=>{await eventsScanCommand(options)})}import{execSync as execSync8}from"child_process";import{existsSync as existsSync26,mkdirSync as mkdirSync11,readFileSync as readFileSync12,writeFileSync as writeFileSync10}from"fs";import{dirname as dirname6,join as join35}from"path";var _boardService;async function getBoardService(){if(!_boardService)_boardService=await Promise.resolve().then(() => (init_board_service(),exports_board_service));return _boardService}var _taskService3;async function getTaskService3(){if(!_taskService3)_taskService3=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService3}var _templateService;async function getTemplateService(){if(!_templateService)_templateService=await Promise.resolve().then(() => (init_template_service(),exports_template_service));return _templateService}async function resolveProjectId(name){let project=await(await getTaskService3()).getProjectByName(name);if(!project)throw Error(`Project not found: ${name}`);return project.id}async function resolveBoard(name,projectName){let bs=await getBoardService(),projectId;if(projectName)projectId=await resolveProjectId(projectName);let board=await bs.getBoard(name,projectId);if(!board)throw Error(`Board not found: ${name}`);return board}function printBoardTable(boards,projectMap){console.log(` ${padRight("NAME",24)} ${padRight("PROJECT",20)} ${padRight("COLUMNS",10)} CREATED`),console.log(` ${"\u2500".repeat(70)}`);for(let b2 of boards){let projName=b2.projectId?projectMap.get(b2.projectId)??b2.projectId:"-",colCount=String(b2.columns.length),created=formatDate(b2.createdAt);console.log(` ${padRight(truncate2(b2.name,22),24)} ${padRight(truncate2(projName,18),20)} ${padRight(colCount,10)} ${created}`)}console.log(`
2238
2238
  ${boards.length} board${boards.length===1?"":"s"}`)}function printColumnPipeline(columns,header){console.log(`
2239
2239
  ${header}`),console.log("\u2500".repeat(60));let sorted=[...columns].sort((a,b2)=>a.position-b2.position);for(let i2=0;i2<sorted.length;i2++){let c=sorted[i2],arrow=i2<sorted.length-1?" \u2192":"",gate=` [gate: ${c.gate}]`,action=c.action?` (action: ${c.action})`:"";console.log(` ${i2+1}. ${c.label??c.name}${gate}${action}${arrow}`)}console.log("")}function printTemplateTable(templates){console.log(` ${padRight("NAME",24)} ${padRight("COLUMNS",10)} ${padRight("BUILTIN",10)} DESCRIPTION`),console.log(` ${"\u2500".repeat(80)}`);for(let t of templates){let colCount=String(t.columns.length),builtin=t.isBuiltin?"yes":"no",desc=t.description?truncate2(t.description,30):"-";console.log(` ${padRight(truncate2(t.name,22),24)} ${padRight(colCount,10)} ${padRight(builtin,10)} ${desc}`)}console.log(`
2240
2240
  ${templates.length} template${templates.length===1?"":"s"}`)}async function handleBoardCreate(name,options){let bs=await getBoardService(),tmpl=await getTemplateService(),projectId;if(options.project)projectId=await resolveProjectId(options.project);let columns;if(options.from){let template=await tmpl.getTemplate(options.from);if(!template)throw Error(`Template not found: ${options.from}`);columns=template.columns}else if(options.columns)columns=options.columns.split(",").map((colName,i2)=>({name:colName.trim(),label:colName.trim(),gate:"human",position:i2}));let board=await bs.createBoard({name,projectId,description:options.description,columns});console.log(`Created board "${board.name}" (${board.id}) with ${board.columns.length} columns`)}async function handleBoardList(options){let bs=await getBoardService(),ts3=await getTaskService3(),projectId;if(options.project)projectId=await resolveProjectId(options.project);let boards=await bs.listBoards(projectId,options.all);if(options.json){console.log(JSON.stringify(boards,null,2));return}let projects=await ts3.listProjects(),projectMap=new Map;for(let p of projects)projectMap.set(p.id,p.name);printBoardTable(boards,projectMap)}async function handleBoardShow(name,options){let ts3=await getTaskService3(),board=await resolveBoard(name,options.project);if(options.json){console.log(JSON.stringify(board,null,2));return}let projectName="-";if(board.projectId){let proj=(await ts3.listProjects()).find((p)=>p.id===board.projectId);if(proj)projectName=proj.name}if(console.log(`
@@ -2262,8 +2262,8 @@ SyslogIdentifier=genie-scheduler
2262
2262
 
2263
2263
  [Install]
2264
2264
  WantedBy=default.target
2265
- `}async function daemonInstallCommand(){let unitContent=generateSystemdUnit(),unitPath=systemdUnitPath();mkdirSync12(systemdDir(),{recursive:!0}),writeFileSync11(unitPath,unitContent,"utf-8"),console.log(`Wrote systemd unit: ${unitPath}`);let{spawnSync:spawnSync3}=await import("child_process"),reloadResult=spawnSync3("systemctl",["--user","daemon-reload"],{stdio:["ignore","pipe","pipe"]});if(reloadResult.status!==0){let stderr=reloadResult.stderr?.toString().trim();if(console.log(`
2266
- Note: systemctl daemon-reload failed (systemd may not be available).`),stderr)console.log(` ${stderr}`);console.log("You can still run the daemon manually: genie daemon start --foreground");return}let enableResult=spawnSync3("systemctl",["--user","enable","genie-scheduler.service"],{stdio:["ignore","pipe","pipe"]});if(enableResult.status===0)console.log("Enabled genie-scheduler.service"),console.log(`
2265
+ `}async function daemonInstallCommand(){let unitContent=generateSystemdUnit(),unitPath=systemdUnitPath();mkdirSync12(systemdDir(),{recursive:!0}),writeFileSync11(unitPath,unitContent,"utf-8"),console.log(`Wrote systemd unit: ${unitPath}`);let{spawnSync:spawnSync4}=await import("child_process"),reloadResult=spawnSync4("systemctl",["--user","daemon-reload"],{stdio:["ignore","pipe","pipe"]});if(reloadResult.status!==0){let stderr=reloadResult.stderr?.toString().trim();if(console.log(`
2266
+ Note: systemctl daemon-reload failed (systemd may not be available).`),stderr)console.log(` ${stderr}`);console.log("You can still run the daemon manually: genie daemon start --foreground");return}let enableResult=spawnSync4("systemctl",["--user","enable","genie-scheduler.service"],{stdio:["ignore","pipe","pipe"]});if(enableResult.status===0)console.log("Enabled genie-scheduler.service"),console.log(`
2267
2267
  To start: systemctl --user start genie-scheduler`),console.log("Or: genie daemon start");else{let stderr=enableResult.stderr?.toString().trim();if(console.log(`
2268
2268
  Note: systemctl enable failed.`),stderr)console.log(` ${stderr}`);console.log("You can start manually: genie daemon start")}}async function daemonStartCommand(options){let existingPid=readPid();if(existingPid&&isProcessAlive2(existingPid))console.log(`Scheduler daemon already running (PID ${existingPid})`),process.exit(0);if(existingPid)removePid();if(options.foreground)await runForeground();else await runBackground()}async function runForeground(){let{startDaemon:startDaemon2}=await Promise.resolve().then(() => (init_scheduler_daemon(),exports_scheduler_daemon));process.env.GENIE_IS_DAEMON="1",writePid(process.pid),console.log(`Scheduler daemon starting (PID ${process.pid}, foreground)`);let handle=startDaemon2(),shutdown3=()=>{console.log("Shutting down scheduler daemon..."),handle.stop()};process.on("SIGTERM",shutdown3),process.on("SIGINT",shutdown3),await handle.done,removePid(),console.log("Scheduler daemon stopped.")}async function runBackground(){let{spawn:spawn4}=await import("child_process"),genieBin=process.argv[1]??"genie",bunPath=process.execPath??"bun",child=spawn4(bunPath,[genieBin,"daemon","start","--foreground"],{detached:!0,stdio:"ignore",env:{...process.env,GENIE_IS_DAEMON:"1"}});if(child.unref(),child.pid)if(await new Promise((resolve5)=>setTimeout(resolve5,500)),isProcessAlive2(child.pid))console.log(`Scheduler daemon started (PID ${child.pid})`),console.log(` Log: ${logFilePath()}`);else console.error("Error: daemon process exited immediately. Check logs:"),console.error(` ${logFilePath()}`),process.exit(1);else console.error("Error: failed to spawn daemon process"),process.exit(1)}async function daemonStopCommand(){let pid=readPid();if(!pid){console.log("No scheduler daemon PID file found. Daemon is not running.");return}if(!isProcessAlive2(pid)){console.log(`Stale PID file (PID ${pid} is not running). Cleaning up.`),removePid();return}console.log(`Stopping scheduler daemon (PID ${pid})...`),process.kill(pid,"SIGTERM");let deadline=Date.now()+1e4;while(Date.now()<deadline&&isProcessAlive2(pid))await new Promise((resolve5)=>setTimeout(resolve5,250));if(isProcessAlive2(pid)){console.log("Daemon did not stop within 10s. Sending SIGKILL.");try{process.kill(pid,"SIGKILL")}catch{}}removePid(),console.log("Scheduler daemon stopped.")}function getProcessUptime(pid){try{let procStat=readFileSync13(`/proc/${pid}/stat`,"utf-8"),bootTimeJiffies=Number.parseInt(procStat.split(" ")[21],10);if(Number.isNaN(bootTimeJiffies))return null;let uptimeSec=readFileSync13("/proc/uptime","utf-8"),processUptimeS=Number.parseFloat(uptimeSec.split(" ")[0])-bootTimeJiffies/100;return processUptimeS>0?formatUptime(processUptimeS*1000):null}catch{return null}}async function printDaemonStats(){try{let{getConnection:getConnection2,shutdown:shutdown3}=await Promise.resolve().then(() => (init_db(),exports_db)),sql=await getConnection2(),firedResult=await sql`
2269
2269
  SELECT count(*)::int AS cnt FROM triggers WHERE status IN ('executing', 'completed')
@@ -2282,7 +2282,7 @@ Genie Scheduler Daemon`),console.log("\u2500".repeat(50)),console.log(` Status:
2282
2282
  (showing last ${lines} of ${allLines.length} entries)`)}async function tailFollow(filePath,initialLines){let{watch:watch2}=await import("fs");tailStatic(filePath,initialLines),console.log(`
2283
2283
  --- following (Ctrl+C to exit) ---
2284
2284
  `);let lastSize=existsSync27(filePath)?readFileSync13(filePath).length:0,watcher2=watch2(filePath,()=>{try{let content=readFileSync13(filePath,"utf-8");if(content.length>lastSize){let newLines=content.slice(lastSize).trim().split(`
2285
- `).filter(Boolean);for(let line of newLines)printLogLine(line);lastSize=content.length}}catch{}});process.on("SIGINT",()=>{watcher2.close(),process.exit(0)}),await new Promise(()=>{})}function printLogLine(raw){try{let entry=JSON.parse(raw),ts3=entry.timestamp?new Date(entry.timestamp).toLocaleTimeString("en-US",{hour12:!1}):"??:??:??",level=(entry.level??"info").toUpperCase().padEnd(5),event=entry.event??"unknown",extras=Object.entries(entry).filter(([k])=>!["timestamp","level","event"].includes(k)).map(([k,v])=>`${k}=${typeof v==="object"?JSON.stringify(v):v}`).join(" ");console.log(`${ts3} ${level} ${event}${extras?` ${extras}`:""}`)}catch{console.log(raw)}}function formatUptime(ms){let seconds=Math.floor(ms/1000),minutes=Math.floor(seconds/60),hours=Math.floor(minutes/60),days=Math.floor(hours/24);if(days>0)return`${days}d ${hours%24}h ${minutes%60}m`;if(hours>0)return`${hours}h ${minutes%60}m`;if(minutes>0)return`${minutes}m ${seconds%60}s`;return`${seconds}s`}function registerDaemonCommands(program2){let daemon=program2.command("daemon").description("Manage scheduler daemon lifecycle");daemon.command("install").description("Generate systemd service unit and enable it").action(async()=>{await daemonInstallCommand()}),daemon.command("start").description("Start the scheduler daemon").option("--foreground","Run in foreground (for systemd ExecStart)").action(async(options)=>{await daemonStartCommand(options)}),daemon.command("stop").description("Stop the scheduler daemon gracefully").action(async()=>{await daemonStopCommand()}),daemon.command("status").description("Show daemon state, PID, uptime, and trigger stats").action(async()=>{await daemonStatusCommand()}),daemon.command("logs").description("Tail structured JSON scheduler log").option("--follow, -f","Follow log output").option("--lines <n>","Number of lines to show (default: 20)",Number.parseInt).action(async(options)=>{await daemonLogsCommand(options)})}import{createInterface as createInterface3}from"readline";init_db();init_wish_state();import{spawnSync as spawnSync3}from"child_process";import{existsSync as existsSync28,mkdirSync as mkdirSync13,readFileSync as readFileSync14,renameSync as renameSync2,statSync,writeFileSync as writeFileSync12}from"fs";import{join as join37}from"path";import{gunzipSync,gzipSync}from"zlib";var DB_NAME2="genie",DB_USER="postgres",DB_HOST="127.0.0.1",SNAPSHOT_FILE="snapshot.sql.gz";function getSnapshotPath(cwd){let repoRoot=resolveRepoPath(cwd);return join37(repoRoot,".genie",SNAPSHOT_FILE)}function pgEnv(port){return{...process.env,PGHOST:DB_HOST,PGPORT:String(port),PGUSER:DB_USER,PGPASSWORD:DB_USER,PGDATABASE:DB_NAME2}}function backup(cwd){let port=getActivePort(),snapshotPath=getSnapshotPath(cwd),genieDir=join37(resolveRepoPath(cwd),".genie"),tmpPath=`${snapshotPath}.tmp`;mkdirSync13(genieDir,{recursive:!0});let result=spawnSync3("pg_dump",["--no-owner","--no-acl"],{env:pgEnv(port),stdio:["pipe","pipe","pipe"],timeout:120000,maxBuffer:1073741824});if(result.status!==0){let stderr=result.stderr?.toString().trim()||"unknown error";throw Error(`pg_dump failed (exit ${result.status}): ${stderr}`)}let compressed=gzipSync(result.stdout);writeFileSync12(tmpPath,compressed),renameSync2(tmpPath,snapshotPath);let compressedBytes=statSync(snapshotPath).size,uncompressedBytes=0;try{let sizeResult=spawnSync3("psql",["-t","-A","-c",`SELECT pg_database_size('${DB_NAME2}')`],{env:pgEnv(port),encoding:"utf-8",timeout:1e4});if(sizeResult.status===0)uncompressedBytes=Number.parseInt(sizeResult.stdout.trim(),10)||0}catch{}return{path:snapshotPath,compressedBytes,uncompressedBytes}}function restore(snapshotFile,cwd){let port=getActivePort(),filePath=snapshotFile??getSnapshotPath(cwd);if(!existsSync28(filePath))throw Error(`Snapshot not found: ${filePath}`);let env=pgEnv(port),adminEnv={...env,PGDATABASE:"postgres"};spawnSync3("psql",["-c",`SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${DB_NAME2}' AND pid <> pg_backend_pid()`],{env:adminEnv,stdio:["pipe","pipe","pipe"],timeout:1e4});let dropResult=spawnSync3("psql",["-c",`DROP DATABASE IF EXISTS ${DB_NAME2}`],{env:adminEnv,stdio:["pipe","pipe","pipe"],timeout:1e4});if(dropResult.status!==0)throw Error(`Failed to drop database: ${dropResult.stderr?.toString().trim()}`);let createResult=spawnSync3("psql",["-c",`CREATE DATABASE ${DB_NAME2}`],{env:adminEnv,stdio:["pipe","pipe","pipe"],timeout:1e4});if(createResult.status!==0)throw Error(`Failed to create database: ${createResult.stderr?.toString().trim()}`);let compressed=readFileSync14(filePath),sql=gunzipSync(compressed),restoreResult=spawnSync3("psql",[],{env:{...env,PGDATABASE:DB_NAME2},input:sql,stdio:["pipe","pipe","pipe"],timeout:300000,maxBuffer:1073741824});if(restoreResult.status!==0)throw Error(`psql restore failed (exit ${restoreResult.status}): ${restoreResult.stderr?.toString().trim()}`)}init_db_migrations();init_db();function printTable(rows){if(rows.length===0){console.log("(0 rows)");return}let columns=Object.keys(rows[0]),widths=columns.map((col)=>{let values2=rows.map((r)=>String(r[col]??"NULL"));return Math.max(col.length,...values2.map((v)=>v.length))}),header=columns.map((col,i2)=>padRight(col,widths[i2])).join(" | ");console.log(header),console.log(widths.map((w)=>"-".repeat(w)).join("-+-"));for(let row of rows){let line=columns.map((col,i2)=>padRight(String(row[col]??"NULL"),widths[i2])).join(" | ");console.log(line)}console.log(`(${rows.length} row${rows.length===1?"":"s"})`)}async function dbStatusCommand(){let port=getActivePort(),dataDir=getDataDir();if(console.log(`
2285
+ `).filter(Boolean);for(let line of newLines)printLogLine(line);lastSize=content.length}}catch{}});process.on("SIGINT",()=>{watcher2.close(),process.exit(0)}),await new Promise(()=>{})}function printLogLine(raw){try{let entry=JSON.parse(raw),ts3=entry.timestamp?new Date(entry.timestamp).toLocaleTimeString("en-US",{hour12:!1}):"??:??:??",level=(entry.level??"info").toUpperCase().padEnd(5),event=entry.event??"unknown",extras=Object.entries(entry).filter(([k])=>!["timestamp","level","event"].includes(k)).map(([k,v])=>`${k}=${typeof v==="object"?JSON.stringify(v):v}`).join(" ");console.log(`${ts3} ${level} ${event}${extras?` ${extras}`:""}`)}catch{console.log(raw)}}function formatUptime(ms){let seconds=Math.floor(ms/1000),minutes=Math.floor(seconds/60),hours=Math.floor(minutes/60),days=Math.floor(hours/24);if(days>0)return`${days}d ${hours%24}h ${minutes%60}m`;if(hours>0)return`${hours}h ${minutes%60}m`;if(minutes>0)return`${minutes}m ${seconds%60}s`;return`${seconds}s`}function registerDaemonCommands(program2){let daemon=program2.command("daemon").description("Manage scheduler daemon lifecycle");daemon.command("install").description("Generate systemd service unit and enable it").action(async()=>{await daemonInstallCommand()}),daemon.command("start").description("Start the scheduler daemon").option("--foreground","Run in foreground (for systemd ExecStart)").action(async(options)=>{await daemonStartCommand(options)}),daemon.command("stop").description("Stop the scheduler daemon gracefully").action(async()=>{await daemonStopCommand()}),daemon.command("status").description("Show daemon state, PID, uptime, and trigger stats").action(async()=>{await daemonStatusCommand()}),daemon.command("logs").description("Tail structured JSON scheduler log").option("--follow, -f","Follow log output").option("--lines <n>","Number of lines to show (default: 20)",Number.parseInt).action(async(options)=>{await daemonLogsCommand(options)})}import{createInterface as createInterface3}from"readline";init_db();init_wish_state();import{spawnSync as spawnSync4}from"child_process";import{existsSync as existsSync28,mkdirSync as mkdirSync13,readFileSync as readFileSync14,renameSync as renameSync2,statSync,writeFileSync as writeFileSync12}from"fs";import{join as join37}from"path";import{gunzipSync,gzipSync}from"zlib";var DB_NAME2="genie",DB_USER="postgres",DB_HOST="127.0.0.1",SNAPSHOT_FILE="snapshot.sql.gz";function getSnapshotPath(cwd){let repoRoot=resolveRepoPath(cwd);return join37(repoRoot,".genie",SNAPSHOT_FILE)}function pgEnv(port){return{...process.env,PGHOST:DB_HOST,PGPORT:String(port),PGUSER:DB_USER,PGPASSWORD:DB_USER,PGDATABASE:DB_NAME2}}function backup(cwd){let port=getActivePort(),snapshotPath=getSnapshotPath(cwd),genieDir=join37(resolveRepoPath(cwd),".genie"),tmpPath=`${snapshotPath}.tmp`;mkdirSync13(genieDir,{recursive:!0});let result=spawnSync4("pg_dump",["--no-owner","--no-acl"],{env:pgEnv(port),stdio:["pipe","pipe","pipe"],timeout:120000,maxBuffer:1073741824});if(result.status!==0){let stderr=result.stderr?.toString().trim()||"unknown error";throw Error(`pg_dump failed (exit ${result.status}): ${stderr}`)}let compressed=gzipSync(result.stdout);writeFileSync12(tmpPath,compressed),renameSync2(tmpPath,snapshotPath);let compressedBytes=statSync(snapshotPath).size,uncompressedBytes=0;try{let sizeResult=spawnSync4("psql",["-t","-A","-c",`SELECT pg_database_size('${DB_NAME2}')`],{env:pgEnv(port),encoding:"utf-8",timeout:1e4});if(sizeResult.status===0)uncompressedBytes=Number.parseInt(sizeResult.stdout.trim(),10)||0}catch{}return{path:snapshotPath,compressedBytes,uncompressedBytes}}function restore(snapshotFile,cwd){let port=getActivePort(),filePath=snapshotFile??getSnapshotPath(cwd);if(!existsSync28(filePath))throw Error(`Snapshot not found: ${filePath}`);let env=pgEnv(port),adminEnv={...env,PGDATABASE:"postgres"};spawnSync4("psql",["-c",`SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${DB_NAME2}' AND pid <> pg_backend_pid()`],{env:adminEnv,stdio:["pipe","pipe","pipe"],timeout:1e4});let dropResult=spawnSync4("psql",["-c",`DROP DATABASE IF EXISTS ${DB_NAME2}`],{env:adminEnv,stdio:["pipe","pipe","pipe"],timeout:1e4});if(dropResult.status!==0)throw Error(`Failed to drop database: ${dropResult.stderr?.toString().trim()}`);let createResult=spawnSync4("psql",["-c",`CREATE DATABASE ${DB_NAME2}`],{env:adminEnv,stdio:["pipe","pipe","pipe"],timeout:1e4});if(createResult.status!==0)throw Error(`Failed to create database: ${createResult.stderr?.toString().trim()}`);let compressed=readFileSync14(filePath),sql=gunzipSync(compressed),restoreResult=spawnSync4("psql",[],{env:{...env,PGDATABASE:DB_NAME2},input:sql,stdio:["pipe","pipe","pipe"],timeout:300000,maxBuffer:1073741824});if(restoreResult.status!==0)throw Error(`psql restore failed (exit ${restoreResult.status}): ${restoreResult.stderr?.toString().trim()}`)}init_db_migrations();init_db();function printTable(rows){if(rows.length===0){console.log("(0 rows)");return}let columns=Object.keys(rows[0]),widths=columns.map((col)=>{let values2=rows.map((r)=>String(r[col]??"NULL"));return Math.max(col.length,...values2.map((v)=>v.length))}),header=columns.map((col,i2)=>padRight(col,widths[i2])).join(" | ");console.log(header),console.log(widths.map((w)=>"-".repeat(w)).join("-+-"));for(let row of rows){let line=columns.map((col,i2)=>padRight(String(row[col]??"NULL"),widths[i2])).join(" | ");console.log(line)}console.log(`(${rows.length} row${rows.length===1?"":"s"})`)}async function dbStatusCommand(){let port=getActivePort(),dataDir=getDataDir();if(console.log(`
2286
2286
  Genie Database Status`),console.log("\u2500".repeat(50)),console.log(` Port: ${port}`),console.log(" Host: 127.0.0.1"),console.log(` Data dir: ${dataDir}`),!await isAvailable()){console.log(" Status: stopped"),console.log(`
2287
2287
  pgserve is not running. It will auto-start on first use.`),console.log("");return}console.log(" Status: running");try{let sql=await getConnection(),sizeResult=await sql`SELECT pg_size_pretty(pg_database_size(current_database())) AS size`;console.log(` DB size: ${sizeResult[0].size}`);let migrations=await getMigrationStatus(sql);console.log(`
2288
2288
  Migrations: ${migrations.applied.length} applied, ${migrations.pending.length} pending`);let tables=await sql`
@@ -2556,7 +2556,7 @@ Name: ${t.name}`),console.log(`ID: ${t.id}`),console.log(`Builti
2556
2556
  ${types3.length} type${types3.length===1?"":"s"}`)}function printTypePipeline(t){if(console.log(`
2557
2557
  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(`
2558
2558
  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 types3=await(await getTaskService9()).listTypes();if(options.json){console.log(JSON.stringify(types3,null,2));return}printTypeTable(types3)}async function handleTypeShow(id,options){console.warn("Warning: `genie type` is deprecated. Use `genie board` instead.");let t=await(await getTaskService9()).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 getTaskService9(),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);program2.configureHelp({sortSubcommands:!0,showGlobalOptions:!0});program2.configureOutput({outputError:(str5,write)=>{let cmd=program2.commands.find((c)=>process.argv.slice(2,6).includes(c.name())),prefix=cmd?`genie ${cmd.name()}`:"genie";write(`\x1B[31mError (${prefix}): ${str5}\x1B[0m
2559
- `)}});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);registerAgentCommands(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);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").addHelpText("after",`
2559
+ `)}});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:spawnSync5}=await import("child_process"),result=spawnSync5("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);registerAgentCommands(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);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").addHelpText("after",`
2560
2560
  Examples:
2561
2561
  genie spawn engineer # Spawn built-in engineer role
2562
2562
  genie spawn researcher --model sonnet # Spawn with model override
@@ -2,7 +2,7 @@
2
2
  "id": "genie",
3
3
  "name": "Genie",
4
4
  "description": "Skills, agents, and hooks for the Genie CLI terminal orchestration toolkit",
5
- "version": "4.260331.19",
5
+ "version": "4.260331.20",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/genie",
3
- "version": "4.260331.19",
3
+ "version": "4.260331.20",
4
4
  "description": "Collaborative terminal toolkit for human + AI workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie",
3
- "version": "4.260331.19",
3
+ "version": "4.260331.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"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie-plugin",
3
- "version": "4.260331.19",
3
+ "version": "4.260331.20",
4
4
  "private": true,
5
5
  "description": "Runtime dependencies for genie bundled CLIs",
6
6
  "type": "module",
@@ -0,0 +1,35 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { getTuiKeybindings, getTuiQuitBindingArgs } from './serve.js';
3
+
4
+ describe('getTuiKeybindings', () => {
5
+ test('includes explicit left and right pane focus bindings', () => {
6
+ const bindings = getTuiKeybindings();
7
+
8
+ expect(bindings).toContain('bind-key -T root C-1 select-pane -t genie-tui:0.0');
9
+ expect(bindings).toContain('bind-key -T root C-2 select-pane -t genie-tui:0.1');
10
+ });
11
+
12
+ test('keeps existing tab toggle and quit passthrough bindings', () => {
13
+ const bindings = getTuiKeybindings();
14
+
15
+ expect(bindings.some((binding) => binding.includes('bind-key -T root Tab if-shell'))).toBe(true);
16
+ expect(bindings).toContain('bind-key -T root C-q select-pane -t genie-tui:0.0 \\; send-keys -t genie-tui:0.0 C-q');
17
+ });
18
+
19
+ test('builds quit passthrough binding args without shell escaping', () => {
20
+ expect(getTuiQuitBindingArgs()).toEqual([
21
+ 'bind-key',
22
+ '-T',
23
+ 'root',
24
+ 'C-q',
25
+ 'select-pane',
26
+ '-t',
27
+ 'genie-tui:0.0',
28
+ '\\;',
29
+ 'send-keys',
30
+ '-t',
31
+ 'genie-tui:0.0',
32
+ 'C-q',
33
+ ]);
34
+ });
35
+ });
@@ -16,7 +16,7 @@
16
16
  * genie serve status — show service health
17
17
  */
18
18
 
19
- import { execSync, spawn } from 'node:child_process';
19
+ import { execSync, spawn, spawnSync } from 'node:child_process';
20
20
  import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
21
21
  import { homedir } from 'node:os';
22
22
  import { join } from 'node:path';
@@ -118,6 +118,41 @@ const TUI_STYLE = {
118
118
  inactiveBorder: '#414868',
119
119
  };
120
120
 
121
+ export function getTuiKeybindings(sessionName = TUI_SESSION): string[] {
122
+ return [
123
+ // Tab: toggle focus between left nav (pane 0) and right terminal (pane 1)
124
+ `bind-key -T root Tab if-shell "[ '#{pane_index}' = '0' ]" "select-pane -t ${sessionName}:0.1" "select-pane -t ${sessionName}:0.0"`,
125
+ // Ctrl+1 / Ctrl+2: explicit left/right focus, even when the right pane is running a nested agent session
126
+ `bind-key -T root C-1 select-pane -t ${sessionName}:0.0`,
127
+ `bind-key -T root C-2 select-pane -t ${sessionName}:0.1`,
128
+ // Ctrl+B: toggle sidebar width (collapse/expand)
129
+ `bind-key -T root C-b if-shell "[ $(tmux display-message -p '#\\{pane_width\\}' -t ${sessionName}:0.0) -gt 5 ]" "resize-pane -t ${sessionName}:0.0 -x 0" "resize-pane -t ${sessionName}:0.0 -x ${NAV_WIDTH}"`,
130
+ // Ctrl+T: new window in agent session (sends C-b c to the nested tmux in right pane)
131
+ `bind-key -T root C-t send-keys -t ${sessionName}:0.1 C-b c`,
132
+ // Ctrl+D: detach from TUI (leave running)
133
+ 'bind-key -T root C-d detach-client',
134
+ // Ctrl+Q: focus nav pane + pass through for quit confirmation popup
135
+ `bind-key -T root C-q select-pane -t ${sessionName}:0.0 \\; send-keys -t ${sessionName}:0.0 C-q`,
136
+ ];
137
+ }
138
+
139
+ export function getTuiQuitBindingArgs(sessionName = TUI_SESSION): string[] {
140
+ return [
141
+ 'bind-key',
142
+ '-T',
143
+ 'root',
144
+ 'C-q',
145
+ 'select-pane',
146
+ '-t',
147
+ `${sessionName}:0.0`,
148
+ '\\;',
149
+ 'send-keys',
150
+ '-t',
151
+ `${sessionName}:0.0`,
152
+ 'C-q',
153
+ ];
154
+ }
155
+
121
156
  /** Apply visual theme to TUI session */
122
157
  function applyTuiStyle(): void {
123
158
  const cmds = [
@@ -136,21 +171,15 @@ function applyTuiStyle(): void {
136
171
 
137
172
  /** Set up keybindings in root table so they work immediately */
138
173
  function setupTuiKeybindings(): void {
139
- const bindings = [
140
- // Tab: toggle focus between left nav (pane 0) and right terminal (pane 1)
141
- `bind-key -T root Tab if-shell "[ '#{pane_index}' = '0' ]" "select-pane -t ${TUI_SESSION}:0.1" "select-pane -t ${TUI_SESSION}:0.0"`,
142
- // Ctrl+B: toggle sidebar width (collapse/expand)
143
- `bind-key -T root C-b if-shell "[ $(tmux 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}"`,
144
- // Ctrl+T: new window in agent session (sends C-b c to the nested tmux in right pane)
145
- `bind-key -T root C-t send-keys -t ${TUI_SESSION}:0.1 C-b c`,
146
- // Ctrl+D: detach from TUI (leave running)
147
- 'bind-key -T root C-d detach-client',
148
- // Ctrl+Q: focus nav pane + pass through for quit confirmation popup
149
- `bind-key -T root C-q select-pane -t ${TUI_SESSION}:0.0 \\; send-keys -t ${TUI_SESSION}:0.0 C-q`,
150
- ];
151
- for (const cmd of bindings) {
174
+ for (const cmd of getTuiKeybindings()) {
152
175
  try {
153
- execSync(tuiTmux(cmd), { stdio: 'ignore' });
176
+ if (cmd.startsWith('bind-key -T root C-q ')) {
177
+ spawnSync(tmuxBin(), ['-L', 'genie-tui', '-f', tuiTmuxConf(), ...getTuiQuitBindingArgs()], {
178
+ stdio: 'ignore',
179
+ });
180
+ } else {
181
+ execSync(tuiTmux(cmd), { stdio: 'ignore' });
182
+ }
154
183
  } catch {}
155
184
  }
156
185
  }
package/src/tui/app.tsx CHANGED
@@ -55,7 +55,12 @@ export function App({ rightPane, workspaceRoot, initialAgent }: AppProps) {
55
55
 
56
56
  return (
57
57
  <box width="100%" height="100%">
58
- <Nav onTmuxSessionSelect={handleTmuxSessionSelect} workspaceRoot={workspaceRoot} initialAgent={initialAgent} />
58
+ <Nav
59
+ onTmuxSessionSelect={handleTmuxSessionSelect}
60
+ workspaceRoot={workspaceRoot}
61
+ initialAgent={initialAgent}
62
+ keyboardDisabled={showQuit}
63
+ />
59
64
  {showQuit ? <QuitDialog onConfirm={handleQuit} onCancel={() => setShowQuit(false)} /> : null}
60
65
  </box>
61
66
  );
@@ -23,9 +23,11 @@ interface NavProps {
23
23
  workspaceRoot?: string;
24
24
  /** Pre-select this agent on initial render */
25
25
  initialAgent?: string;
26
+ /** Disable nav keyboard shortcuts while a modal owns input */
27
+ keyboardDisabled?: boolean;
26
28
  }
27
29
 
28
- export function Nav({ onTmuxSessionSelect, workspaceRoot, initialAgent }: NavProps) {
30
+ export function Nav({ onTmuxSessionSelect, workspaceRoot, initialAgent, keyboardDisabled = false }: NavProps) {
29
31
  const [diagnostics, setDiagnostics] = useState<DiagnosticSnapshot | null>(null);
30
32
  const [sessionTree, setSessionTree] = useState<TreeNode[]>([]);
31
33
  const [selectedIndex, setSelectedIndex] = useState(0);
@@ -177,6 +179,7 @@ export function Nav({ onTmuxSessionSelect, workspaceRoot, initialAgent }: NavPro
177
179
  }, [flatNodes, selectedIndex, handleToggle, onTmuxSessionSelect]);
178
180
 
179
181
  useKeyboard((key) => {
182
+ if (keyboardDisabled) return;
180
183
  if (key.name === 'up' || key.name === 'k' || key.name === 'down' || key.name === 'j') {
181
184
  handleVerticalNav(key.name);
182
185
  } else if (key.name === 'right' || key.name === 'l' || key.name === 'left' || key.name === 'h') {