@automagik/genie 4.260328.5 → 4.260328.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,7 +10,7 @@
10
10
  "plugins": [
11
11
  {
12
12
  "name": "genie",
13
- "version": "4.260328.5",
13
+ "version": "4.260328.7",
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
@@ -1487,7 +1487,7 @@ ${updated}/${gitItems.length} items updated successfully.`);return}if(!nameOrVer
1487
1487
  SELECT * FROM team_chat
1488
1488
  WHERE team = ${teamName} AND repo_path = ${repoPath}
1489
1489
  ORDER BY created_at ASC
1490
- `).map(rowToMessage2)}function isSystemNoise(text){let trimmed=text.trimStart();return trimmed.startsWith("<command-name>")||trimmed.startsWith("<command-message>")||trimmed.startsWith("Base directory for this skill:")||trimmed.startsWith("<system-reminder>")||trimmed.startsWith("<local-command")}function transcriptToLogEvent(entry,agent,team){let kindMap={user:"user",assistant:"assistant",system:"system",tool_call:"tool_call",tool_result:"tool_result"},text=entry.text.trim();if(isSystemNoise(text))return null;if(!text)return null;return{timestamp:entry.timestamp,kind:kindMap[entry.role]??"assistant",agent,team,text,data:{role:entry.role,...entry.toolCall?{toolCall:entry.toolCall}:{},...entry.model?{model:entry.model}:{},...entry.usage?{usage:entry.usage}:{}},source:"provider"}}function inboxMessageToLogEvent(msg,agent,team){return{timestamp:msg.createdAt,kind:"message",agent,team,direction:"in",peer:msg.from,text:msg.body,data:{messageId:msg.id,from:msg.from,to:msg.to,read:msg.read},source:"mailbox"}}function outboxMessageToLogEvent(msg,agent,team){return{timestamp:msg.createdAt,kind:"message",agent,team,direction:"out",peer:msg.to,text:msg.body,data:{messageId:msg.id,from:msg.from,to:msg.to},source:"mailbox"}}function chatMessageToLogEvent(msg,team){return{timestamp:msg.timestamp,kind:"message",agent:msg.sender,team,text:msg.body,data:{chatId:msg.id,sender:msg.sender},source:"chat"}}function applyLogFilter(events,filter){if(!filter)return events;let result=events;if(filter.since){let sinceMs=new Date(filter.since).getTime();result=result.filter((e)=>new Date(e.timestamp).getTime()>=sinceMs)}if(filter.kinds&&filter.kinds.length>0){let kinds=new Set(filter.kinds);result=result.filter((e)=>kinds.has(e.kind))}if(filter.last&&filter.last>0)result=result.slice(-filter.last);return result}function sortByTimestamp(events){return events.sort((a,b2)=>new Date(a.timestamp).getTime()-new Date(b2.timestamp).getTime())}async function readAgentLog(agent,repoPath,filter){let{id:agentName,team}=agent,[transcriptEntries,inboxMessages,outboxMessages,chatMessages]=await Promise.all([readTranscriptSafe(agent),inbox(repoPath,agentName),readOutbox(repoPath,agentName),team?readMessages(repoPath,team):Promise.resolve([])]),events=[];for(let entry of transcriptEntries){let event=transcriptToLogEvent(entry,agentName,team);if(event)events.push(event)}for(let msg of inboxMessages)events.push(inboxMessageToLogEvent(msg,agentName,team));for(let msg of outboxMessages)events.push(outboxMessageToLogEvent(msg,agentName,team));if(team)for(let msg of chatMessages)events.push(chatMessageToLogEvent(msg,team));let sorted=sortByTimestamp(events);return applyLogFilter(sorted,filter)}async function readTeamLog(agents,repoPath,teamName,filter){let chatEvents=(await readMessages(repoPath,teamName)).map((msg)=>chatMessageToLogEvent(msg,teamName)),perAgentEvents=await Promise.all(agents.map(async(agent)=>{let agentName=agent.id,[transcriptEntries,inboxMessages,outboxMessages]=await Promise.all([readTranscriptSafe(agent),inbox(repoPath,agentName),readOutbox(repoPath,agentName)]),events=[];for(let entry of transcriptEntries){let event=transcriptToLogEvent(entry,agentName,teamName);if(event)events.push(event)}for(let msg of inboxMessages)events.push(inboxMessageToLogEvent(msg,agentName,teamName));for(let msg of outboxMessages)events.push(outboxMessageToLogEvent(msg,agentName,teamName));return events})),allEvents=[...chatEvents,...perAgentEvents.flat()],sorted=sortByTimestamp(allEvents);return applyLogFilter(sorted,filter)}async function followAgentLog(agent,repoPath,filter,onEvent){return startNatsFollow([agent],repoPath,agent.team,filter,onEvent)}async function followTeamLog(agents,repoPath,teamName,filter,onEvent){return startNatsFollow(agents,repoPath,teamName,filter,onEvent)}async function startNatsFollow(_agents,_repoPath,_team,filter,onEvent){let nats=await Promise.resolve().then(() => (init_nats_client(),exports_nats_client));if(!await nats.isAvailable())throw Error("NATS is not available. Install nats package and ensure NATS server is running.");let kindsFilter=filter?.kinds?new Set(filter.kinds):null,subs=[],seenKeys=new Set,eventKey=(e)=>`${e.timestamp}|${e.kind}|${e.agent}|${e.text.slice(0,80)}`,handleNatsEvent=(_subject,data)=>{let event=data;if(!event?.timestamp||!event?.kind)return;if(kindsFilter&&!kindsFilter.has(event.kind))return;let key=eventKey(event);if(seenKeys.has(key))return;seenKeys.add(key),onEvent(event)},allSub=await nats.subscribe("genie.>",handleNatsEvent);return subs.push(allSub),{mode:"nats",stop:async()=>{for(let sub of subs)sub.unsubscribe()}}}async function readTranscriptSafe(agent){try{let{readTranscript:readTranscript2}=await Promise.resolve().then(() => exports_transcript);return await readTranscript2(agent)}catch{return[]}}function kindIcon(kind){switch(kind){case"user":return"U";case"assistant":return"A";case"message":return"M";case"state":return"S";case"tool_call":return"C";case"tool_result":return"R";case"system":return"*";default:return"?"}}function kindColor(kind){switch(kind){case"user":return"\x1B[33m";case"assistant":return"\x1B[36m";case"message":return"\x1B[35m";case"state":return"\x1B[35m";case"tool_call":return"\x1B[32m";case"tool_result":return"\x1B[90m";case"system":return"\x1B[34m";default:return"\x1B[0m"}}var RESET="\x1B[0m",DIM="\x1B[90m",BOLD="\x1B[1m";function summarizeToolCall2(event){let tc=event.data?.toolCall;if(!tc)return event.text;let input=tc.input;switch(tc.name){case"Read":case"Edit":case"Write":return`${tc.name} ${input.file_path??""}`;case"Bash":return`$ ${String(input.command??"").split(`
1490
+ `).map(rowToMessage2)}function isSystemNoise(text){let trimmed=text.trimStart();return trimmed.startsWith("<command-name>")||trimmed.startsWith("<command-message>")||trimmed.startsWith("Base directory for this skill:")||trimmed.startsWith("<system-reminder>")||trimmed.startsWith("<local-command")}function transcriptToLogEvent(entry,agent,team){let kindMap={user:"user",assistant:"assistant",system:"system",tool_call:"tool_call",tool_result:"tool_result"},text=entry.text.trim();if(isSystemNoise(text))return null;if(!text)return null;return{timestamp:entry.timestamp,kind:kindMap[entry.role]??"assistant",agent,team,text,data:{role:entry.role,...entry.toolCall?{toolCall:entry.toolCall}:{},...entry.model?{model:entry.model}:{},...entry.usage?{usage:entry.usage}:{}},source:"provider"}}function inboxMessageToLogEvent(msg,agent,team){return{timestamp:msg.createdAt,kind:"message",agent,team,direction:"in",peer:msg.from,text:msg.body,data:{messageId:msg.id,from:msg.from,to:msg.to,read:msg.read},source:"mailbox"}}function outboxMessageToLogEvent(msg,agent,team){return{timestamp:msg.createdAt,kind:"message",agent,team,direction:"out",peer:msg.to,text:msg.body,data:{messageId:msg.id,from:msg.from,to:msg.to},source:"mailbox"}}function chatMessageToLogEvent(msg,team){return{timestamp:msg.timestamp,kind:"message",agent:msg.sender,team,text:msg.body,data:{chatId:msg.id,sender:msg.sender},source:"chat"}}function applyLogFilter(events,filter){if(!filter)return events;let result=events;if(filter.since){let sinceMs=new Date(filter.since).getTime();result=result.filter((e)=>new Date(e.timestamp).getTime()>=sinceMs)}if(filter.kinds&&filter.kinds.length>0){let kinds=new Set(filter.kinds);result=result.filter((e)=>kinds.has(e.kind))}if(filter.last&&filter.last>0)result=result.slice(-filter.last);return result}function sortByTimestamp(events){return events.sort((a,b2)=>new Date(a.timestamp).getTime()-new Date(b2.timestamp).getTime())}async function readPgMessages(agentName){try{let ts2=await Promise.resolve().then(() => (init_task_service(),exports_task_service)),actor={actorType:"local",actorId:agentName},conversations=await ts2.listConversations(actor),events=[];for(let conv of conversations){let messages2=await ts2.getMessages(conv.id,{limit:100}),peerMember=(await ts2.getMembers(conv.id)).find((m)=>m.actorId!==agentName);for(let msg of messages2){let isOutgoing=msg.senderId===agentName,peer=isOutgoing?peerMember?.actorId:msg.senderId;events.push({timestamp:msg.createdAt,kind:"message",agent:agentName,direction:isOutgoing?"out":"in",peer,text:msg.body,data:{messageId:msg.id,conversationId:msg.conversationId,senderId:msg.senderId},source:"send"})}}return events}catch{return[]}}async function readAgentLog(agent,repoPath,filter){let{id:agentName,team}=agent,[transcriptEntries,inboxMessages,outboxMessages,chatMessages,pgMessages]=await Promise.all([readTranscriptSafe(agent),inbox(repoPath,agentName),readOutbox(repoPath,agentName),team?readMessages(repoPath,team):Promise.resolve([]),readPgMessages(agentName)]),events=[...pgMessages];for(let entry of transcriptEntries){let event=transcriptToLogEvent(entry,agentName,team);if(event)events.push(event)}for(let msg of inboxMessages)events.push(inboxMessageToLogEvent(msg,agentName,team));for(let msg of outboxMessages)events.push(outboxMessageToLogEvent(msg,agentName,team));if(team)for(let msg of chatMessages)events.push(chatMessageToLogEvent(msg,team));let sorted=sortByTimestamp(events);return applyLogFilter(sorted,filter)}async function readTeamLog(agents,repoPath,teamName,filter){let chatEvents=(await readMessages(repoPath,teamName)).map((msg)=>chatMessageToLogEvent(msg,teamName)),perAgentEvents=await Promise.all(agents.map(async(agent)=>{let agentName=agent.id,[transcriptEntries,inboxMessages,outboxMessages,pgMessages]=await Promise.all([readTranscriptSafe(agent),inbox(repoPath,agentName),readOutbox(repoPath,agentName),readPgMessages(agentName)]),events=[...pgMessages];for(let entry of transcriptEntries){let event=transcriptToLogEvent(entry,agentName,teamName);if(event)events.push(event)}for(let msg of inboxMessages)events.push(inboxMessageToLogEvent(msg,agentName,teamName));for(let msg of outboxMessages)events.push(outboxMessageToLogEvent(msg,agentName,teamName));return events})),allEvents=[...chatEvents,...perAgentEvents.flat()],sorted=sortByTimestamp(allEvents);return applyLogFilter(sorted,filter)}async function followAgentLog(agent,repoPath,filter,onEvent){return startNatsFollow([agent],repoPath,agent.team,filter,onEvent)}async function followTeamLog(agents,repoPath,teamName,filter,onEvent){return startNatsFollow(agents,repoPath,teamName,filter,onEvent)}async function startNatsFollow(_agents,_repoPath,_team,filter,onEvent){let nats=await Promise.resolve().then(() => (init_nats_client(),exports_nats_client));if(!await nats.isAvailable())throw Error("NATS is not available. Install nats package and ensure NATS server is running.");let kindsFilter=filter?.kinds?new Set(filter.kinds):null,subs=[],seenKeys=new Set,eventKey=(e)=>`${e.timestamp}|${e.kind}|${e.agent}|${e.text.slice(0,80)}`,handleNatsEvent=(_subject,data)=>{let event=data;if(!event?.timestamp||!event?.kind)return;if(kindsFilter&&!kindsFilter.has(event.kind))return;let key=eventKey(event);if(seenKeys.has(key))return;seenKeys.add(key),onEvent(event)},allSub=await nats.subscribe("genie.>",handleNatsEvent);return subs.push(allSub),{mode:"nats",stop:async()=>{for(let sub of subs)sub.unsubscribe()}}}async function readTranscriptSafe(agent){try{let{readTranscript:readTranscript2}=await Promise.resolve().then(() => exports_transcript);return await readTranscript2(agent)}catch{return[]}}function kindIcon(kind){switch(kind){case"user":return"U";case"assistant":return"A";case"message":return"M";case"state":return"S";case"tool_call":return"C";case"tool_result":return"R";case"system":return"*";default:return"?"}}function kindColor(kind){switch(kind){case"user":return"\x1B[33m";case"assistant":return"\x1B[36m";case"message":return"\x1B[35m";case"state":return"\x1B[35m";case"tool_call":return"\x1B[32m";case"tool_result":return"\x1B[90m";case"system":return"\x1B[34m";default:return"\x1B[0m"}}var RESET="\x1B[0m",DIM="\x1B[90m",BOLD="\x1B[1m";function summarizeToolCall2(event){let tc=event.data?.toolCall;if(!tc)return event.text;let input=tc.input;switch(tc.name){case"Read":case"Edit":case"Write":return`${tc.name} ${input.file_path??""}`;case"Bash":return`$ ${String(input.command??"").split(`
1491
1491
  `)[0]}`;case"Grep":return`Grep "${input.pattern}" ${input.path??""}`;case"Glob":return`Glob ${input.pattern}`;case"Agent":return`Agent: ${input.description??""}`;case"SendMessage":return`SendMessage \u2192 ${input.to}: ${String(input.message??"").slice(0,80)}`;case"shell":case"exec_command":return`$ ${(Array.isArray(input.command)?input.command.join(" "):String(input.command??"")).split(`
1492
1492
  `)[0]}`;case"web_search":return`Search: ${input.query??""}`;default:return`${tc.name}`}}function formatEventBlock(event){let time=formatTime(event.timestamp,{seconds:!0,fallback:"??:??:??"}),icon=kindIcon(event.kind),color=kindColor(event.kind),agent=event.agent;if(event.direction==="in")agent=`${event.peer} \u2192 ${event.agent}`;else if(event.direction==="out")agent=`${event.agent} \u2192 ${event.peer}`;let header=`${DIM}${time}${RESET} ${color}[${icon}]${RESET} ${BOLD}${agent}${RESET}`;if(event.kind==="tool_call"){let summary=summarizeToolCall2(event);return`${header} ${DIM}${summary}${RESET}`}if(event.kind==="tool_result"){let line=event.text.split(`
1493
1493
  `)[0].slice(0,120);return`${header} ${DIM}${line}${RESET}`}let text=event.text.trim();if(text.length<80&&!text.includes(`
@@ -1497,12 +1497,12 @@ ${updated}/${gitItems.length} items updated successfully.`);return}if(!nameOrVer
1497
1497
  `);return`${header}
1498
1498
  ${indented}`}function formatHumanOutput(events,label){let lines=[];if(lines.push(""),lines.push(`${BOLD}Log: ${label}${RESET} (${events.length} events)`),lines.push(""),events.length===0)return lines.push(" No events found."),lines.push(""),lines.join(`
1499
1499
  `);let lastKind=null;for(let event of events){if(lastKind!==null&&!(lastKind==="tool_call"&&event.kind==="tool_call"))lines.push("");lines.push(formatEventBlock(event)),lastKind=event.kind}return lines.push(""),lines.join(`
1500
- `)}async function findAgent(identifier){let agent=await get(identifier);if(agent)return agent;if(agent=await findByTask(identifier),agent)return agent;return(await list()).find((w)=>w.id.includes(identifier)||w.taskId?.includes(identifier)||w.taskTitle?.toLowerCase().includes(identifier.toLowerCase()))??null}async function findTeamAgents(teamName){return(await list()).filter((a)=>a.team===teamName)}function buildFilter2(options){let filter={},hasFilter=!1;if(options.last&&options.last>0)filter.last=options.last,hasFilter=!0;if(options.since)filter.since=options.since,hasFilter=!0;if(options.type)filter.kinds=[options.type],hasFilter=!0;return hasFilter?filter:void 0}function outputNdjson(events){for(let event of events)process.stdout.write(`${JSON.stringify(event)}
1500
+ `)}async function findAgent(identifier,teamName){let agent=await get(identifier);if(agent)return agent;if(agent=await findByTask(identifier),agent)return agent;let all=await list(),teamMatch=(teamName?all.filter((a)=>a.team===teamName):[]).find((w)=>w.id.includes(identifier)||w.taskId?.includes(identifier)||w.taskTitle?.toLowerCase().includes(identifier.toLowerCase()));if(teamMatch)return teamMatch;return all.find((w)=>w.id.includes(identifier)||w.taskId?.includes(identifier)||w.taskTitle?.toLowerCase().includes(identifier.toLowerCase()))??null}async function findTeamAgents(teamName){return(await list()).filter((a)=>a.team===teamName)}function buildFilter2(options){let filter={},hasFilter=!1;if(options.last&&options.last>0)filter.last=options.last,hasFilter=!0;if(options.since)filter.since=options.since,hasFilter=!0;if(options.type)filter.kinds=[options.type],hasFilter=!0;return hasFilter?filter:void 0}function outputNdjson(events){for(let event of events)process.stdout.write(`${JSON.stringify(event)}
1501
1501
  `)}function outputJson(events){process.stdout.write(`${JSON.stringify(events,null,2)}
1502
- `)}async function logCommand(agentName,options){let repoPath=process.cwd(),filter=buildFilter2(options);if(options.follow){await followCommand(agentName,options,repoPath,filter);return}let events,label;if(options.team){let agents=await findTeamAgents(options.team);if(agents.length===0)console.error(`No agents found for team "${options.team}".`),process.exit(1);events=await readTeamLog(agents,repoPath,options.team,filter),label=`team:${options.team} (${agents.length} agents)`}else if(agentName){let agent=await findAgent(agentName);if(!agent)console.error(`Agent "${agentName}" not found. Run \`genie ls\` to see agents.`),process.exit(1);events=await readAgentLog(agent,repoPath,filter),label=agent.id}else{let allAgents=await list();if(allAgents.length===0)console.error("No agents found. Run `genie ls` to see agents."),process.exit(1);events=await readTeamLog(allAgents,repoPath,"all",filter),label=`all agents (${allAgents.length})`}if(options.ndjson){outputNdjson(events);return}if(options.json){outputJson(events);return}console.log(formatHumanOutput(events,label))}async function followCommand(agentName,options,repoPath,filter){let lastFollowKind=null,outputEvent=(event)=>{if(options.ndjson)process.stdout.write(`${JSON.stringify(event)}
1502
+ `)}async function logCommand(agentName,options){let repoPath=process.cwd(),filter=buildFilter2(options);if(options.follow){await followCommand(agentName,options,repoPath,filter);return}let events,label;if(options.team){let agents=await findTeamAgents(options.team);if(agents.length===0)console.error(`No agents found for team "${options.team}".`),process.exit(1);events=await readTeamLog(agents,repoPath,options.team,filter),label=`team:${options.team} (${agents.length} agents)`}else if(agentName){let agent=await findAgent(agentName,options.team);if(!agent)console.error(`Agent "${agentName}" not found. Run \`genie ls\` to see agents.`),process.exit(1);events=await readAgentLog(agent,repoPath,filter),label=agent.id}else{let allAgents=await list();if(allAgents.length===0)console.error("No agents found. Run `genie ls` to see agents."),process.exit(1);events=await readTeamLog(allAgents,repoPath,"all",filter),label=`all agents (${allAgents.length})`}if(options.ndjson){outputNdjson(events);return}if(options.json){outputJson(events);return}console.log(formatHumanOutput(events,label))}async function followCommand(agentName,options,repoPath,filter){let lastFollowKind=null,outputEvent=(event)=>{if(options.ndjson)process.stdout.write(`${JSON.stringify(event)}
1503
1503
  `);else{if(lastFollowKind!==null&&!(lastFollowKind==="tool_call"&&event.kind==="tool_call"))process.stdout.write(`
1504
1504
  `);process.stdout.write(`${formatEventBlock(event)}
1505
- `),lastFollowKind=event.kind}},label;if(options.team){let agents=await findTeamAgents(options.team);if(agents.length===0)console.error(`No agents found for team "${options.team}".`),process.exit(1);label=`team:${options.team} (${agents.length} agents)`;let handle=await followTeamLog(agents,repoPath,options.team,filter,outputEvent);console.error(`Following ${label} via ${handle.mode==="nats"?"NATS":"file polling"} (Ctrl+C to stop)...`),setupShutdown(handle.stop)}else if(agentName){let agent=await findAgent(agentName);if(!agent)console.error(`Agent "${agentName}" not found. Run \`genie ls\` to see agents.`),process.exit(1);label=agent.id;let handle=await followAgentLog(agent,repoPath,filter,outputEvent);console.error(`Following ${label} via ${handle.mode==="nats"?"NATS":"file polling"} (Ctrl+C to stop)...`),setupShutdown(handle.stop)}else{let allAgents=await list();if(allAgents.length===0)console.error("No agents found. Run `genie ls` to see agents."),process.exit(1);label=`all agents (${allAgents.length})`;let handle=await followTeamLog(allAgents,repoPath,"all",filter,outputEvent);console.error(`Following ${label} via ${handle.mode==="nats"?"NATS":"file polling"} (Ctrl+C to stop)...`),setupShutdown(handle.stop)}await new Promise(()=>{})}function setupShutdown(stop){let shutdown2=async()=>{await stop(),process.exit(0)};process.on("SIGINT",shutdown2),process.on("SIGTERM",shutdown2)}init_db();function parseSince2(since){let match=since.match(/^(\d+)([smhd])$/);if(!match)return since;let amount=Number.parseInt(match[1],10),unit=match[2],ms={s:1000,m:60000,h:3600000,d:86400000}[unit]??3600000;return new Date(Date.now()-amount*ms).toISOString()}async function metricsNowCommand(options){if(!await isAvailable())console.error("Database not available."),process.exit(1);let sql=await getConnection(),snapshots=await sql`SELECT * FROM machine_snapshots ORDER BY created_at DESC LIMIT 1`,agentCount=await sql`SELECT count(*)::int as cnt FROM agents WHERE state NOT IN ('done', 'error', 'suspended')`,teamCount=await sql`SELECT count(*)::int as cnt FROM teams WHERE status = 'in_progress'`,snapshot=snapshots[0]??{},data={active_workers:snapshot.active_workers??agentCount[0]?.cnt??0,active_teams:snapshot.active_teams??teamCount[0]?.cnt??0,tmux_sessions:snapshot.tmux_sessions??0,cpu_percent:snapshot.cpu_percent??null,memory_mb:snapshot.memory_mb??null,snapshot_at:snapshot.created_at?new Date(snapshot.created_at).toISOString():null};if(options.json){console.log(JSON.stringify(data,null,2));return}if(console.log("Machine State:"),console.log(` Workers: ${data.active_workers}`),console.log(` Teams: ${data.active_teams}`),console.log(` Tmux: ${data.tmux_sessions} sessions`),data.cpu_percent!==null)console.log(` CPU: ${data.cpu_percent}%`);if(data.memory_mb!==null)console.log(` Memory: ${data.memory_mb} MB`);if(data.snapshot_at)console.log(` As of: ${formatRelativeTimestamp(data.snapshot_at)}`)}async function metricsHistoryCommand(options){if(!await isAvailable())console.error("Database not available."),process.exit(1);let sql=await getConnection(),sinceTs=parseSince2(options.since??"1h"),rows=await sql`
1505
+ `),lastFollowKind=event.kind}},label;if(options.team){let agents=await findTeamAgents(options.team);if(agents.length===0)console.error(`No agents found for team "${options.team}".`),process.exit(1);label=`team:${options.team} (${agents.length} agents)`;let handle=await followTeamLog(agents,repoPath,options.team,filter,outputEvent);console.error(`Following ${label} via ${handle.mode==="nats"?"NATS":"file polling"} (Ctrl+C to stop)...`),setupShutdown(handle.stop)}else if(agentName){let agent=await findAgent(agentName,options.team);if(!agent)console.error(`Agent "${agentName}" not found. Run \`genie ls\` to see agents.`),process.exit(1);label=agent.id;let handle=await followAgentLog(agent,repoPath,filter,outputEvent);console.error(`Following ${label} via ${handle.mode==="nats"?"NATS":"file polling"} (Ctrl+C to stop)...`),setupShutdown(handle.stop)}else{let allAgents=await list();if(allAgents.length===0)console.error("No agents found. Run `genie ls` to see agents."),process.exit(1);label=`all agents (${allAgents.length})`;let handle=await followTeamLog(allAgents,repoPath,"all",filter,outputEvent);console.error(`Following ${label} via ${handle.mode==="nats"?"NATS":"file polling"} (Ctrl+C to stop)...`),setupShutdown(handle.stop)}await new Promise(()=>{})}function setupShutdown(stop){let shutdown2=async()=>{await stop(),process.exit(0)};process.on("SIGINT",shutdown2),process.on("SIGTERM",shutdown2)}init_db();function parseSince2(since){let match=since.match(/^(\d+)([smhd])$/);if(!match)return since;let amount=Number.parseInt(match[1],10),unit=match[2],ms={s:1000,m:60000,h:3600000,d:86400000}[unit]??3600000;return new Date(Date.now()-amount*ms).toISOString()}async function metricsNowCommand(options){if(!await isAvailable())console.error("Database not available."),process.exit(1);let sql=await getConnection(),snapshots=await sql`SELECT * FROM machine_snapshots ORDER BY created_at DESC LIMIT 1`,agentCount=await sql`SELECT count(*)::int as cnt FROM agents WHERE state NOT IN ('done', 'error', 'suspended')`,teamCount=await sql`SELECT count(*)::int as cnt FROM teams WHERE status = 'in_progress'`,snapshot=snapshots[0]??{},data={active_workers:snapshot.active_workers??agentCount[0]?.cnt??0,active_teams:snapshot.active_teams??teamCount[0]?.cnt??0,tmux_sessions:snapshot.tmux_sessions??0,cpu_percent:snapshot.cpu_percent??null,memory_mb:snapshot.memory_mb??null,snapshot_at:snapshot.created_at?new Date(snapshot.created_at).toISOString():null};if(options.json){console.log(JSON.stringify(data,null,2));return}if(console.log("Machine State:"),console.log(` Workers: ${data.active_workers}`),console.log(` Teams: ${data.active_teams}`),console.log(` Tmux: ${data.tmux_sessions} sessions`),data.cpu_percent!==null)console.log(` CPU: ${data.cpu_percent}%`);if(data.memory_mb!==null)console.log(` Memory: ${data.memory_mb} MB`);if(data.snapshot_at)console.log(` As of: ${formatRelativeTimestamp(data.snapshot_at)}`)}async function metricsHistoryCommand(options){if(!await isAvailable())console.error("Database not available."),process.exit(1);let sql=await getConnection(),sinceTs=parseSince2(options.since??"1h"),rows=await sql`
1506
1506
  SELECT active_workers, active_teams, tmux_sessions, cpu_percent, memory_mb, created_at
1507
1507
  FROM machine_snapshots
1508
1508
  WHERE created_at >= ${sinceTs}::timestamptz
@@ -1684,7 +1684,7 @@ Stopped following`),process.exit(0)}),await new Promise(()=>{});return}let conte
1684
1684
  LIMIT ${limit}
