@automagik/genie 4.260401.6 → 4.260402.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/dist/genie.js +4 -3
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/plugins/genie/.claude-plugin/plugin.json +1 -1
- package/plugins/genie/package.json +1 -1
- package/src/genie.ts +11 -4
- package/src/term-commands/serve.ts +27 -2
- package/src/tui/components/Nav.tsx +12 -2
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "genie",
|
|
13
|
-
"version": "4.
|
|
13
|
+
"version": "4.260402.1",
|
|
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
|
@@ -1534,7 +1534,8 @@ Stopped following`),process.exit(0)}),await new Promise(()=>{});return}let conte
|
|
|
1534
1534
|
INSERT INTO machine_snapshots (id, active_workers, active_teams, tmux_sessions, cpu_percent, memory_mb, created_at)
|
|
1535
1535
|
VALUES (${snapshotId}, ${activeWorkers}, ${teams.size}, ${tmuxSessions}, ${cpuPercent}, ${memoryMb}, ${now})
|
|
1536
1536
|
`,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,leaseRecoveryTimer=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(leaseRecoveryTimer)clearInterval(leaseRecoveryTimer),leaseRecoveryTimer=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 startLeaseRecoveryTimer(d,cfg,dId){return setInterval(async()=>{if(!running2)return;try{await reclaimExpiredLeases(d,dId)}catch(err){let message=err instanceof Error?err.message:String(err);d.log({timestamp:d.now().toISOString(),level:"error",event:"lease_recovery_error",error:message})}},cfg.leaseRecoveryIntervalMs)}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),leaseRecoveryTimer=startLeaseRecoveryTimer(deps,config,daemonId),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 mkdirSync10,readFileSync as readFileSync11,unlinkSync as unlinkSync6,writeFileSync as writeFileSync9}from"fs";import{homedir as homedir23}from"os";import{join as join33}from"path";function genieHome2(){return process.env.GENIE_HOME??join33(homedir23(),".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=readFileSync11(path2,"utf-8").trim(),pid=Number.parseInt(raw,10);if(Number.isNaN(pid)||pid<=0)return null;return pid}function writeServePid(pid){mkdirSync10(genieHome2(),{recursive:!0}),writeFileSync9(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 select-pane -t ${sessionName}:0.0 \\; send-keys -t ${sessionName}:0.0 C-t`,"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(`
|
|
1537
|
-
`);return{leftPane:panes2[0],rightPane:panes2[1]
|
|
1537
|
+
`);if(panes2.length>=2){try{execSync7(tuiTmux(`respawn-pane -k -t ${panes2[1]} 'cat'`),{stdio:"ignore"})}catch{}return{leftPane:panes2[0],rightPane:panes2[1]}}let cols2=Number.parseInt(execSync7(tuiTmux(`display-message -t ${TUI_SESSION}:0 -p '#{window_width}'`),{encoding:"utf-8"}).trim(),10)||120;execSync7(tuiTmux(`split-window -h -t ${TUI_SESSION}:0 -l ${cols2-NAV_WIDTH-1}`),{stdio:"ignore"});let refreshed=execSync7(tuiTmux(`list-panes -t ${TUI_SESSION}:0 -F '#{pane_id}'`),{encoding:"utf-8"}).trim().split(`
|
|
1538
|
+
`);applyTuiStyle(),setupTuiKeybindings();try{execSync7(tuiTmux(`select-pane -t ${refreshed[0]}`),{stdio:"ignore"})}catch{}return{leftPane:refreshed[0],rightPane:refreshed[1]||refreshed[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(`
|
|
1538
1539
|
`);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
|
|
1539
1540
|
export ${envVars.join(`
|
|
1540
1541
|
export `)}
|
|
@@ -2216,7 +2217,7 @@ $ bun add react-devtools-core@7 -d
|
|
|
2216
2217
|
AND a.ended_at IS NULL
|
|
2217
2218
|
ORDER BY a.started_at DESC
|
|
2218
2219
|
`).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(`
|
|
2219
|
-
`)){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 isPidAlive2(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&&!isPidAlive2(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 readFileSync18,unlinkSync as unlinkSync8}from"fs";import{homedir as homedir28}from"os";import{join as join51}from"path";function getInitialAgentFilePath(){let genieHome4=process.env.GENIE_HOME??join51(homedir28(),".genie");return join51(genieHome4,"tui-initial-agent")}function consumeInitialAgentSignal(){let filePath=getInitialAgentFilePath();if(!existsSync38(filePath))return;try{let agent=readFileSync18(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{topLevel,subsByParent}=groupAgentNames(agentNames),nodes=topLevel.map((name)=>{let node=buildAgentNode(name,sessionByName.get(name),executorsByAgent.get(name)??[],executorByPaneId);return appendSubAgentNodes(node,subsByParent.get(name),sessionByName,executorsByAgent,executorByPaneId),node}),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 groupAgentNames(names){let topLevel=[],subsByParent=new Map;for(let name of names){let slashIdx=name.indexOf("/");if(slashIdx===-1)topLevel.push(name);else{let parent=name.slice(0,slashIdx),subs=subsByParent.get(parent)??[];subs.push(name),subsByParent.set(parent,subs)}}return{topLevel,subsByParent}}function appendSubAgentNodes(parent,subs,sessionByName,executorsByAgent,executorByPaneId){if(!subs)return;for(let subName of subs){let subLabel=subName.slice(subName.indexOf("/")+1),subNode=buildAgentNode(subName,sessionByName.get(subName),executorsByAgent.get(subName)??[],executorByPaneId);subNode.label=subLabel,subNode.depth=1,parent.children.push(subNode)}}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"){if(node.wsAgentState==="spawning"&&node.activePanes===0)return" [stuck \u2014 press R to retry]";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,onNewAgentWindow,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(agentNameFromNode(node),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"&¤t.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(agentNameFromNode(node),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]),handleRetry=import_react15.useCallback(()=>{let node=flatNodes[selectedIndex]?.node;if(!node||node.type!=="agent")return;if(node.wsAgentState!=="spawning"&&node.wsAgentState!=="error")return;(async()=>{try{let{reconcileStaleSpawns:reconcileStaleSpawns2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));await reconcileStaleSpawns2()}catch{}spawnAgent(agentNameFromNode(node),onTmuxSessionSelect)})()},[flatNodes,selectedIndex,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();else if(key.name==="r")handleRetry();else if(key.ctrl&&key.name==="t"){let node=flatNodes[selectedIndex]?.node;if(node?.type==="agent"&&node.wsAgentState==="running"&&onNewAgentWindow)onNewAgentWindow(agentNameFromNode(node))}});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"," ^T:new R:retry"]},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 agentNameFromNode(node){return node.id.replace(/^agent:/,"")}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,sessionName=name.replace(/\//g,"-"),cwd;if(wsRoot){let parentName=name.includes("/")?name.slice(0,name.indexOf("/")):name,agentDir=resolve10(join52(wsRoot,"agents",parentName));if(existsSync39(agentDir))cwd=agentDir}if((genieBin&&genieBin!=="genie"?spawn4(bunPath,[genieBin,"spawn",name,"--session",sessionName],{detached:!0,stdio:"ignore",cwd}):spawn4("genie",["spawn",name,"--session",sessionName],{detached:!0,stdio:"ignore",cwd})).unref(),onTmuxSessionSelect)attachSpawnedAgentWhenReady(sessionName,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 readFileSync19}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=readFileSync19(`${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,onNewAgentWindow:newAgentWindow,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(`
|
|
2220
|
+
`)){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 isPidAlive2(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&&!isPidAlive2(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 readFileSync18,unlinkSync as unlinkSync8}from"fs";import{homedir as homedir28}from"os";import{join as join51}from"path";function getInitialAgentFilePath(){let genieHome4=process.env.GENIE_HOME??join51(homedir28(),".genie");return join51(genieHome4,"tui-initial-agent")}function consumeInitialAgentSignal(){let filePath=getInitialAgentFilePath();if(!existsSync38(filePath))return;try{let agent=readFileSync18(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{topLevel,subsByParent}=groupAgentNames(agentNames),nodes=topLevel.map((name)=>{let node=buildAgentNode(name,sessionByName.get(name),executorsByAgent.get(name)??[],executorByPaneId);return appendSubAgentNodes(node,subsByParent.get(name),sessionByName,executorsByAgent,executorByPaneId),node}),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 groupAgentNames(names){let topLevel=[],subsByParent=new Map;for(let name of names){let slashIdx=name.indexOf("/");if(slashIdx===-1)topLevel.push(name);else{let parent=name.slice(0,slashIdx),subs=subsByParent.get(parent)??[];subs.push(name),subsByParent.set(parent,subs)}}return{topLevel,subsByParent}}function appendSubAgentNodes(parent,subs,sessionByName,executorsByAgent,executorByPaneId){if(!subs)return;for(let subName of subs){let subLabel=subName.slice(subName.indexOf("/")+1),subNode=buildAgentNode(subName,sessionByName.get(subName),executorsByAgent.get(subName)??[],executorByPaneId);subNode.label=subLabel,subNode.depth=1,parent.children.push(subNode)}}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"){if(node.wsAgentState==="spawning"&&node.activePanes===0)return" [stuck \u2014 press R to retry]";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,onNewAgentWindow,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(agentNameFromNode(node),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"&¤t.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(agentNameFromNode(node),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]),handleRetry=import_react15.useCallback(()=>{let node=flatNodes[selectedIndex]?.node;if(!node||node.type!=="agent")return;if(node.wsAgentState!=="spawning"&&node.wsAgentState!=="error")return;(async()=>{try{let{reconcileStaleSpawns:reconcileStaleSpawns2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));await reconcileStaleSpawns2()}catch{}spawnAgent(agentNameFromNode(node),onTmuxSessionSelect)})()},[flatNodes,selectedIndex,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();else if(key.name==="r")handleRetry();else if(key.ctrl&&key.name==="t"){let node=flatNodes[selectedIndex]?.node;if(node?.type==="agent"&&node.wsAgentState==="running"&&onNewAgentWindow)onNewAgentWindow(agentNameFromNode(node))}});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"," ^T:new R:retry"]},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 agentNameFromNode(node){return node.id.replace(/^agent:/,"")}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,sessionName=name.replace(/\//g,"-"),cwd;if(wsRoot){let parentName=name.includes("/")?name.slice(0,name.indexOf("/")):name,agentDir=resolve10(join52(wsRoot,"agents",parentName));if(existsSync39(agentDir))cwd=agentDir}let{GENIE_TUI_PANE:_a,GENIE_TUI_RIGHT:_b,GENIE_TUI_WORKSPACE:_c,GENIE_IS_DAEMON:_d,...cleanEnv}=process.env,spawnOpts={detached:!0,stdio:"ignore",cwd,env:cleanEnv};if((genieBin&&genieBin!=="genie"?spawn4(bunPath,[genieBin,"spawn",name,"--session",sessionName],spawnOpts):spawn4("genie",["spawn",name,"--session",sessionName],spawnOpts)).unref(),onTmuxSessionSelect)attachSpawnedAgentWhenReady(sessionName,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 readFileSync19}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=readFileSync19(`${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,onNewAgentWindow:newAgentWindow,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(`
|
|
2220
2221
|
\x1B[1mGenie Doctor \u2014 Auto Fix\x1B[0m`),console.log(`\x1B[2m${"\u2500".repeat(40)}\x1B[0m
|
|
2221
2222
|
`),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(`
|
|
2222
2223
|
\x1B[2m${"\u2500".repeat(40)}\x1B[0m`),console.log(`\x1B[32mFix complete.\x1B[0m Run \x1B[36mgenie doctor\x1B[0m to verify.
|
|
@@ -2602,4 +2603,4 @@ Examples:
|
|
|
2602
2603
|
genie spawn engineer # Spawn built-in engineer role
|
|
2603
2604
|
genie spawn researcher --model sonnet # Spawn with model override
|
|
2604
2605
|
genie spawn my-agent --team my-feature # Spawn into a specific team
|
|
2605
|
-
genie spawn council--questioner --provider codex # Use Codex provider`).action(async(name,options)=>{try{await handleWorkerSpawn(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("kill <name>").description("Force kill an agent by name").action(async(name)=>{try{await handleWorkerKill(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("stop <name>").description("Stop an agent (preserves session for resume)").action(async(name)=>{try{await handleWorkerStop(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("resume [name]").description("Resume a suspended/failed agent with its Claude session").option("--all","Resume all eligible agents").action(async(name,options)=>{try{await handleWorkerResume(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("history <name>").description("Show compressed session history for an agent").option("--full","Show full conversation without compression").option("--since <n>","Show last N user/assistant exchanges",Number.parseInt).option("--last <n>","Show last N transcript entries",Number.parseInt).option("--type <role>","Filter by role (user, assistant, tool_call)").option("--after <timestamp>","Only entries after ISO timestamp").option("--json","Output as JSON").option("--ndjson","Output as newline-delimited JSON (pipeable to jq)").option("--raw","Output raw JSONL entries").option("--log-file <path>","Direct path to log file (for testing)").action(async(name,options)=>{await historyCommand(name,options)});program2.command("log [agent]").description("Unified observability feed \u2014 aggregates transcript, DMs, team chat").option("--team <name>","Show interleaved feed for all agents in a team").option("--type <kind>","Filter by event kind (transcript, message, tool_call, state, system)").option("--since <timestamp>","Only events after ISO timestamp").option("--last <n>","Show last N events",Number.parseInt).option("--ndjson","Output as newline-delimited JSON (pipeable to jq)").option("--json","Output as pretty JSON").option("-f, --follow","Follow mode \u2014 real-time streaming").action(async(agent,options)=>{await logCommand(agent,options)});var qaCmd=program2.command("qa").description("QA \u2014 self-testing system for genie CLI");qaCmd.command("run [target]",{isDefault:!0}).description("Run QA specs (all, a domain, or a single spec)").option("--timeout <seconds>","Max seconds per spec",(v2)=>Number(v2),3600).option("--parallel <n>","Max specs to run in parallel",(v2)=>Number(v2),5).option("--verbose","Show all collected events").option("--ndjson","Machine-readable NDJSON output").action(async(target,options)=>{await qaCommand(target,options)});qaCmd.command("status").description("Show QA dashboard with last results per spec").option("--json","Output as JSON").action(async(options)=>{await qaStatusCommand(options)});qaCmd.command("history").description("Show recent QA runs").action(async()=>{await qaHistoryCommand()});qaCmd.command("check <specFile>").description("Evaluate a QA spec against current team logs and publish qa-report").option("--team <name>","Team name (defaults to GENIE_TEAM)").option("--since <timestamp>","Only consider events after this ISO timestamp").option("--since-file <path>","Read the lower-bound timestamp from a file").action(async(specFile,options)=>{await qaCheckCommand(specFile,options)});program2.command("qa-report <json>").description("Publish QA result to the PG event log (called by QA team-lead)").action(async(json2)=>{let team=process.env.GENIE_TEAM;if(!team)console.error("Error: GENIE_TEAM not set. This command must be run by a QA team-lead agent."),process.exit(1);try{let data=JSON.parse(json2),{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(process.cwd(),`genie.qa.${team}.result`,{kind:"qa",agent:"qa",team,text:`QA result: ${String(data.result??"unknown")}`,data,source:"hook"}),console.log(`QA result published to PG event log as genie.qa.${team}.result`)}catch(err){console.error(`Failed to publish QA result: ${err}`),process.exit(1)}});program2.command("read <name>").description("Read terminal output from an agent pane").option("-n, --lines <number>","Number of lines to read").option("--from <line>","Start line").option("--to <line>","End line").option("--range <range>",'Line range (e.g., "10-20")').option("--search <text>","Search for text").option("--grep <pattern>","Grep for pattern").option("-f, --follow","Follow mode (like tail -f)").option("--all","Show all output").option("-r, --reverse","Reverse order").option("--json","Output as JSON").action(async(name,options)=>{await readSessionLogs2(name,options)});program2.command("answer <name> <choice>").description('Answer a question for an agent (use "text:..." for text input)').action(async(name,choice)=>{await answerQuestion(name,choice)});program2.command("ls").description("List registered agents with runtime status").option("--json","Output as JSON").action(async(options)=>{try{await handleLsCommand(options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});var args=process.argv.slice(2)
|
|
2606
|
+
genie spawn council--questioner --provider codex # Use Codex provider`).action(async(name,options)=>{try{await handleWorkerSpawn(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("kill <name>").description("Force kill an agent by name").action(async(name)=>{try{await handleWorkerKill(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("stop <name>").description("Stop an agent (preserves session for resume)").action(async(name)=>{try{await handleWorkerStop(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("resume [name]").description("Resume a suspended/failed agent with its Claude session").option("--all","Resume all eligible agents").action(async(name,options)=>{try{await handleWorkerResume(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("history <name>").description("Show compressed session history for an agent").option("--full","Show full conversation without compression").option("--since <n>","Show last N user/assistant exchanges",Number.parseInt).option("--last <n>","Show last N transcript entries",Number.parseInt).option("--type <role>","Filter by role (user, assistant, tool_call)").option("--after <timestamp>","Only entries after ISO timestamp").option("--json","Output as JSON").option("--ndjson","Output as newline-delimited JSON (pipeable to jq)").option("--raw","Output raw JSONL entries").option("--log-file <path>","Direct path to log file (for testing)").action(async(name,options)=>{await historyCommand(name,options)});program2.command("log [agent]").description("Unified observability feed \u2014 aggregates transcript, DMs, team chat").option("--team <name>","Show interleaved feed for all agents in a team").option("--type <kind>","Filter by event kind (transcript, message, tool_call, state, system)").option("--since <timestamp>","Only events after ISO timestamp").option("--last <n>","Show last N events",Number.parseInt).option("--ndjson","Output as newline-delimited JSON (pipeable to jq)").option("--json","Output as pretty JSON").option("-f, --follow","Follow mode \u2014 real-time streaming").action(async(agent,options)=>{await logCommand(agent,options)});var qaCmd=program2.command("qa").description("QA \u2014 self-testing system for genie CLI");qaCmd.command("run [target]",{isDefault:!0}).description("Run QA specs (all, a domain, or a single spec)").option("--timeout <seconds>","Max seconds per spec",(v2)=>Number(v2),3600).option("--parallel <n>","Max specs to run in parallel",(v2)=>Number(v2),5).option("--verbose","Show all collected events").option("--ndjson","Machine-readable NDJSON output").action(async(target,options)=>{await qaCommand(target,options)});qaCmd.command("status").description("Show QA dashboard with last results per spec").option("--json","Output as JSON").action(async(options)=>{await qaStatusCommand(options)});qaCmd.command("history").description("Show recent QA runs").action(async()=>{await qaHistoryCommand()});qaCmd.command("check <specFile>").description("Evaluate a QA spec against current team logs and publish qa-report").option("--team <name>","Team name (defaults to GENIE_TEAM)").option("--since <timestamp>","Only consider events after this ISO timestamp").option("--since-file <path>","Read the lower-bound timestamp from a file").action(async(specFile,options)=>{await qaCheckCommand(specFile,options)});program2.command("qa-report <json>").description("Publish QA result to the PG event log (called by QA team-lead)").action(async(json2)=>{let team=process.env.GENIE_TEAM;if(!team)console.error("Error: GENIE_TEAM not set. This command must be run by a QA team-lead agent."),process.exit(1);try{let data=JSON.parse(json2),{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(process.cwd(),`genie.qa.${team}.result`,{kind:"qa",agent:"qa",team,text:`QA result: ${String(data.result??"unknown")}`,data,source:"hook"}),console.log(`QA result published to PG event log as genie.qa.${team}.result`)}catch(err){console.error(`Failed to publish QA result: ${err}`),process.exit(1)}});program2.command("read <name>").description("Read terminal output from an agent pane").option("-n, --lines <number>","Number of lines to read").option("--from <line>","Start line").option("--to <line>","End line").option("--range <range>",'Line range (e.g., "10-20")').option("--search <text>","Search for text").option("--grep <pattern>","Grep for pattern").option("-f, --follow","Follow mode (like tail -f)").option("--all","Show all output").option("-r, --reverse","Reverse order").option("--json","Output as JSON").action(async(name,options)=>{await readSessionLogs2(name,options)});program2.command("answer <name> <choice>").description('Answer a question for an agent (use "text:..." for text input)').action(async(name,choice)=>{await answerQuestion(name,choice)});program2.command("ls").description("List registered agents with runtime status").option("--json","Output as JSON").action(async(options)=>{try{await handleLsCommand(options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});var args=process.argv.slice(2),isTuiPane=process.env.GENIE_TUI_PANE==="left"&&args.length===0;delete process.env.GENIE_TUI_PANE;delete process.env.GENIE_TUI_RIGHT;delete process.env.GENIE_IS_DAEMON;if(isTuiPane){let{launchTui:launchTui2}=await Promise.resolve().then(() => exports_tui);await launchTui2(),process.exit(0)}if(args.length===0){let{findWorkspace:findWorkspace2,scanAgents:scanAgents3}=await Promise.resolve().then(() => (init_workspace(),exports_workspace)),ws=findWorkspace2();if(!ws)console.error("Not in a genie workspace. Run `genie init` to create one."),process.exit(1);let{isServeRunning:isServeRunning2,autoStartServe:autoStartServe2,isTuiSessionReady:isTuiSessionReady2,ensureTuiSession:ensureTuiSession2}=await Promise.resolve().then(() => (init_serve(),exports_serve));if(!isServeRunning2())console.log("Starting genie serve..."),await autoStartServe2();else if(!isTuiSessionReady2())ensureTuiSession2(ws.root);let initialAgent=ws.agent??scanAgents3(ws.root)[0];if(ws.root)process.env.GENIE_TUI_WORKSPACE=ws.root;if(initialAgent)process.env.GENIE_TUI_AGENT=initialAgent;if(ws.agent){let{execSync:execSync15}=await import("child_process");try{execSync15(`tmux has-session -t ${ws.agent} 2>/dev/null`,{stdio:"pipe"})}catch{console.log(`Spawning ${ws.agent}...`);try{execSync15(`genie spawn ${ws.agent}`,{stdio:"inherit",timeout:15000})}catch{}}}if(initialAgent){let{writeFileSync:writeFileSync17}=await import("fs"),{join:join52}=await import("path"),home=process.env.GENIE_HOME??join52((await import("os")).homedir(),".genie");try{writeFileSync17(join52(home,"tui-initial-agent"),initialAgent,"utf-8")}catch{}}let{attachTuiSession:attachTuiSession2}=await Promise.resolve().then(() => (init_tmux2(),exports_tmux2));attachTuiSession2(),process.exit(0)}if(args.every((a)=>a==="--reset")){let{sessionCommand:sessionCommand2}=await Promise.resolve().then(() => (init_session(),exports_session));await sessionCommand2({reset:!0}),process.exit(0)}var sessionIdx=args.indexOf("--session");if(sessionIdx!==-1&&sessionIdx+1<args.length){let sessionName=args[sessionIdx+1];if(!args.filter((_2,i2)=>i2!==sessionIdx&&i2!==sessionIdx+1).some((a)=>!a.startsWith("-")))try{await startNamedSession(sessionName),process.exit(0)}catch(err){console.error(`Error: ${err instanceof Error?err.message:err}`),process.exit(1)}else try{await program2.parseAsync(process.argv)}finally{stopOtelReceiver(),await shutdown().catch(()=>{})}}else try{await program2.parseAsync(process.argv)}finally{stopOtelReceiver(),await shutdown().catch(()=>{})}
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genie",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.260402.1",
|
|
4
4
|
"description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Namastex Labs"
|
package/src/genie.ts
CHANGED
|
@@ -483,14 +483,21 @@ const args = process.argv.slice(2);
|
|
|
483
483
|
|
|
484
484
|
// TUI renderer mode: only when GENIE_TUI_PANE=left AND no subcommand given.
|
|
485
485
|
// Any subcommand (work, spawn, team, etc.) runs normally regardless of env.
|
|
486
|
-
//
|
|
487
|
-
|
|
488
|
-
|
|
486
|
+
// Delete TUI env vars so child processes never inherit them and accidentally
|
|
487
|
+
// launch a second TUI nav (note: `= undefined` sets the STRING "undefined",
|
|
488
|
+
// only `delete` actually removes the var from the environment).
|
|
489
|
+
const isTuiPane = process.env.GENIE_TUI_PANE === 'left' && args.length === 0;
|
|
490
|
+
// biome-ignore lint/performance/noDelete: process.env requires delete — assignment sets the string "undefined"
|
|
491
|
+
delete process.env.GENIE_TUI_PANE;
|
|
492
|
+
// biome-ignore lint/performance/noDelete: process.env requires delete
|
|
493
|
+
delete process.env.GENIE_TUI_RIGHT;
|
|
494
|
+
// biome-ignore lint/performance/noDelete: process.env requires delete
|
|
495
|
+
delete process.env.GENIE_IS_DAEMON;
|
|
496
|
+
if (isTuiPane) {
|
|
489
497
|
const { launchTui } = await import('./tui/index.js');
|
|
490
498
|
await launchTui();
|
|
491
499
|
process.exit(0);
|
|
492
500
|
}
|
|
493
|
-
process.env.GENIE_TUI_PANE = undefined;
|
|
494
501
|
|
|
495
502
|
// Default command: genie (no args) → thin TUI client (attach to serve).
|
|
496
503
|
if (args.length === 0) {
|
|
@@ -193,11 +193,36 @@ function startTuiTmuxServer(): { leftPane: string; rightPane: string } {
|
|
|
193
193
|
// Check if session already exists
|
|
194
194
|
try {
|
|
195
195
|
execSync(tuiTmux(`has-session -t ${TUI_SESSION}`), { stdio: 'ignore' });
|
|
196
|
-
// Session exists — reuse it
|
|
196
|
+
// Session exists — reuse it, but ensure the split is healthy
|
|
197
197
|
const panes = execSync(tuiTmux(`list-panes -t ${TUI_SESSION}:0 -F '#{pane_id}'`), { encoding: 'utf-8' })
|
|
198
198
|
.trim()
|
|
199
199
|
.split('\n');
|
|
200
|
-
|
|
200
|
+
|
|
201
|
+
if (panes.length >= 2) {
|
|
202
|
+
// Both panes exist — clear the right pane so it doesn't show stale content
|
|
203
|
+
// (e.g., a leftover TUI nav renderer from a crashed serve process)
|
|
204
|
+
try {
|
|
205
|
+
execSync(tuiTmux(`respawn-pane -k -t ${panes[1]} 'cat'`), { stdio: 'ignore' });
|
|
206
|
+
} catch {}
|
|
207
|
+
return { leftPane: panes[0], rightPane: panes[1] };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Only 1 pane — re-create the split
|
|
211
|
+
const cols =
|
|
212
|
+
Number.parseInt(
|
|
213
|
+
execSync(tuiTmux(`display-message -t ${TUI_SESSION}:0 -p '#{window_width}'`), { encoding: 'utf-8' }).trim(),
|
|
214
|
+
10,
|
|
215
|
+
) || 120;
|
|
216
|
+
execSync(tuiTmux(`split-window -h -t ${TUI_SESSION}:0 -l ${cols - NAV_WIDTH - 1}`), { stdio: 'ignore' });
|
|
217
|
+
const refreshed = execSync(tuiTmux(`list-panes -t ${TUI_SESSION}:0 -F '#{pane_id}'`), { encoding: 'utf-8' })
|
|
218
|
+
.trim()
|
|
219
|
+
.split('\n');
|
|
220
|
+
applyTuiStyle();
|
|
221
|
+
setupTuiKeybindings();
|
|
222
|
+
try {
|
|
223
|
+
execSync(tuiTmux(`select-pane -t ${refreshed[0]}`), { stdio: 'ignore' });
|
|
224
|
+
} catch {}
|
|
225
|
+
return { leftPane: refreshed[0], rightPane: refreshed[1] || refreshed[0] };
|
|
201
226
|
} catch {
|
|
202
227
|
// Session doesn't exist — create it
|
|
203
228
|
}
|
|
@@ -314,10 +314,20 @@ function spawnAgent(name: string, onTmuxSessionSelect?: (sessionName: string, wi
|
|
|
314
314
|
const agentDir = resolve(join(wsRoot, 'agents', parentName));
|
|
315
315
|
if (existsSync(agentDir)) cwd = agentDir;
|
|
316
316
|
}
|
|
317
|
+
// Strip TUI env vars so spawned agents don't inherit them and accidentally
|
|
318
|
+
// launch a second TUI nav sidebar instead of claude.
|
|
319
|
+
const {
|
|
320
|
+
GENIE_TUI_PANE: _a,
|
|
321
|
+
GENIE_TUI_RIGHT: _b,
|
|
322
|
+
GENIE_TUI_WORKSPACE: _c,
|
|
323
|
+
GENIE_IS_DAEMON: _d,
|
|
324
|
+
...cleanEnv
|
|
325
|
+
} = process.env;
|
|
326
|
+
const spawnOpts = { detached: true, stdio: 'ignore' as const, cwd, env: cleanEnv };
|
|
317
327
|
const child =
|
|
318
328
|
genieBin && genieBin !== 'genie'
|
|
319
|
-
? spawn(bunPath, [genieBin, 'spawn', name, '--session', sessionName],
|
|
320
|
-
: spawn('genie', ['spawn', name, '--session', sessionName],
|
|
329
|
+
? spawn(bunPath, [genieBin, 'spawn', name, '--session', sessionName], spawnOpts)
|
|
330
|
+
: spawn('genie', ['spawn', name, '--session', sessionName], spawnOpts);
|
|
321
331
|
child.unref();
|
|
322
332
|
if (onTmuxSessionSelect) {
|
|
323
333
|
attachSpawnedAgentWhenReady(sessionName, onTmuxSessionSelect);
|