@automagik/genie 4.260504.20 → 4.260504.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/genie.js CHANGED
@@ -1520,7 +1520,9 @@ Synced: ${total} agent(s), ${result2.archived.length} removed.`)}async function
1520
1520
  OFFSET ${opts.offset??0}
1521
1521
  `).map(mapMessage)}async function getMessage(id){let rows=await(await getConnection())`SELECT * FROM messages WHERE id = ${id}`;return rows.length>0?mapMessage(rows[0]):null}async function updateMessage(id,body){let rows=await(await getConnection())`
1522
1522
  UPDATE messages SET body = ${body}, updated_at = now() WHERE id = ${id} RETURNING *
1523
- `;return rows.length>0?mapMessage(rows[0]):null}async function commentOnTask(taskIdOrSeq,sender,body,repoPath,replyToId){let repo=repoPath??getRepoPath(),taskId=await resolveTaskId(taskIdOrSeq,repo);if(!taskId)throw Error(`Task not found: ${taskIdOrSeq}`);let conv=await findOrCreateConversation({linkedEntity:"task",linkedEntityId:taskId,name:`Task ${taskIdOrSeq}`,createdBy:sender,members:[sender]});return await addMember(conv.id,sender),sendMessage(conv.id,sender,body,replyToId)}async function tagTask(idOrSeq,tagIds,actor,repoPath){let sql=await getConnection(),repo=repoPath??getRepoPath(),id=await resolveTaskId(idOrSeq,repo);if(!id)throw Error(`Task not found: ${idOrSeq}`);for(let tagId of tagIds)await sql`
1523
+ `;return rows.length>0?mapMessage(rows[0]):null}async function commentOnTask(taskIdOrSeq,sender,body,repoPath,replyToId){let repo=repoPath??getRepoPath(),taskId=await resolveTaskId(taskIdOrSeq,repo);if(!taskId)throw Error(`Task not found: ${taskIdOrSeq}`);let conv=await findOrCreateConversation({linkedEntity:"task",linkedEntityId:taskId,name:`Task ${taskIdOrSeq}`,createdBy:sender,members:[sender]});return await addMember(conv.id,sender),sendMessage(conv.id,sender,body,replyToId)}async function tagTask(idOrSeq,tagIds,actor,repoPath){let sql=await getConnection(),repo=repoPath??getRepoPath(),id=await resolveTaskId(idOrSeq,repo);if(!id)throw Error(`Task not found: ${idOrSeq}`);let existing=await sql`SELECT id FROM tags WHERE id = ANY(${tagIds})`,existingIds=new Set(existing.map((r)=>r.id)),missing=tagIds.filter((t)=>!existingIds.has(t));if(missing.length>0){let available=(await sql`SELECT id FROM tags ORDER BY id`).map((r)=>r.id).join(", ")||"(none registered)";throw Error(`Tag(s) not found: ${missing.join(", ")}.
1524
+ Available tags: ${available}
1525
+ Create a tag first with \`genie tag create <id>\`.`)}for(let tagId of tagIds)await sql`
1524
1526
  INSERT INTO task_tags (task_id, tag_id, added_by_type, added_by_id)
1525
1527
  VALUES (${id}, ${tagId}, ${actor?.actorType??null}, ${actor?.actorId??null})
1526
1528
  ON CONFLICT DO NOTHING
@@ -1650,7 +1652,8 @@ Examples:
1650
1652
  genie send 'deploy ready' --team my-feature # Message within team context
1651
1653
  genie send 'hi felipe' --to felipe-3 --bridge # Scope violation \u2192 print relay hint instead of erroring`).action(async(body,options)=>{try{await handleSend(body,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),program2.command("broadcast <body>").description("Send a message to your team conversation (PG-backed)").option("--from <sender>","Sender ID (auto-detected from context)").option("--team <name>","Team name (auto-detected from context)").action(async(body,options)=>{try{await handleBroadcast(body,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}});let inbox2=program2.command("inbox").description("Inbox management \u2014 list messages or watch for new ones");inbox2.command("list [agent]",{isDefault:!0}).description("List conversations with recent messages (PG-backed)").option("--json","Output as JSON").action(async(agent,options)=>{try{await handleInbox(agent,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),inbox2.command("watch").description("Run inbox watcher in foreground (Ctrl+C to stop)").action(async()=>{let{checkInboxes:checkInboxes2,getInboxPollIntervalMs:getInboxPollIntervalMs2,startInboxWatcher:startInboxWatcher2,stopInboxWatcher:stopInboxWatcher2}=await Promise.resolve().then(() => (init_inbox_watcher(),exports_inbox_watcher)),pollMs=getInboxPollIntervalMs2();if(pollMs===0)console.log("Inbox watcher is disabled (GENIE_INBOX_POLL_MS=0)"),process.exit(0);console.log(`Inbox watcher starting (poll every ${pollMs/1000}s)`),console.log(`Press Ctrl+C to stop.
1652
1654
  `);let initial=await checkInboxes2();if(initial.length>0)console.log(`[inbox-watcher] Spawned team-leads for: ${initial.join(", ")}`);let handle=startInboxWatcher2({listTeamsWithUnreadInbox:(await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams))).listTeamsWithUnreadInbox,isTeamActive:async(teamName)=>{let{isTeamActive:isTeamActive2}=await Promise.resolve().then(() => (init_team_auto_spawn(),exports_team_auto_spawn));return isTeamActive2(teamName)},isAgentAlive:async(agentName)=>{let{isAgentAlive:isAgentAlive2}=await Promise.resolve().then(() => (init_team_auto_spawn(),exports_team_auto_spawn));return isAgentAlive2(agentName)},ensureTeamLead:async(teamName,workingDir)=>{let{ensureTeamLead:ensureTeamLead2}=await Promise.resolve().then(() => (init_team_auto_spawn(),exports_team_auto_spawn)),result2=await ensureTeamLead2(teamName,workingDir);return console.log(`[inbox-watcher] Spawned team-lead for "${teamName}" in ${workingDir}`),result2},warn:(msg)=>console.log(msg),emitDeadInbox:(payload)=>{try{emitEvent("rot.inbox-watcher-spawn-loop.detected",payload)}catch{}}}),shutdown2=()=>{console.log(`
1653
- Stopping inbox watcher...`),stopInboxWatcher2(handle),process.exit(0)};process.on("SIGINT",shutdown2),process.on("SIGTERM",shutdown2),await new Promise(()=>{})});let chat=program2.command("chat").description("Conversation management (PG-backed)");chat.command("send <conversationId> <message>").description("Send a message to a specific conversation").option("--reply-to <msgId>","Reply to a specific message ID").option("--from <sender>","Sender ID (auto-detected)").action(async(conversationId,message,options)=>{try{let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),replyTo=options.replyTo?Number(options.replyTo):void 0,msg=await ts3.sendMessage(conversationId,actor,message,replyTo);console.log(`Message #${msg.id} sent to conversation ${conversationId}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("thread <messageId>").description("Create a threaded sub-conversation from a message").option("--name <name>","Thread name").option("--from <sender>","Sender ID (auto-detected)").action(async(messageId,options)=>{try{await handleChatThread(messageId,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("list").description("List conversations with filters").option("--type <type>","Filter by type: dm, group").option("--linked <entity>","Filter by linked entity: task, team").option("--json","Output as JSON").option("--from <sender>","Actor ID (auto-detected)").action(async(options)=>{try{await handleChatList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("read <conversationId>").description("Read messages in a conversation").option("--since <timestamp>","Show messages since timestamp").option("--limit <n>","Limit number of messages","50").option("--json","Output as JSON").action(async(conversationId,options)=>{try{await handleChatRead(conversationId,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}var _registry,_taskService,_teamManager,_mailbox,PARENT_CHAIN_MAX_DEPTH=3,DEFAULT_REACHBACK_PREFIXES;var init_msg=__esm(()=>{init_emit();init_term_format();DEFAULT_REACHBACK_PREFIXES=["council-"]});function buildLayoutCommand(windowTarget,mode="mosaic"){return`select-layout -t '${windowTarget}' ${mode==="vertical"?"even-horizontal":"tiled"}`}function resolveLayoutMode(layoutFlag){if(layoutFlag==="vertical")return"vertical";return"mosaic"}var exports_claude_logs={};__export(exports_claude_logs,{projectPathToHash:()=>projectPathToHash,parseLogEntry:()=>parseLogEntry,listSessions:()=>listSessions2,findClaudeProjectDir:()=>findClaudeProjectDir,findActiveSession:()=>findActiveSession,claudeTranscriptProvider:()=>claudeTranscriptProvider,claudeEntryToTranscript:()=>claudeEntryToTranscript});import{access,readFile as readFile8,readdir as readdir5,stat as stat4}from"fs/promises";import{join as join47}from"path";function projectPathToHash(projectPath){let normalized=projectPath.replace(/\/+$/,"");if(!normalized)normalized="/";return normalized.replace(/[/.]/g,"-")}function getClaudeDir(){return join47(process.env.HOME||"",".claude")}function getProjectsDir(claudeDir){return join47(claudeDir||getClaudeDir(),"projects")}async function findClaudeProjectDir(projectPath,claudeDir){let projectsDir=getProjectsDir(claudeDir),expectedHash=projectPathToHash(projectPath);try{await access(projectsDir);let projectDir=join47(projectsDir,expectedHash);return await access(projectDir),projectDir}catch{return null}}async function listSessions2(projectDir){let indexPath=join47(projectDir,"sessions-index.json");try{let content=await readFile8(indexPath,"utf-8");return JSON.parse(content).entries}catch{return await scanForSessions(projectDir)}}async function scanForSessions(projectDir){let sessions=[];try{let entries=await readdir5(projectDir);for(let entry2 of entries)if(entry2.endsWith(".jsonl")&&!entry2.startsWith(".")){let filePath=join47(projectDir,entry2),stats2=await stat4(filePath),sessionId=entry2.replace(".jsonl","");sessions.push({sessionId,fullPath:filePath,fileMtime:stats2.mtimeMs,messageCount:0,created:stats2.birthtime.toISOString(),modified:stats2.mtime.toISOString(),projectPath:"",isSidechain:!1})}}catch{}return sessions}async function findActiveSession(projectDir){let sessions=await listSessions2(projectDir);if(sessions.length===0)return null;return sessions.sort((a,b2)=>{let timeA=new Date(a.modified).getTime();return new Date(b2.modified).getTime()-timeA}),sessions[0]}async function findSessionById(projectDir,sessionId){return(await listSessions2(projectDir)).find((session)=>session.sessionId===sessionId)??null}function extractToolCalls(content){let toolCalls=[];for(let item of content)if(item.type==="tool_use")toolCalls.push({id:item.id||"",name:item.name||"",input:item.input||{}});return toolCalls.length>0?toolCalls:void 0}function populateAssistantFields(entry2,raw){if(raw.type!=="assistant")return;if(Array.isArray(raw.message.content))entry2.toolCalls=extractToolCalls(raw.message.content);if(raw.message.model)entry2.model=raw.message.model;if(raw.message.usage)entry2.usage=raw.message.usage}function parseLogEntry(line){if(!line||!line.trim())return null;try{let raw=JSON.parse(line);if(!raw.type)return null;let entry2={type:raw.type,sessionId:raw.sessionId||"",uuid:raw.uuid||"",parentUuid:raw.parentUuid||null,timestamp:raw.timestamp||"",cwd:raw.cwd||"",gitBranch:raw.gitBranch,version:raw.version,raw};if(raw.message)entry2.message={role:raw.message.role,content:raw.message.content},populateAssistantFields(entry2,raw);if(raw.data)entry2.data=raw.data;return entry2}catch{return null}}async function readLogFile(logPath){let entries=[];try{let lines=(await readFile8(logPath,"utf-8")).split(`
1655
+ Stopping inbox watcher...`),stopInboxWatcher2(handle),process.exit(0)};process.on("SIGINT",shutdown2),process.on("SIGTERM",shutdown2),await new Promise(()=>{})});let chat=program2.command("chat").description("Conversation management (PG-backed)");chat.command("send <conversationId> <message>").description("Send a message to a specific conversation").option("--reply-to <msgId>","Reply to a specific message ID").option("--from <sender>","Sender ID (auto-detected)").action(async(conversationId,message,options)=>{try{let ts3=await getTaskService();if(!await ts3.getConversation(conversationId))console.error(`Error: conversation "${conversationId}" not found.
1656
+ Run \`genie chat list\` to see available conversations, or \`genie inbox\` to start one.`),process.exit(1);let from=options.from??await detectSenderIdentity(),actor=localActor(from),replyTo=options.replyTo?Number(options.replyTo):void 0,msg=await ts3.sendMessage(conversationId,actor,message,replyTo);console.log(`Message #${msg.id} sent to conversation ${conversationId}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("thread <messageId>").description("Create a threaded sub-conversation from a message").option("--name <name>","Thread name").option("--from <sender>","Sender ID (auto-detected)").action(async(messageId,options)=>{try{await handleChatThread(messageId,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("list").description("List conversations with filters").option("--type <type>","Filter by type: dm, group").option("--linked <entity>","Filter by linked entity: task, team").option("--json","Output as JSON").option("--from <sender>","Actor ID (auto-detected)").action(async(options)=>{try{await handleChatList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("read <conversationId>").description("Read messages in a conversation").option("--since <timestamp>","Show messages since timestamp").option("--limit <n>","Limit number of messages","50").option("--json","Output as JSON").action(async(conversationId,options)=>{try{await handleChatRead(conversationId,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}var _registry,_taskService,_teamManager,_mailbox,PARENT_CHAIN_MAX_DEPTH=3,DEFAULT_REACHBACK_PREFIXES;var init_msg=__esm(()=>{init_emit();init_term_format();DEFAULT_REACHBACK_PREFIXES=["council-"]});function buildLayoutCommand(windowTarget,mode="mosaic"){return`select-layout -t '${windowTarget}' ${mode==="vertical"?"even-horizontal":"tiled"}`}function resolveLayoutMode(layoutFlag){if(layoutFlag==="vertical")return"vertical";return"mosaic"}var exports_claude_logs={};__export(exports_claude_logs,{projectPathToHash:()=>projectPathToHash,parseLogEntry:()=>parseLogEntry,listSessions:()=>listSessions2,findClaudeProjectDir:()=>findClaudeProjectDir,findActiveSession:()=>findActiveSession,claudeTranscriptProvider:()=>claudeTranscriptProvider,claudeEntryToTranscript:()=>claudeEntryToTranscript});import{access,readFile as readFile8,readdir as readdir5,stat as stat4}from"fs/promises";import{join as join47}from"path";function projectPathToHash(projectPath){let normalized=projectPath.replace(/\/+$/,"");if(!normalized)normalized="/";return normalized.replace(/[/.]/g,"-")}function getClaudeDir(){return join47(process.env.HOME||"",".claude")}function getProjectsDir(claudeDir){return join47(claudeDir||getClaudeDir(),"projects")}async function findClaudeProjectDir(projectPath,claudeDir){let projectsDir=getProjectsDir(claudeDir),expectedHash=projectPathToHash(projectPath);try{await access(projectsDir);let projectDir=join47(projectsDir,expectedHash);return await access(projectDir),projectDir}catch{return null}}async function listSessions2(projectDir){let indexPath=join47(projectDir,"sessions-index.json");try{let content=await readFile8(indexPath,"utf-8");return JSON.parse(content).entries}catch{return await scanForSessions(projectDir)}}async function scanForSessions(projectDir){let sessions=[];try{let entries=await readdir5(projectDir);for(let entry2 of entries)if(entry2.endsWith(".jsonl")&&!entry2.startsWith(".")){let filePath=join47(projectDir,entry2),stats2=await stat4(filePath),sessionId=entry2.replace(".jsonl","");sessions.push({sessionId,fullPath:filePath,fileMtime:stats2.mtimeMs,messageCount:0,created:stats2.birthtime.toISOString(),modified:stats2.mtime.toISOString(),projectPath:"",isSidechain:!1})}}catch{}return sessions}async function findActiveSession(projectDir){let sessions=await listSessions2(projectDir);if(sessions.length===0)return null;return sessions.sort((a,b2)=>{let timeA=new Date(a.modified).getTime();return new Date(b2.modified).getTime()-timeA}),sessions[0]}async function findSessionById(projectDir,sessionId){return(await listSessions2(projectDir)).find((session)=>session.sessionId===sessionId)??null}function extractToolCalls(content){let toolCalls=[];for(let item of content)if(item.type==="tool_use")toolCalls.push({id:item.id||"",name:item.name||"",input:item.input||{}});return toolCalls.length>0?toolCalls:void 0}function populateAssistantFields(entry2,raw){if(raw.type!=="assistant")return;if(Array.isArray(raw.message.content))entry2.toolCalls=extractToolCalls(raw.message.content);if(raw.message.model)entry2.model=raw.message.model;if(raw.message.usage)entry2.usage=raw.message.usage}function parseLogEntry(line){if(!line||!line.trim())return null;try{let raw=JSON.parse(line);if(!raw.type)return null;let entry2={type:raw.type,sessionId:raw.sessionId||"",uuid:raw.uuid||"",parentUuid:raw.parentUuid||null,timestamp:raw.timestamp||"",cwd:raw.cwd||"",gitBranch:raw.gitBranch,version:raw.version,raw};if(raw.message)entry2.message={role:raw.message.role,content:raw.message.content},populateAssistantFields(entry2,raw);if(raw.data)entry2.data=raw.data;return entry2}catch{return null}}async function readLogFile(logPath){let entries=[];try{let lines=(await readFile8(logPath,"utf-8")).split(`
1654
1657
  `);for(let line of lines){let entry2=parseLogEntry(line);if(entry2)entries.push(entry2)}}catch{}return entries}async function findLogsForWorkspace(workspacePath,claudeDir){let projectDir=await findClaudeProjectDir(workspacePath,claudeDir);if(!projectDir)return null;let session=await findActiveSession(projectDir);if(!session)return null;return{projectDir,session}}async function getLogsForPane(paneWorkdir,claudeDir){let result2=await findLogsForWorkspace(paneWorkdir,claudeDir);if(!result2)return null;return{logPath:result2.session.fullPath,session:result2.session,projectDir:result2.projectDir}}function extractText(content){if(typeof content==="string")return content;if(!Array.isArray(content))return"";let parts=[];for(let item of content)if(typeof item==="string")parts.push(item);else if(item&&typeof item==="object"&&"text"in item)parts.push(String(item.text));return parts.join(" ")}function convertAssistant(entry2,base){let results=[],text=entry2.message?extractText(entry2.message.content):"";if(text)results.push({...base,role:"assistant",timestamp:entry2.timestamp,text,usage:entry2.usage?{input:entry2.usage.input_tokens,output:entry2.usage.output_tokens}:void 0});if(entry2.toolCalls)for(let tc of entry2.toolCalls)results.push({...base,role:"tool_call",timestamp:entry2.timestamp,text:`${tc.name}: ${JSON.stringify(tc.input).slice(0,200)}`,toolCall:{id:tc.id,name:tc.name,input:tc.input}});return results}function claudeEntryToTranscript(entry2){let base={provider:"claude",model:entry2.model,raw:entry2.raw};if(entry2.type==="user"&&entry2.message){let text=extractText(entry2.message.content);return text?[{...base,role:"user",timestamp:entry2.timestamp,text}]:[]}if(entry2.type==="assistant")return convertAssistant(entry2,base);if(entry2.type==="system"||entry2.type==="progress"){let text=entry2.message?extractText(entry2.message.content):"";return text?[{...base,role:"system",timestamp:entry2.timestamp,text}]:[]}return[]}var claudeTranscriptProvider;var init_claude_logs=__esm(()=>{claudeTranscriptProvider={async discoverLogPath(worker){let workspacePath=worker.worktree||worker.repoPath,projectDir=await findClaudeProjectDir(workspacePath);if(!projectDir)return null;let{getCurrentExecutor:getCurrentExecutor2}=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),sessionId=(await getCurrentExecutor2(worker.id))?.claudeSessionId??null;if(sessionId){let directPath=join47(projectDir,`${sessionId}.jsonl`);try{return await access(directPath),directPath}catch{let session=await findSessionById(projectDir,sessionId);if(session?.fullPath)return session.fullPath}}return(await getLogsForPane(workspacePath))?.logPath??null},async readEntries(logPath){return(await readLogFile(logPath)).flatMap(claudeEntryToTranscript)}}});class AppPtyProvider{name="app-pty";transport="process";buildSpawnCommand(ctx){let params=spawnContextToParams(ctx),launch=buildClaudeCommand(params);return{...launch,env:{...launch.env,GENIE_APP_PTY:"true"}}}async extractSession(executor){let sessionId=executor.claudeSessionId;if(!sessionId)return null;let{access:access2,readdir:readdir6}=await import("fs/promises"),{join:join48}=await import("path"),claudeDir=join48(process.env.HOME||"",".claude","projects");try{await access2(claudeDir)}catch{return{sessionId}}let jsonlName=`${sessionId}.jsonl`;if(executor.repoPath||executor.worktree){let{projectPathToHash:projectPathToHash2}=await Promise.resolve().then(() => (init_claude_logs(),exports_claude_logs)),path3=executor.repoPath??executor.worktree,hash=projectPathToHash2(path3),candidate=join48(claudeDir,hash,jsonlName);try{return await access2(candidate),{sessionId,logPath:candidate}}catch{}}try{let dirs=await readdir6(claudeDir);for(let dir of dirs){let candidate=join48(claudeDir,dir,jsonlName);try{return await access2(candidate),{sessionId,logPath:candidate}}catch{}}}catch{}return{sessionId}}async detectState(executor){if(!executor.pid)return"terminated";try{process.kill(executor.pid,0);let metadata=executor.metadata;if(metadata.appPtyState&&typeof metadata.appPtyState==="string")return metadata.appPtyState;return"running"}catch{return"terminated"}}async terminate(executor){if(!executor.pid)return;try{process.kill(executor.pid,"SIGTERM"),await new Promise((r)=>setTimeout(r,500))}catch{}try{process.kill(executor.pid,0),process.kill(executor.pid,"SIGKILL")}catch{}}canResume(){return!0}buildResumeCommand(ctx){let params=resumeContextToParams(ctx),launch=buildClaudeCommand(params);return{...launch,env:{...launch.env,GENIE_APP_PTY:"true"}}}async deliverMessage(executorId,message){let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),rows=await(await getConnection2())`
1655
1658
  SELECT a.custom_name, a.team
1656
1659
  FROM executors e
@@ -4805,7 +4808,9 @@ Valid columns for board "${board.name}": ${validCols}`),process.exit(1)}catch{}}
4805
4808
  Stage History:`);for(let entry2 of stageLog.slice(0,10)){let who=entry2.actorId??"system";console.log(` ${formatTimestamp(entry2.createdAt)}: ${entry2.fromStage??"(new)"} \u2192 ${entry2.toStage} by ${who}`)}}}async function printTaskMessages(task){let ts3=await getTaskService8(),conv=await ts3.findOrCreateConversation({linkedEntity:"task",linkedEntityId:task.id,name:`Task #${task.seq}`}),messages2=await ts3.getMessages(conv.id,{limit:20});if(messages2.length>0){console.log(`
4806
4809
  Messages:`);for(let msg of messages2){let time=formatTimestamp(msg.createdAt),reply2=msg.replyToId?` (reply to #${msg.replyToId})`:"";console.log(` [${time}] ${msg.senderId}: ${msg.body}${reply2}`)}}}async function printTaskDetail(task){printTaskFields(task),await printTaskRelations(task),await printTaskMessages(task),console.log("")}function printColumnTasks(label,colTasks,useColor=!0){if(console.log(`
4807
4810
  \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(truncate2(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(`
4808
- 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("")}var WISH_SLUG_PATTERN=/^[a-z0-9][a-z0-9-]*$/;function validateWishSlug(slug){if(!WISH_SLUG_PATTERN.test(slug))throw Error(`Invalid --wish slug: "${slug}". Slug must match /^[a-z0-9][a-z0-9-]*$/ (lowercase letters, digits, hyphens; no leading hyphen, no spaces, no path separators).`);return slug}function wishFileFromSlug(slug){return`.genie/wishes/${slug}/WISH.md`}function parseGhRef(gh){let match=gh.match(/^([^#]+)#(\d+)$/);if(!match)console.error(`Error: Invalid --gh format. Expected owner/repo#N, got: ${gh}`),process.exit(1);let[,ownerRepo,num]=match;return{externalId:`${ownerRepo}#${num}`,externalUrl:`https://github.com/${ownerRepo}/issues/${num}`}}async function handleTaskCreate(title,options){let ts3=await getTaskService8(),actor=currentActor3(),repoPath,projectId;if(options.project){let project=await ts3.getProjectByName(options.project);if(!project)project=await ts3.createProject({name:options.project});projectId=project.id,repoPath=project.repoPath??void 0}let parentId;if(options.parent){if(parentId=await ts3.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),externalId=options.externalId,externalUrl=options.externalUrl;if(options.gh){let parsed=parseGhRef(options.gh);externalId=parsed.externalId,externalUrl=parsed.externalUrl}let wishFile=options.wish?wishFileFromSlug(validateWishSlug(options.wish)):void 0,task=await ts3.createTask({title,typeId:options.type,stage:options.stage,priority:options.priority,dueDate:options.due,startDate:options.start,parentId,description:options.description,estimatedEffort:options.effort,boardId,externalId,externalUrl,wishFile},repoPath,projectId);if(await ts3.assignTask(task.id,actor,"creator",{},task.repoPath),options.assign)await ts3.assignTask(task.id,localActor2(options.assign),"assignee",{},task.repoPath);if(options.tags){let tagIds=options.tags.split(",").map((t)=>t.trim());await ts3.tagTask(task.id,tagIds,actor,task.repoPath)}if(options.comment)await ts3.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}`)}async function handleCloseMerged(options){let result2=await(await getCloseMergedService()).closeMergedTasks({since:options.since,dryRun:options.dryRun,repo:options.repo});if(options.dryRun)console.log("[dry-run] Would close:");for(let d of result2.details){let prefix=options.dryRun?" [dry-run]":" \u2713";console.log(`${prefix} #${d.taskSeq} "${d.taskTitle}" \u2190 PR #${d.prNumber} (${d.slug})`)}let mode=options.dryRun?"[dry-run] ":"";console.log(`
4811
+ 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("")}var WISH_SLUG_PATTERN=/^[a-z0-9][a-z0-9-]*$/;function validateWishSlug(slug){if(!WISH_SLUG_PATTERN.test(slug))throw Error(`Invalid --wish slug: "${slug}". Slug must match /^[a-z0-9][a-z0-9-]*$/ (lowercase letters, digits, hyphens; no leading hyphen, no spaces, no path separators).`);return slug}function wishFileFromSlug(slug){return`.genie/wishes/${slug}/WISH.md`}function parseGhRef(gh){let match=gh.match(/^([^#]+)#(\d+)$/);if(!match)console.error(`Error: Invalid --gh format. Expected owner/repo#N, got: ${gh}`),process.exit(1);let[,ownerRepo,num]=match;return{externalId:`${ownerRepo}#${num}`,externalUrl:`https://github.com/${ownerRepo}/issues/${num}`}}async function handleTaskCreate(title,options){let ts3=await getTaskService8(),actor=currentActor3(),repoPath,projectId;if(options.project){let project=await ts3.getProjectByName(options.project);if(!project)project=await ts3.createProject({name:options.project});projectId=project.id,repoPath=project.repoPath??void 0}let parentId;if(options.parent){if(parentId=await ts3.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);if(options.type){if(!await ts3.getType(options.type)){let validIds=(await ts3.listTypes()).map((t)=>t.id).join(", ")||"(none registered)";console.error(`Error: task type "${options.type}" does not exist.
4812
+ Valid types: ${validIds}
4813
+ Or omit --type to use the default ("software").`),process.exit(1)}}let{externalId,externalUrl}=options;if(options.gh){let parsed=parseGhRef(options.gh);externalId=parsed.externalId,externalUrl=parsed.externalUrl}let wishFile=options.wish?wishFileFromSlug(validateWishSlug(options.wish)):void 0,task=await ts3.createTask({title,typeId:options.type,stage:options.stage,priority:options.priority,dueDate:options.due,startDate:options.start,parentId,description:options.description,estimatedEffort:options.effort,boardId,externalId,externalUrl,wishFile},repoPath,projectId);if(await ts3.assignTask(task.id,actor,"creator",{},task.repoPath),options.assign)await ts3.assignTask(task.id,localActor2(options.assign),"assignee",{},task.repoPath);if(options.tags){let tagIds=options.tags.split(",").map((t)=>t.trim());await ts3.tagTask(task.id,tagIds,actor,task.repoPath)}if(options.comment)await ts3.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}`)}async function handleCloseMerged(options){let result2=await(await getCloseMergedService()).closeMergedTasks({since:options.since,dryRun:options.dryRun,repo:options.repo});if(options.dryRun)console.log("[dry-run] Would close:");for(let d of result2.details){let prefix=options.dryRun?" [dry-run]":" \u2713";console.log(`${prefix} #${d.taskSeq} "${d.taskTitle}" \u2190 PR #${d.prNumber} (${d.slug})`)}let mode=options.dryRun?"[dry-run] ":"";console.log(`
4809
4814
  ${mode}Closed ${result2.closed} task${result2.closed===1?"":"s"} from ${result2.prsScanned} merged PR${result2.prsScanned===1?"":"s"} (${result2.alreadyShipped} already shipped)`)}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("--stage <name>","Initial stage (defaults to first stage of the task type)").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").option("--gh <owner/repo#N>","Link to GitHub issue (sets external_id + external_url)").option("--external-id <id>","External tracker ID (e.g., JIRA-123)").option("--external-url <url>","External tracker URL").option("--wish <slug>","Associate task with a wish (sets wish_file to .genie/wishes/<slug>/WISH.md)").action(async(title,options)=>{try{await handleTaskCreate(title,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}});function buildTaskFilters(options){return{stage:options.stage,typeId:options.type,status:options.status,priority:options.priority,releaseId:options.release,dueBefore:options.dueBefore,projectName:options.project,boardName:options.board,externalId:options.gh?parseGhRef(options.gh).externalId:void 0,allProjects:options.all,includeArchived:options.all,limit:Number(options.limit)||100,offset:Number(options.offset)||0,...options.all?{limit:1e4}:{}}}async function handleTaskList(options){let ts3=await getTaskService8(),filters=buildTaskFilters(options),tasks;if(options.mine)tasks=await ts3.listTasksForActor(currentActor3(),filters);else tasks=await ts3.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){let indent2=process.stdout.isTTY?2:0;process.stdout.write(`${JSON.stringify(tasks,null,indent2)}
4810
4815
  `);return}printTaskList(tasks,options.all)}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("--gh <owner/repo#N>","Filter by GitHub issue link").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{await handleTaskList(options)}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 getTaskService8()).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("link <id>").description("Link task to an external tracker (GitHub, Jira, etc.)").option("--gh <owner/repo#N>","Link to GitHub issue").option("--external-id <id>","External tracker ID").option("--external-url <url>","External tracker URL").action(async(id,options)=>{try{let ts3=await getTaskService8(),externalId,externalUrl;if(options.gh){let parsed=parseGhRef(options.gh);externalId=parsed.externalId,externalUrl=parsed.externalUrl}else if(options.externalId&&options.externalUrl)externalId=options.externalId,externalUrl=options.externalUrl;else console.error("Error: Provide --gh or both --external-id and --external-url."),process.exit(1);let t=await ts3.linkTask(id,externalId,externalUrl);if(!t)console.error(`Error: Task not found: ${id}`),process.exit(1);if(console.log(`Linked task #${t.seq} to ${externalId}`),t.externalUrl)console.log(` URL: ${t.externalUrl}`)}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 ts3=await getTaskService8(),actor=currentActor3(),t=await ts3.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 ts3=await getTaskService8(),actor=currentActor3();if(await ts3.assignTask(id,localActor2(options.to),options.role,{}),options.comment)await ts3.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 getTaskService8()).tagTask(id,tags,currentActor3()),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 ts3=await getTaskService8(),replyTo=options.replyTo?Number(options.replyTo):void 0,msg=await ts3.commentOnTask(id,currentActor3(),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 ts3=await getTaskService8(),actor=currentActor3(),t=await ts3.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 ts3=await getTaskService8(),actor=currentActor3(),t=await ts3.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("priority <id> <level>").description("Set the priority of an existing task (urgent|high|normal|low)").option("--comment <msg>","Audit comment for the priority change").action(async(id,level,options)=>{try{let validLevels=["urgent","high","normal","low"];if(!validLevels.includes(level))console.error(`Error: invalid priority "${level}". Valid: ${validLevels.join(", ")}`),process.exit(1);let priority=level,ts3=await getTaskService8(),actor=currentActor3(),comment=options.comment?{actor,body:options.comment}:void 0,t=await ts3.updateTask(id,{priority},void 0,comment);if(!t)console.error(`Error: Task not found: ${id}`),process.exit(1);let colorCode=PRIORITY_COLORS[level]??"";console.log(`Task #${t.seq} priority set to ${colorCode}${level}${RESET2}.`)}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 ts3=await getTaskService8(),actor=currentActor3(),t=await ts3.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 ts3=await getTaskService8(),runId=getRunId(),t=await ts3.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 ts3=await getTaskService8(),runId=getRunId(),t=await ts3.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 getTaskService8()).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("close-merged").description("Auto-close tasks whose wish slugs match recently merged PRs").option("--since <duration>",'Time window for merged PRs (e.g., "24h", "7d")',"24h").option("--dry-run","Show what would be closed without acting").option("--repo <owner/repo>","Override GitHub remote detection").action(async(options)=>{try{await handleCloseMerged(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("archive <id>").description("Archive a task (soft-delete \u2014 preserves all data)").action(async(id)=>{try{let ts3=await getTaskService8(),actor=currentActor3(),t=await ts3.archiveTask(id,actor);console.log(`Archived task #${t.seq}: ${t.title}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("unarchive <id>").description("Restore an archived task to its previous status").action(async(id)=>{try{let ts3=await getTaskService8(),actor=currentActor3(),t=await ts3.unarchiveTask(id,actor);console.log(`Unarchived task #${t.seq}: ${t.title} (status: ${t.status})`)}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 ts3=await getTaskService8();if(options.remove){if(await ts3.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 ts3.addDependency(id,options.dependsOn,"depends_on"),console.log(`${id} now depends on ${options.dependsOn}.`);if(options.blocks)await ts3.addDependency(id,options.blocks,"blocks"),console.log(`${id} now blocks ${options.blocks}.`);if(options.relatesTo)await ts3.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();init_term_format();function registerTemplateCommands(program2){let tmpl=program2.command("template").description("Board template management");tmpl.command("list",{isDefault:!0}).description("List available templates").option("--json","Output as JSON").action(async(options)=>{let{listTemplates:listTemplates3}=await Promise.resolve().then(() => (init_template_service(),exports_template_service)),templates=await listTemplates3();if(options.json){console.log(JSON.stringify(templates,null,2));return}if(templates.length===0){console.log("No templates found.");return}let maxName=Math.max(...templates.map((t)=>t.name.length),4);for(let t of templates){let cols=t.columns?.length??0,tag=t.isBuiltin?" (builtin)":"";console.log(` ${padRight(t.name,maxName)} ${cols} columns${tag} ${t.id}`)}console.log(`
4811
4816
  ${templates.length} template${templates.length===1?"":"s"}`)}),tmpl.command("show <name>").description("Show template details").option("--json","Output as JSON").action(async(name,options)=>{let{getTemplate:getTemplate2}=await Promise.resolve().then(() => (init_template_service(),exports_template_service)),t=await getTemplate2(name);if(!t)console.error(`Template "${name}" not found.`),process.exit(1);if(options.json){console.log(JSON.stringify(t,null,2));return}if(console.log(`
@@ -4885,4 +4890,4 @@ Examples:
4885
4890
  genie spawn researcher --model sonnet # Spawn with model override
4886
4891
  genie spawn my-agent --team my-feature # Spawn into a specific team
4887
4892
  genie spawn council--questioner --provider codex # Use Codex provider`).action(async(name,options)=>{if(options.autoSync===!1)options.noAutoSync=!0;try{await handleWorkerSpawn(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("kill <name>").description("Force kill an agent by name").action(async(name)=>{try{await handleWorkerKill(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("stop <name>").description("Stop an agent (preserves session for resume)").action(async(name)=>{try{await handleWorkerStop(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("resume [name]").description("Resume a suspended/failed agent with its Claude session").option("--all","Resume all eligible agents").action(async(name,options)=>{try{await handleWorkerResume(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("history <name>").description("Show compressed session history for an agent").option("--full","Show full conversation without compression").option("--since <n>","Show last N user/assistant exchanges",Number.parseInt).option("--last <n>","Show last N transcript entries",Number.parseInt).option("--type <role>","Filter by role (user, assistant, tool_call)").option("--after <timestamp>","Only entries after ISO timestamp").option("--json","Output as JSON").option("--ndjson","Output as newline-delimited JSON (pipeable to jq)").option("--raw","Output raw JSONL entries").option("--log-file <path>","Direct path to log file (for testing)").action(async(name,options)=>{await historyCommand(name,options)});program2.command("log [agent]").description("Unified observability feed \u2014 aggregates transcript, DMs, team chat").option("--team <name>","Show interleaved feed for all agents in a team").option("--type <kind>","Filter by event kind (transcript, message, tool_call, state, system)").option("--since <timestamp>","Only events after ISO timestamp").option("--last <n>","Show last N events",Number.parseInt).option("--ndjson","Output as newline-delimited JSON (pipeable to jq)").option("--json","Output as pretty JSON").option("-f, --follow","Follow mode \u2014 real-time streaming").action(async(agent,options)=>{await logCommand(agent,options)});var qaCmd=program2.command("qa").description("QA \u2014 self-testing system for genie CLI");qaCmd.command("run [target]",{isDefault:!0}).description("Run QA specs (all, a domain, or a single spec)").option("--timeout <seconds>","Max seconds per spec",(v2)=>Number(v2),3600).option("--parallel <n>","Max specs to run in parallel",(v2)=>Number(v2),5).option("--verbose","Show all collected events").option("--ndjson","Machine-readable NDJSON output").action(async(target,options)=>{await qaCommand(target,options)});qaCmd.command("status").description("Show QA dashboard with last results per spec").option("--json","Output as JSON").action(async(options)=>{await qaStatusCommand(options)});qaCmd.command("history").description("Show recent QA runs").action(async()=>{await qaHistoryCommand()});qaCmd.command("check <specFile>").description("Evaluate a QA spec against current team logs and publish qa-report").option("--team <name>","Team name (defaults to GENIE_TEAM)").option("--since <timestamp>","Only consider events after this ISO timestamp").option("--since-file <path>","Read the lower-bound timestamp from a file").action(async(specFile,options)=>{await qaCheckCommand(specFile,options)});program2.command("qa-report <json>").description("Publish QA result to the PG event log (called by QA team-lead)").action(async(json2)=>{let team=process.env.GENIE_TEAM;if(!team)console.error("Error: GENIE_TEAM not set. This command must be run by a QA team-lead agent."),process.exit(1);try{let data=JSON.parse(json2),{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(process.cwd(),`genie.qa.${team}.result`,{kind:"qa",agent:"qa",team,text:`QA result: ${String(data.result??"unknown")}`,data,source:"hook"}),console.log(`QA result published to PG event log as genie.qa.${team}.result`)}catch(err){console.error(`Failed to publish QA result: ${err}`),process.exit(1)}});program2.command("read <name>").description("Read terminal output from an agent pane").option("-n, --lines <number>","Number of lines to read").option("--from <line>","Start line").option("--to <line>","End line").option("--range <range>",'Line range (e.g., "10-20")').option("--search <text>","Search for text").option("--grep <pattern>","Grep for pattern").option("-f, --follow","Follow mode (like tail -f)").option("--all","Show all output").option("-r, --reverse","Reverse order").option("--json","Output as JSON").action(async(name,options)=>{await readSessionLogs2(name,options)});program2.command("answer <name> <choice>").description('Answer a question for an agent (use "text:..." for text input)').action(async(name,choice)=>{await answerQuestion(name,choice)});program2.command("status").description("Aggregated observability \u2014 agents to resume, active alerts, optional health checklist").option("--health","Add the four-item health checklist (partition, watchdog, spill, watcher metrics)").option("--all","Include archived / done agents").option("--debug","Add structural-inference audit (former `doctor --state`)").option("--json","Emit JSON instead of human output").action(async(options)=>{try{await statusCommand2(options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("ls").description("List registered agents with runtime status").option("--json","Output as JSON").option("--source <name>","Filter by executor metadata source (e.g. omni)").option("--all","Include archived agents (hidden by default)").action(async(options)=>{try{await handleLsCommand(options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});var args=process.argv.slice(2),isTuiPane=process.env.GENIE_TUI_PANE==="left"&&args.length===0,tuiRightPane=process.env.GENIE_TUI_RIGHT;delete process.env.GENIE_TUI_PANE;delete process.env.GENIE_TUI_RIGHT;delete process.env.GENIE_IS_DAEMON;if(isTuiPane){let{isTuiDisabled:isTuiDisabled2,noticeTuiSkipped:noticeTuiSkipped2}=await Promise.resolve().then(() => (init_tui_disable(),exports_tui_disable));if(isTuiDisabled2())noticeTuiSkipped2("renderer"),await new Promise(()=>{}),process.exit(0);if(tuiRightPane)process.env.GENIE_TUI_RIGHT=tuiRightPane;let{launchTui:launchTui2}=await Promise.resolve().then(() => (init_tui(),exports_tui));await launchTui2(),process.exit(0)}if(args.length===0){{let{isTuiDisabled:isTuiDisabled2,noticeTuiSkipped:noticeTuiSkipped2}=await Promise.resolve().then(() => (init_tui_disable(),exports_tui_disable));if(isTuiDisabled2())noticeTuiSkipped2("attach"),console.error(" Use `genie ls`, `genie spawn <agent>`, `genie log`, etc. directly."),console.error(" Unset GENIE_TUI_DISABLE (or omit --no-tui) to re-enable the TUI."),process.exit(0)}if(process.env.TMUX?.includes("genie-tui")){let{findWorkspace:findWorkspace3}=await Promise.resolve().then(() => (init_workspace(),exports_workspace)),ws2=findWorkspace3();if(ws2){let{resolveAgentFromCwd:resolveAgentFromCwd3}=await Promise.resolve().then(() => (init_resolve_agent_cwd(),exports_resolve_agent_cwd)),resolved2=resolveAgentFromCwd3(process.cwd(),ws2.root);if(resolved2.source!=="default"){let{writeFileSync:writeFileSync34}=await import("fs"),{join:join95}=await import("path"),home=process.env.GENIE_HOME??join95((await import("os")).homedir(),".genie");try{writeFileSync34(join95(home,"tui-initial-agent"),resolved2.agent,"utf-8")}catch{}console.log(`Navigating to ${resolved2.agent}...`)}else console.log("Already inside the genie TUI. Use Ctrl-b d to detach, or run genie commands directly.")}else console.log("Already inside the genie TUI. Use Ctrl-b d to detach, or run genie commands directly.");process.exit(0)}if(process.env.TMUX)console.warn("Note: switching to genie TUI from within another tmux session.");let{findWorkspace:findWorkspace2}=await Promise.resolve().then(() => (init_workspace(),exports_workspace)),ws=findWorkspace2();if(!ws){let{isInteractive:isInteractive2}=await Promise.resolve().then(() => (init_interactivity(),exports_interactivity));if(!isInteractive2())console.error("No workspace found. Run `genie init` to set up."),process.exit(2);let{confirm:confirm3}=await Promise.resolve().then(() => (init_esm14(),exports_esm));if(!await confirm3({message:"No workspace found. Initialize? [Y/n]",default:!0}))console.error("No workspace found. Run `genie init` to set up."),process.exit(2);let{mkdirSync:mkdirSync36,writeFileSync:writeFileSync34}=await import("fs"),{basename:basename17,join:join95}=await import("path"),cwd=process.cwd(),genieDir=join95(cwd,".genie");mkdirSync36(genieDir,{recursive:!0});let config={name:basename17(cwd),agents:{defaults:{}},tmux:{socket:"genie"},sdk:{}};if(writeFileSync34(join95(genieDir,"workspace.json"),`${JSON.stringify(config,null,2)}
4888
- `),console.log(`Workspace initialized: ${cwd}`),ws=findWorkspace2(),!ws)console.error("Failed to initialize workspace."),process.exit(1)}let{resolveAgentFromCwd:resolveAgentFromCwd2}=await Promise.resolve().then(() => (init_resolve_agent_cwd(),exports_resolve_agent_cwd)),resolved=resolveAgentFromCwd2(process.cwd(),ws.root),initialAgent=resolved.agent,{isServeRunning:isServeRunning2,autoStartServe:autoStartServe2,isTuiSessionReady:isTuiSessionReady2,ensureTuiSession:ensureTuiSession2}=await Promise.resolve().then(() => (init_serve(),exports_serve));if(!isServeRunning2())console.log("Starting genie serve..."),await autoStartServe2();else if(!isTuiSessionReady2())ensureTuiSession2(ws.root);if(ws.root)process.env.GENIE_TUI_WORKSPACE=ws.root;if(initialAgent)process.env.GENIE_TUI_AGENT=initialAgent;if(resolved.source!=="default"){let{execSync:execSync19}=await import("child_process");try{execSync19(`tmux has-session -t =${initialAgent} 2>/dev/null`,{stdio:"pipe"})}catch{console.log(`Spawning ${initialAgent}...`);try{execSync19(`genie spawn ${initialAgent}`,{stdio:"inherit",timeout:15000})}catch{}}}if(initialAgent){let{writeFileSync:writeFileSync34}=await import("fs"),{join:join95}=await import("path"),home=process.env.GENIE_HOME??join95((await import("os")).homedir(),".genie");try{writeFileSync34(join95(home,"tui-initial-agent"),initialAgent,"utf-8")}catch{}}let{attachTuiSession:attachTuiSession2}=await Promise.resolve().then(() => (init_tmux2(),exports_tmux2));attachTuiSession2(),process.exit(0)}if(args.every((a)=>a==="--reset")){let{sessionCommand:sessionCommand2}=await Promise.resolve().then(() => (init_session(),exports_session));await sessionCommand2({reset:!0}),process.exit(0)}var sessionIdx=args.indexOf("--session");if(sessionIdx!==-1&&sessionIdx+1<args.length){let sessionName=args[sessionIdx+1];if(!args.filter((_2,i2)=>i2!==sessionIdx&&i2!==sessionIdx+1).some((a)=>!a.startsWith("-")))try{await startNamedSession(sessionName),process.exit(0)}catch(err){console.error(`Error: ${err instanceof Error?err.message:err}`),process.exit(1)}else try{await program2.parseAsync(process.argv)}finally{await stopOtelReceiver().catch(()=>{}),await shutdown().catch(()=>{})}}else try{let _cmdStart=Date.now();if(await program2.parseAsync(process.argv),process.env.GENIE_PROFILE_DB)console.error(`[profile] parseAsync=${Date.now()-_cmdStart}ms`)}finally{let _shutStart=Date.now();if(await stopOtelReceiver().catch(()=>{}),await shutdown().catch(()=>{}),process.env.GENIE_PROFILE_DB)console.error(`[profile] shutdown=${Date.now()-_shutStart}ms`)}
4893
+ `),console.log(`Workspace initialized: ${cwd}`),ws=findWorkspace2(),!ws)console.error("Failed to initialize workspace."),process.exit(1)}let{resolveAgentFromCwd:resolveAgentFromCwd2}=await Promise.resolve().then(() => (init_resolve_agent_cwd(),exports_resolve_agent_cwd)),resolved=resolveAgentFromCwd2(process.cwd(),ws.root),initialAgent=resolved.agent,{isServeRunning:isServeRunning2,autoStartServe:autoStartServe2,isTuiSessionReady:isTuiSessionReady2,ensureTuiSession:ensureTuiSession2}=await Promise.resolve().then(() => (init_serve(),exports_serve));if(!isServeRunning2())console.log("Starting genie serve..."),await autoStartServe2();if(!isTuiSessionReady2())ensureTuiSession2(ws.root);if(ws.root)process.env.GENIE_TUI_WORKSPACE=ws.root;if(initialAgent)process.env.GENIE_TUI_AGENT=initialAgent;if(resolved.source!=="default"){let{execSync:execSync19}=await import("child_process");try{execSync19(`tmux has-session -t =${initialAgent} 2>/dev/null`,{stdio:"pipe"})}catch{console.log(`Spawning ${initialAgent}...`);try{execSync19(`genie spawn ${initialAgent}`,{stdio:"inherit",timeout:15000})}catch{}}}if(initialAgent){let{writeFileSync:writeFileSync34}=await import("fs"),{join:join95}=await import("path"),home=process.env.GENIE_HOME??join95((await import("os")).homedir(),".genie");try{writeFileSync34(join95(home,"tui-initial-agent"),initialAgent,"utf-8")}catch{}}let{attachTuiSession:attachTuiSession2}=await Promise.resolve().then(() => (init_tmux2(),exports_tmux2));attachTuiSession2(),process.exit(0)}if(args.every((a)=>a==="--reset")){let{sessionCommand:sessionCommand2}=await Promise.resolve().then(() => (init_session(),exports_session));await sessionCommand2({reset:!0}),process.exit(0)}var sessionIdx=args.indexOf("--session");if(sessionIdx!==-1&&sessionIdx+1<args.length){let sessionName=args[sessionIdx+1];if(!args.filter((_2,i2)=>i2!==sessionIdx&&i2!==sessionIdx+1).some((a)=>!a.startsWith("-")))try{await startNamedSession(sessionName),process.exit(0)}catch(err){console.error(`Error: ${err instanceof Error?err.message:err}`),process.exit(1)}else try{await program2.parseAsync(process.argv)}finally{await stopOtelReceiver().catch(()=>{}),await shutdown().catch(()=>{})}}else try{let _cmdStart=Date.now();if(await program2.parseAsync(process.argv),process.env.GENIE_PROFILE_DB)console.error(`[profile] parseAsync=${Date.now()-_cmdStart}ms`)}finally{let _shutStart=Date.now();if(await stopOtelReceiver().catch(()=>{}),await shutdown().catch(()=>{}),process.env.GENIE_PROFILE_DB)console.error(`[profile] shutdown=${Date.now()-_shutStart}ms`)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/genie",
3
- "version": "4.260504.20",
3
+ "version": "4.260504.22",
4
4
  "description": "Collaborative terminal toolkit for human + AI workflows. NOTE: the npm distribution is being soft-deprecated — the canonical install is `curl -fsSL https://get.automagik.dev/genie | bash` (cosign + SLSA verified). See https://automagik.dev/genie/security/distribution-sovereignty",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie",
3
- "version": "4.260504.20",
3
+ "version": "4.260504.22",
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.260504.20",
3
+ "version": "4.260504.22",
4
4
  "private": true,
5
5
  "description": "Runtime dependencies for genie bundled CLIs",
6
6
  "type": "module",