1685
1685
  `;if(rows.length===0){console.log(`No execution history for schedule "${schedule.name}"`),await shutdown();return}console.log(`
1686
1686
  History for "${schedule.name}":
1687
- `);let tableRows=rows.map((r)=>[formatTimestamp2(r.due_at),r.trigger_status,r.run_status??"-",formatDuration(r.duration_ms),r.error?r.error.slice(0,60):"-"]);printTable2(["DUE AT","TRIGGER","RUN","DURATION","ERROR"],tableRows),await shutdown()}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}function registerScheduleCommands(program2){let schedule=program2.command("schedule").description("Manage scheduled triggers");schedule.command("create <name>").description("Create a new schedule").requiredOption("--command <cmd>",'Command to execute (e.g., "genie spawn reviewer")').option("--at <time>","One-time schedule at absolute time (ISO 8601)").option("--every <interval>","Repeating schedule: duration (10m, 2h, 24h) or cron expression").option("--after <duration>","One-time schedule after delay (10m, 2h)").option("--timezone <tz>","Timezone for schedule (default: UTC)","UTC").option("--lease-timeout <duration>","Lease timeout for runs (default: 5m)").action(async(name,options)=>{await scheduleCreateCommand(name,options)}),schedule.command("list").description("List schedules with next due trigger").option("--json","Output as JSON").option("--watch","Refresh every 2s").action(async(options)=>{await scheduleListCommand(options)}),schedule.command("cancel <name>").description("Cancel a schedule and skip pending triggers").option("--filter <expr>","Filter expression (e.g., status=pending)").action(async(name,options)=>{await scheduleCancelCommand(name,options)}),schedule.command("retry <name>").description("Reset a failed trigger to pending").action(async(name)=>{await scheduleRetryCommand(name)}),schedule.command("history <name>").description("Show past executions for a schedule").option("--limit <n>","Max rows to show (default: 20)",Number.parseInt).action(async(name,options)=>{await scheduleHistoryCommand(name,options)})}init_db();init_session_backfill();async function sessionsListCommand(options){if(!await isAvailable())console.error("Database not available."),process.exit(1);let sql=await getConnection(),rows;if(options.active)rows=await sql`SELECT * FROM sessions WHERE status = 'active' ORDER BY started_at DESC LIMIT 50`;else if(options.orphaned)rows=await sql`SELECT * FROM sessions WHERE status = 'orphaned' ORDER BY started_at DESC LIMIT 50`;else if(options.agent)rows=await sql`SELECT * FROM sessions WHERE agent_id = ${options.agent} OR agent_id LIKE ${`%${options.agent}%`} ORDER BY started_at DESC LIMIT 50`;else rows=await sql`SELECT * FROM sessions ORDER BY started_at DESC LIMIT 50`;if(options.json){console.log(JSON.stringify(rows,null,2));return}if(rows.length===0){console.log("No sessions found.");return}let headers=["ID","Agent","Team","Status","Turns","Started"],data=rows.map((r)=>[r.id.slice(0,12),r.agent_id??"(orphaned)",r.team??"-",r.status,String(r.total_turns??0),formatRelativeTimestamp(r.started_at??r.created_at)]),widths=headers.map((h,i2)=>{let colVals=data.map((row)=>row[i2]);return Math.min(30,Math.max(h.length,...colVals.map((v)=>v.length)))});console.log(headers.map((h,i2)=>padRight(h,widths[i2])).join(" | ")),console.log(widths.map((w)=>"-".repeat(w)).join("-+-"));for(let row of data)console.log(row.map((v,i2)=>padRight(v.slice(0,widths[i2]),widths[i2])).join(" | "));console.log(`
1687
+ `);let tableRows=rows.map((r)=>[formatTimestamp2(r.due_at),r.trigger_status,r.run_status??"-",formatDuration(r.duration_ms),r.error?r.error.slice(0,60):"-"]);printTable2(["DUE AT","TRIGGER","RUN","DURATION","ERROR"],tableRows),await shutdown()}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}function registerScheduleCommands(program2){let schedule=program2.command("schedule").description("Manage scheduled triggers");schedule.command("create <name>").description("Create a new schedule").requiredOption("--command <cmd>",'Command to execute (e.g., "genie spawn reviewer")').option("--at <time>","One-time schedule at absolute time (ISO 8601)").option("--every <interval>","Repeating schedule: duration (10m, 2h, 24h) or cron expression").option("--after <duration>","One-time schedule after delay (10m, 2h)").option("--timezone <tz>","Timezone for schedule (default: UTC)","UTC").option("--lease-timeout <duration>","Lease timeout for runs (default: 5m)").action(async(name,options)=>{await scheduleCreateCommand(name,options)}),schedule.command("list").description("List schedules with next due trigger").option("--json","Output as JSON").option("--watch","Refresh every 2s").action(async(options)=>{await scheduleListCommand(options)}),schedule.command("cancel <name>").description("Cancel a schedule and skip pending triggers").option("--filter <expr>","Filter expression (e.g., status=pending)").action(async(name,options)=>{await scheduleCancelCommand(name,options)}),schedule.command("retry <name>").description("Reset a failed trigger to pending").action(async(name)=>{await scheduleRetryCommand(name)}),schedule.command("history <name>").description("Show past executions for a schedule").option("--limit <n>","Max rows to show (default: 20)",Number.parseInt).action(async(name,options)=>{await scheduleHistoryCommand(name,options)})}init_db();init_session_backfill();async function sessionsListCommand(options){if(!await isAvailable())console.error("Database not available."),process.exit(1);let sql=await getConnection(),limit=Number(options.limit)||50,rows;if(options.active)rows=await sql`SELECT * FROM sessions WHERE status = 'active' ORDER BY started_at DESC LIMIT ${limit}`;else if(options.orphaned)rows=await sql`SELECT * FROM sessions WHERE status = 'orphaned' ORDER BY started_at DESC LIMIT ${limit}`;else if(options.agent)rows=await sql`SELECT * FROM sessions WHERE agent_id = ${options.agent} OR agent_id LIKE ${`%${options.agent}%`} ORDER BY started_at DESC LIMIT ${limit}`;else rows=await sql`SELECT * FROM sessions ORDER BY started_at DESC LIMIT ${limit}`;if(options.json){console.log(JSON.stringify(rows,null,2));return}if(rows.length===0){console.log("No sessions found.");return}let headers=["ID","Agent","Team","Status","Turns","Started"],data=rows.map((r)=>[r.id.slice(0,12),r.agent_id??"(orphaned)",r.team??"-",r.status,String(r.total_turns??0),formatRelativeTimestamp(r.started_at??r.created_at)]),widths=headers.map((h,i2)=>{let colVals=data.map((row)=>row[i2]);return Math.min(30,Math.max(h.length,...colVals.map((v)=>v.length)))});console.log(headers.map((h,i2)=>padRight(h,widths[i2])).join(" | ")),console.log(widths.map((w)=>"-".repeat(w)).join("-+-"));for(let row of data)console.log(row.map((v,i2)=>padRight(v.slice(0,widths[i2]),widths[i2])).join(" | "));console.log(`
1688
1688
  (${rows.length} session${rows.length===1?"":"s"})`)}function buildTimeline(content,events){let timeline=[];for(let c of content){let prefix=c.role==="assistant"?"[assistant]":c.role==="tool_input"?"[tool_in]":"[tool_out]",toolLabel=c.tool_name?` [${c.tool_name}]`:"";timeline.push({ts:c.timestamp,type:`${prefix}${toolLabel}`,text:c.content.slice(0,200)})}for(let e of events)timeline.push({ts:e.created_at,type:`[event] ${e.event_type}`,text:JSON.stringify(e.details).slice(0,100)});return timeline.sort((a,b2)=>new Date(a.ts).getTime()-new Date(b2.ts).getTime()),timeline}async function sessionsReplayCommand(sessionId,options){if(!await isAvailable())console.error("Database not available."),process.exit(1);let sql=await getConnection(),sessions=await sql`SELECT * FROM sessions WHERE id = ${sessionId}`;if(sessions.length===0)console.error(`Session "${sessionId}" not found.`),process.exit(1);let content=await sql`
