@agimon-ai/workflow-mcp 0.3.4 → 0.4.0
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/cli.cjs +3 -3
- package/dist/cli.mjs +4 -4
- package/package.json +5 -5
package/dist/cli.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const e=require(`./stdio-CMbfCrs1.cjs`);let t=require(`node:child_process`),n=require(`node:fs/promises`),r=require(`node:path`),i=require(`node:fs`),a=require(`@agimon-ai/foundation-process-registry`),o=require(`node:readline`),s=require(`commander`),c=require(`node:module`);var l=`0.3.
|
|
3
|
-
`,`utf8`)},workflowStatusFile:process.env[u]};async function ne(e=te){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=re(n);if(!r?.conversationId){await p(e,n,r,{decision:`ignore`,reason:r?`missing conversation id`:`invalid stop hook JSON`});return}let i=oe(await e.readStatusFile(t));if(i.status!==`unbound`){await p(e,n,r,{decision:`ignore`,reason:se(i.status)});return}if(!r.fullyIdle){e.writeStdout?.(ie(d)),await p(e,n,r,{decision:`continue`,reason:d});return}if(f(r.error)||r.terminationReason!==`model_stop`){await p(e,n,r,{decision:`ignore`,reason:f(r.error)?`stop hook reported an error`:`unsupported termination reason: ${r.terminationReason??`missing`}`});return}await e.writeStatusFile(t),await p(e,n,r,{decision:`complete`})}function re(e){try{let t=JSON.parse(e);return ce(t)?{conversationId:typeof t.conversationId==`string`?t.conversationId:void 0,transcriptPath:ae(t.transcriptPath),terminationReason:typeof t.terminationReason==`string`?t.terminationReason:void 0,error:typeof t.error==`string`?t.error:void 0,fullyIdle:t.fullyIdle===!0}:null}catch{return null}}function ie(e){return`${JSON.stringify({decision:`continue`,reason:e})}\n`}function ae(e){return typeof e==`string`&&e.trim().length>0?e:null}function f(e){return typeof e==`string`&&e.trim().length>0}function oe(e){if(e===null)return{status:`missing`};let t=e.trim();if(!t)return{status:`unbound`};if(t===`YES`)return{status:`completed`};try{let e=JSON.parse(t);return typeof e.sessionId==`string`&&e.sessionId.length>0?{status:`bound`,sessionId:e.sessionId}:{status:`invalid`}}catch{return{status:`invalid`}}}function se(e){switch(e){case`bound`:return`workflow status file is bound to a session`;case`completed`:return`workflow status file already completed`;case`invalid`:return`invalid workflow status file binding`;case`missing`:return`missing workflow status file`}}function ce(e){return typeof e==`object`&&!!e}async function le(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(String(t)));return Buffer.concat(e).toString(`utf8`)}async function ue(e){try{return await(0,n.readFile)(e,`utf8`)}catch{return null}}async function p(e,t,n,r){try{await e.writeDebugLog?.({timestamp:new Date().toISOString(),rawInput:t,input:n,decision:r})}catch{}}async function de(e,t){let i=await fe(e);i.push(t),await(0,n.mkdir)((0,r.dirname)(e),{recursive:!0}),await(0,n.writeFile)(e,`${JSON.stringify(i.slice(-50),null,2)}\n`,`utf8`)}async function fe(e){try{let t=JSON.parse(await(0,n.readFile)(e,`utf8`));return Array.isArray(t)?t:[]}catch{return[]}}const pe=new s.Command(`antigravity-stop-hook`).description(`Antigravity stop hook: reads Stop hook JSON on stdin and marks ${u} when the execution is fully idle`).action(async()=>{await ne()});var m=class extends Error{constructor(e,t,n={},r){super(e,r),this.code=t,this.context=n,this.name=`WorkflowCommandError`}};const h=`check-codex-quota`;function me(t={}){let n=t.createService??(()=>new e.a),r=t.exit??(e=>process.exit(e)),i=t.logError??((e,t)=>console.error(e,t)),a=t.writeStdout??(e=>process.stdout.write(e));return new s.Command(h).description(`Check whether Codex quota is currently blocking new work`).action(async()=>{try{let e=await n().getQuotaStatus();if(a(`${JSON.stringify({blockingLimit:e?.blockingLimit??null,planType:e?.planType??null},null,2)}\n`),e?.blockingLimit){let t=new m(`Codex quota is blocking work at ${e.blockingLimit.limitId}/${e.blockingLimit.window}.`,`CODEX_QUOTA_BLOCKED`,{command:h,limitId:e.blockingLimit.limitId,limitName:e.blockingLimit.limitName,window:e.blockingLimit.window});i(`${t.message} [${t.code}]`,t),r(2);return}r(0)}catch(e){let t=e instanceof m?e:new m(`Error checking Codex quota.`,`CHECK_CODEX_QUOTA_COMMAND_FAILED`,{command:h},{cause:e});i(`${t.message} [${t.code}]`,t),r(1)}})}const he=me(),g=`WORKFLOW_STATUS_FILE`,ge=(0,r.join)(process.cwd(),`tmp`,`claude-stop-hook.json`),_=`In-progress work is still running. Wait for spawned agents and running tool calls to finish before ending the turn.`,_e=new Set([`aborted`,`cancelled`,`canceled`,`completed`,`crashed`,`done`,`error`,`failed`,`killed`,`not_running`,`retired`,`settled`,`stopped`,`success`,`succeeded`]),ve={readStdin:y,readStatusFile:
|
|
4
|
-
`,`utf8`)},workflowStatusFile:process.env[g]},ye={readStdin:y,
|
|
2
|
+
const e=require(`./stdio-CMbfCrs1.cjs`);let t=require(`node:child_process`),n=require(`node:fs/promises`),r=require(`node:path`),i=require(`node:fs`),a=require(`@agimon-ai/foundation-process-registry`),o=require(`node:readline`),s=require(`commander`),c=require(`node:module`);var l=`0.3.5`;const u=`WORKFLOW_STATUS_FILE`,ee=(0,r.join)(process.cwd(),`tmp`,`antigravity-stop-hook.json`),d=`In-progress work is still running. Continue the execution loop until background tasks finish.`,te={readStdin:le,readStatusFile:ue,writeDebugLog:async e=>{await de(ee,e)},writeStdout:e=>{process.stdout.write(e)},writeStatusFile:async e=>{await(0,n.writeFile)(e,`YES
|
|
3
|
+
`,`utf8`)},workflowStatusFile:process.env[u]};async function ne(e=te){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=re(n);if(!r?.conversationId){await p(e,n,r,{decision:`ignore`,reason:r?`missing conversation id`:`invalid stop hook JSON`});return}let i=oe(await e.readStatusFile(t));if(i.status!==`unbound`){await p(e,n,r,{decision:`ignore`,reason:se(i.status)});return}if(!r.fullyIdle){e.writeStdout?.(ie(d)),await p(e,n,r,{decision:`continue`,reason:d});return}if(f(r.error)||r.terminationReason!==`model_stop`){await p(e,n,r,{decision:`ignore`,reason:f(r.error)?`stop hook reported an error`:`unsupported termination reason: ${r.terminationReason??`missing`}`});return}await e.writeStatusFile(t),await p(e,n,r,{decision:`complete`})}function re(e){try{let t=JSON.parse(e);return ce(t)?{conversationId:typeof t.conversationId==`string`?t.conversationId:void 0,transcriptPath:ae(t.transcriptPath),terminationReason:typeof t.terminationReason==`string`?t.terminationReason:void 0,error:typeof t.error==`string`?t.error:void 0,fullyIdle:t.fullyIdle===!0}:null}catch{return null}}function ie(e){return`${JSON.stringify({decision:`continue`,reason:e})}\n`}function ae(e){return typeof e==`string`&&e.trim().length>0?e:null}function f(e){return typeof e==`string`&&e.trim().length>0}function oe(e){if(e===null)return{status:`missing`};let t=e.trim();if(!t)return{status:`unbound`};if(t===`YES`)return{status:`completed`};try{let e=JSON.parse(t);return typeof e.sessionId==`string`&&e.sessionId.length>0?{status:`bound`,sessionId:e.sessionId}:{status:`invalid`}}catch{return{status:`invalid`}}}function se(e){switch(e){case`bound`:return`workflow status file is bound to a session`;case`completed`:return`workflow status file already completed`;case`invalid`:return`invalid workflow status file binding`;case`missing`:return`missing workflow status file`}}function ce(e){return typeof e==`object`&&!!e}async function le(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(String(t)));return Buffer.concat(e).toString(`utf8`)}async function ue(e){try{return await(0,n.readFile)(e,`utf8`)}catch{return null}}async function p(e,t,n,r){try{await e.writeDebugLog?.({timestamp:new Date().toISOString(),rawInput:t,input:n,decision:r})}catch{}}async function de(e,t){let i=await fe(e);i.push(t),await(0,n.mkdir)((0,r.dirname)(e),{recursive:!0}),await(0,n.writeFile)(e,`${JSON.stringify(i.slice(-50),null,2)}\n`,`utf8`)}async function fe(e){try{let t=JSON.parse(await(0,n.readFile)(e,`utf8`));return Array.isArray(t)?t:[]}catch{return[]}}const pe=new s.Command(`antigravity-stop-hook`).description(`Antigravity stop hook: reads Stop hook JSON on stdin and marks ${u} when the execution is fully idle`).action(async()=>{await ne()});var m=class extends Error{constructor(e,t,n={},r){super(e,r),this.code=t,this.context=n,this.name=`WorkflowCommandError`}};const h=`check-codex-quota`;function me(t={}){let n=t.createService??(()=>new e.a),r=t.exit??(e=>process.exit(e)),i=t.logError??((e,t)=>console.error(e,t)),a=t.writeStdout??(e=>process.stdout.write(e));return new s.Command(h).description(`Check whether Codex quota is currently blocking new work`).action(async()=>{try{let e=await n().getQuotaStatus();if(a(`${JSON.stringify({blockingLimit:e?.blockingLimit??null,planType:e?.planType??null},null,2)}\n`),e?.blockingLimit){let t=new m(`Codex quota is blocking work at ${e.blockingLimit.limitId}/${e.blockingLimit.window}.`,`CODEX_QUOTA_BLOCKED`,{command:h,limitId:e.blockingLimit.limitId,limitName:e.blockingLimit.limitName,window:e.blockingLimit.window});i(`${t.message} [${t.code}]`,t),r(2);return}r(0)}catch(e){let t=e instanceof m?e:new m(`Error checking Codex quota.`,`CHECK_CODEX_QUOTA_COMMAND_FAILED`,{command:h},{cause:e});i(`${t.message} [${t.code}]`,t),r(1)}})}const he=me(),g=`WORKFLOW_STATUS_FILE`,ge=(0,r.join)(process.cwd(),`tmp`,`claude-stop-hook.json`),_=`In-progress work is still running. Wait for spawned agents and running tool calls to finish before ending the turn.`,_e=new Set([`aborted`,`cancelled`,`canceled`,`completed`,`crashed`,`done`,`error`,`failed`,`killed`,`not_running`,`retired`,`settled`,`stopped`,`success`,`succeeded`]),ve={readStdin:y,readStatusFile:ke,writeDebugLog:async e=>{await x(ge,e)},writeStdout:e=>{process.stdout.write(e)},writeStatusFile:async e=>{await(0,n.writeFile)(e,`YES
|
|
4
|
+
`,`utf8`)},workflowStatusFile:process.env[g]},ye={readStdin:y,workflowStatusFile:process.env[g]};async function be(e=ye){e.workflowStatusFile&&(await e.readStdin()).trim().length}async function xe(e=ve){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=Ce(n);if(!r){await b(e,n,null,{decision:`ignore`,reason:`invalid stop hook JSON`});return}if(r.hook_event_name&&r.hook_event_name!==`Stop`){await b(e,n,r,{decision:`ignore`,reason:`unsupported hook event: ${r.hook_event_name}`});return}let i=Ee(await e.readStatusFile(t));if(!De(i)){await b(e,n,r,{decision:`ignore`,reason:Oe(i.status)});return}if(r.background_tasks.some(we)){e.writeStdout?.(Se(_)),await b(e,n,r,{decision:`block`,reason:_});return}await e.writeStatusFile(t),await b(e,n,r,{decision:`complete`})}function Se(e){return`${JSON.stringify({decision:`block`,reason:e,suppressOutput:!0})}\n`}function Ce(e){try{let t=JSON.parse(e);if(!v(t))return null;let n=Array.isArray(t.background_tasks)?t.background_tasks:[],r=Array.isArray(t.session_crons)?t.session_crons:[];return{hook_event_name:typeof t.hook_event_name==`string`?t.hook_event_name:void 0,session_id:typeof t.session_id==`string`?t.session_id:void 0,stop_hook_active:t.stop_hook_active===!0,background_tasks:n,session_crons:r}}catch{return null}}function we(e){if(!v(e))return!0;let t=Te(e.status);return!t||!_e.has(t)}function Te(e){return typeof e==`string`&&e.trim().length>0?e.trim().toLowerCase().replaceAll(`-`,`_`):null}function Ee(e){if(e===null)return{status:`missing`};let t=e.trim();if(!t)return{status:`unbound`};if(t===`YES`)return{status:`completed`};try{let e=JSON.parse(t);return typeof e.sessionId==`string`&&e.sessionId.length>0?{status:`bound`,sessionId:e.sessionId}:{status:`invalid`}}catch{return{status:`invalid`}}}function De(e){return e.status===`bound`||e.status===`unbound`}function Oe(e){switch(e){case`completed`:return`workflow status file already completed`;case`invalid`:return`invalid workflow status file binding`;case`missing`:return`missing workflow status file`;case`unbound`:return`workflow status file is unbound`}}function v(e){return typeof e==`object`&&!!e}async function y(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(String(t)));return Buffer.concat(e).toString(`utf8`)}async function ke(e){try{return await(0,n.readFile)(e,`utf8`)}catch{return null}}async function b(e,t,n,r){try{await e.writeDebugLog?.({timestamp:new Date().toISOString(),rawInput:t,input:n,decision:r})}catch{}}async function x(e,t){let i=await S(e);i.push(t),await(0,n.mkdir)((0,r.dirname)(e),{recursive:!0}),await(0,n.writeFile)(e,`${JSON.stringify(i.slice(-50),null,2)}\n`,`utf8`)}async function S(e){try{let t=JSON.parse(await(0,n.readFile)(e,`utf8`));return Array.isArray(t)?t:[]}catch{return[]}}const C=new s.Command(`claude-session-start-hook`).description(`Claude Code SessionStart hook compatibility no-op for ${g}`).action(async()=>{await be()}),w=new s.Command(`claude-stop-hook`).description(`Claude Code stop hook: reads Stop hook JSON on stdin and marks ${g} when the root session is complete`).action(async()=>{await xe()}),T=`WORKFLOW_STATUS_FILE`,Ae=(0,r.join)(process.cwd(),`tmp`,`codex-stop-hook.json`),E=`session_meta`,D=`In-progress work is still running. Wait for spawned agents and running tool calls to finish before ending the turn.`,je=[`sub_agent`,`subagent`],Me=new Set([`in_progress`,`inProgress`,`running`,`pending_init`,`pendingInit`]),Ne=new Set([`collabAgentToolCall`,`commandExecution`,`dynamicToolCall`,`imageGeneration`,`local_shell_call`,`mcpToolCall`]),Pe={readStdin:A,readStatusFile:j,readTranscriptHead:N,readTranscriptState:P,writeDebugLog:async e=>{await Ge(Ae,e)},writeStdout:e=>{process.stdout.write(e)},writeStatusFile:async e=>{await(0,n.writeFile)(e,`YES
|
|
5
5
|
`,`utf8`)},workflowStatusFile:process.env[T]},Fe={readStdin:A,readStatusFile:j,writeSessionBinding:async(e,t)=>{await(0,n.writeFile)(e,`${JSON.stringify({sessionId:t})}\n`,`utf8`)},workflowStatusFile:process.env[T]};async function Ie(e=Fe){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=Be(n);if(!r?.session_id)return;let i=await e.readStatusFile(t);i===null||i.trim().length>0||await e.writeSessionBinding(t,r.session_id)}async function Le(e=Pe){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=Ve(n);if(!r){await M(e,n,null,{decision:`ignore`,reason:`invalid stop hook JSON`});return}if(r.hook_event_name&&r.hook_event_name!==`Stop`){await M(e,n,r,{decision:`ignore`,reason:`unsupported hook event: ${r.hook_event_name}`});return}if(r.stop_hook_active&&!r.hasTaskRegistry){await M(e,n,r,{decision:`ignore`,reason:`recursive stop hook without task registry`});return}let i=He(await e.readStatusFile(t));if(i.status!==`bound`){await M(e,n,r,{decision:`ignore`,reason:Ue(i.status)});return}if(!r.session_id||r.session_id!==i.sessionId){await M(e,n,r,{decision:`ignore`,reason:`stop hook session is not bound to the workflow status file`});return}let a=O(r.transcript_path);if(!a){await M(e,n,r,{decision:`ignore`,reason:`missing transcript path`});return}let o=await ze(e,a),s=o.sessionMeta;if(!s){await M(e,n,r,{decision:`ignore`,reason:`missing transcript session metadata`});return}if(r.session_id&&s.id&&r.session_id!==s.id){await M(e,n,r,{decision:`ignore`,reason:`stop hook session does not match transcript session`});return}if(We(s.source)){await M(e,n,r,{decision:`ignore`,reason:`transcript session is a subagent`});return}let c=r.hasTaskRegistry&&(r.background_tasks?.length??0)>0,l=r.hasTaskRegistry&&(r.session_crons?.length??0)>0;if(c||!r.hasTaskRegistry&&o.hasPendingWork){e.writeStdout?.(Re(D)),await M(e,n,r,{decision:`block`,reason:D});return}if(l){await M(e,n,r,{decision:`ignore`,reason:`session crons are still scheduled`});return}await e.writeStatusFile(t),await M(e,n,r,{decision:`complete`})}function Re(e){return`${JSON.stringify({decision:`block`,reason:e,suppressOutput:!0})}\n`}async function ze(e,t){return e.readTranscriptState?e.readTranscriptState(t):e.readTranscriptHead===N?P(t):{sessionMeta:await e.readTranscriptHead(t),hasPendingWork:!1}}function Be(e){try{let t=JSON.parse(e);return k(t)?{session_id:typeof t.session_id==`string`?t.session_id:void 0}:null}catch{return null}}function Ve(e){try{let t=JSON.parse(e);if(!k(t))return null;let n=Array.isArray(t.background_tasks)?t.background_tasks:void 0,r=Array.isArray(t.session_crons)?t.session_crons:void 0;return{hook_event_name:typeof t.hook_event_name==`string`?t.hook_event_name:void 0,session_id:typeof t.session_id==`string`?t.session_id:void 0,transcript_path:O(t.transcript_path),stop_hook_active:t.stop_hook_active===!0,background_tasks:n,session_crons:r,hasTaskRegistry:n!==void 0||r!==void 0}}catch{return null}}function O(e){return typeof e==`string`&&e.trim().length>0?e:null}function He(e){if(e===null)return{status:`missing`};let t=e.trim();if(!t)return{status:`unbound`};if(t===`YES`)return{status:`completed`};try{let e=JSON.parse(t);return typeof e.sessionId==`string`&&e.sessionId.length>0?{status:`bound`,sessionId:e.sessionId}:{status:`invalid`}}catch{return{status:`invalid`}}}function Ue(e){switch(e){case`completed`:return`workflow status file already completed`;case`invalid`:return`invalid workflow status file binding`;case`missing`:return`missing workflow status file`;case`unbound`:return`workflow status file is unbound`}}function We(e){return typeof e==`string`?e.startsWith(`subagent_`)||e.startsWith(`internal_`):k(e)?je.some(t=>t in e):!1}function k(e){return typeof e==`object`&&!!e}async function A(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(String(t)));return Buffer.concat(e).toString(`utf8`)}async function j(e){try{return await(0,n.readFile)(e,`utf8`)}catch{return null}}async function M(e,t,n,r){try{await e.writeDebugLog?.({timestamp:new Date().toISOString(),rawInput:t,input:n,decision:r})}catch{}}async function Ge(e,t){let i=await Ke(e);i.push(t),await(0,n.mkdir)((0,r.dirname)(e),{recursive:!0}),await(0,n.writeFile)(e,`${JSON.stringify(i.slice(-50),null,2)}\n`,`utf8`)}async function Ke(e){try{let t=JSON.parse(await(0,n.readFile)(e,`utf8`));return Array.isArray(t)?t:[]}catch{return[]}}async function N(e){return(await P(e)).sessionMeta}async function P(e){let t=(0,i.createReadStream)(e,{encoding:`utf8`}),n=(0,o.createInterface)({input:t,crlfDelay:1/0}),r=new Set,a=new Map,s=null;try{for await(let e of n){let t=e.trim();if(t.length===0)continue;let n=qe(t);n&&(s||=Je(n),Ye(n,r),Xe(n,a))}}finally{n.close(),t.destroy()}return{sessionMeta:s,hasPendingWork:r.size>0||Array.from(a.values()).some(L)}}function qe(e){try{let t=JSON.parse(e);return k(t)?t:null}catch{return null}}function Je(e){return e.type===E?e.payload??null:e.item?.type===E?e.item.payload??null:null}function Ye(e,t){let n=R(e,[`payload`,`item`])??R(e,[`item`,`payload`,`item`]);if(!n)return;let r=z(n.id),i=z(n.type);!r||!i||!Ne.has(i)||(L(n.status)?t.add(r):t.delete(r))}function Xe(e,t){let n=R(e,[`payload`])??R(e,[`item`,`payload`]);if(!n||!z(n.type)?.startsWith(`collab_`))return;F(t,z(n.new_thread_id),n.status),F(t,z(n.receiver_thread_id),n.status);let r=R(n,[`statuses`]);if(r)for(let[e,n]of Object.entries(r))F(t,e,n)}function F(e,t,n){let r=I(n);!t||!r||e.set(t,r)}function I(e){if(typeof e==`string`)return e;if(!k(e))return null;let[t]=Object.keys(e);return t??null}function L(e){let t=typeof e==`string`?e:I(e);return t!==null&&Me.has(t)}function R(e,t){let n=e;for(let e of t){if(!k(n))return null;n=n[e]}return k(n)?n:null}function z(e){return typeof e==`string`&&e.length>0?e:void 0}const Ze=new s.Command(`codex-session-start-hook`).description(`Codex session-start hook: binds ${T} to the first workflow Codex session id`).action(async()=>{await Ie()}),Qe=new s.Command(`codex-stop-hook`).description(`Codex stop hook: reads stop-hook JSON on stdin and marks ${T} when the root session ends`).action(async()=>{await Le()}),B=`list-crons`;function $e(t={}){let n=t.createService??(()=>new e.s),r=t.exit??(e=>process.exit(e)),i=t.logError??((e,t)=>console.error(e,t)),a=t.writeStdout??(e=>process.stdout.write(e));return new s.Command(B).description(`List cron jobs scheduled via workflow-mcp`).action(async()=>{try{let e=await n().list();a(`${JSON.stringify(e,null,2)}\n`),r(0)}catch(e){let t=new m(`Error listing cron jobs.`,`LIST_CRONS_COMMAND_FAILED`,{command:B},{cause:e});i(`${t.message} [${t.code}]`,t),r(1)}})}const et=$e(),V=`list-workflow-statuses`;function H(e){let t=Number(e);if(!Number.isInteger(t)||t<1)throw new s.InvalidArgumentError(`Expected a positive integer.`);return t}function tt(t={}){let n=t.createRegistry??(()=>new e.o),r=t.exit??(e=>process.exit(e)),i=t.logError??((e,t)=>console.error(e,t)),a=t.writeStdout??(e=>process.stdout.write(e));return new s.Command(V).description(`List tracked workflow runs and their current stages`).option(`--page <number>`,`Page number to return`,H,1).option(`--page-size <number>`,`Number of workflow runs per page`,H,20).option(`-w, --workspace <name>`,`Filter workflow runs by workspace`,`all`).action(async e=>{try{let t=n(),i=e.workspace===`all`?void 0:e.workspace,o=await t.listRunsPage({page:e.page,pageSize:e.pageSize,workspace:i});a(`${JSON.stringify(o,null,2)}\n`),r(0)}catch(t){let n=new m(`Error listing workflow statuses.`,`LIST_WORKFLOW_STATUSES_FAILED`,{command:V,workspace:e.workspace??`all`},{cause:t});i(`${n.message} [${n.code}]`,n),r(1)}})}const nt=tt(),rt=()=>{try{return(0,c.createRequire)(require(`url`).pathToFileURL(__filename).href)(`@agimon-ai/foundation-process-registry`)}catch{return null}};function U(e,t){for(let n of e){if(n==null||typeof n!=`object`&&typeof n!=`function`)continue;let e=n;for(let n of t){let t=e[n];if(typeof t==`function`)return t}}return null}function it(e){let t=[e];if(e&&typeof e==`object`){let n=e.default;if(n&&(t.push(n),typeof n==`function`))try{t.push(new n)}catch{}}return t}async function at(e){let t=rt();if(!t)return async()=>{};let n=it(t),r=U(n,[`registerProcess`,`register`,`registerProcessInstance`]),i=U(n,[`unregisterProcess`,`unregister`,`releaseProcess`]);if(!r)return async()=>{};try{await Promise.resolve(r({name:e,pid:process.pid,command:process.argv.join(` `)}))}catch{return async()=>{}}return i?async()=>{await Promise.resolve(i({name:e,pid:process.pid}))}:async()=>{}}async function ot(e,t){await e.start();let n=async n=>{console.error(`\\nReceived ${n}, shutting down gracefully...`);let r=0;try{await e.stop()}catch(e){console.error(`Error during shutdown:`,e),r=1}try{await t()}catch(e){console.error(`Error during resource cleanup:`,e),r=1}process.exit(r)};process.on(`SIGINT`,()=>{n(`SIGINT`)}),process.on(`SIGTERM`,()=>{n(`SIGTERM`)})}async function st(e){try{await e()}catch{}}const ct=new s.Command(`mcp-serve`).description(`Start MCP server with specified transport`).option(`-t, --type <type>`,`Transport type: stdio`,`stdio`).option(`--service-name <name>`,`Service name for registry tracking`,`workflow-mcp`).action(async t=>{let n=await at(t.serviceName);try{let r=t.type.toLowerCase();r===`stdio`?await ot(new e.t(e.n()),n):(console.error(`Unknown transport type: ${r}. Use: stdio`),process.exit(1))}catch(e){await st(n),console.error(`Failed to start MCP server:`,e),process.exit(1)}}),W=`recover-workflow`;function lt(t={}){let n=t.createService??(()=>new e.r),r=t.exit??(e=>process.exit(e)),i=t.logError??((e,t)=>console.error(e,t));return new s.Command(W).description(`Recover a failed workflow run from the local workflow registry`).argument(`<run-key>`,`Failed workflow run key`).option(`-w, --workspace <name>`,`Workspace containing the failed workflow run`,`default`).option(`-j, --job <name>`,`Override the job to recover from`).option(`--runner <runner>`,`Override the runner key for step command maps`).option(`--dry-run`,`Print recovery steps without executing`).action(async(e,t)=>{try{r((await n().recover({dryRun:t.dryRun,job:t.job,runKey:e,runner:t.runner,workspace:t.workspace})).exitCode)}catch(n){let a=new m(`Error recovering workflow.`,`RECOVER_WORKFLOW_FAILED`,{command:W,runKey:e,workspace:t.workspace},{cause:n});i(`${a.message} [${a.code}]`,a),r(1)}})}const G=lt(),K=`run-workflow`,q=`RUN_WORKFLOW_COMMAND_FAILED`,J=`WORKFLOW_MCP_BACKGROUND_CHILD`,Y=`workflow-mcp-background-run`;function X(e){if(!e||e.length===0)return;let t={};for(let n of e){let[e,...r]=n.split(`=`);t[e]=r.join(`=`)}return Object.keys(t).length>0?t:void 0}function Z(e){if(e.runner&&e.cliAgent&&e.runner!==e.cliAgent)throw new m(`Conflicting runner selectors.`,q,{cliAgent:e.cliAgent,command:K,runner:e.runner});return e.runner??e.cliAgent}function ut(e,n){let r=process.argv[1];if(!r)throw new m(`Unable to determine the workflow-mcp CLI entry point for background execution.`,`RUN_WORKFLOW_BACKGROUND_LAUNCH_FAILED`,{command:K,workflow:e,workspace:n.workspace});let i=[r,`run-workflow`,e];n.job&&i.push(`--job`,n.job);for(let e of n.input??[])i.push(`--input`,e);for(let e of n.env??[])i.push(`--env`,e);n.secretFile&&i.push(`--secret-file`,n.secretFile),n.dryRun&&i.push(`--dry-run`),n.continueOnError&&i.push(`--continue-on-error`),n.keepWorktree&&i.push(`--keep-worktree`);let a=Z(n);a&&i.push(`--runner`,a),n.prompt&&i.push(`--prompt`,n.prompt),n.name&&i.push(`--name`,n.name),n.workspace&&i.push(`--workspace`,n.workspace);let o=(0,t.spawn)(process.execPath,i,{detached:!0,env:{...process.env,[J]:`1`},stdio:`ignore`});return o.unref(),o.pid}async function dt(e,t){if(process.env[J]!==`1`)return async()=>{};let n=new a.ProcessRegistryService(process.env.PROCESS_REGISTRY_PATH),i=(0,r.resolve)(process.cwd()),o=process.env.NODE_ENV??`development`;return(await n.registerProcess({repositoryPath:i,serviceName:Y,serviceType:`tool`,environment:o,pid:process.pid,command:process.argv.join(` `),args:process.argv.slice(2),metadata:{workflow:e,workspace:t.workspace,job:t.job,name:t.name,runner:t.runner??t.cliAgent},force:!0})).success?async()=>{let e=await n.releaseProcess({repositoryPath:i,serviceName:Y,serviceType:`tool`,environment:o,pid:process.pid,kill:!1,releasePort:!1,force:!0});if(!e.success&&!e.error?.includes(`No matching process entry`))throw Error(e.error??`Failed to release workflow background process`)}:async()=>{}}function ft(t={}){let n=t.createService??(()=>new e.i),r=t.exit??(e=>process.exit(e)),i=t.launchBackgroundRun??ut,a=t.registerBackgroundChild??dt,o=t.logError??((e,t)=>console.error(e,t)),c=t.logInfo??(e=>process.stdout.write(`${e}\n`));return new s.Command(K).description(`Run a GitHub Actions workflow file locally on macOS`).argument(`<workflow>`,`Path to the workflow YAML file`).option(`-j, --job <name>`,`Run only this job (and its dependencies)`).option(`-i, --input <key=value...>`,`Set workflow_dispatch input (repeatable)`).option(`-e, --env <key=value...>`,`Set extra environment variable (repeatable)`).option(`--secret-file <path>`,`Load secrets from a dotenv-style file`).option(`--dry-run`,`Print steps without executing`).option(`--continue-on-error`,`Continue past step failures`).option(`--runner <runner>`,`Preferred runner key for step command maps`).option(`--cli-agent <agent>`,`Deprecated alias for --runner`).option(`-p, --prompt <text>`,`User prompt for user_prompt trigger workflows`).option(`-n, --name <name>`,`Name for the workflow run context directory`).option(`-w, --workspace <name>`,`Workspace for workflow registry storage`).option(`--keep-worktree`,`Keep worktree on completion (skip merge and cleanup for retry)`).option(`--skip-launch`,`Skip launch-command delegation (used by inner invocations)`).option(`-b, --background`,`Run the workflow in a detached background process`).action(async(e,t)=>{try{if(t.background){let n=i(e,t);c(`Started workflow in background${n?` (PID: ${n})`:``}`),r(0);return}let o=await a(e,t),s=n();try{let n=Z(t),i=await s.run({cliAgent:t.cliAgent,runner:n,workflowPath:e,job:t.job,inputs:X(t.input),env:X(t.env),secretFile:t.secretFile,dryRun:t.dryRun,continueOnError:t.continueOnError,keepWorktree:t.keepWorktree,prompt:t.prompt,name:t.name,workspace:t.workspace,skipLaunch:t.skipLaunch});await o(),r(i.exitCode)}catch(e){throw await o(),e}}catch(n){let i=n instanceof m?n:new m(`Error executing run-workflow.`,q,{background:!!t.background,command:K,workflow:e,workspace:t.workspace},{cause:n});o(`${i.message} [${i.code}]`,i),r(1)}})}const pt=ft(),Q=`schedule-cron`;function mt(t={}){let n=t.createService??(()=>new e.s),r=t.exit??(e=>process.exit(e)),i=t.logError??((e,t)=>console.error(e,t)),a=t.writeStdout??(e=>process.stdout.write(`${e}\n`));return new s.Command(Q).description(`Schedule a headless Claude Code or Codex CLI run via system crontab`).argument(`<name>`,`Unique name for this cron job`).option(`-d, --cwd <path>`,`Working directory for the CLI run`,process.cwd()).option(`-c, --cli <cli>`,`CLI to use: claude or codex`,e.c).option(`-p, --prompt <text>`,`Prompt to pass to the CLI`).option(`-f, --prompt-file <path>`,`Path to a file whose content is used as the prompt (read at cron execution time)`).option(`-s, --schedule <cron>`,`Cron expression (e.g., "*/10 * * * *")`).option(`-i, --interval-minutes <minutes>`,`Run every N minutes (alternative to --schedule)`).action(async(t,o)=>{try{let i=e.l.parse({name:t,cwd:o.cwd??process.cwd(),cli:o.cli??`claude`,prompt:o.prompt,promptFile:o.promptFile,schedule:o.schedule,intervalMinutes:o.intervalMinutes?Number.parseInt(o.intervalMinutes,10):void 0}),s=await n().schedule(i);a(`Scheduled cron job "${s.name}" with schedule: ${s.schedule}`),a(`CLI: ${s.cli} | CWD: ${s.cwd}`),s.prompt&&a(`Prompt: ${s.prompt}`),r(0)}catch(e){let n=e instanceof m?e:new m(`Error scheduling cron job.`,`SCHEDULE_CRON_COMMAND_FAILED`,{command:Q,name:t},{cause:e});i(`${n.message} [${n.code}]`,n),r(1)}})}const ht=mt(),$=`stop-workflow`;function gt(t={}){let n=t.createRegistry??(()=>new e.o),r=t.exit??(e=>process.exit(e)),i=t.logError??((e,t)=>console.error(e,t)),a=t.writeStdout??(e=>process.stdout.write(e));return new s.Command($).description(`Request a running workflow to stop gracefully`).argument(`<run-key>`,`Running workflow run key`).option(`-w, --workspace <name>`,`Workspace containing the running workflow`,`default`).option(`-r, --reason <text>`,`Optional stop reason to record`).action(async(e,t)=>{try{let i=n(),o=await i.requestStop(t.workspace,e,t.reason);a(`${JSON.stringify({reason:o.reason,requestedAt:o.requestedAt,runKey:e,workspace:i.resolveWorkspace(t.workspace)},null,2)}\n`),r(0)}catch(n){let a=new m(`Error requesting workflow stop.`,`STOP_WORKFLOW_FAILED`,{command:$,runKey:e,workspace:t.workspace},{cause:n});i(`${a.message} [${a.code}]`,a),r(1)}})}const _t=gt();async function vt(){let e=new s.Command;e.name(`workflow-mcp`).description(`MCP server for running GitHub Actions workflows locally`).version(l),e.addCommand(et),e.addCommand(nt),e.addCommand(he),e.addCommand(pe),e.addCommand(C),e.addCommand(w),e.addCommand(Ze),e.addCommand(Qe),e.addCommand(ct),e.addCommand(G),e.addCommand(pt),e.addCommand(ht),e.addCommand(_t),await e.parseAsync(process.argv)}vt().catch(e=>{console.error(`[CLI_STARTUP_ERROR] workflow-mcp startup failed:`,e),process.exit(1)});
|
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{a as e,c as t,i as n,l as r,n as i,o as a,r as o,s,t as c}from"./stdio-k5DKbtyw.mjs";import{createRequire as l}from"node:module";import{spawn as ee}from"node:child_process";import{mkdir as u,readFile as d,writeFile as f}from"node:fs/promises";import{dirname as p,join as m,resolve as te}from"node:path";import{createReadStream as ne}from"node:fs";import{ProcessRegistryService as re}from"@agimon-ai/foundation-process-registry";import{createInterface as ie}from"node:readline";import{Command as h,InvalidArgumentError as ae}from"commander";var oe=`0.3.
|
|
3
|
-
`,`utf8`)},workflowStatusFile:process.env[g]};async function le(e=ce){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=ue(n);if(!r?.conversationId){await y(e,n,r,{decision:`ignore`,reason:r?`missing conversation id`:`invalid stop hook JSON`});return}let i=pe(await e.readStatusFile(t));if(i.status!==`unbound`){await y(e,n,r,{decision:`ignore`,reason:me(i.status)});return}if(!r.fullyIdle){e.writeStdout?.(de(_)),await y(e,n,r,{decision:`continue`,reason:_});return}if(v(r.error)||r.terminationReason!==`model_stop`){await y(e,n,r,{decision:`ignore`,reason:v(r.error)?`stop hook reported an error`:`unsupported termination reason: ${r.terminationReason??`missing`}`});return}await e.writeStatusFile(t),await y(e,n,r,{decision:`complete`})}function ue(e){try{let t=JSON.parse(e);return he(t)?{conversationId:typeof t.conversationId==`string`?t.conversationId:void 0,transcriptPath:fe(t.transcriptPath),terminationReason:typeof t.terminationReason==`string`?t.terminationReason:void 0,error:typeof t.error==`string`?t.error:void 0,fullyIdle:t.fullyIdle===!0}:null}catch{return null}}function de(e){return`${JSON.stringify({decision:`continue`,reason:e})}\n`}function fe(e){return typeof e==`string`&&e.trim().length>0?e:null}function v(e){return typeof e==`string`&&e.trim().length>0}function pe(e){if(e===null)return{status:`missing`};let t=e.trim();if(!t)return{status:`unbound`};if(t===`YES`)return{status:`completed`};try{let e=JSON.parse(t);return typeof e.sessionId==`string`&&e.sessionId.length>0?{status:`bound`,sessionId:e.sessionId}:{status:`invalid`}}catch{return{status:`invalid`}}}function me(e){switch(e){case`bound`:return`workflow status file is bound to a session`;case`completed`:return`workflow status file already completed`;case`invalid`:return`invalid workflow status file binding`;case`missing`:return`missing workflow status file`}}function he(e){return typeof e==`object`&&!!e}async function ge(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(String(t)));return Buffer.concat(e).toString(`utf8`)}async function _e(e){try{return await d(e,`utf8`)}catch{return null}}async function y(e,t,n,r){try{await e.writeDebugLog?.({timestamp:new Date().toISOString(),rawInput:t,input:n,decision:r})}catch{}}async function ve(e,t){let n=await ye(e);n.push(t),await u(p(e),{recursive:!0}),await f(e,`${JSON.stringify(n.slice(-50),null,2)}\n`,`utf8`)}async function ye(e){try{let t=JSON.parse(await d(e,`utf8`));return Array.isArray(t)?t:[]}catch{return[]}}const be=new h(`antigravity-stop-hook`).description(`Antigravity stop hook: reads Stop hook JSON on stdin and marks ${g} when the execution is fully idle`).action(async()=>{await le()});var b=class extends Error{constructor(e,t,n={},r){super(e,r),this.code=t,this.context=n,this.name=`WorkflowCommandError`}};const x=`check-codex-quota`;function xe(t={}){let n=t.createService??(()=>new e),r=t.exit??(e=>process.exit(e)),i=t.logError??((e,t)=>console.error(e,t)),a=t.writeStdout??(e=>process.stdout.write(e));return new h(x).description(`Check whether Codex quota is currently blocking new work`).action(async()=>{try{let e=await n().getQuotaStatus();if(a(`${JSON.stringify({blockingLimit:e?.blockingLimit??null,planType:e?.planType??null},null,2)}\n`),e?.blockingLimit){let t=new b(`Codex quota is blocking work at ${e.blockingLimit.limitId}/${e.blockingLimit.window}.`,`CODEX_QUOTA_BLOCKED`,{command:x,limitId:e.blockingLimit.limitId,limitName:e.blockingLimit.limitName,window:e.blockingLimit.window});i(`${t.message} [${t.code}]`,t),r(2);return}r(0)}catch(e){let t=e instanceof b?e:new b(`Error checking Codex quota.`,`CHECK_CODEX_QUOTA_COMMAND_FAILED`,{command:x},{cause:e});i(`${t.message} [${t.code}]`,t),r(1)}})}const Se=xe(),S=`WORKFLOW_STATUS_FILE`,Ce=m(process.cwd(),`tmp`,`claude-stop-hook.json`),C=`In-progress work is still running. Wait for spawned agents and running tool calls to finish before ending the turn.`,we=new Set([`aborted`,`cancelled`,`canceled`,`completed`,`crashed`,`done`,`error`,`failed`,`killed`,`not_running`,`retired`,`settled`,`stopped`,`success`,`succeeded`]),Te={readStdin:T,readStatusFile:
|
|
4
|
-
`,`utf8`)},workflowStatusFile:process.env[S]},Ee={readStdin:T,
|
|
5
|
-
`,`utf8`)},workflowStatusFile:process.env[O]},Ge={readStdin:N,readStatusFile:P,writeSessionBinding:async(e,t)=>{await f(e,`${JSON.stringify({sessionId:t})}\n`,`utf8`)},workflowStatusFile:process.env[O]};async function Ke(e=Ge){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=Xe(n);if(!r?.session_id)return;let i=await e.readStatusFile(t);i===null||i.trim().length>0||await e.writeSessionBinding(t,r.session_id)}async function qe(e=We){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=Ze(n);if(!r){await F(e,n,null,{decision:`ignore`,reason:`invalid stop hook JSON`});return}if(r.hook_event_name&&r.hook_event_name!==`Stop`){await F(e,n,r,{decision:`ignore`,reason:`unsupported hook event: ${r.hook_event_name}`});return}if(r.stop_hook_active&&!r.hasTaskRegistry){await F(e,n,r,{decision:`ignore`,reason:`recursive stop hook without task registry`});return}let i=Qe(await e.readStatusFile(t));if(i.status!==`bound`){await F(e,n,r,{decision:`ignore`,reason:$e(i.status)});return}if(!r.session_id||r.session_id!==i.sessionId){await F(e,n,r,{decision:`ignore`,reason:`stop hook session is not bound to the workflow status file`});return}let a=j(r.transcript_path);if(!a){await F(e,n,r,{decision:`ignore`,reason:`missing transcript path`});return}let o=await Ye(e,a),s=o.sessionMeta;if(!s){await F(e,n,r,{decision:`ignore`,reason:`missing transcript session metadata`});return}if(r.session_id&&s.id&&r.session_id!==s.id){await F(e,n,r,{decision:`ignore`,reason:`stop hook session does not match transcript session`});return}if(et(s.source)){await F(e,n,r,{decision:`ignore`,reason:`transcript session is a subagent`});return}let c=r.hasTaskRegistry&&(r.background_tasks?.length??0)>0,l=r.hasTaskRegistry&&(r.session_crons?.length??0)>0;if(c||!r.hasTaskRegistry&&o.hasPendingWork){e.writeStdout?.(Je(A)),await F(e,n,r,{decision:`block`,reason:A});return}if(l){await F(e,n,r,{decision:`ignore`,reason:`session crons are still scheduled`});return}await e.writeStatusFile(t),await F(e,n,r,{decision:`complete`})}function Je(e){return`${JSON.stringify({decision:`block`,reason:e,suppressOutput:!0})}\n`}async function Ye(e,t){return e.readTranscriptState?e.readTranscriptState(t):e.readTranscriptHead===I?L(t):{sessionMeta:await e.readTranscriptHead(t),hasPendingWork:!1}}function Xe(e){try{let t=JSON.parse(e);return M(t)?{session_id:typeof t.session_id==`string`?t.session_id:void 0}:null}catch{return null}}function Ze(e){try{let t=JSON.parse(e);if(!M(t))return null;let n=Array.isArray(t.background_tasks)?t.background_tasks:void 0,r=Array.isArray(t.session_crons)?t.session_crons:void 0;return{hook_event_name:typeof t.hook_event_name==`string`?t.hook_event_name:void 0,session_id:typeof t.session_id==`string`?t.session_id:void 0,transcript_path:j(t.transcript_path),stop_hook_active:t.stop_hook_active===!0,background_tasks:n,session_crons:r,hasTaskRegistry:n!==void 0||r!==void 0}}catch{return null}}function j(e){return typeof e==`string`&&e.trim().length>0?e:null}function Qe(e){if(e===null)return{status:`missing`};let t=e.trim();if(!t)return{status:`unbound`};if(t===`YES`)return{status:`completed`};try{let e=JSON.parse(t);return typeof e.sessionId==`string`&&e.sessionId.length>0?{status:`bound`,sessionId:e.sessionId}:{status:`invalid`}}catch{return{status:`invalid`}}}function $e(e){switch(e){case`completed`:return`workflow status file already completed`;case`invalid`:return`invalid workflow status file binding`;case`missing`:return`missing workflow status file`;case`unbound`:return`workflow status file is unbound`}}function et(e){return typeof e==`string`?e.startsWith(`subagent_`)||e.startsWith(`internal_`):M(e)?Ve.some(t=>t in e):!1}function M(e){return typeof e==`object`&&!!e}async function N(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(String(t)));return Buffer.concat(e).toString(`utf8`)}async function P(e){try{return await d(e,`utf8`)}catch{return null}}async function F(e,t,n,r){try{await e.writeDebugLog?.({timestamp:new Date().toISOString(),rawInput:t,input:n,decision:r})}catch{}}async function tt(e,t){let n=await nt(e);n.push(t),await u(p(e),{recursive:!0}),await f(e,`${JSON.stringify(n.slice(-50),null,2)}\n`,`utf8`)}async function nt(e){try{let t=JSON.parse(await d(e,`utf8`));return Array.isArray(t)?t:[]}catch{return[]}}async function I(e){return(await L(e)).sessionMeta}async function L(e){let t=ne(e,{encoding:`utf8`}),n=ie({input:t,crlfDelay:1/0}),r=new Set,i=new Map,a=null;try{for await(let e of n){let t=e.trim();if(t.length===0)continue;let n=rt(t);n&&(a||=it(n),at(n,r),ot(n,i))}}finally{n.close(),t.destroy()}return{sessionMeta:a,hasPendingWork:r.size>0||Array.from(i.values()).some(B)}}function rt(e){try{let t=JSON.parse(e);return M(t)?t:null}catch{return null}}function it(e){return e.type===k?e.payload??null:e.item?.type===k?e.item.payload??null:null}function at(e,t){let n=V(e,[`payload`,`item`])??V(e,[`item`,`payload`,`item`]);if(!n)return;let r=H(n.id),i=H(n.type);!r||!i||!Ue.has(i)||(B(n.status)?t.add(r):t.delete(r))}function ot(e,t){let n=V(e,[`payload`])??V(e,[`item`,`payload`]);if(!n||!H(n.type)?.startsWith(`collab_`))return;R(t,H(n.new_thread_id),n.status),R(t,H(n.receiver_thread_id),n.status);let r=V(n,[`statuses`]);if(r)for(let[e,n]of Object.entries(r))R(t,e,n)}function R(e,t,n){let r=z(n);!t||!r||e.set(t,r)}function z(e){if(typeof e==`string`)return e;if(!M(e))return null;let[t]=Object.keys(e);return t??null}function B(e){let t=typeof e==`string`?e:z(e);return t!==null&&He.has(t)}function V(e,t){let n=e;for(let e of t){if(!M(n))return null;n=n[e]}return M(n)?n:null}function H(e){return typeof e==`string`&&e.length>0?e:void 0}const st=new h(`codex-session-start-hook`).description(`Codex session-start hook: binds ${O} to the first workflow Codex session id`).action(async()=>{await Ke()}),ct=new h(`codex-stop-hook`).description(`Codex stop hook: reads stop-hook JSON on stdin and marks ${O} when the root session ends`).action(async()=>{await qe()}),U=`list-crons`;function lt(e={}){let t=e.createService??(()=>new s),n=e.exit??(e=>process.exit(e)),r=e.logError??((e,t)=>console.error(e,t)),i=e.writeStdout??(e=>process.stdout.write(e));return new h(U).description(`List cron jobs scheduled via workflow-mcp`).action(async()=>{try{let e=await t().list();i(`${JSON.stringify(e,null,2)}\n`),n(0)}catch(e){let t=new b(`Error listing cron jobs.`,`LIST_CRONS_COMMAND_FAILED`,{command:U},{cause:e});r(`${t.message} [${t.code}]`,t),n(1)}})}const ut=lt(),W=`list-workflow-statuses`;function G(e){let t=Number(e);if(!Number.isInteger(t)||t<1)throw new ae(`Expected a positive integer.`);return t}function dt(e={}){let t=e.createRegistry??(()=>new a),n=e.exit??(e=>process.exit(e)),r=e.logError??((e,t)=>console.error(e,t)),i=e.writeStdout??(e=>process.stdout.write(e));return new h(W).description(`List tracked workflow runs and their current stages`).option(`--page <number>`,`Page number to return`,G,1).option(`--page-size <number>`,`Number of workflow runs per page`,G,20).option(`-w, --workspace <name>`,`Filter workflow runs by workspace`,`all`).action(async e=>{try{let r=t(),a=e.workspace===`all`?void 0:e.workspace,o=await r.listRunsPage({page:e.page,pageSize:e.pageSize,workspace:a});i(`${JSON.stringify(o,null,2)}\n`),n(0)}catch(t){let i=new b(`Error listing workflow statuses.`,`LIST_WORKFLOW_STATUSES_FAILED`,{command:W,workspace:e.workspace??`all`},{cause:t});r(`${i.message} [${i.code}]`,i),n(1)}})}const ft=dt(),pt=()=>{try{return l(import.meta.url)(`@agimon-ai/foundation-process-registry`)}catch{return null}};function K(e,t){for(let n of e){if(n==null||typeof n!=`object`&&typeof n!=`function`)continue;let e=n;for(let n of t){let t=e[n];if(typeof t==`function`)return t}}return null}function mt(e){let t=[e];if(e&&typeof e==`object`){let n=e.default;if(n&&(t.push(n),typeof n==`function`))try{t.push(new n)}catch{}}return t}async function ht(e){let t=pt();if(!t)return async()=>{};let n=mt(t),r=K(n,[`registerProcess`,`register`,`registerProcessInstance`]),i=K(n,[`unregisterProcess`,`unregister`,`releaseProcess`]);if(!r)return async()=>{};try{await Promise.resolve(r({name:e,pid:process.pid,command:process.argv.join(` `)}))}catch{return async()=>{}}return i?async()=>{await Promise.resolve(i({name:e,pid:process.pid}))}:async()=>{}}async function gt(e,t){await e.start();let n=async n=>{console.error(`\\nReceived ${n}, shutting down gracefully...`);let r=0;try{await e.stop()}catch(e){console.error(`Error during shutdown:`,e),r=1}try{await t()}catch(e){console.error(`Error during resource cleanup:`,e),r=1}process.exit(r)};process.on(`SIGINT`,()=>{n(`SIGINT`)}),process.on(`SIGTERM`,()=>{n(`SIGTERM`)})}async function _t(e){try{await e()}catch{}}const vt=new h(`mcp-serve`).description(`Start MCP server with specified transport`).option(`-t, --type <type>`,`Transport type: stdio`,`stdio`).option(`--service-name <name>`,`Service name for registry tracking`,`workflow-mcp`).action(async e=>{let t=await ht(e.serviceName);try{let n=e.type.toLowerCase();n===`stdio`?await gt(new c(i()),t):(console.error(`Unknown transport type: ${n}. Use: stdio`),process.exit(1))}catch(e){await _t(t),console.error(`Failed to start MCP server:`,e),process.exit(1)}}),q=`recover-workflow`;function yt(e={}){let t=e.createService??(()=>new o),n=e.exit??(e=>process.exit(e)),r=e.logError??((e,t)=>console.error(e,t));return new h(q).description(`Recover a failed workflow run from the local workflow registry`).argument(`<run-key>`,`Failed workflow run key`).option(`-w, --workspace <name>`,`Workspace containing the failed workflow run`,`default`).option(`-j, --job <name>`,`Override the job to recover from`).option(`--runner <runner>`,`Override the runner key for step command maps`).option(`--dry-run`,`Print recovery steps without executing`).action(async(e,i)=>{try{n((await t().recover({dryRun:i.dryRun,job:i.job,runKey:e,runner:i.runner,workspace:i.workspace})).exitCode)}catch(t){let a=new b(`Error recovering workflow.`,`RECOVER_WORKFLOW_FAILED`,{command:q,runKey:e,workspace:i.workspace},{cause:t});r(`${a.message} [${a.code}]`,a),n(1)}})}const bt=yt(),J=`run-workflow`,Y=`RUN_WORKFLOW_COMMAND_FAILED`,X=`WORKFLOW_MCP_BACKGROUND_CHILD`,Z=`workflow-mcp-background-run`;function Q(e){if(!e||e.length===0)return;let t={};for(let n of e){let[e,...r]=n.split(`=`);t[e]=r.join(`=`)}return Object.keys(t).length>0?t:void 0}function $(e){if(e.runner&&e.cliAgent&&e.runner!==e.cliAgent)throw new b(`Conflicting runner selectors.`,Y,{cliAgent:e.cliAgent,command:J,runner:e.runner});return e.runner??e.cliAgent}function xt(e,t){let n=process.argv[1];if(!n)throw new b(`Unable to determine the workflow-mcp CLI entry point for background execution.`,`RUN_WORKFLOW_BACKGROUND_LAUNCH_FAILED`,{command:J,workflow:e,workspace:t.workspace});let r=[n,`run-workflow`,e];t.job&&r.push(`--job`,t.job);for(let e of t.input??[])r.push(`--input`,e);for(let e of t.env??[])r.push(`--env`,e);t.secretFile&&r.push(`--secret-file`,t.secretFile),t.dryRun&&r.push(`--dry-run`),t.continueOnError&&r.push(`--continue-on-error`),t.keepWorktree&&r.push(`--keep-worktree`);let i=$(t);i&&r.push(`--runner`,i),t.prompt&&r.push(`--prompt`,t.prompt),t.name&&r.push(`--name`,t.name),t.workspace&&r.push(`--workspace`,t.workspace);let a=ee(process.execPath,r,{detached:!0,env:{...process.env,[X]:`1`},stdio:`ignore`});return a.unref(),a.pid}async function St(e,t){if(process.env[X]!==`1`)return async()=>{};let n=new re(process.env.PROCESS_REGISTRY_PATH),r=te(process.cwd()),i=process.env.NODE_ENV??`development`;return(await n.registerProcess({repositoryPath:r,serviceName:Z,serviceType:`tool`,environment:i,pid:process.pid,command:process.argv.join(` `),args:process.argv.slice(2),metadata:{workflow:e,workspace:t.workspace,job:t.job,name:t.name,runner:t.runner??t.cliAgent},force:!0})).success?async()=>{let e=await n.releaseProcess({repositoryPath:r,serviceName:Z,serviceType:`tool`,environment:i,pid:process.pid,kill:!1,releasePort:!1,force:!0});if(!e.success&&!e.error?.includes(`No matching process entry`))throw Error(e.error??`Failed to release workflow background process`)}:async()=>{}}function Ct(e={}){let t=e.createService??(()=>new n),r=e.exit??(e=>process.exit(e)),i=e.launchBackgroundRun??xt,a=e.registerBackgroundChild??St,o=e.logError??((e,t)=>console.error(e,t)),s=e.logInfo??(e=>process.stdout.write(`${e}\n`));return new h(J).description(`Run a GitHub Actions workflow file locally on macOS`).argument(`<workflow>`,`Path to the workflow YAML file`).option(`-j, --job <name>`,`Run only this job (and its dependencies)`).option(`-i, --input <key=value...>`,`Set workflow_dispatch input (repeatable)`).option(`-e, --env <key=value...>`,`Set extra environment variable (repeatable)`).option(`--secret-file <path>`,`Load secrets from a dotenv-style file`).option(`--dry-run`,`Print steps without executing`).option(`--continue-on-error`,`Continue past step failures`).option(`--runner <runner>`,`Preferred runner key for step command maps`).option(`--cli-agent <agent>`,`Deprecated alias for --runner`).option(`-p, --prompt <text>`,`User prompt for user_prompt trigger workflows`).option(`-n, --name <name>`,`Name for the workflow run context directory`).option(`-w, --workspace <name>`,`Workspace for workflow registry storage`).option(`--keep-worktree`,`Keep worktree on completion (skip merge and cleanup for retry)`).option(`--skip-launch`,`Skip launch-command delegation (used by inner invocations)`).option(`-b, --background`,`Run the workflow in a detached background process`).action(async(e,n)=>{try{if(n.background){let t=i(e,n);s(`Started workflow in background${t?` (PID: ${t})`:``}`),r(0);return}let o=await a(e,n),c=t();try{let t=$(n),i=await c.run({cliAgent:n.cliAgent,runner:t,workflowPath:e,job:n.job,inputs:Q(n.input),env:Q(n.env),secretFile:n.secretFile,dryRun:n.dryRun,continueOnError:n.continueOnError,keepWorktree:n.keepWorktree,prompt:n.prompt,name:n.name,workspace:n.workspace,skipLaunch:n.skipLaunch});await o(),r(i.exitCode)}catch(e){throw await o(),e}}catch(t){let i=t instanceof b?t:new b(`Error executing run-workflow.`,Y,{background:!!n.background,command:J,workflow:e,workspace:n.workspace},{cause:t});o(`${i.message} [${i.code}]`,i),r(1)}})}const wt=Ct(),Tt=`schedule-cron`;function Et(e={}){let n=e.createService??(()=>new s),i=e.exit??(e=>process.exit(e)),a=e.logError??((e,t)=>console.error(e,t)),o=e.writeStdout??(e=>process.stdout.write(`${e}\n`));return new h(Tt).description(`Schedule a headless Claude Code or Codex CLI run via system crontab`).argument(`<name>`,`Unique name for this cron job`).option(`-d, --cwd <path>`,`Working directory for the CLI run`,process.cwd()).option(`-c, --cli <cli>`,`CLI to use: claude or codex`,t).option(`-p, --prompt <text>`,`Prompt to pass to the CLI`).option(`-f, --prompt-file <path>`,`Path to a file whose content is used as the prompt (read at cron execution time)`).option(`-s, --schedule <cron>`,`Cron expression (e.g., "*/10 * * * *")`).option(`-i, --interval-minutes <minutes>`,`Run every N minutes (alternative to --schedule)`).action(async(e,t)=>{try{let a=r.parse({name:e,cwd:t.cwd??process.cwd(),cli:t.cli??`claude`,prompt:t.prompt,promptFile:t.promptFile,schedule:t.schedule,intervalMinutes:t.intervalMinutes?Number.parseInt(t.intervalMinutes,10):void 0}),s=await n().schedule(a);o(`Scheduled cron job "${s.name}" with schedule: ${s.schedule}`),o(`CLI: ${s.cli} | CWD: ${s.cwd}`),s.prompt&&o(`Prompt: ${s.prompt}`),i(0)}catch(t){let n=t instanceof b?t:new b(`Error scheduling cron job.`,`SCHEDULE_CRON_COMMAND_FAILED`,{command:Tt,name:e},{cause:t});a(`${n.message} [${n.code}]`,n),i(1)}})}const Dt=Et(),Ot=`stop-workflow`;function kt(e={}){let t=e.createRegistry??(()=>new a),n=e.exit??(e=>process.exit(e)),r=e.logError??((e,t)=>console.error(e,t)),i=e.writeStdout??(e=>process.stdout.write(e));return new h(Ot).description(`Request a running workflow to stop gracefully`).argument(`<run-key>`,`Running workflow run key`).option(`-w, --workspace <name>`,`Workspace containing the running workflow`,`default`).option(`-r, --reason <text>`,`Optional stop reason to record`).action(async(e,a)=>{try{let r=t(),o=await r.requestStop(a.workspace,e,a.reason);i(`${JSON.stringify({reason:o.reason,requestedAt:o.requestedAt,runKey:e,workspace:r.resolveWorkspace(a.workspace)},null,2)}\n`),n(0)}catch(t){let i=new b(`Error requesting workflow stop.`,`STOP_WORKFLOW_FAILED`,{command:Ot,runKey:e,workspace:a.workspace},{cause:t});r(`${i.message} [${i.code}]`,i),n(1)}})}const At=kt();async function jt(){let e=new h;e.name(`workflow-mcp`).description(`MCP server for running GitHub Actions workflows locally`).version(oe),e.addCommand(ut),e.addCommand(ft),e.addCommand(Se),e.addCommand(be),e.addCommand(Re),e.addCommand(ze),e.addCommand(st),e.addCommand(ct),e.addCommand(vt),e.addCommand(bt),e.addCommand(wt),e.addCommand(Dt),e.addCommand(At),await e.parseAsync(process.argv)}jt().catch(e=>{console.error(`[CLI_STARTUP_ERROR] workflow-mcp startup failed:`,e),process.exit(1)});export{};
|
|
2
|
+
import{a as e,c as t,i as n,l as r,n as i,o as a,r as o,s,t as c}from"./stdio-k5DKbtyw.mjs";import{createRequire as l}from"node:module";import{spawn as ee}from"node:child_process";import{mkdir as u,readFile as d,writeFile as f}from"node:fs/promises";import{dirname as p,join as m,resolve as te}from"node:path";import{createReadStream as ne}from"node:fs";import{ProcessRegistryService as re}from"@agimon-ai/foundation-process-registry";import{createInterface as ie}from"node:readline";import{Command as h,InvalidArgumentError as ae}from"commander";var oe=`0.3.5`;const g=`WORKFLOW_STATUS_FILE`,se=m(process.cwd(),`tmp`,`antigravity-stop-hook.json`),_=`In-progress work is still running. Continue the execution loop until background tasks finish.`,ce={readStdin:ge,readStatusFile:_e,writeDebugLog:async e=>{await ve(se,e)},writeStdout:e=>{process.stdout.write(e)},writeStatusFile:async e=>{await f(e,`YES
|
|
3
|
+
`,`utf8`)},workflowStatusFile:process.env[g]};async function le(e=ce){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=ue(n);if(!r?.conversationId){await y(e,n,r,{decision:`ignore`,reason:r?`missing conversation id`:`invalid stop hook JSON`});return}let i=pe(await e.readStatusFile(t));if(i.status!==`unbound`){await y(e,n,r,{decision:`ignore`,reason:me(i.status)});return}if(!r.fullyIdle){e.writeStdout?.(de(_)),await y(e,n,r,{decision:`continue`,reason:_});return}if(v(r.error)||r.terminationReason!==`model_stop`){await y(e,n,r,{decision:`ignore`,reason:v(r.error)?`stop hook reported an error`:`unsupported termination reason: ${r.terminationReason??`missing`}`});return}await e.writeStatusFile(t),await y(e,n,r,{decision:`complete`})}function ue(e){try{let t=JSON.parse(e);return he(t)?{conversationId:typeof t.conversationId==`string`?t.conversationId:void 0,transcriptPath:fe(t.transcriptPath),terminationReason:typeof t.terminationReason==`string`?t.terminationReason:void 0,error:typeof t.error==`string`?t.error:void 0,fullyIdle:t.fullyIdle===!0}:null}catch{return null}}function de(e){return`${JSON.stringify({decision:`continue`,reason:e})}\n`}function fe(e){return typeof e==`string`&&e.trim().length>0?e:null}function v(e){return typeof e==`string`&&e.trim().length>0}function pe(e){if(e===null)return{status:`missing`};let t=e.trim();if(!t)return{status:`unbound`};if(t===`YES`)return{status:`completed`};try{let e=JSON.parse(t);return typeof e.sessionId==`string`&&e.sessionId.length>0?{status:`bound`,sessionId:e.sessionId}:{status:`invalid`}}catch{return{status:`invalid`}}}function me(e){switch(e){case`bound`:return`workflow status file is bound to a session`;case`completed`:return`workflow status file already completed`;case`invalid`:return`invalid workflow status file binding`;case`missing`:return`missing workflow status file`}}function he(e){return typeof e==`object`&&!!e}async function ge(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(String(t)));return Buffer.concat(e).toString(`utf8`)}async function _e(e){try{return await d(e,`utf8`)}catch{return null}}async function y(e,t,n,r){try{await e.writeDebugLog?.({timestamp:new Date().toISOString(),rawInput:t,input:n,decision:r})}catch{}}async function ve(e,t){let n=await ye(e);n.push(t),await u(p(e),{recursive:!0}),await f(e,`${JSON.stringify(n.slice(-50),null,2)}\n`,`utf8`)}async function ye(e){try{let t=JSON.parse(await d(e,`utf8`));return Array.isArray(t)?t:[]}catch{return[]}}const be=new h(`antigravity-stop-hook`).description(`Antigravity stop hook: reads Stop hook JSON on stdin and marks ${g} when the execution is fully idle`).action(async()=>{await le()});var b=class extends Error{constructor(e,t,n={},r){super(e,r),this.code=t,this.context=n,this.name=`WorkflowCommandError`}};const x=`check-codex-quota`;function xe(t={}){let n=t.createService??(()=>new e),r=t.exit??(e=>process.exit(e)),i=t.logError??((e,t)=>console.error(e,t)),a=t.writeStdout??(e=>process.stdout.write(e));return new h(x).description(`Check whether Codex quota is currently blocking new work`).action(async()=>{try{let e=await n().getQuotaStatus();if(a(`${JSON.stringify({blockingLimit:e?.blockingLimit??null,planType:e?.planType??null},null,2)}\n`),e?.blockingLimit){let t=new b(`Codex quota is blocking work at ${e.blockingLimit.limitId}/${e.blockingLimit.window}.`,`CODEX_QUOTA_BLOCKED`,{command:x,limitId:e.blockingLimit.limitId,limitName:e.blockingLimit.limitName,window:e.blockingLimit.window});i(`${t.message} [${t.code}]`,t),r(2);return}r(0)}catch(e){let t=e instanceof b?e:new b(`Error checking Codex quota.`,`CHECK_CODEX_QUOTA_COMMAND_FAILED`,{command:x},{cause:e});i(`${t.message} [${t.code}]`,t),r(1)}})}const Se=xe(),S=`WORKFLOW_STATUS_FILE`,Ce=m(process.cwd(),`tmp`,`claude-stop-hook.json`),C=`In-progress work is still running. Wait for spawned agents and running tool calls to finish before ending the turn.`,we=new Set([`aborted`,`cancelled`,`canceled`,`completed`,`crashed`,`done`,`error`,`failed`,`killed`,`not_running`,`retired`,`settled`,`stopped`,`success`,`succeeded`]),Te={readStdin:T,readStatusFile:Ie,writeDebugLog:async e=>{await Le(Ce,e)},writeStdout:e=>{process.stdout.write(e)},writeStatusFile:async e=>{await f(e,`YES
|
|
4
|
+
`,`utf8`)},workflowStatusFile:process.env[S]},Ee={readStdin:T,workflowStatusFile:process.env[S]};async function De(e=Ee){e.workflowStatusFile&&(await e.readStdin()).trim().length}async function Oe(e=Te){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=Ae(n);if(!r){await E(e,n,null,{decision:`ignore`,reason:`invalid stop hook JSON`});return}if(r.hook_event_name&&r.hook_event_name!==`Stop`){await E(e,n,r,{decision:`ignore`,reason:`unsupported hook event: ${r.hook_event_name}`});return}let i=Ne(await e.readStatusFile(t));if(!Pe(i)){await E(e,n,r,{decision:`ignore`,reason:Fe(i.status)});return}if(r.background_tasks.some(je)){e.writeStdout?.(ke(C)),await E(e,n,r,{decision:`block`,reason:C});return}await e.writeStatusFile(t),await E(e,n,r,{decision:`complete`})}function ke(e){return`${JSON.stringify({decision:`block`,reason:e,suppressOutput:!0})}\n`}function Ae(e){try{let t=JSON.parse(e);if(!w(t))return null;let n=Array.isArray(t.background_tasks)?t.background_tasks:[],r=Array.isArray(t.session_crons)?t.session_crons:[];return{hook_event_name:typeof t.hook_event_name==`string`?t.hook_event_name:void 0,session_id:typeof t.session_id==`string`?t.session_id:void 0,stop_hook_active:t.stop_hook_active===!0,background_tasks:n,session_crons:r}}catch{return null}}function je(e){if(!w(e))return!0;let t=Me(e.status);return!t||!we.has(t)}function Me(e){return typeof e==`string`&&e.trim().length>0?e.trim().toLowerCase().replaceAll(`-`,`_`):null}function Ne(e){if(e===null)return{status:`missing`};let t=e.trim();if(!t)return{status:`unbound`};if(t===`YES`)return{status:`completed`};try{let e=JSON.parse(t);return typeof e.sessionId==`string`&&e.sessionId.length>0?{status:`bound`,sessionId:e.sessionId}:{status:`invalid`}}catch{return{status:`invalid`}}}function Pe(e){return e.status===`bound`||e.status===`unbound`}function Fe(e){switch(e){case`completed`:return`workflow status file already completed`;case`invalid`:return`invalid workflow status file binding`;case`missing`:return`missing workflow status file`;case`unbound`:return`workflow status file is unbound`}}function w(e){return typeof e==`object`&&!!e}async function T(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(String(t)));return Buffer.concat(e).toString(`utf8`)}async function Ie(e){try{return await d(e,`utf8`)}catch{return null}}async function E(e,t,n,r){try{await e.writeDebugLog?.({timestamp:new Date().toISOString(),rawInput:t,input:n,decision:r})}catch{}}async function Le(e,t){let n=await Re(e);n.push(t),await u(p(e),{recursive:!0}),await f(e,`${JSON.stringify(n.slice(-50),null,2)}\n`,`utf8`)}async function Re(e){try{let t=JSON.parse(await d(e,`utf8`));return Array.isArray(t)?t:[]}catch{return[]}}const ze=new h(`claude-session-start-hook`).description(`Claude Code SessionStart hook compatibility no-op for ${S}`).action(async()=>{await De()}),Be=new h(`claude-stop-hook`).description(`Claude Code stop hook: reads Stop hook JSON on stdin and marks ${S} when the root session is complete`).action(async()=>{await Oe()}),D=`WORKFLOW_STATUS_FILE`,Ve=m(process.cwd(),`tmp`,`codex-stop-hook.json`),O=`session_meta`,k=`In-progress work is still running. Wait for spawned agents and running tool calls to finish before ending the turn.`,He=[`sub_agent`,`subagent`],Ue=new Set([`in_progress`,`inProgress`,`running`,`pending_init`,`pendingInit`]),We=new Set([`collabAgentToolCall`,`commandExecution`,`dynamicToolCall`,`imageGeneration`,`local_shell_call`,`mcpToolCall`]),Ge={readStdin:M,readStatusFile:N,readTranscriptHead:F,readTranscriptState:I,writeDebugLog:async e=>{await nt(Ve,e)},writeStdout:e=>{process.stdout.write(e)},writeStatusFile:async e=>{await f(e,`YES
|
|
5
|
+
`,`utf8`)},workflowStatusFile:process.env[D]},Ke={readStdin:M,readStatusFile:N,writeSessionBinding:async(e,t)=>{await f(e,`${JSON.stringify({sessionId:t})}\n`,`utf8`)},workflowStatusFile:process.env[D]};async function qe(e=Ke){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=Ze(n);if(!r?.session_id)return;let i=await e.readStatusFile(t);i===null||i.trim().length>0||await e.writeSessionBinding(t,r.session_id)}async function Je(e=Ge){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=Qe(n);if(!r){await P(e,n,null,{decision:`ignore`,reason:`invalid stop hook JSON`});return}if(r.hook_event_name&&r.hook_event_name!==`Stop`){await P(e,n,r,{decision:`ignore`,reason:`unsupported hook event: ${r.hook_event_name}`});return}if(r.stop_hook_active&&!r.hasTaskRegistry){await P(e,n,r,{decision:`ignore`,reason:`recursive stop hook without task registry`});return}let i=$e(await e.readStatusFile(t));if(i.status!==`bound`){await P(e,n,r,{decision:`ignore`,reason:et(i.status)});return}if(!r.session_id||r.session_id!==i.sessionId){await P(e,n,r,{decision:`ignore`,reason:`stop hook session is not bound to the workflow status file`});return}let a=A(r.transcript_path);if(!a){await P(e,n,r,{decision:`ignore`,reason:`missing transcript path`});return}let o=await Xe(e,a),s=o.sessionMeta;if(!s){await P(e,n,r,{decision:`ignore`,reason:`missing transcript session metadata`});return}if(r.session_id&&s.id&&r.session_id!==s.id){await P(e,n,r,{decision:`ignore`,reason:`stop hook session does not match transcript session`});return}if(tt(s.source)){await P(e,n,r,{decision:`ignore`,reason:`transcript session is a subagent`});return}let c=r.hasTaskRegistry&&(r.background_tasks?.length??0)>0,l=r.hasTaskRegistry&&(r.session_crons?.length??0)>0;if(c||!r.hasTaskRegistry&&o.hasPendingWork){e.writeStdout?.(Ye(k)),await P(e,n,r,{decision:`block`,reason:k});return}if(l){await P(e,n,r,{decision:`ignore`,reason:`session crons are still scheduled`});return}await e.writeStatusFile(t),await P(e,n,r,{decision:`complete`})}function Ye(e){return`${JSON.stringify({decision:`block`,reason:e,suppressOutput:!0})}\n`}async function Xe(e,t){return e.readTranscriptState?e.readTranscriptState(t):e.readTranscriptHead===F?I(t):{sessionMeta:await e.readTranscriptHead(t),hasPendingWork:!1}}function Ze(e){try{let t=JSON.parse(e);return j(t)?{session_id:typeof t.session_id==`string`?t.session_id:void 0}:null}catch{return null}}function Qe(e){try{let t=JSON.parse(e);if(!j(t))return null;let n=Array.isArray(t.background_tasks)?t.background_tasks:void 0,r=Array.isArray(t.session_crons)?t.session_crons:void 0;return{hook_event_name:typeof t.hook_event_name==`string`?t.hook_event_name:void 0,session_id:typeof t.session_id==`string`?t.session_id:void 0,transcript_path:A(t.transcript_path),stop_hook_active:t.stop_hook_active===!0,background_tasks:n,session_crons:r,hasTaskRegistry:n!==void 0||r!==void 0}}catch{return null}}function A(e){return typeof e==`string`&&e.trim().length>0?e:null}function $e(e){if(e===null)return{status:`missing`};let t=e.trim();if(!t)return{status:`unbound`};if(t===`YES`)return{status:`completed`};try{let e=JSON.parse(t);return typeof e.sessionId==`string`&&e.sessionId.length>0?{status:`bound`,sessionId:e.sessionId}:{status:`invalid`}}catch{return{status:`invalid`}}}function et(e){switch(e){case`completed`:return`workflow status file already completed`;case`invalid`:return`invalid workflow status file binding`;case`missing`:return`missing workflow status file`;case`unbound`:return`workflow status file is unbound`}}function tt(e){return typeof e==`string`?e.startsWith(`subagent_`)||e.startsWith(`internal_`):j(e)?He.some(t=>t in e):!1}function j(e){return typeof e==`object`&&!!e}async function M(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(String(t)));return Buffer.concat(e).toString(`utf8`)}async function N(e){try{return await d(e,`utf8`)}catch{return null}}async function P(e,t,n,r){try{await e.writeDebugLog?.({timestamp:new Date().toISOString(),rawInput:t,input:n,decision:r})}catch{}}async function nt(e,t){let n=await rt(e);n.push(t),await u(p(e),{recursive:!0}),await f(e,`${JSON.stringify(n.slice(-50),null,2)}\n`,`utf8`)}async function rt(e){try{let t=JSON.parse(await d(e,`utf8`));return Array.isArray(t)?t:[]}catch{return[]}}async function F(e){return(await I(e)).sessionMeta}async function I(e){let t=ne(e,{encoding:`utf8`}),n=ie({input:t,crlfDelay:1/0}),r=new Set,i=new Map,a=null;try{for await(let e of n){let t=e.trim();if(t.length===0)continue;let n=it(t);n&&(a||=at(n),ot(n,r),st(n,i))}}finally{n.close(),t.destroy()}return{sessionMeta:a,hasPendingWork:r.size>0||Array.from(i.values()).some(z)}}function it(e){try{let t=JSON.parse(e);return j(t)?t:null}catch{return null}}function at(e){return e.type===O?e.payload??null:e.item?.type===O?e.item.payload??null:null}function ot(e,t){let n=B(e,[`payload`,`item`])??B(e,[`item`,`payload`,`item`]);if(!n)return;let r=V(n.id),i=V(n.type);!r||!i||!We.has(i)||(z(n.status)?t.add(r):t.delete(r))}function st(e,t){let n=B(e,[`payload`])??B(e,[`item`,`payload`]);if(!n||!V(n.type)?.startsWith(`collab_`))return;L(t,V(n.new_thread_id),n.status),L(t,V(n.receiver_thread_id),n.status);let r=B(n,[`statuses`]);if(r)for(let[e,n]of Object.entries(r))L(t,e,n)}function L(e,t,n){let r=R(n);!t||!r||e.set(t,r)}function R(e){if(typeof e==`string`)return e;if(!j(e))return null;let[t]=Object.keys(e);return t??null}function z(e){let t=typeof e==`string`?e:R(e);return t!==null&&Ue.has(t)}function B(e,t){let n=e;for(let e of t){if(!j(n))return null;n=n[e]}return j(n)?n:null}function V(e){return typeof e==`string`&&e.length>0?e:void 0}const ct=new h(`codex-session-start-hook`).description(`Codex session-start hook: binds ${D} to the first workflow Codex session id`).action(async()=>{await qe()}),lt=new h(`codex-stop-hook`).description(`Codex stop hook: reads stop-hook JSON on stdin and marks ${D} when the root session ends`).action(async()=>{await Je()}),H=`list-crons`;function ut(e={}){let t=e.createService??(()=>new s),n=e.exit??(e=>process.exit(e)),r=e.logError??((e,t)=>console.error(e,t)),i=e.writeStdout??(e=>process.stdout.write(e));return new h(H).description(`List cron jobs scheduled via workflow-mcp`).action(async()=>{try{let e=await t().list();i(`${JSON.stringify(e,null,2)}\n`),n(0)}catch(e){let t=new b(`Error listing cron jobs.`,`LIST_CRONS_COMMAND_FAILED`,{command:H},{cause:e});r(`${t.message} [${t.code}]`,t),n(1)}})}const dt=ut(),U=`list-workflow-statuses`;function W(e){let t=Number(e);if(!Number.isInteger(t)||t<1)throw new ae(`Expected a positive integer.`);return t}function ft(e={}){let t=e.createRegistry??(()=>new a),n=e.exit??(e=>process.exit(e)),r=e.logError??((e,t)=>console.error(e,t)),i=e.writeStdout??(e=>process.stdout.write(e));return new h(U).description(`List tracked workflow runs and their current stages`).option(`--page <number>`,`Page number to return`,W,1).option(`--page-size <number>`,`Number of workflow runs per page`,W,20).option(`-w, --workspace <name>`,`Filter workflow runs by workspace`,`all`).action(async e=>{try{let r=t(),a=e.workspace===`all`?void 0:e.workspace,o=await r.listRunsPage({page:e.page,pageSize:e.pageSize,workspace:a});i(`${JSON.stringify(o,null,2)}\n`),n(0)}catch(t){let i=new b(`Error listing workflow statuses.`,`LIST_WORKFLOW_STATUSES_FAILED`,{command:U,workspace:e.workspace??`all`},{cause:t});r(`${i.message} [${i.code}]`,i),n(1)}})}const pt=ft(),mt=()=>{try{return l(import.meta.url)(`@agimon-ai/foundation-process-registry`)}catch{return null}};function G(e,t){for(let n of e){if(n==null||typeof n!=`object`&&typeof n!=`function`)continue;let e=n;for(let n of t){let t=e[n];if(typeof t==`function`)return t}}return null}function ht(e){let t=[e];if(e&&typeof e==`object`){let n=e.default;if(n&&(t.push(n),typeof n==`function`))try{t.push(new n)}catch{}}return t}async function gt(e){let t=mt();if(!t)return async()=>{};let n=ht(t),r=G(n,[`registerProcess`,`register`,`registerProcessInstance`]),i=G(n,[`unregisterProcess`,`unregister`,`releaseProcess`]);if(!r)return async()=>{};try{await Promise.resolve(r({name:e,pid:process.pid,command:process.argv.join(` `)}))}catch{return async()=>{}}return i?async()=>{await Promise.resolve(i({name:e,pid:process.pid}))}:async()=>{}}async function _t(e,t){await e.start();let n=async n=>{console.error(`\\nReceived ${n}, shutting down gracefully...`);let r=0;try{await e.stop()}catch(e){console.error(`Error during shutdown:`,e),r=1}try{await t()}catch(e){console.error(`Error during resource cleanup:`,e),r=1}process.exit(r)};process.on(`SIGINT`,()=>{n(`SIGINT`)}),process.on(`SIGTERM`,()=>{n(`SIGTERM`)})}async function vt(e){try{await e()}catch{}}const yt=new h(`mcp-serve`).description(`Start MCP server with specified transport`).option(`-t, --type <type>`,`Transport type: stdio`,`stdio`).option(`--service-name <name>`,`Service name for registry tracking`,`workflow-mcp`).action(async e=>{let t=await gt(e.serviceName);try{let n=e.type.toLowerCase();n===`stdio`?await _t(new c(i()),t):(console.error(`Unknown transport type: ${n}. Use: stdio`),process.exit(1))}catch(e){await vt(t),console.error(`Failed to start MCP server:`,e),process.exit(1)}}),K=`recover-workflow`;function bt(e={}){let t=e.createService??(()=>new o),n=e.exit??(e=>process.exit(e)),r=e.logError??((e,t)=>console.error(e,t));return new h(K).description(`Recover a failed workflow run from the local workflow registry`).argument(`<run-key>`,`Failed workflow run key`).option(`-w, --workspace <name>`,`Workspace containing the failed workflow run`,`default`).option(`-j, --job <name>`,`Override the job to recover from`).option(`--runner <runner>`,`Override the runner key for step command maps`).option(`--dry-run`,`Print recovery steps without executing`).action(async(e,i)=>{try{n((await t().recover({dryRun:i.dryRun,job:i.job,runKey:e,runner:i.runner,workspace:i.workspace})).exitCode)}catch(t){let a=new b(`Error recovering workflow.`,`RECOVER_WORKFLOW_FAILED`,{command:K,runKey:e,workspace:i.workspace},{cause:t});r(`${a.message} [${a.code}]`,a),n(1)}})}const xt=bt(),q=`run-workflow`,J=`RUN_WORKFLOW_COMMAND_FAILED`,Y=`WORKFLOW_MCP_BACKGROUND_CHILD`,X=`workflow-mcp-background-run`;function Z(e){if(!e||e.length===0)return;let t={};for(let n of e){let[e,...r]=n.split(`=`);t[e]=r.join(`=`)}return Object.keys(t).length>0?t:void 0}function Q(e){if(e.runner&&e.cliAgent&&e.runner!==e.cliAgent)throw new b(`Conflicting runner selectors.`,J,{cliAgent:e.cliAgent,command:q,runner:e.runner});return e.runner??e.cliAgent}function St(e,t){let n=process.argv[1];if(!n)throw new b(`Unable to determine the workflow-mcp CLI entry point for background execution.`,`RUN_WORKFLOW_BACKGROUND_LAUNCH_FAILED`,{command:q,workflow:e,workspace:t.workspace});let r=[n,`run-workflow`,e];t.job&&r.push(`--job`,t.job);for(let e of t.input??[])r.push(`--input`,e);for(let e of t.env??[])r.push(`--env`,e);t.secretFile&&r.push(`--secret-file`,t.secretFile),t.dryRun&&r.push(`--dry-run`),t.continueOnError&&r.push(`--continue-on-error`),t.keepWorktree&&r.push(`--keep-worktree`);let i=Q(t);i&&r.push(`--runner`,i),t.prompt&&r.push(`--prompt`,t.prompt),t.name&&r.push(`--name`,t.name),t.workspace&&r.push(`--workspace`,t.workspace);let a=ee(process.execPath,r,{detached:!0,env:{...process.env,[Y]:`1`},stdio:`ignore`});return a.unref(),a.pid}async function Ct(e,t){if(process.env[Y]!==`1`)return async()=>{};let n=new re(process.env.PROCESS_REGISTRY_PATH),r=te(process.cwd()),i=process.env.NODE_ENV??`development`;return(await n.registerProcess({repositoryPath:r,serviceName:X,serviceType:`tool`,environment:i,pid:process.pid,command:process.argv.join(` `),args:process.argv.slice(2),metadata:{workflow:e,workspace:t.workspace,job:t.job,name:t.name,runner:t.runner??t.cliAgent},force:!0})).success?async()=>{let e=await n.releaseProcess({repositoryPath:r,serviceName:X,serviceType:`tool`,environment:i,pid:process.pid,kill:!1,releasePort:!1,force:!0});if(!e.success&&!e.error?.includes(`No matching process entry`))throw Error(e.error??`Failed to release workflow background process`)}:async()=>{}}function wt(e={}){let t=e.createService??(()=>new n),r=e.exit??(e=>process.exit(e)),i=e.launchBackgroundRun??St,a=e.registerBackgroundChild??Ct,o=e.logError??((e,t)=>console.error(e,t)),s=e.logInfo??(e=>process.stdout.write(`${e}\n`));return new h(q).description(`Run a GitHub Actions workflow file locally on macOS`).argument(`<workflow>`,`Path to the workflow YAML file`).option(`-j, --job <name>`,`Run only this job (and its dependencies)`).option(`-i, --input <key=value...>`,`Set workflow_dispatch input (repeatable)`).option(`-e, --env <key=value...>`,`Set extra environment variable (repeatable)`).option(`--secret-file <path>`,`Load secrets from a dotenv-style file`).option(`--dry-run`,`Print steps without executing`).option(`--continue-on-error`,`Continue past step failures`).option(`--runner <runner>`,`Preferred runner key for step command maps`).option(`--cli-agent <agent>`,`Deprecated alias for --runner`).option(`-p, --prompt <text>`,`User prompt for user_prompt trigger workflows`).option(`-n, --name <name>`,`Name for the workflow run context directory`).option(`-w, --workspace <name>`,`Workspace for workflow registry storage`).option(`--keep-worktree`,`Keep worktree on completion (skip merge and cleanup for retry)`).option(`--skip-launch`,`Skip launch-command delegation (used by inner invocations)`).option(`-b, --background`,`Run the workflow in a detached background process`).action(async(e,n)=>{try{if(n.background){let t=i(e,n);s(`Started workflow in background${t?` (PID: ${t})`:``}`),r(0);return}let o=await a(e,n),c=t();try{let t=Q(n),i=await c.run({cliAgent:n.cliAgent,runner:t,workflowPath:e,job:n.job,inputs:Z(n.input),env:Z(n.env),secretFile:n.secretFile,dryRun:n.dryRun,continueOnError:n.continueOnError,keepWorktree:n.keepWorktree,prompt:n.prompt,name:n.name,workspace:n.workspace,skipLaunch:n.skipLaunch});await o(),r(i.exitCode)}catch(e){throw await o(),e}}catch(t){let i=t instanceof b?t:new b(`Error executing run-workflow.`,J,{background:!!n.background,command:q,workflow:e,workspace:n.workspace},{cause:t});o(`${i.message} [${i.code}]`,i),r(1)}})}const Tt=wt(),Et=`schedule-cron`;function Dt(e={}){let n=e.createService??(()=>new s),i=e.exit??(e=>process.exit(e)),a=e.logError??((e,t)=>console.error(e,t)),o=e.writeStdout??(e=>process.stdout.write(`${e}\n`));return new h(Et).description(`Schedule a headless Claude Code or Codex CLI run via system crontab`).argument(`<name>`,`Unique name for this cron job`).option(`-d, --cwd <path>`,`Working directory for the CLI run`,process.cwd()).option(`-c, --cli <cli>`,`CLI to use: claude or codex`,t).option(`-p, --prompt <text>`,`Prompt to pass to the CLI`).option(`-f, --prompt-file <path>`,`Path to a file whose content is used as the prompt (read at cron execution time)`).option(`-s, --schedule <cron>`,`Cron expression (e.g., "*/10 * * * *")`).option(`-i, --interval-minutes <minutes>`,`Run every N minutes (alternative to --schedule)`).action(async(e,t)=>{try{let a=r.parse({name:e,cwd:t.cwd??process.cwd(),cli:t.cli??`claude`,prompt:t.prompt,promptFile:t.promptFile,schedule:t.schedule,intervalMinutes:t.intervalMinutes?Number.parseInt(t.intervalMinutes,10):void 0}),s=await n().schedule(a);o(`Scheduled cron job "${s.name}" with schedule: ${s.schedule}`),o(`CLI: ${s.cli} | CWD: ${s.cwd}`),s.prompt&&o(`Prompt: ${s.prompt}`),i(0)}catch(t){let n=t instanceof b?t:new b(`Error scheduling cron job.`,`SCHEDULE_CRON_COMMAND_FAILED`,{command:Et,name:e},{cause:t});a(`${n.message} [${n.code}]`,n),i(1)}})}const Ot=Dt(),$=`stop-workflow`;function kt(e={}){let t=e.createRegistry??(()=>new a),n=e.exit??(e=>process.exit(e)),r=e.logError??((e,t)=>console.error(e,t)),i=e.writeStdout??(e=>process.stdout.write(e));return new h($).description(`Request a running workflow to stop gracefully`).argument(`<run-key>`,`Running workflow run key`).option(`-w, --workspace <name>`,`Workspace containing the running workflow`,`default`).option(`-r, --reason <text>`,`Optional stop reason to record`).action(async(e,a)=>{try{let r=t(),o=await r.requestStop(a.workspace,e,a.reason);i(`${JSON.stringify({reason:o.reason,requestedAt:o.requestedAt,runKey:e,workspace:r.resolveWorkspace(a.workspace)},null,2)}\n`),n(0)}catch(t){let i=new b(`Error requesting workflow stop.`,`STOP_WORKFLOW_FAILED`,{command:$,runKey:e,workspace:a.workspace},{cause:t});r(`${i.message} [${i.code}]`,i),n(1)}})}const At=kt();async function jt(){let e=new h;e.name(`workflow-mcp`).description(`MCP server for running GitHub Actions workflows locally`).version(oe),e.addCommand(dt),e.addCommand(pt),e.addCommand(Se),e.addCommand(be),e.addCommand(ze),e.addCommand(Be),e.addCommand(ct),e.addCommand(lt),e.addCommand(yt),e.addCommand(xt),e.addCommand(Tt),e.addCommand(Ot),e.addCommand(At),await e.parseAsync(process.argv)}jt().catch(e=>{console.error(`[CLI_STARTUP_ERROR] workflow-mcp startup failed:`,e),process.exit(1)});export{};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/workflow-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "MCP server for running GitHub Actions workflows locally",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
"commander": "14.0.3",
|
|
37
37
|
"js-yaml": "4.1.1",
|
|
38
38
|
"zod": "4.4.1",
|
|
39
|
-
"@agimon-ai/foundation-port-registry": "0.
|
|
40
|
-
"@agimon-ai/
|
|
41
|
-
"@agimon-ai/
|
|
42
|
-
"@agimon-ai/foundation-
|
|
39
|
+
"@agimon-ai/foundation-port-registry": "0.11.0",
|
|
40
|
+
"@agimon-ai/log-sink-mcp": "0.11.0",
|
|
41
|
+
"@agimon-ai/foundation-validator": "0.8.0",
|
|
42
|
+
"@agimon-ai/foundation-process-registry": "0.11.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/js-yaml": "4.0.9",
|