1689
1689
  SELECT turn_index, role, content, tool_name, timestamp
1690
1690
  FROM session_content
@@ -1707,7 +1707,7 @@ History for "${schedule.name}":
1707
1707
  ORDER BY sc.timestamp DESC
1708
1708
  LIMIT ${limit}
1709
1709
  `;if(options.json){console.log(JSON.stringify(rows,null,2));return}if(rows.length===0){console.log(`No results for "${query}".`);return}for(let r of rows)console.log(`[${formatRelativeTimestamp(r.timestamp)}] ${r.agent_id??"orphaned"} / ${r.session_id.slice(0,12)}`),console.log(` ${r.role}${r.tool_name?` [${r.tool_name}]`:""}: ${r.headline}`);console.log(`
1710
- (${rows.length} result${rows.length===1?"":"s"})`)}async function sessionsSyncStatusCommand(){if(!await isAvailable())console.error("Database not available."),process.exit(1);let sql=await getConnection(),status=await getBackfillStatus(sql);if(!status){console.log("No backfill has been started. It runs automatically on first daemon start.");return}let pct=status.totalFiles>0?(status.processedFiles/status.totalFiles*100).toFixed(1):"0.0",mbRead=(status.processedBytes/1024/1024).toFixed(1),mbTotal=(status.totalBytes/1024/1024).toFixed(1);console.log(`Session backfill: ${status.processedFiles} / ${status.totalFiles} files (${pct}%)`),console.log(`Bytes read: ${mbRead} MB / ${mbTotal} MB`),console.log(`Errors: ${status.errors}`),console.log(`Status: ${status.status}`)}function registerSessionsCommands(program2){let sessions=program2.command("sessions").description("Session history \u2014 list, replay, search");sessions.command("list",{isDefault:!0}).description("List Claude Code sessions").option("--active","Show only active sessions").option("--orphaned","Show only orphaned sessions").option("--agent <name>","Filter by agent").option("--json","Output as JSON").action(async(options)=>{await sessionsListCommand(options)}),sessions.command("replay <session-id>").description("Replay a session \u2014 interleave content + events").option("--json","Output as JSON").action(async(sessionId,options)=>{await sessionsReplayCommand(sessionId,options)}),sessions.command("search <query>").description("Full-text search across session content").option("--json","Output as JSON").option("--limit <n>","Max results","20").action(async(query,options)=>{await sessionsSearchCommand(query,options)}),sessions.command("sync").description("Check session backfill progress").action(async()=>{await sessionsSyncStatusCommand()})}var _taskService6;async function getTaskService6(){if(!_taskService6)_taskService6=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService6}function registerTagCommands(program2){let tag=program2.command("tag").description("Tag management");tag.command("list").description("List all tags").option("--type <typeId>","Filter by task type").option("--json","Output as JSON").action(async(options)=>{try{let tags=await(await getTaskService6()).listTags(options.type);if(options.json){console.log(JSON.stringify(tags,null,2));return}console.log(` ${padRight("ID",20)} ${padRight("NAME",20)} ${padRight("COLOR",10)} TYPE`),console.log(` ${"\u2500".repeat(55)}`);for(let t of tags)console.log(` ${padRight(t.id,20)} ${padRight(t.name,20)} ${padRight(t.color,10)} ${t.typeId??"-"}`);console.log(`
1710
+ (${rows.length} result${rows.length===1?"":"s"})`)}async function sessionsSyncStatusCommand(){if(!await isAvailable())console.error("Database not available."),process.exit(1);let sql=await getConnection(),status=await getBackfillStatus(sql);if(!status){console.log("No backfill has been started. It runs automatically on first daemon start.");return}let pct=status.totalFiles>0?(status.processedFiles/status.totalFiles*100).toFixed(1):"0.0",mbRead=(status.processedBytes/1024/1024).toFixed(1),mbTotal=(status.totalBytes/1024/1024).toFixed(1);console.log(`Session backfill: ${status.processedFiles} / ${status.totalFiles} files (${pct}%)`),console.log(`Bytes read: ${mbRead} MB / ${mbTotal} MB`),console.log(`Errors: ${status.errors}`),console.log(`Status: ${status.status}`)}function registerSessionsCommands(program2){let sessions=program2.command("sessions").description("Session history \u2014 list, replay, search");sessions.command("list",{isDefault:!0}).description("List Claude Code sessions").option("--active","Show only active sessions").option("--orphaned","Show only orphaned sessions").option("--agent <name>","Filter by agent").option("--limit <n>","Max number of sessions to return (default: 50)").option("--json","Output as JSON").action(async(options)=>{await sessionsListCommand(options)}),sessions.command("replay <session-id>").description("Replay a session \u2014 interleave content + events").option("--json","Output as JSON").action(async(sessionId,options)=>{await sessionsReplayCommand(sessionId,options)}),sessions.command("search <query>").description("Full-text search across session content").option("--json","Output as JSON").option("--limit <n>","Max results","20").action(async(query,options)=>{await sessionsSearchCommand(query,options)}),sessions.command("sync").description("Check session backfill progress").action(async()=>{await sessionsSyncStatusCommand()})}var _taskService6;async function getTaskService6(){if(!_taskService6)_taskService6=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService6}function registerTagCommands(program2){let tag=program2.command("tag").description("Tag management");tag.command("list").description("List all tags").option("--type <typeId>","Filter by task type").option("--json","Output as JSON").action(async(options)=>{try{let tags=await(await getTaskService6()).listTags(options.type);if(options.json){console.log(JSON.stringify(tags,null,2));return}console.log(` ${padRight("ID",20)} ${padRight("NAME",20)} ${padRight("COLOR",10)} TYPE`),console.log(` ${"\u2500".repeat(55)}`);for(let t of tags)console.log(` ${padRight(t.id,20)} ${padRight(t.name,20)} ${padRight(t.color,10)} ${t.typeId??"-"}`);console.log(`
1711
1711
  ${tags.length} tag${tags.length===1?"":"s"}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),tag.command("create <name>").description("Create a custom tag").option("--color <hex>","Tag color (hex)","#9ca3af").option("--type <typeId>","Associate with a task type").action(async(name,options)=>{try{let ts2=await getTaskService6(),id=name.toLowerCase().replace(/\s+/g,"-"),t=await ts2.createTag({id,name,color:options.color,typeId:options.type});console.log(`Created tag "${t.name}" (${t.id}) with color ${t.color}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}var _taskService7;async function getTaskService7(){if(!_taskService7)_taskService7=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService7}var _boardService2;async function getBoardService2(){if(!_boardService2)_boardService2=await Promise.resolve().then(() => (init_board_service(),exports_board_service));return _boardService2}function localActor2(name){return{actorType:"local",actorId:name}}function currentActor2(){let name=process.env.GENIE_AGENT_NAME??"cli";return localActor2(name)}function getRunId(){return process.env.GENIE_RUN_ID??`run-${Date.now()}`}var PRIORITY_COLORS={urgent:"\x1B[31m",high:"\x1B[33m",normal:"\x1B[0m",low:"\x1B[90m"},RESET2="\x1B[0m";async function resolveDefaultBoardId(){try{let{execSync:execSync11}=await import("child_process"),repoRoot=execSync11("git rev-parse --show-toplevel",{encoding:"utf-8"}).trim(),{join:join42}=await import("path"),configPath2=join42(repoRoot,".genie","config.json"),{existsSync:existsSync30,readFileSync:readFileSync15}=await import("fs");if(existsSync30(configPath2)){let config=JSON.parse(readFileSync15(configPath2,"utf-8"));if(config.activeBoard)return config.activeBoard}}catch{}return null}async function handleInvalidStageError(taskId,message){try{let task=await(await getTaskService7()).getTask(taskId);if(!task?.boardId)return;let board=await(await getBoardService2()).getBoard(task.boardId);if(!board)return;let validCols=board.columns.sort((a,b2)=>a.position-b2.position).map((c)=>c.name).join(" \u2192 ");console.error(`Error: ${message}
1712
1712
  Valid columns for board "${board.name}": ${validCols}`),process.exit(1)}catch{}}async function resolveBoardOption(boardName){if(boardName){let board=await(await getBoardService2()).getBoard(boardName);if(!board)console.error(`Error: Board not found: ${boardName}`),process.exit(1);return board.id}return await resolveDefaultBoardId()??void 0}function getProjectName(repoPath){let parts=repoPath.split("/");return parts[parts.length-1]||repoPath}function printTaskList(tasks,showProject=!1){if(tasks.length===0){console.log("No tasks found.");return}let projCol=showProject?`${padRight("PROJECT",16)} `:"",header=` ${padRight("#",6)} ${projCol}${padRight("TITLE",40)} ${padRight("STAGE",12)} ${padRight("STATUS",12)} ${padRight("PRIORITY",10)} ${padRight("DUE",12)}`,lineLen=showProject?108:92;console.log(header),console.log(` ${"\u2500".repeat(lineLen)}`);for(let t of tasks){let seq2=showProject?`${getProjectName(t.repoPath)}#${t.seq}`:`#${t.seq}`,title=truncate(t.title,38),color=PRIORITY_COLORS[t.priority]??"",due=formatDate(t.dueDate),proj=showProject?`${padRight(getProjectName(t.repoPath),16)} `:"";console.log(` ${padRight(seq2,showProject?22:6)} ${proj}${padRight(title,40)} ${padRight(t.stage,12)} ${padRight(t.status,12)} ${color}${padRight(t.priority,10)}${RESET2} ${padRight(due,12)}`)}console.log(`
1713
1713
  ${tasks.length} task${tasks.length===1?"":"s"}`)}function printTaskFields(task){console.log(""),console.log(`Task #${task.seq}: ${task.title}`),console.log("\u2500".repeat(60)),console.log(` ID: ${task.id}`),console.log(` Type: ${task.typeId}`),console.log(` Stage: ${task.stage}`),console.log(` Status: ${task.status}`),console.log(` Priority: ${task.priority}`);let optionalFields=[["Description",task.description],["Criteria",task.acceptanceCriteria],["Effort",task.estimatedEffort],["Start",task.startDate?formatDate(task.startDate):null],["Due",task.dueDate?formatDate(task.dueDate):null],["Blocked",task.blockedReason],["Parent",task.parentId],["Release",task.releaseId],["Wish",task.wishFile]];for(let[label,value]of optionalFields)if(value)console.log(` ${padRight(`${label}:`,12)} ${value}`);if(task.checkoutRunId)console.log(` Checkout: ${task.checkoutRunId} (since ${formatTimestamp(task.executionLockedAt)})`);if(console.log(` Created: ${formatTimestamp(task.createdAt)}`),task.startedAt)console.log(` Started: ${formatTimestamp(task.startedAt)}`);if(task.endedAt)console.log(` Ended: ${formatTimestamp(task.endedAt)}`)}async function printTaskRelations(task){let ts2=await getTaskService7(),actors=await ts2.getTaskActors(task.id,task.repoPath);if(actors.length>0){console.log(`
@@ -1717,7 +1717,7 @@ Valid columns for board "${board.name}": ${validCols}`),process.exit(1)}catch{}}
1717
1717
  Stage History:`);for(let entry of stageLog.slice(0,10)){let who=entry.actorId??"system";console.log(` ${formatTimestamp(entry.createdAt)}: ${entry.fromStage??"(new)"} \u2192 ${entry.toStage} by ${who}`)}}}async function printTaskMessages(task){let ts2=await getTaskService7(),conv=await ts2.findOrCreateConversation({linkedEntity:"task",linkedEntityId:task.id,name:`Task #${task.seq}`}),messages2=await ts2.getMessages(conv.id,{limit:20});if(messages2.length>0){console.log(`
1718
1718
  Messages:`);for(let msg of messages2){let time=formatTimestamp(msg.createdAt),reply=msg.replyToId?` (reply to #${msg.replyToId})`:"";console.log(` [${time}] ${msg.senderId}: ${msg.body}${reply}`)}}}async function printTaskDetail(task){printTaskFields(task),await printTaskRelations(task),await printTaskMessages(task),console.log("")}function printColumnTasks(label,colTasks,useColor=!0){if(console.log(`
1719
1719
  \u2500\u2500 ${label} (${colTasks.length} task${colTasks.length===1?"":"s"}) \u2500\u2500`),colTasks.length===0){console.log(" (empty)");return}for(let t of colTasks){let pc=useColor?PRIORITY_COLORS[t.priority]??"":"",reset2=useColor?RESET2:"";console.log(` ${pc}#${t.seq}${reset2} ${padRight(truncate(t.title,35),37)} ${padRight(t.status,14)} ${t.priority}`)}}async function printByColumn(tasks,boardName){let board=await(await getBoardService2()).getBoard(boardName);if(!board)console.error(`Error: Board not found: ${boardName}`),process.exit(1);console.log(`
1720
- Board: ${board.name} (${board.id})`),console.log("\u2550".repeat(40));let columns=[...board.columns].sort((a,b2)=>a.position-b2.position);for(let col of columns)printColumnTasks(col.label,tasks.filter((t)=>t.columnId===col.id));let columnIds=new Set(columns.map((c)=>c.id)),orphaned=tasks.filter((t)=>t.columnId&&!columnIds.has(t.columnId));if(orphaned.length>0)printColumnTasks("Orphaned",orphaned,!1);console.log("")}async function handleTaskCreate(title,options){let ts2=await getTaskService7(),actor=currentActor2(),repoPath,projectId;if(options.project){let project=await ts2.getProjectByName(options.project);if(!project)project=await ts2.createProject({name:options.project});projectId=project.id,repoPath=project.repoPath??void 0}let parentId;if(options.parent){if(parentId=await ts2.resolveTaskId(options.parent,repoPath)??void 0,!parentId)console.error(`Error: Parent task not found: ${options.parent}`),process.exit(1)}let boardId=await resolveBoardOption(options.board),task=await ts2.createTask({title,typeId:options.type,priority:options.priority,dueDate:options.due,startDate:options.start,parentId,description:options.description,estimatedEffort:options.effort,boardId},repoPath,projectId);if(await ts2.assignTask(task.id,actor,"creator",{},task.repoPath),options.assign)await ts2.assignTask(task.id,localActor2(options.assign),"assignee",{},task.repoPath);if(options.tags){let tagIds=options.tags.split(",").map((t)=>t.trim());await ts2.tagTask(task.id,tagIds,actor,task.repoPath)}if(options.comment)await ts2.commentOnTask(task.id,actor,options.comment,task.repoPath);if(console.log(`Created task #${task.seq}: ${task.title}`),console.log(` ID: ${task.id}`),console.log(` Stage: ${task.stage} | Priority: ${task.priority}`),options.due)console.log(` Due: ${options.due}`)}function registerTaskCommands(program2){let task=program2.command("task").description("Task lifecycle management");task.command("create <title>").description("Create a new task").option("--type <type>","Task type","software").option("--priority <priority>","Priority: urgent, high, normal, low","normal").option("--due <date>","Due date (YYYY-MM-DD)").option("--start <date>","Start date (YYYY-MM-DD)").option("--tags <tags>","Comma-separated tag IDs").option("--parent <id>","Parent task ID or #seq").option("--assign <name>","Assign to local actor").option("--description <text>","Task description").option("--effort <effort>",'Estimated effort (e.g., "2h", "3 points")').option("--comment <msg>","Initial comment on the task").option("--project <name>","Create task in a specific project (overrides CWD)").option("--board <name>","Board name to assign task to").action(async(title,options)=>{try{await handleTaskCreate(title,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("list").description("List tasks with filters").option("--stage <stage>","Filter by stage").option("--type <type>","Filter by type").option("--status <status>","Filter by status").option("--priority <priority>","Filter by priority").option("--release <release>","Filter by release").option("--due-before <date>","Filter by due date").option("--mine","Show only tasks assigned to me").option("--project <name>","Show tasks for a specific project").option("--board <name>","Filter by board name").option("--by-column","Group tasks by board column (kanban view)").option("--include-done","Include done tasks in kanban view (hidden by default)").option("--all","Show tasks from ALL projects").option("--json","Output as JSON").action(async(options)=>{try{let ts2=await getTaskService7(),filters={stage:options.stage,typeId:options.type,status:options.status,priority:options.priority,releaseId:options.release,dueBefore:options.dueBefore,projectName:options.project,boardName:options.board,allProjects:options.all,...options.all?{limit:1e4}:{}},tasks;if(options.mine)tasks=await ts2.listTasksForActor(currentActor2(),filters);else tasks=await ts2.listTasks(filters);if(options.byColumn){if(!options.board)console.error("Error: --by-column requires --board"),process.exit(1);if(!options.includeDone)tasks=tasks.filter((t)=>t.status!=="done");await printByColumn(tasks,options.board);return}if(options.json){console.log(JSON.stringify(tasks,null,2));return}printTaskList(tasks,options.all)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("show <id>").description("Show task detail (accepts task-id or #seq)").option("--json","Output as JSON").action(async(id,options)=>{try{let t=await(await getTaskService7()).getTask(id);if(!t)console.error(`Error: Task not found: ${id}`),process.exit(1);if(options.json){console.log(JSON.stringify(t,null,2));return}await printTaskDetail(t)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("move <id>").description("Move task to a new stage").requiredOption("--to <stage>","Target stage").option("--comment <msg>","Comment on the move").action(async(id,options)=>{try{let ts2=await getTaskService7(),actor=currentActor2(),t=await ts2.moveTask(id,options.to,actor,options.comment);console.log(`Moved task #${t.seq} to stage "${t.stage}".`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);if(message.includes("Invalid stage"))await handleInvalidStageError(id,message);console.error(`Error: ${message}`),process.exit(1)}}),task.command("assign <id>").description("Assign an actor to a task").requiredOption("--to <name>","Actor name").option("--role <role>","Actor role","assignee").option("--comment <msg>","Comment on the assignment").action(async(id,options)=>{try{let ts2=await getTaskService7(),actor=currentActor2();if(await ts2.assignTask(id,localActor2(options.to),options.role,{}),options.comment)await ts2.commentOnTask(id,actor,options.comment);console.log(`Assigned "${options.to}" as ${options.role??"assignee"} on task ${id}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("tag <id> <tags...>").description("Add tags to a task").action(async(id,tags)=>{try{await(await getTaskService7()).tagTask(id,tags,currentActor2()),console.log(`Tagged task ${id} with: ${tags.join(", ")}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("comment <id> <message>").description("Add a comment to a task").option("--reply-to <msgId>","Reply to a specific message ID").action(async(id,message,options)=>{try{let ts2=await getTaskService7(),replyTo=options.replyTo?Number(options.replyTo):void 0,msg=await ts2.commentOnTask(id,currentActor2(),message,void 0,replyTo);console.log(`Comment #${msg.id} added to task ${id}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("block <id>").description("Mark task as blocked").requiredOption("--reason <reason>","Reason for blocking").option("--comment <msg>","Additional comment").action(async(id,options)=>{try{let ts2=await getTaskService7(),actor=currentActor2(),t=await ts2.blockTask(id,options.reason,actor,options.comment);console.log(`Task #${t.seq} blocked: ${options.reason}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("unblock <id>").description("Unblock a task").option("--comment <msg>","Comment on unblock").action(async(id,options)=>{try{let ts2=await getTaskService7(),actor=currentActor2(),t=await ts2.unblockTask(id,actor,options.comment);console.log(`Task #${t.seq} unblocked.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("done <id>").description("Mark task as done").option("--comment <msg>","Comment on completion").action(async(id,options)=>{try{let ts2=await getTaskService7(),actor=currentActor2(),t=await ts2.markDone(id,actor,options.comment);console.log(`Task #${t.seq} marked as done.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("checkout <id>").description("Atomically claim a task for execution").action(async(id)=>{try{let ts2=await getTaskService7(),runId=getRunId(),t=await ts2.checkoutTask(id,runId);console.log(`Checked out task #${t.seq} for run: ${runId}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("release <id>").description("Release task checkout claim").action(async(id)=>{try{let ts2=await getTaskService7(),runId=getRunId(),t=await ts2.releaseTask(id,runId);console.log(`Released task #${t.seq} from run: ${runId}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("unlock <id>").description("Force-release a stale checkout (admin override)").action(async(id)=>{try{let t=await(await getTaskService7()).forceUnlockTask(id);console.log(`Force-unlocked task #${t.seq}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("dep <id>").description("Manage task dependencies").option("--depends-on <id2>","This task depends on id2").option("--blocks <id2>","This task blocks id2").option("--relates-to <id2>","This task relates to id2").option("--remove <id2>","Remove dependency on id2").action(async(id,options)=>{try{let ts2=await getTaskService7();if(options.remove){if(await ts2.removeDependency(id,options.remove))console.log(`Removed dependency between ${id} and ${options.remove}.`);else console.log("No dependency found to remove.");return}if(options.dependsOn)await ts2.addDependency(id,options.dependsOn,"depends_on"),console.log(`${id} now depends on ${options.dependsOn}.`);if(options.blocks)await ts2.addDependency(id,options.blocks,"blocks"),console.log(`${id} now blocks ${options.blocks}.`);if(options.relatesTo)await ts2.addDependency(id,options.relatesTo,"relates_to"),console.log(`${id} now relates to ${options.relatesTo}.`);if(!options.dependsOn&&!options.blocks&&!options.relatesTo)console.error("Error: Specify --depends-on, --blocks, --relates-to, or --remove."),process.exit(1)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_team_manager();import{existsSync as existsSync30}from"fs";import{copyFile as copyFile2,cp,mkdir as mkdir7}from"fs/promises";import{join as join42,resolve as resolve6}from"path";function registerTeamNamespace(program2){let team=program2.command("team").description("Team lifecycle management");team.command("create <name>").description("Create a new team with a git worktree").requiredOption("--repo <path>","Path to the git repository").option("--branch <branch>","Base branch to create from","dev").option("--wish <slug>","Wish slug \u2014 auto-spawns a task leader with wish context").option("--session <name>","Tmux session name (avoids session explosion on parallel creates)").option("--no-spawn","Create team and copy wish without spawning the leader (useful for testing)").action(async(name,options)=>{try{await handleTeamCreate(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("hire <agent>").description('Add an agent to a team ("council" hires all 10 council members)').option("--team <name>","Team name (auto-detects from leader context if omitted)").action(async(agent,options)=>{try{let teamName=options.team??await autoDetectTeam();if(!teamName)console.error("Error: Could not detect team. Use --team <name> to specify."),process.exit(1);let added=await hireAgent(teamName,agent);if(added.length===0)console.log(`Agent "${agent}" is already a member of "${teamName}".`);else if(agent==="council"){console.log(`Hired ${added.length} council members to "${teamName}":`);for(let name of added)console.log(` + ${name}`)}else console.log(`Hired "${agent}" to team "${teamName}".`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("fire <agent>").description("Remove an agent from a team").option("--team <name>","Team name (auto-detects from leader context if omitted)").action(async(agent,options)=>{try{let teamName=options.team??await autoDetectTeam();if(!teamName)console.error("Error: Could not detect team. Use --team <name> to specify."),process.exit(1);if(await fireAgent(teamName,agent))console.log(`Fired "${agent}" from team "${teamName}".`);else console.error(`Agent "${agent}" is not a member of "${teamName}".`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("ls [name]").alias("list").description("List teams or members of a team").option("--json","Output as JSON").action(async(name,options)=>{try{if(name)await printMembers(name,options.json);else await printTeams(options.json)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("disband <name>").description("Disband a team: kill members, remove worktree, delete config").action(async(name)=>{try{if(await disbandTeam(name))console.log(`Team "${name}" disbanded.`);else console.error(`Team "${name}" not found.`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("done <name>").description("Mark a team as done and kill all members").action(async(name)=>{try{await setTeamStatus(name,"done"),await killTeamMembers(name),console.log(`Team "${name}" marked as done. All members killed.`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("blocked <name>").description("Mark a team as blocked and kill all members").action(async(name)=>{try{await setTeamStatus(name,"blocked"),await killTeamMembers(name),console.log(`Team "${name}" marked as blocked. All members killed.`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}async function handleTeamCreate(name,options){if(options.wish){let resolvedRepo=resolve6(options.repo),wishPath=join42(resolvedRepo,".genie","wishes",options.wish,"WISH.md");if(!existsSync30(wishPath)){let cwdWishDir=join42(process.cwd(),".genie","wishes",options.wish),cwdWishPath=join42(cwdWishDir,"WISH.md");if(existsSync30(cwdWishPath)){let destDir=join42(resolvedRepo,".genie","wishes",options.wish);await mkdir7(destDir,{recursive:!0}),await cp(cwdWishDir,destDir,{recursive:!0}),console.log(`Wish: copied ${options.wish}/WISH.md to repo`)}else console.error(`Error: Wish not found at ${wishPath}`),process.exit(1)}}let config=await createTeam(name,options.repo,options.branch),needsUpdate=!1;if(options.wish)config.wishSlug=options.wish,needsUpdate=!0;if(options.session)config.tmuxSessionName=options.session,needsUpdate=!0;if(needsUpdate)await updateTeamConfig(name,config);if(console.log(`Team "${config.name}" created.`),console.log(` Worktree: ${config.worktreePath}`),console.log(` Branch: ${config.name} (from ${config.baseBranch})`),config.tmuxSessionName)console.log(` Session: ${config.tmuxSessionName}`);if(config.nativeTeamsEnabled)console.log(" Native teams: enabled");if(options.wish&&options.spawn!==!1)await spawnLeaderWithWish(config,options.wish,options.repo,options.session)}async function spawnLeaderWithWish(config,slug,repoPath,sessionOverride){let{handleWorkerSpawn:handleWorkerSpawn2}=await Promise.resolve().then(() => (init_agents(),exports_agents)),{getCurrentSessionName:getCurrentSessionName2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux)),resolvedRepo=resolve6(repoPath),tmuxSession=sessionOverride??await getCurrentSessionName2(config.name)??config.name;config.tmuxSessionName=tmuxSession,await updateTeamConfig(config.name,config);let sourceWishPath=join42(resolvedRepo,".genie","wishes",slug,"WISH.md");if(!existsSync30(sourceWishPath))console.error(`Error: Wish not found at ${sourceWishPath}`),process.exit(1);let destWishDir=join42(config.worktreePath,".genie","wishes",slug);await mkdir7(destWishDir,{recursive:!0});let destWishPath=join42(destWishDir,"WISH.md");await copyFile2(sourceWishPath,destWishPath),console.log(` Wish: copied ${slug}/WISH.md into worktree`);let standardTeam=["team-lead","engineer","reviewer","qa","fix"];for(let role of standardTeam)await hireAgent(config.name,role);console.log(` Team: hired ${standardTeam.join(", ")}`);let members=standardTeam.filter((r)=>r!=="team-lead").join(", "),kickoffPrompt=`Your team is "${config.name}". Repo: ${config.repo}. Branch: ${config.name}. Worktree: ${config.worktreePath}. Wish slug: ${slug}. Your team members are: ${members} (already hired \u2014 genie work will spawn them automatically). Read the wish at .genie/wishes/${slug}/WISH.md and execute the full lifecycle autonomously.`;await handleWorkerSpawn2("team-lead",{provider:"claude",team:config.name,cwd:config.worktreePath,session:tmuxSession,initialPrompt:kickoffPrompt});let result=await(await Promise.resolve().then(() => (init_protocol_router(),exports_protocol_router))).sendMessage(config.worktreePath,"cli","team-lead",kickoffPrompt);if(!result.delivered)console.warn(`\u26A0 Backup delivery to team-lead failed: ${result.reason??"unknown"}`);console.log(" Leader: spawned and working")}async function autoDetectTeam(){let envTeam=process.env.GENIE_TEAM;if(envTeam)return envTeam;let teams=await listTeams2();if(teams.length===1)return teams[0].name;return null}async function printMembers(name,json2){let members=await listMembers(name);if(members===null)console.error(`Team "${name}" not found.`),process.exit(1);if(json2){console.log(JSON.stringify(members,null,2));return}if(members.length===0){console.log(`Team "${name}" has no members. Hire agents with: genie team hire <agent> --team ${name}`);return}console.log(""),console.log(`MEMBERS of "${name}"`),console.log("-".repeat(60));for(let m of members)console.log(` ${m}`);console.log("")}async function printTeams(json2){let teams=await listTeams2();if(json2){console.log(JSON.stringify(teams,null,2));return}if(teams.length===0){console.log("No teams found. Create one with: genie team create <name> --repo <path>");return}console.log(""),console.log("TEAMS"),console.log("-".repeat(60));for(let t of teams)printTeamSummary(t);console.log("")}function printTeamSummary(t){let status=t.status??"in_progress";console.log(` ${t.name} [${status}]`),console.log(` Repo: ${t.repo}`),console.log(` Branch: ${t.name} (from ${t.baseBranch})`),console.log(` Worktree: ${t.worktreePath}`),console.log(` Members: ${t.members.length}`)}var _taskService8;async function getTaskService8(){if(!_taskService8)_taskService8=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService8}function printTypeTable(types4){console.log(` ${padRight("ID",20)} ${padRight("NAME",30)} ${padRight("STAGES",8)} BUILTIN`),console.log(` ${"\u2500".repeat(70)}`);for(let t of types4){let stageCount=Array.isArray(t.stages)?t.stages.length:0,builtin=t.isBuiltin?"yes":"no";console.log(` ${padRight(t.id,20)} ${padRight(t.name,30)} ${padRight(String(stageCount),8)} ${builtin}`)}console.log(`
1720
+ Board: ${board.name} (${board.id})`),console.log("\u2550".repeat(40));let columns=[...board.columns].sort((a,b2)=>a.position-b2.position);for(let col of columns)printColumnTasks(col.label,tasks.filter((t)=>t.columnId===col.id));let columnIds=new Set(columns.map((c)=>c.id)),orphaned=tasks.filter((t)=>t.columnId&&!columnIds.has(t.columnId));if(orphaned.length>0)printColumnTasks("Orphaned",orphaned,!1);console.log("")}async function handleTaskCreate(title,options){let ts2=await getTaskService7(),actor=currentActor2(),repoPath,projectId;if(options.project){let project=await ts2.getProjectByName(options.project);if(!project)project=await ts2.createProject({name:options.project});projectId=project.id,repoPath=project.repoPath??void 0}let parentId;if(options.parent){if(parentId=await ts2.resolveTaskId(options.parent,repoPath)??void 0,!parentId)console.error(`Error: Parent task not found: ${options.parent}`),process.exit(1)}let boardId=await resolveBoardOption(options.board),task=await ts2.createTask({title,typeId:options.type,priority:options.priority,dueDate:options.due,startDate:options.start,parentId,description:options.description,estimatedEffort:options.effort,boardId},repoPath,projectId);if(await ts2.assignTask(task.id,actor,"creator",{},task.repoPath),options.assign)await ts2.assignTask(task.id,localActor2(options.assign),"assignee",{},task.repoPath);if(options.tags){let tagIds=options.tags.split(",").map((t)=>t.trim());await ts2.tagTask(task.id,tagIds,actor,task.repoPath)}if(options.comment)await ts2.commentOnTask(task.id,actor,options.comment,task.repoPath);if(console.log(`Created task #${task.seq}: ${task.title}`),console.log(` ID: ${task.id}`),console.log(` Stage: ${task.stage} | Priority: ${task.priority}`),options.due)console.log(` Due: ${options.due}`)}function registerTaskCommands(program2){let task=program2.command("task").description("Task lifecycle management");task.command("create <title>").description("Create a new task").option("--type <type>","Task type","software").option("--priority <priority>","Priority: urgent, high, normal, low","normal").option("--due <date>","Due date (YYYY-MM-DD)").option("--start <date>","Start date (YYYY-MM-DD)").option("--tags <tags>","Comma-separated tag IDs").option("--parent <id>","Parent task ID or #seq").option("--assign <name>","Assign to local actor").option("--description <text>","Task description").option("--effort <effort>",'Estimated effort (e.g., "2h", "3 points")').option("--comment <msg>","Initial comment on the task").option("--project <name>","Create task in a specific project (overrides CWD)").option("--board <name>","Board name to assign task to").action(async(title,options)=>{try{await handleTaskCreate(title,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("list").description("List tasks with filters").option("--stage <stage>","Filter by stage").option("--type <type>","Filter by type").option("--status <status>","Filter by status").option("--priority <priority>","Filter by priority").option("--release <release>","Filter by release").option("--due-before <date>","Filter by due date").option("--mine","Show only tasks assigned to me").option("--project <name>","Show tasks for a specific project").option("--board <name>","Filter by board name").option("--by-column","Group tasks by board column (kanban view)").option("--include-done","Include done tasks in kanban view (hidden by default)").option("--all","Show tasks from ALL projects").option("--limit <n>","Max number of tasks to return","100").option("--offset <n>","Skip first N tasks (for pagination)","0").option("--json","Output as JSON").action(async(options)=>{try{let ts2=await getTaskService7(),filters={stage:options.stage,typeId:options.type,status:options.status,priority:options.priority,releaseId:options.release,dueBefore:options.dueBefore,projectName:options.project,boardName:options.board,allProjects:options.all,limit:Number(options.limit)||100,offset:Number(options.offset)||0,...options.all?{limit:1e4}:{}},tasks;if(options.mine)tasks=await ts2.listTasksForActor(currentActor2(),filters);else tasks=await ts2.listTasks(filters);if(options.byColumn){if(!options.board)console.error("Error: --by-column requires --board"),process.exit(1);if(!options.includeDone)tasks=tasks.filter((t)=>t.status!=="done");await printByColumn(tasks,options.board);return}if(options.json){console.log(JSON.stringify(tasks,null,2));return}printTaskList(tasks,options.all)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("show <id>").description("Show task detail (accepts task-id or #seq)").option("--json","Output as JSON").action(async(id,options)=>{try{let t=await(await getTaskService7()).getTask(id);if(!t)console.error(`Error: Task not found: ${id}`),process.exit(1);if(options.json){console.log(JSON.stringify(t,null,2));return}await printTaskDetail(t)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("move <id>").description("Move task to a new stage").requiredOption("--to <stage>","Target stage").option("--comment <msg>","Comment on the move").action(async(id,options)=>{try{let ts2=await getTaskService7(),actor=currentActor2(),t=await ts2.moveTask(id,options.to,actor,options.comment);console.log(`Moved task #${t.seq} to stage "${t.stage}".`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);if(message.includes("Invalid stage"))await handleInvalidStageError(id,message);console.error(`Error: ${message}`),process.exit(1)}}),task.command("assign <id>").description("Assign an actor to a task").requiredOption("--to <name>","Actor name").option("--role <role>","Actor role","assignee").option("--comment <msg>","Comment on the assignment").action(async(id,options)=>{try{let ts2=await getTaskService7(),actor=currentActor2();if(await ts2.assignTask(id,localActor2(options.to),options.role,{}),options.comment)await ts2.commentOnTask(id,actor,options.comment);console.log(`Assigned "${options.to}" as ${options.role??"assignee"} on task ${id}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("tag <id> <tags...>").description("Add tags to a task").action(async(id,tags)=>{try{await(await getTaskService7()).tagTask(id,tags,currentActor2()),console.log(`Tagged task ${id} with: ${tags.join(", ")}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("comment <id> <message>").description("Add a comment to a task").option("--reply-to <msgId>","Reply to a specific message ID").action(async(id,message,options)=>{try{let ts2=await getTaskService7(),replyTo=options.replyTo?Number(options.replyTo):void 0,msg=await ts2.commentOnTask(id,currentActor2(),message,void 0,replyTo);console.log(`Comment #${msg.id} added to task ${id}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("block <id>").description("Mark task as blocked").requiredOption("--reason <reason>","Reason for blocking").option("--comment <msg>","Additional comment").action(async(id,options)=>{try{let ts2=await getTaskService7(),actor=currentActor2(),t=await ts2.blockTask(id,options.reason,actor,options.comment);console.log(`Task #${t.seq} blocked: ${options.reason}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("unblock <id>").description("Unblock a task").option("--comment <msg>","Comment on unblock").action(async(id,options)=>{try{let ts2=await getTaskService7(),actor=currentActor2(),t=await ts2.unblockTask(id,actor,options.comment);console.log(`Task #${t.seq} unblocked.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("done <id>").description("Mark task as done").option("--comment <msg>","Comment on completion").action(async(id,options)=>{try{let ts2=await getTaskService7(),actor=currentActor2(),t=await ts2.markDone(id,actor,options.comment);console.log(`Task #${t.seq} marked as done.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("checkout <id>").description("Atomically claim a task for execution").action(async(id)=>{try{let ts2=await getTaskService7(),runId=getRunId(),t=await ts2.checkoutTask(id,runId);console.log(`Checked out task #${t.seq} for run: ${runId}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("release <id>").description("Release task checkout claim").action(async(id)=>{try{let ts2=await getTaskService7(),runId=getRunId(),t=await ts2.releaseTask(id,runId);console.log(`Released task #${t.seq} from run: ${runId}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("unlock <id>").description("Force-release a stale checkout (admin override)").action(async(id)=>{try{let t=await(await getTaskService7()).forceUnlockTask(id);console.log(`Force-unlocked task #${t.seq}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("dep <id>").description("Manage task dependencies").option("--depends-on <id2>","This task depends on id2").option("--blocks <id2>","This task blocks id2").option("--relates-to <id2>","This task relates to id2").option("--remove <id2>","Remove dependency on id2").action(async(id,options)=>{try{let ts2=await getTaskService7();if(options.remove){if(await ts2.removeDependency(id,options.remove))console.log(`Removed dependency between ${id} and ${options.remove}.`);else console.log("No dependency found to remove.");return}if(options.dependsOn)await ts2.addDependency(id,options.dependsOn,"depends_on"),console.log(`${id} now depends on ${options.dependsOn}.`);if(options.blocks)await ts2.addDependency(id,options.blocks,"blocks"),console.log(`${id} now blocks ${options.blocks}.`);if(options.relatesTo)await ts2.addDependency(id,options.relatesTo,"relates_to"),console.log(`${id} now relates to ${options.relatesTo}.`);if(!options.dependsOn&&!options.blocks&&!options.relatesTo)console.error("Error: Specify --depends-on, --blocks, --relates-to, or --remove."),process.exit(1)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_team_manager();import{existsSync as existsSync30}from"fs";import{copyFile as copyFile2,cp,mkdir as mkdir7}from"fs/promises";import{join as join42,resolve as resolve6}from"path";function registerTeamNamespace(program2){let team=program2.command("team").description("Team lifecycle management");team.command("create <name>").description("Create a new team with a git worktree").requiredOption("--repo <path>","Path to the git repository").option("--branch <branch>","Base branch to create from","dev").option("--wish <slug>","Wish slug \u2014 auto-spawns a task leader with wish context").option("--session <name>","Tmux session name (avoids session explosion on parallel creates)").option("--no-spawn","Create team and copy wish without spawning the leader (useful for testing)").action(async(name,options)=>{try{await handleTeamCreate(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("hire <agent>").description('Add an agent to a team ("council" hires all 10 council members)').option("--team <name>","Team name (auto-detects from leader context if omitted)").action(async(agent,options)=>{try{let teamName=options.team??await autoDetectTeam();if(!teamName)console.error("Error: Could not detect team. Use --team <name> to specify."),process.exit(1);let added=await hireAgent(teamName,agent);if(added.length===0)console.log(`Agent "${agent}" is already a member of "${teamName}".`);else if(agent==="council"){console.log(`Hired ${added.length} council members to "${teamName}":`);for(let name of added)console.log(` + ${name}`)}else console.log(`Hired "${agent}" to team "${teamName}".`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("fire <agent>").description("Remove an agent from a team").option("--team <name>","Team name (auto-detects from leader context if omitted)").action(async(agent,options)=>{try{let teamName=options.team??await autoDetectTeam();if(!teamName)console.error("Error: Could not detect team. Use --team <name> to specify."),process.exit(1);if(await fireAgent(teamName,agent))console.log(`Fired "${agent}" from team "${teamName}".`);else console.error(`Agent "${agent}" is not a member of "${teamName}".`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("ls [name]").alias("list").description("List teams or members of a team").option("--json","Output as JSON").action(async(name,options)=>{try{if(name)await printMembers(name,options.json);else await printTeams(options.json)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("disband <name>").description("Disband a team: kill members, remove worktree, delete config").action(async(name)=>{try{if(await disbandTeam(name))console.log(`Team "${name}" disbanded.`);else console.error(`Team "${name}" not found.`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("done <name>").description("Mark a team as done and kill all members").action(async(name)=>{try{await setTeamStatus(name,"done"),await killTeamMembers(name),console.log(`Team "${name}" marked as done. All members killed.`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("blocked <name>").description("Mark a team as blocked and kill all members").action(async(name)=>{try{await setTeamStatus(name,"blocked"),await killTeamMembers(name),console.log(`Team "${name}" marked as blocked. All members killed.`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}async function handleTeamCreate(name,options){if(options.wish){let resolvedRepo=resolve6(options.repo),wishPath=join42(resolvedRepo,".genie","wishes",options.wish,"WISH.md");if(!existsSync30(wishPath)){let cwdWishDir=join42(process.cwd(),".genie","wishes",options.wish),cwdWishPath=join42(cwdWishDir,"WISH.md");if(existsSync30(cwdWishPath)){let destDir=join42(resolvedRepo,".genie","wishes",options.wish);await mkdir7(destDir,{recursive:!0}),await cp(cwdWishDir,destDir,{recursive:!0}),console.log(`Wish: copied ${options.wish}/WISH.md to repo`)}else console.error(`Error: Wish not found at ${wishPath}`),process.exit(1)}}let config=await createTeam(name,options.repo,options.branch),needsUpdate=!1;if(options.wish)config.wishSlug=options.wish,needsUpdate=!0;if(options.session)config.tmuxSessionName=options.session,needsUpdate=!0;if(needsUpdate)await updateTeamConfig(name,config);if(console.log(`Team "${config.name}" created.`),console.log(` Worktree: ${config.worktreePath}`),console.log(` Branch: ${config.name} (from ${config.baseBranch})`),config.tmuxSessionName)console.log(` Session: ${config.tmuxSessionName}`);if(config.nativeTeamsEnabled)console.log(" Native teams: enabled");if(options.wish&&options.spawn!==!1)await spawnLeaderWithWish(config,options.wish,options.repo,options.session)}async function spawnLeaderWithWish(config,slug,repoPath,sessionOverride){let{handleWorkerSpawn:handleWorkerSpawn2}=await Promise.resolve().then(() => (init_agents(),exports_agents)),{getCurrentSessionName:getCurrentSessionName2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux)),resolvedRepo=resolve6(repoPath),tmuxSession=sessionOverride??await getCurrentSessionName2(config.name)??config.name;config.tmuxSessionName=tmuxSession,await updateTeamConfig(config.name,config);let sourceWishPath=join42(resolvedRepo,".genie","wishes",slug,"WISH.md");if(!existsSync30(sourceWishPath))console.error(`Error: Wish not found at ${sourceWishPath}`),process.exit(1);let destWishDir=join42(config.worktreePath,".genie","wishes",slug);await mkdir7(destWishDir,{recursive:!0});let destWishPath=join42(destWishDir,"WISH.md");await copyFile2(sourceWishPath,destWishPath),console.log(` Wish: copied ${slug}/WISH.md into worktree`);let standardTeam=["team-lead","engineer","reviewer","qa","fix"];for(let role of standardTeam)await hireAgent(config.name,role);console.log(` Team: hired ${standardTeam.join(", ")}`);let members=standardTeam.filter((r)=>r!=="team-lead").join(", "),kickoffPrompt=`Your team is "${config.name}". Repo: ${config.repo}. Branch: ${config.name}. Worktree: ${config.worktreePath}. Wish slug: ${slug}. Your team members are: ${members} (already hired \u2014 genie work will spawn them automatically). Read the wish at .genie/wishes/${slug}/WISH.md and execute the full lifecycle autonomously.`;await handleWorkerSpawn2("team-lead",{provider:"claude",team:config.name,cwd:config.worktreePath,session:tmuxSession,initialPrompt:kickoffPrompt});let result=await(await Promise.resolve().then(() => (init_protocol_router(),exports_protocol_router))).sendMessage(config.worktreePath,"cli","team-lead",kickoffPrompt);if(!result.delivered)console.warn(`\u26A0 Backup delivery to team-lead failed: ${result.reason??"unknown"}`);console.log(" Leader: spawned and working")}async function autoDetectTeam(){let envTeam=process.env.GENIE_TEAM;if(envTeam)return envTeam;let teams=await listTeams2();if(teams.length===1)return teams[0].name;return null}async function printMembers(name,json2){let members=await listMembers(name);if(members===null)console.error(`Team "${name}" not found.`),process.exit(1);if(json2){console.log(JSON.stringify(members,null,2));return}if(members.length===0){console.log(`Team "${name}" has no members. Hire agents with: genie team hire <agent> --team ${name}`);return}console.log(""),console.log(`MEMBERS of "${name}"`),console.log("-".repeat(60));for(let m of members)console.log(` ${m}`);console.log("")}async function printTeams(json2){let teams=await listTeams2();if(json2){console.log(JSON.stringify(teams,null,2));return}if(teams.length===0){console.log("No teams found. Create one with: genie team create <name> --repo <path>");return}console.log(""),console.log("TEAMS"),console.log("-".repeat(60));for(let t of teams)printTeamSummary(t);console.log("")}function printTeamSummary(t){let status=t.status??"in_progress";console.log(` ${t.name} [${status}]`),console.log(` Repo: ${t.repo}`),console.log(` Branch: ${t.name} (from ${t.baseBranch})`),console.log(` Worktree: ${t.worktreePath}`),console.log(` Members: ${t.members.length}`)}var _taskService8;async function getTaskService8(){if(!_taskService8)_taskService8=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService8}function printTypeTable(types4){console.log(` ${padRight("ID",20)} ${padRight("NAME",30)} ${padRight("STAGES",8)} BUILTIN`),console.log(` ${"\u2500".repeat(70)}`);for(let t of types4){let stageCount=Array.isArray(t.stages)?t.stages.length:0,builtin=t.isBuiltin?"yes":"no";console.log(` ${padRight(t.id,20)} ${padRight(t.name,30)} ${padRight(String(stageCount),8)} ${builtin}`)}console.log(`
1721
1721
  ${types4.length} type${types4.length===1?"":"s"}`)}function printTypePipeline(t){if(console.log(`
1722
1722
  Type: ${t.name} (${t.id})`),t.description)console.log(`Description: ${t.description}`);if(t.icon)console.log(`Icon: ${t.icon}`);console.log(`Built-in: ${t.isBuiltin?"yes":"no"}`),console.log("\u2500".repeat(60)),console.log(`
1723
1723
  Stage Pipeline:`);let stages=t.stages;for(let i2=0;i2<stages.length;i2++){let s=stages[i2],arrow=i2<stages.length-1?" \u2192":"",gate=s.gate?` [gate: ${s.gate}]`:"",action=s.action?` (action: ${s.action})`:"",auto=s.auto_advance?" [auto]":"";console.log(` ${i2+1}. ${s.label??s.name}${gate}${action}${auto}${arrow}`)}console.log("")}async function handleTypeList(options){console.warn("Warning: `genie type` is deprecated. Use `genie board` instead.");let types4=await(await getTaskService8()).listTypes();if(options.json){console.log(JSON.stringify(types4,null,2));return}printTypeTable(types4)}async function handleTypeShow(id,options){console.warn("Warning: `genie type` is deprecated. Use `genie board` instead.");let t=await(await getTaskService8()).getType(id);if(!t)console.error(`Error: Type not found: ${id}`),process.exit(1);if(options.json){console.log(JSON.stringify(t,null,2));return}printTypePipeline(t)}async function handleTypeCreate(name,options){console.warn("Warning: `genie type` is deprecated. Use `genie board` instead.");let ts2=await getTaskService8(),stages;try{if(stages=JSON.parse(options.stages),!Array.isArray(stages))throw Error("Stages must be a JSON array")}catch(err){console.error(`Error: Invalid stages JSON. ${err instanceof Error?err.message:String(err)}`),process.exit(1)}for(let s of stages)if(typeof s!=="object"||s===null||!("name"in s))console.error('Error: Each stage must have at least a "name" field.'),process.exit(1);let id=name.toLowerCase().replace(/\s+/g,"-"),t=await ts2.createType({id,name,description:options.description,icon:options.icon,stages});console.log(`Created type "${t.name}" (${t.id}) with ${stages.length} stages.`)}function registerTypeCommands(program2){let type2=program2.command("type").description("Task type management");type2.command("list").description("List all task types").option("--json","Output as JSON").action(async(options)=>{try{await handleTypeList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),type2.command("show <id>").description("Show task type detail with stage pipeline").option("--json","Output as JSON").action(async(id,options)=>{try{await handleTypeShow(id,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),type2.command("create <name>").description("Create a custom task type").requiredOption("--stages <json>","Stages JSON array").option("--description <text>","Type description").option("--icon <icon>","Type icon").action(async(name,options)=>{try{await handleTypeCreate(name,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}try{let{execSync:execSyncStartup}=__require("child_process");if(execSyncStartup("git config core.bare",{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()==="true")execSyncStartup("git config core.bare false",{stdio:["pipe","pipe","pipe"]})}catch{}var program2=new Command;program2.name("genie").description("Genie CLI - AI-assisted development").version(VERSION);async function startNamedSession(name){let{buildTeamLeadCommand:buildTeamLeadCommand2,sessionExists:sessionExists2}=await Promise.resolve().then(() => (init_team_lead_command(),exports_team_lead_command)),{getAgentsFilePath:getAgentsFilePath2}=await Promise.resolve().then(() => (init_session(),exports_session)),systemPromptFile=getAgentsFilePath2(),hasPriorSession=sessionExists2(name),cmd=buildTeamLeadCommand2(name,{systemPromptFile:systemPromptFile??void 0,continueName:hasPriorSession?name:void 0});console.log(hasPriorSession?`Resuming session: ${name}`:`Starting new session: ${name}`);let{spawnSync:spawnSync2}=await import("child_process"),result=spawnSync2("sh",["-c",cmd],{stdio:"inherit"});if(result.status)process.exit(result.status)}program2.command("setup").description("Configure genie settings").option("--quick","Accept all defaults").option("--shortcuts","Only configure keyboard shortcuts").option("--codex","Only configure Codex integration").option("--terminal","Only configure terminal defaults").option("--session","Only configure session settings").option("--reset","Reset configuration to defaults").option("--show","Show current configuration").action(async(options)=>{await setupCommand(options)});program2.command("doctor").description("Run diagnostic checks on genie installation").option("--fix","Auto-fix: kill zombie postgres, clean shared memory, restart daemon").action(doctorCommand);program2.command("update").description("Update Genie CLI to the latest version").option("--next","Switch to dev builds (npm @next tag)").option("--stable","Switch to stable releases (npm @latest tag)").action(updateCommand);program2.command("uninstall").description("Remove Genie CLI and clean up hooks").action(uninstallCommand);var shortcuts=program2.command("shortcuts").description("Manage tmux keyboard shortcuts");shortcuts.action(shortcutsShowCommand);shortcuts.command("show").description("Show available shortcuts and installation status").action(shortcutsShowCommand);shortcuts.command("install").description("Install shortcuts to config files (~/.tmux.conf, shell rc)").action(shortcutsInstallCommand);shortcuts.command("uninstall").description("Remove shortcuts from config files").action(shortcutsUninstallCommand);registerTeamNamespace(program2);registerDirNamespace(program2);registerAgentNamespace(program2);registerSendInboxCommands(program2);registerStateCommands(program2);registerDispatchCommands(program2);registerHookNamespace(program2);registerDbCommands(program2);registerScheduleCommands(program2);registerDaemonCommands(program2);registerTaskCommands(program2);registerTypeCommands(program2);registerBoardCommands(program2);registerTagCommands(program2);registerReleaseCommands(program2);registerProjectCommands(program2);registerNotifyCommands(program2);registerEventsCommands(program2);registerSessionsCommands(program2);registerMetricsCommands(program2);registerExportCommands(program2);registerImportCommands(program2);registerInstallCommand(program2);registerPublishCommand(program2);var itemCmd=program2.command("item").description("Item registry management");registerItemUninstallCommand(itemCmd);registerItemUpdateCommand(itemCmd);var auditTimers=new Map;program2.hook("preAction",(_thisCommand,actionCommand)=>{let name=actionCommand.name();auditTimers.set(name,Date.now()),Promise.resolve().then(() => (init_db(),exports_db)).then(({isConnected:isConnected2})=>{if(!isConnected2())return;recordAuditEvent("command",name,"command_start",getActor(),{args:actionCommand.args}).catch(()=>{})}).catch(()=>{})});program2.hook("postAction",(_thisCommand,actionCommand)=>{let name=actionCommand.name(),startMs=auditTimers.get(name),durationMs=startMs?Date.now()-startMs:void 0;auditTimers.delete(name),Promise.resolve().then(() => (init_db(),exports_db)).then(({isConnected:isConnected2})=>{if(!isConnected2())return;recordAuditEvent("command",name,"command_success",getActor(),{args:actionCommand.args,duration_ms:durationMs}).catch(()=>{})}).catch(()=>{})});program2.command("spawn <name>").description("Spawn a new agent by name (resolves from directory or built-ins)").option("--provider <provider>","Provider: claude or codex","claude").option("--team <team>","Team name",process.env.GENIE_TEAM??"genie").option("--model <model>","Model override (e.g., sonnet, opus)").option("--skill <skill>","Skill to load (optional)").option("--layout <layout>","Layout mode: mosaic (default) or vertical").option("--color <color>","Teammate pane border color").option("--plan-mode","Start teammate in plan mode").option("--permission-mode <mode>","Permission mode (e.g., acceptEdits)").option("--extra-args <args...>","Extra CLI args forwarded to provider").option("--cwd <path>","Working directory for the agent (overrides directory entry)").option("--session <session>","Tmux session name to spawn into").option("--no-auto-resume","Disable auto-resume on pane death").action(async(name,options)=>{try{await handleWorkerSpawn(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("kill <name>").description("Force kill an agent by name").action(async(name)=>{try{await handleWorkerKill(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("stop <name>").description("Stop an agent (preserves session for resume)").action(async(name)=>{try{await handleWorkerStop(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("resume [name]").description("Resume a suspended/failed agent with its Claude session").option("--all","Resume all eligible agents").action(async(name,options)=>{try{await handleWorkerResume(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("history <name>").description("Show compressed session history for an agent").option("--full","Show full conversation without compression").option("--since <n>","Show last N user/assistant exchanges",Number.parseInt).option("--last <n>","Show last N transcript entries",Number.parseInt).option("--type <role>","Filter by role (user, assistant, tool_call)").option("--after <timestamp>","Only entries after ISO timestamp").option("--json","Output as JSON").option("--ndjson","Output as newline-delimited JSON (pipeable to jq)").option("--raw","Output raw JSONL entries").option("--log-file <path>","Direct path to log file (for testing)").action(async(name,options)=>{await historyCommand(name,options)});program2.command("log [agent]").description("Unified observability feed \u2014 aggregates transcript, DMs, team chat").option("--team <name>","Show interleaved feed for all agents in a team").option("--type <kind>","Filter by event kind (transcript, message, tool_call, state, system)").option("--since <timestamp>","Only events after ISO timestamp").option("--last <n>","Show last N events",Number.parseInt).option("--ndjson","Output as newline-delimited JSON (pipeable to jq)").option("--json","Output as pretty JSON").option("-f, --follow","Follow mode \u2014 real-time streaming").action(async(agent,options)=>{await logCommand(agent,options)});var qaCmd=program2.command("qa").description("QA \u2014 self-testing system for genie CLI");qaCmd.command("run [target]",{isDefault:!0}).description("Run QA specs (all, a domain, or a single spec)").option("--timeout <seconds>","Max seconds per spec",(v)=>Number(v),3600).option("--parallel <n>","Max specs to run in parallel",(v)=>Number(v),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()});program2.command("qa-report <json>").description("Publish QA result via NATS (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{publish:publish2,close:close2}=await Promise.resolve().then(() => (init_nats_client(),exports_nats_client)),data=JSON.parse(json2);await publish2(`genie.qa.${team}.result`,data),await close2(),console.log(`QA result published to genie.qa.${team}.result`)}catch(err){console.error(`Failed to publish QA result: ${err}`),process.exit(1)}});program2.command("read <name>").description("Read terminal output from an agent pane").option("-n, --lines <number>","Number of lines to read").option("--from <line>","Start line").option("--to <line>","End line").option("--range <range>",'Line range (e.g., "10-20")').option("--search <text>","Search for text").option("--grep <pattern>","Grep for pattern").option("-f, --follow","Follow mode (like tail -f)").option("--all","Show all output").option("-r, --reverse","Reverse order").option("--json","Output as JSON").action(async(name,options)=>{await readSessionLogs2(name,options)});program2.command("answer <name> <choice>").description('Answer a question for an agent (use "text:..." for text input)').action(async(name,choice)=>{await answerQuestion(name,choice)});program2.command("ls").description("List registered agents with runtime status").option("--json","Output as JSON").action(async(options)=>{try{await handleLsCommand(options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});var args=process.argv.slice(2);if(args.length===0||args.every((a)=>a==="--reset")){let{sessionCommand:sessionCommand2}=await Promise.resolve().then(() => (init_session(),exports_session));await sessionCommand2({reset:args.includes("--reset")}),process.exit(0)}var sessionIdx=args.indexOf("--session");if(sessionIdx!==-1&&sessionIdx+1<args.length){let sessionName=args[sessionIdx+1];if(!args.filter((_,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 program2.parse()}else program2.parse();
@@ -2,7 +2,7 @@
2
2
  "id": "genie",
3
3
  "name": "Genie",
4
4
  "description": "Skills, agents, and hooks for the Genie CLI terminal orchestration toolkit",
5
- "version": "4.260328.5",
5
+ "version": "4.260328.7",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/genie",
3
- "version": "4.260328.5",
3
+ "version": "4.260328.7",
4
4
  "description": "Collaborative terminal toolkit for human + AI workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie",
3
- "version": "4.260328.5",
3
+ "version": "4.260328.7",
4
4
  "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.",
5
5
  "author": {
6
6
  "name": "Namastex Labs"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie-plugin",
3
- "version": "4.260328.5",
3
+ "version": "4.260328.7",
4
4
  "private": true,
5
5
  "description": "Runtime dependencies for genie bundled CLIs",
6
6
  "type": "module",
@@ -1,9 +1,9 @@
1
1
  /**
2
- * Unified Log — Canonical event format and file-based aggregator.
2
+ * Unified Log — Canonical event format and aggregator.
3
3
  *
4
- * Aggregates 5 event sources (transcript, mailbox inbox, mailbox outbox,
5
- * team chat, registry state changes) into a single `LogEvent` stream
6
- * sorted by timestamp.
4
+ * Aggregates 6 event sources (transcript, mailbox inbox, mailbox outbox,
5
+ * team chat, PG messages, registry state changes) into a single `LogEvent`
6
+ * stream sorted by timestamp.
7
7
  *
8
8
  * Usage:
9
9
  * const events = await readAgentLog('engineer', repoPath, { last: 50 });
@@ -20,7 +20,7 @@ import type { TranscriptEntry } from './transcript.js';
20
20
  // ============================================================================
21
21
 
22
22
  export type LogEventKind = 'user' | 'assistant' | 'message' | 'state' | 'tool_call' | 'tool_result' | 'system' | 'qa';
23
- export type LogEventSource = 'provider' | 'mailbox' | 'chat' | 'registry' | 'hook';
23
+ export type LogEventSource = 'provider' | 'mailbox' | 'chat' | 'registry' | 'hook' | 'send';
24
24
 
25
25
  export interface LogEvent {
26
26
  /** ISO timestamp */
@@ -175,6 +175,44 @@ export function sortByTimestamp(events: LogEvent[]): LogEvent[] {
175
175
  return events.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
176
176
  }
177
177
 
178
+ // ============================================================================
179
+ // PG Message Source
180
+ // ============================================================================
181
+
182
+ /** Read messages from PG TaskService for a given agent. Returns [] if PG unavailable. */
183
+ async function readPgMessages(agentName: string): Promise<LogEvent[]> {
184
+ try {
185
+ const ts = await import('./task-service.js');
186
+ const actor = { actorType: 'local' as const, actorId: agentName };
187
+ const conversations = await ts.listConversations(actor);
188
+
189
+ const events: LogEvent[] = [];
190
+ for (const conv of conversations) {
191
+ const messages = await ts.getMessages(conv.id, { limit: 100 });
192
+ const members = await ts.getMembers(conv.id);
193
+ const peerMember = members.find((m) => m.actorId !== agentName);
194
+
195
+ for (const msg of messages) {
196
+ const isOutgoing = msg.senderId === agentName;
197
+ const peer = isOutgoing ? peerMember?.actorId : msg.senderId;
198
+ events.push({
199
+ timestamp: msg.createdAt,
200
+ kind: 'message',
201
+ agent: agentName,
202
+ direction: isOutgoing ? 'out' : 'in',
203
+ peer,
204
+ text: msg.body,
205
+ data: { messageId: msg.id, conversationId: msg.conversationId, senderId: msg.senderId },
206
+ source: 'send',
207
+ });
208
+ }
209
+ }
210
+ return events;
211
+ } catch {
212
+ return [];
213
+ }
214
+ }
215
+
178
216
  // ============================================================================
179
217
  // Aggregators
180
218
  // ============================================================================
@@ -188,15 +226,16 @@ export async function readAgentLog(agent: Agent, repoPath: string, filter?: LogF
188
226
  const team = agent.team;
189
227
 
190
228
  // Read all sources in parallel
191
- const [transcriptEntries, inboxMessages, outboxMessages, chatMessages] = await Promise.all([
229
+ const [transcriptEntries, inboxMessages, outboxMessages, chatMessages, pgMessages] = await Promise.all([
192
230
  readTranscriptSafe(agent),
193
231
  inbox(repoPath, agentName),
194
232
  readOutbox(repoPath, agentName),
195
233
  team ? readMessages(repoPath, team) : Promise.resolve([]),
234
+ readPgMessages(agentName),
196
235
  ]);
197
236
 
198
237
  // Convert to LogEvents
199
- const events: LogEvent[] = [];
238
+ const events: LogEvent[] = [...pgMessages];
200
239
 
201
240
  for (const entry of transcriptEntries) {
202
241
  const event = transcriptToLogEvent(entry, agentName, team);
@@ -241,13 +280,14 @@ export async function readTeamLog(
241
280
  agents.map(async (agent) => {
242
281
  const agentName = agent.id;
243
282
 
244
- const [transcriptEntries, inboxMessages, outboxMessages] = await Promise.all([
283
+ const [transcriptEntries, inboxMessages, outboxMessages, pgMessages] = await Promise.all([
245
284
  readTranscriptSafe(agent),
246
285
  inbox(repoPath, agentName),
247
286
  readOutbox(repoPath, agentName),
287
+ readPgMessages(agentName),
248
288
  ]);
249
289
 
250
- const events: LogEvent[] = [];
290
+ const events: LogEvent[] = [...pgMessages];
251
291
  for (const entry of transcriptEntries) {
252
292
  const event = transcriptToLogEvent(entry, agentName, teamName);
253
293
  if (event) events.push(event);
@@ -196,7 +196,7 @@ function formatHumanOutput(events: LogEvent[], label: string): string {
196
196
  // Agent Resolution
197
197
  // ============================================================================
198
198
 
199
- async function findAgent(identifier: string): Promise<agentRegistry.Agent | null> {
199
+ async function findAgent(identifier: string, teamName?: string): Promise<agentRegistry.Agent | null> {
200
200
  let agent = await agentRegistry.get(identifier);
201
201
  if (agent) return agent;
202
202
 
@@ -204,6 +204,17 @@ async function findAgent(identifier: string): Promise<agentRegistry.Agent | null
204
204
  if (agent) return agent;
205
205
 
206
206
  const all = await agentRegistry.list();
207
+
208
+ // Scope fuzzy match to team first, then fall back to global
209
+ const pool = teamName ? all.filter((a) => a.team === teamName) : [];
210
+ const teamMatch = pool.find(
211
+ (w) =>
212
+ w.id.includes(identifier) ||
213
+ w.taskId?.includes(identifier) ||
214
+ w.taskTitle?.toLowerCase().includes(identifier.toLowerCase()),
215
+ );
216
+ if (teamMatch) return teamMatch;
217
+
207
218
  return (
208
219
  all.find(
209
220
  (w) =>
@@ -286,8 +297,8 @@ export async function logCommand(agentName: string | undefined, options: LogOpti
286
297
  events = await readTeamLog(agents, repoPath, options.team, filter);
287
298
  label = `team:${options.team} (${agents.length} agents)`;
288
299
  } else if (agentName) {
289
- // Single agent mode
290
- const agent = await findAgent(agentName);
300
+ // Single agent mode — scope to team if specified
301
+ const agent = await findAgent(agentName, options.team);
291
302
  if (!agent) {
292
303
  console.error(`Agent "${agentName}" not found. Run \`genie ls\` to see agents.`);
293
304
  process.exit(1);
@@ -357,7 +368,7 @@ async function followCommand(
357
368
  console.error(`Following ${label} via ${handle.mode === 'nats' ? 'NATS' : 'file polling'} (Ctrl+C to stop)...`);
358
369
  setupShutdown(handle.stop);
359
370
  } else if (agentName) {
360
- const agent = await findAgent(agentName);
371
+ const agent = await findAgent(agentName, options.team);
361
372
  if (!agent) {
362
373
  console.error(`Agent "${agentName}" not found. Run \`genie ls\` to see agents.`);
363
374
  process.exit(1);
@@ -48,6 +48,7 @@ interface ListOptions {
48
48
  active?: boolean;
49
49
  orphaned?: boolean;
50
50
  agent?: string;
51
+ limit?: string;
51
52
  json?: boolean;
52
53
  }
53
54
 
@@ -58,17 +59,18 @@ async function sessionsListCommand(options: ListOptions): Promise<void> {
58
59
  }
59
60
 
60
61
  const sql = await getConnection();
62
+ const limit = Number(options.limit) || 50;
61
63
 
62
64
  let rows: SessionRow[];
63
65
  if (options.active) {
64
- rows = await sql`SELECT * FROM sessions WHERE status = 'active' ORDER BY started_at DESC LIMIT 50`;
66
+ rows = await sql`SELECT * FROM sessions WHERE status = 'active' ORDER BY started_at DESC LIMIT ${limit}`;
65
67
  } else if (options.orphaned) {
66
- rows = await sql`SELECT * FROM sessions WHERE status = 'orphaned' ORDER BY started_at DESC LIMIT 50`;
68
+ rows = await sql`SELECT * FROM sessions WHERE status = 'orphaned' ORDER BY started_at DESC LIMIT ${limit}`;
67
69
  } else if (options.agent) {
68
70
  rows =
69
- await sql`SELECT * FROM sessions WHERE agent_id = ${options.agent} OR agent_id LIKE ${`%${options.agent}%`} ORDER BY started_at DESC LIMIT 50`;
71
+ await sql`SELECT * FROM sessions WHERE agent_id = ${options.agent} OR agent_id LIKE ${`%${options.agent}%`} ORDER BY started_at DESC LIMIT ${limit}`;
70
72
  } else {
71
- rows = await sql`SELECT * FROM sessions ORDER BY started_at DESC LIMIT 50`;
73
+ rows = await sql`SELECT * FROM sessions ORDER BY started_at DESC LIMIT ${limit}`;
72
74
  }
73
75
 
74
76
  if (options.json) {
@@ -244,6 +246,7 @@ export function registerSessionsCommands(program: Command): void {
244
246
  .option('--active', 'Show only active sessions')
245
247
  .option('--orphaned', 'Show only orphaned sessions')
246
248
  .option('--agent <name>', 'Filter by agent')
249
+ .option('--limit <n>', 'Max number of sessions to return (default: 50)')
247
250
  .option('--json', 'Output as JSON')
248
251
  .action(async (options: ListOptions) => {
249
252
  await sessionsListCommand(options);
@@ -421,6 +421,8 @@ export function registerTaskCommands(program: Command): void {
421
421
  .option('--by-column', 'Group tasks by board column (kanban view)')
422
422
  .option('--include-done', 'Include done tasks in kanban view (hidden by default)')
423
423
  .option('--all', 'Show tasks from ALL projects')
424
+ .option('--limit <n>', 'Max number of tasks to return', '100')
425
+ .option('--offset <n>', 'Skip first N tasks (for pagination)', '0')
424
426
  .option('--json', 'Output as JSON')
425
427
  .action(
426
428
  async (options: {
@@ -436,6 +438,8 @@ export function registerTaskCommands(program: Command): void {
436
438
  byColumn?: boolean;
437
439
  includeDone?: boolean;
438
440
  all?: boolean;
441
+ limit?: string;
442
+ offset?: string;
439
443
  json?: boolean;
440
444
  }) => {
441
445
  try {
@@ -450,6 +454,8 @@ export function registerTaskCommands(program: Command): void {
450
454
  projectName: options.project,
451
455
  boardName: options.board,
452
456
  allProjects: options.all,
457
+ limit: Number(options.limit) || 100,
458
+ offset: Number(options.offset) || 0,
453
459
  ...(options.all ? { limit: 10000 } : {}),
454
460
  };
455
461