@agimon-ai/workflow-mcp 0.1.8 → 0.1.10

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 CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- const e=require(`./stdio-Dk5cRaph.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.1.7`,u=class extends Error{constructor(e,t,n={},r){super(e,r),this.code=t,this.context=n,this.name=`WorkflowCommandError`}};const d=`check-codex-quota`;function f(t={}){let n=t.createService??(()=>new e.i),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(d).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 u(`Codex quota is blocking work at ${e.blockingLimit.limitId}/${e.blockingLimit.window}.`,`CODEX_QUOTA_BLOCKED`,{command:d,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 u?e:new u(`Error checking Codex quota.`,`CHECK_CODEX_QUOTA_COMMAND_FAILED`,{command:d},{cause:e});i(`${t.message} [${t.code}]`,t),r(1)}})}const p=f(),m=`WORKFLOW_STATUS_FILE`,h=`session_meta`,g=[`sub_agent`,`subagent`],_={readStdin:C,readTranscriptHead:w,writeStatusFile:async e=>{await(0,n.writeFile)(e,`YES
3
- `,`utf8`)},workflowStatusFile:process.env[m]};async function v(e=_){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=y(n);if(!r||r.stop_hook_active)return;let i=b(r.transcript_path);if(!i)return;let a=await e.readTranscriptHead(i);a&&(r.session_id&&a.id&&r.session_id!==a.id||x(a.source)||await e.writeStatusFile(t))}function y(e){try{let t=JSON.parse(e);return S(t)?{session_id:typeof t.session_id==`string`?t.session_id:void 0,transcript_path:b(t.transcript_path),stop_hook_active:t.stop_hook_active===!0}:null}catch{return null}}function b(e){return typeof e==`string`&&e.trim().length>0?e:null}function x(e){return typeof e==`string`?e.startsWith(`subagent_`)||e.startsWith(`internal_`):S(e)?g.some(t=>t in e):!1}function S(e){return typeof e==`object`&&!!e}async function C(){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 w(e){let t=(0,i.createReadStream)(e,{encoding:`utf8`}),n=(0,o.createInterface)({input:t,crlfDelay:1/0});try{for await(let e of n){let t=e.trim();if(t.length!==0)return T(t)}}finally{n.close(),t.destroy()}return null}function T(e){try{let t=JSON.parse(e);return t.type===h?t.payload??null:t.item?.type===h?t.item.payload??null:null}catch{return null}}const E=new s.Command(`codex-stop-hook`).description(`Codex stop hook: reads stop-hook JSON on stdin and marks ${m} when the root session ends`).action(async()=>{await v()}),D=`list-crons`;function O(t={}){let n=t.createService??(()=>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(D).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 u(`Error listing cron jobs.`,`LIST_CRONS_COMMAND_FAILED`,{command:D},{cause:e});i(`${t.message} [${t.code}]`,t),r(1)}})}const k=O(),A=`list-workflow-statuses`;function j(e){let t=Number(e);if(!Number.isInteger(t)||t<1)throw new s.InvalidArgumentError(`Expected a positive integer.`);return t}function M(t={}){let n=t.createRegistry??(()=>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(A).description(`List tracked workflow runs and their current stages`).option(`--page <number>`,`Page number to return`,j,1).option(`--page-size <number>`,`Number of workflow runs per page`,j,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 u(`Error listing workflow statuses.`,`LIST_WORKFLOW_STATUSES_FAILED`,{command:A,workspace:e.workspace??`all`},{cause:t});i(`${n.message} [${n.code}]`,n),r(1)}})}const N=M(),P=()=>{try{return(0,c.createRequire)(require(`url`).pathToFileURL(__filename).href)(`@agimon-ai/foundation-process-registry`)}catch{return null}};function F(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 I(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 L(e){let t=P();if(!t)return async()=>{};let n=I(t),r=F(n,[`registerProcess`,`register`,`registerProcessInstance`]),i=F(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 R(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 z(e){try{await e()}catch{}}const B=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 L(t.serviceName);try{let r=t.type.toLowerCase();r===`stdio`?await R(new e.t(e.n()),n):(console.error(`Unknown transport type: ${r}. Use: stdio`),process.exit(1))}catch(e){await z(n),console.error(`Failed to start MCP server:`,e),process.exit(1)}}),V=`run-workflow`,H=`RUN_WORKFLOW_COMMAND_FAILED`,U=`WORKFLOW_MCP_BACKGROUND_CHILD`,W=`workflow-mcp-background-run`;function G(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 K(e){if(e.runner&&e.cliAgent&&e.runner!==e.cliAgent)throw new u(`Conflicting runner selectors.`,H,{cliAgent:e.cliAgent,command:V,runner:e.runner});return e.runner??e.cliAgent}function q(e,n){let r=process.argv[1];if(!r)throw new u(`Unable to determine the workflow-mcp CLI entry point for background execution.`,`RUN_WORKFLOW_BACKGROUND_LAUNCH_FAILED`,{command:V,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=K(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,[U]:`1`},stdio:`ignore`});return o.unref(),o.pid}async function J(e,t){if(process.env[U]!==`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:W,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:W,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 Y(t={}){let n=t.createService??(()=>new e.r),r=t.exit??(e=>process.exit(e)),i=t.launchBackgroundRun??q,a=t.registerBackgroundChild??J,o=t.logError??((e,t)=>console.error(e,t)),c=t.logInfo??(e=>process.stdout.write(`${e}\n`));return new s.Command(V).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=K(t),i=await s.run({cliAgent:t.cliAgent,runner:n,workflowPath:e,job:t.job,inputs:G(t.input),env:G(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 u?n:new u(`Error executing run-workflow.`,H,{background:!!t.background,command:V,workflow:e,workspace:t.workspace},{cause:n});o(`${i.message} [${i.code}]`,i),r(1)}})}const X=Y(),Z=`schedule-cron`;function Q(t={}){let n=t.createService??(()=>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}\n`));return new s.Command(Z).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.s).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.c.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 u?e:new u(`Error scheduling cron job.`,`SCHEDULE_CRON_COMMAND_FAILED`,{command:Z,name:t},{cause:e});i(`${n.message} [${n.code}]`,n),r(1)}})}const $=Q();async function ee(){let e=new s.Command;e.name(`workflow-mcp`).description(`MCP server for running GitHub Actions workflows locally`).version(l),e.addCommand(k),e.addCommand(N),e.addCommand(p),e.addCommand(E),e.addCommand(B),e.addCommand(X),e.addCommand($),await e.parseAsync(process.argv)}ee().catch(e=>{console.error(`[CLI_STARTUP_ERROR] workflow-mcp startup failed:`,e),process.exit(1)});
2
+ const e=require(`./stdio-Dk5cRaph.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.1.9`,u=class extends Error{constructor(e,t,n={},r){super(e,r),this.code=t,this.context=n,this.name=`WorkflowCommandError`}};const d=`check-codex-quota`;function ee(t={}){let n=t.createService??(()=>new e.i),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(d).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 u(`Codex quota is blocking work at ${e.blockingLimit.limitId}/${e.blockingLimit.window}.`,`CODEX_QUOTA_BLOCKED`,{command:d,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 u?e:new u(`Error checking Codex quota.`,`CHECK_CODEX_QUOTA_COMMAND_FAILED`,{command:d},{cause:e});i(`${t.message} [${t.code}]`,t),r(1)}})}const te=ee(),f=`WORKFLOW_STATUS_FILE`,p=`YES
3
+ `,m=`session_meta`,h=[`sub_agent`,`subagent`],g=new Set([`in_progress`,`inProgress`,`running`,`pending_init`,`pendingInit`]),_=new Set([`collabAgentToolCall`,`commandExecution`,`dynamicToolCall`,`imageGeneration`,`local_shell_call`,`mcpToolCall`]),v={readStdin:E,readStatusFile:D,readTranscriptHead:O,readTranscriptState:k,writeStdout:e=>{process.stdout.write(e)},writeStatusFile:async e=>{await(0,n.writeFile)(e,p,`utf8`)},workflowStatusFile:process.env[f]},ne={readStdin:E,readStatusFile:D,writeStdout:e=>{process.stdout.write(e)},writeStatusFile:async e=>{await(0,n.writeFile)(e,p,`utf8`)},workflowStatusFile:process.env[f]},y={readStdin:E,readStatusFile:D,writeSessionBinding:async(e,t)=>{await(0,n.writeFile)(e,`${JSON.stringify({sessionId:t})}\n`,`utf8`)},workflowStatusFile:process.env[f]};async function re(e=y){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=S(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 b(e=v){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=C(n);if(!r||r.hook_event_name&&r.hook_event_name!==`Stop`||r.stop_hook_active&&!r.hasClaudeTaskRegistry)return;let i=le(await e.readStatusFile(t));if(!r.session_id||r.session_id!==i)return;let a=w(r.transcript_path);if(!a)return;let o=await x(e,a),s=o.sessionMeta;if(!s||r.session_id&&s.id&&r.session_id!==s.id||ue(s.source))return;let c=r.hasClaudeTaskRegistry&&(r.background_tasks?.length??0)>0,l=r.hasClaudeTaskRegistry&&(r.session_crons?.length??0)>0;if(c||!r.hasClaudeTaskRegistry&&o.hasPendingWork){e.writeStdout?.(ae(`In-progress work is still running. Wait for spawned agents and running tool calls to finish before ending the turn.`));return}l||await e.writeStatusFile(t)}async function ie(e=ne){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=se(n);if(!r?.conversationId)return;let i=await e.readStatusFile(t);if(!(i===null||i.trim()===`YES`)){if(!r.fullyIdle){e.writeStdout?.(oe(`In-progress work is still running. Continue the execution loop until background tasks finish.`));return}ce(r.error)||r.terminationReason!==`model_stop`||await e.writeStatusFile(t)}}function ae(e){return`${JSON.stringify({decision:`block`,reason:e,suppressOutput:!0})}\n`}function oe(e){return`${JSON.stringify({decision:`continue`,reason:e})}\n`}async function x(e,t){return e.readTranscriptState?e.readTranscriptState(t):e.readTranscriptHead===O?k(t):{sessionMeta:await e.readTranscriptHead(t),hasPendingWork:!1}}function S(e){try{let t=JSON.parse(e);return T(t)?{session_id:typeof t.session_id==`string`?t.session_id:void 0}:null}catch{return null}}function C(e){try{let t=JSON.parse(e);if(!T(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:w(t.transcript_path),stop_hook_active:t.stop_hook_active===!0,background_tasks:n,session_crons:r,hasClaudeTaskRegistry:n!==void 0||r!==void 0}}catch{return null}}function se(e){try{let t=JSON.parse(e);return T(t)?{conversationId:typeof t.conversationId==`string`?t.conversationId:void 0,transcriptPath:w(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 w(e){return typeof e==`string`&&e.trim().length>0?e:null}function ce(e){return typeof e==`string`&&e.trim().length>0}function le(e){if(!e)return null;let t=e.trim();if(!t||t===`YES`)return null;try{let e=JSON.parse(t);return typeof e.sessionId==`string`&&e.sessionId.length>0?e.sessionId:null}catch{return null}}function ue(e){return typeof e==`string`?e.startsWith(`subagent_`)||e.startsWith(`internal_`):T(e)?h.some(t=>t in e):!1}function T(e){return typeof e==`object`&&!!e}async function E(){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 D(e){try{return await(0,n.readFile)(e,`utf8`)}catch{return null}}async function O(e){return(await k(e)).sessionMeta}async function k(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=A(t);n&&(s||=j(n),M(n,r),N(n,a))}}finally{n.close(),t.destroy()}return{sessionMeta:s,hasPendingWork:r.size>0||Array.from(a.values()).some(I)}}function A(e){try{let t=JSON.parse(e);return T(t)?t:null}catch{return null}}function j(e){return e.type===m?e.payload??null:e.item?.type===m?e.item.payload??null:null}function M(e,t){let n=L(e,[`payload`,`item`])??L(e,[`item`,`payload`,`item`]);if(!n)return;let r=R(n.id),i=R(n.type);!r||!i||!_.has(i)||(I(n.status)?t.add(r):t.delete(r))}function N(e,t){let n=L(e,[`payload`])??L(e,[`item`,`payload`]);if(!n||!R(n.type)?.startsWith(`collab_`))return;P(t,R(n.new_thread_id),n.status),P(t,R(n.receiver_thread_id),n.status);let r=L(n,[`statuses`]);if(r)for(let[e,n]of Object.entries(r))P(t,e,n)}function P(e,t,n){let r=F(n);!t||!r||e.set(t,r)}function F(e){if(typeof e==`string`)return e;if(!T(e))return null;let[t]=Object.keys(e);return t??null}function I(e){let t=typeof e==`string`?e:F(e);return t!==null&&g.has(t)}function L(e,t){let n=e;for(let e of t){if(!T(n))return null;n=n[e]}return T(n)?n:null}function R(e){return typeof e==`string`&&e.length>0?e:void 0}const z=new s.Command(`codex-session-start-hook`).description(`Codex session-start hook: binds ${f} to the first workflow Codex session id`).action(async()=>{await re()}),B=new s.Command(`codex-stop-hook`).description(`Codex stop hook: reads stop-hook JSON on stdin and marks ${f} when the root session ends`).action(async()=>{await b()}),V=new s.Command(`claude-stop-hook`).description(`Claude Code stop hook: reads Stop hook JSON on stdin and marks ${f} when the root session is complete`).action(async()=>{await b()}),H=new s.Command(`antigravity-stop-hook`).description(`Antigravity stop hook: reads Stop hook JSON on stdin and marks ${f} when the execution is fully idle`).action(async()=>{await ie()}),U=`list-crons`;function de(t={}){let n=t.createService??(()=>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(U).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 u(`Error listing cron jobs.`,`LIST_CRONS_COMMAND_FAILED`,{command:U},{cause:e});i(`${t.message} [${t.code}]`,t),r(1)}})}const fe=de(),W=`list-workflow-statuses`;function G(e){let t=Number(e);if(!Number.isInteger(t)||t<1)throw new s.InvalidArgumentError(`Expected a positive integer.`);return t}function pe(t={}){let n=t.createRegistry??(()=>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(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 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 u(`Error listing workflow statuses.`,`LIST_WORKFLOW_STATUSES_FAILED`,{command:W,workspace:e.workspace??`all`},{cause:t});i(`${n.message} [${n.code}]`,n),r(1)}})}const me=pe(),he=()=>{try{return(0,c.createRequire)(require(`url`).pathToFileURL(__filename).href)(`@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 ge(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 _e(e){let t=he();if(!t)return async()=>{};let n=ge(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 ve(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 ye(e){try{await e()}catch{}}const be=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 _e(t.serviceName);try{let r=t.type.toLowerCase();r===`stdio`?await ve(new e.t(e.n()),n):(console.error(`Unknown transport type: ${r}. Use: stdio`),process.exit(1))}catch(e){await ye(n),console.error(`Failed to start MCP server:`,e),process.exit(1)}}),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 u(`Conflicting runner selectors.`,J,{cliAgent:e.cliAgent,command:q,runner:e.runner});return e.runner??e.cliAgent}function xe(e,n){let r=process.argv[1];if(!r)throw new u(`Unable to determine the workflow-mcp CLI entry point for background execution.`,`RUN_WORKFLOW_BACKGROUND_LAUNCH_FAILED`,{command:q,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=Q(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,[Y]:`1`},stdio:`ignore`});return o.unref(),o.pid}async function Se(e,t){if(process.env[Y]!==`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:X,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:X,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 Ce(t={}){let n=t.createService??(()=>new e.r),r=t.exit??(e=>process.exit(e)),i=t.launchBackgroundRun??xe,a=t.registerBackgroundChild??Se,o=t.logError??((e,t)=>console.error(e,t)),c=t.logInfo??(e=>process.stdout.write(`${e}\n`));return new s.Command(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,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=Q(t),i=await s.run({cliAgent:t.cliAgent,runner:n,workflowPath:e,job:t.job,inputs:Z(t.input),env:Z(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 u?n:new u(`Error executing run-workflow.`,J,{background:!!t.background,command:q,workflow:e,workspace:t.workspace},{cause:n});o(`${i.message} [${i.code}]`,i),r(1)}})}const we=Ce(),$=`schedule-cron`;function Te(t={}){let n=t.createService??(()=>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}\n`));return new s.Command($).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.s).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.c.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 u?e:new u(`Error scheduling cron job.`,`SCHEDULE_CRON_COMMAND_FAILED`,{command:$,name:t},{cause:e});i(`${n.message} [${n.code}]`,n),r(1)}})}const Ee=Te();async function De(){let e=new s.Command;e.name(`workflow-mcp`).description(`MCP server for running GitHub Actions workflows locally`).version(l),e.addCommand(fe),e.addCommand(me),e.addCommand(te),e.addCommand(H),e.addCommand(V),e.addCommand(z),e.addCommand(B),e.addCommand(be),e.addCommand(we),e.addCommand(Ee),await e.parseAsync(process.argv)}De().catch(e=>{console.error(`[CLI_STARTUP_ERROR] workflow-mcp startup failed:`,e),process.exit(1)});
package/dist/cli.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{a as e,c as t,i as n,n as r,o as i,r as a,s as o,t as s}from"./stdio-ixcSdqgo.mjs";import{createRequire as c}from"node:module";import{spawn as ee}from"node:child_process";import{writeFile as l}from"node:fs/promises";import{resolve as u}from"node:path";import{createReadStream as d}from"node:fs";import{ProcessRegistryService as f}from"@agimon-ai/foundation-process-registry";import{createInterface as p}from"node:readline";import{Command as m,InvalidArgumentError as h}from"commander";var g=`0.1.7`,_=class extends Error{constructor(e,t,n={},r){super(e,r),this.code=t,this.context=n,this.name=`WorkflowCommandError`}};const v=`check-codex-quota`;function y(e={}){let t=e.createService??(()=>new n),r=e.exit??(e=>process.exit(e)),i=e.logError??((e,t)=>console.error(e,t)),a=e.writeStdout??(e=>process.stdout.write(e));return new m(v).description(`Check whether Codex quota is currently blocking new work`).action(async()=>{try{let e=await t().getQuotaStatus();if(a(`${JSON.stringify({blockingLimit:e?.blockingLimit??null,planType:e?.planType??null},null,2)}\n`),e?.blockingLimit){let t=new _(`Codex quota is blocking work at ${e.blockingLimit.limitId}/${e.blockingLimit.window}.`,`CODEX_QUOTA_BLOCKED`,{command:v,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 _?e:new _(`Error checking Codex quota.`,`CHECK_CODEX_QUOTA_COMMAND_FAILED`,{command:v},{cause:e});i(`${t.message} [${t.code}]`,t),r(1)}})}const b=y(),x=`WORKFLOW_STATUS_FILE`,S=`session_meta`,C=[`sub_agent`,`subagent`],w={readStdin:A,readTranscriptHead:j,writeStatusFile:async e=>{await l(e,`YES
3
- `,`utf8`)},workflowStatusFile:process.env[x]};async function T(e=w){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=E(n);if(!r||r.stop_hook_active)return;let i=D(r.transcript_path);if(!i)return;let a=await e.readTranscriptHead(i);a&&(r.session_id&&a.id&&r.session_id!==a.id||O(a.source)||await e.writeStatusFile(t))}function E(e){try{let t=JSON.parse(e);return k(t)?{session_id:typeof t.session_id==`string`?t.session_id:void 0,transcript_path:D(t.transcript_path),stop_hook_active:t.stop_hook_active===!0}:null}catch{return null}}function D(e){return typeof e==`string`&&e.trim().length>0?e:null}function O(e){return typeof e==`string`?e.startsWith(`subagent_`)||e.startsWith(`internal_`):k(e)?C.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){let t=d(e,{encoding:`utf8`}),n=p({input:t,crlfDelay:1/0});try{for await(let e of n){let t=e.trim();if(t.length!==0)return M(t)}}finally{n.close(),t.destroy()}return null}function M(e){try{let t=JSON.parse(e);return t.type===S?t.payload??null:t.item?.type===S?t.item.payload??null:null}catch{return null}}const N=new m(`codex-stop-hook`).description(`Codex stop hook: reads stop-hook JSON on stdin and marks ${x} when the root session ends`).action(async()=>{await T()}),P=`list-crons`;function F(e={}){let t=e.createService??(()=>new i),n=e.exit??(e=>process.exit(e)),r=e.logError??((e,t)=>console.error(e,t)),a=e.writeStdout??(e=>process.stdout.write(e));return new m(P).description(`List cron jobs scheduled via workflow-mcp`).action(async()=>{try{let e=await t().list();a(`${JSON.stringify(e,null,2)}\n`),n(0)}catch(e){let t=new _(`Error listing cron jobs.`,`LIST_CRONS_COMMAND_FAILED`,{command:P},{cause:e});r(`${t.message} [${t.code}]`,t),n(1)}})}const I=F(),L=`list-workflow-statuses`;function R(e){let t=Number(e);if(!Number.isInteger(t)||t<1)throw new h(`Expected a positive integer.`);return t}function z(t={}){let n=t.createRegistry??(()=>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 m(L).description(`List tracked workflow runs and their current stages`).option(`--page <number>`,`Page number to return`,R,1).option(`--page-size <number>`,`Number of workflow runs per page`,R,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 _(`Error listing workflow statuses.`,`LIST_WORKFLOW_STATUSES_FAILED`,{command:L,workspace:e.workspace??`all`},{cause:t});i(`${n.message} [${n.code}]`,n),r(1)}})}const B=z(),V=()=>{try{return c(import.meta.url)(`@agimon-ai/foundation-process-registry`)}catch{return null}};function H(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 te(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 U(e){let t=V();if(!t)return async()=>{};let n=te(t),r=H(n,[`registerProcess`,`register`,`registerProcessInstance`]),i=H(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 W(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 G(e){try{await e()}catch{}}const K=new m(`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 U(e.serviceName);try{let n=e.type.toLowerCase();n===`stdio`?await W(new s(r()),t):(console.error(`Unknown transport type: ${n}. Use: stdio`),process.exit(1))}catch(e){await G(t),console.error(`Failed to start MCP server:`,e),process.exit(1)}}),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 _(`Conflicting runner selectors.`,J,{cliAgent:e.cliAgent,command:q,runner:e.runner});return e.runner??e.cliAgent}function ne(e,t){let n=process.argv[1];if(!n)throw new _(`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 re(e,t){if(process.env[Y]!==`1`)return async()=>{};let n=new f(process.env.PROCESS_REGISTRY_PATH),r=u(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 ie(e={}){let t=e.createService??(()=>new a),n=e.exit??(e=>process.exit(e)),r=e.launchBackgroundRun??ne,i=e.registerBackgroundChild??re,o=e.logError??((e,t)=>console.error(e,t)),s=e.logInfo??(e=>process.stdout.write(`${e}\n`));return new m(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,a)=>{try{if(a.background){let t=r(e,a);s(`Started workflow in background${t?` (PID: ${t})`:``}`),n(0);return}let o=await i(e,a),c=t();try{let t=Q(a),r=await c.run({cliAgent:a.cliAgent,runner:t,workflowPath:e,job:a.job,inputs:Z(a.input),env:Z(a.env),secretFile:a.secretFile,dryRun:a.dryRun,continueOnError:a.continueOnError,keepWorktree:a.keepWorktree,prompt:a.prompt,name:a.name,workspace:a.workspace,skipLaunch:a.skipLaunch});await o(),n(r.exitCode)}catch(e){throw await o(),e}}catch(t){let r=t instanceof _?t:new _(`Error executing run-workflow.`,J,{background:!!a.background,command:q,workflow:e,workspace:a.workspace},{cause:t});o(`${r.message} [${r.code}]`,r),n(1)}})}const ae=ie(),$=`schedule-cron`;function oe(e={}){let n=e.createService??(()=>new i),r=e.exit??(e=>process.exit(e)),a=e.logError??((e,t)=>console.error(e,t)),s=e.writeStdout??(e=>process.stdout.write(`${e}\n`));return new m($).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`,o).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,i)=>{try{let a=t.parse({name:e,cwd:i.cwd??process.cwd(),cli:i.cli??`claude`,prompt:i.prompt,promptFile:i.promptFile,schedule:i.schedule,intervalMinutes:i.intervalMinutes?Number.parseInt(i.intervalMinutes,10):void 0}),o=await n().schedule(a);s(`Scheduled cron job "${o.name}" with schedule: ${o.schedule}`),s(`CLI: ${o.cli} | CWD: ${o.cwd}`),o.prompt&&s(`Prompt: ${o.prompt}`),r(0)}catch(t){let n=t instanceof _?t:new _(`Error scheduling cron job.`,`SCHEDULE_CRON_COMMAND_FAILED`,{command:$,name:e},{cause:t});a(`${n.message} [${n.code}]`,n),r(1)}})}const se=oe();async function ce(){let e=new m;e.name(`workflow-mcp`).description(`MCP server for running GitHub Actions workflows locally`).version(g),e.addCommand(I),e.addCommand(B),e.addCommand(b),e.addCommand(N),e.addCommand(K),e.addCommand(ae),e.addCommand(se),await e.parseAsync(process.argv)}ce().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,n as r,o as i,r as a,s as o,t as s}from"./stdio-ixcSdqgo.mjs";import{createRequire as c}from"node:module";import{spawn as l}from"node:child_process";import{readFile as u,writeFile as d}from"node:fs/promises";import{resolve as ee}from"node:path";import{createReadStream as te}from"node:fs";import{ProcessRegistryService as ne}from"@agimon-ai/foundation-process-registry";import{createInterface as f}from"node:readline";import{Command as p,InvalidArgumentError as m}from"commander";var h=`0.1.9`,g=class extends Error{constructor(e,t,n={},r){super(e,r),this.code=t,this.context=n,this.name=`WorkflowCommandError`}};const _=`check-codex-quota`;function re(e={}){let t=e.createService??(()=>new n),r=e.exit??(e=>process.exit(e)),i=e.logError??((e,t)=>console.error(e,t)),a=e.writeStdout??(e=>process.stdout.write(e));return new p(_).description(`Check whether Codex quota is currently blocking new work`).action(async()=>{try{let e=await t().getQuotaStatus();if(a(`${JSON.stringify({blockingLimit:e?.blockingLimit??null,planType:e?.planType??null},null,2)}\n`),e?.blockingLimit){let t=new g(`Codex quota is blocking work at ${e.blockingLimit.limitId}/${e.blockingLimit.window}.`,`CODEX_QUOTA_BLOCKED`,{command:_,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 g?e:new g(`Error checking Codex quota.`,`CHECK_CODEX_QUOTA_COMMAND_FAILED`,{command:_},{cause:e});i(`${t.message} [${t.code}]`,t),r(1)}})}const ie=re(),v=`WORKFLOW_STATUS_FILE`,y=`YES
3
+ `,b=`session_meta`,ae=[`sub_agent`,`subagent`],x=new Set([`in_progress`,`inProgress`,`running`,`pending_init`,`pendingInit`]),S=new Set([`collabAgentToolCall`,`commandExecution`,`dynamicToolCall`,`imageGeneration`,`local_shell_call`,`mcpToolCall`]),oe={readStdin:k,readStatusFile:A,readTranscriptHead:j,readTranscriptState:M,writeStdout:e=>{process.stdout.write(e)},writeStatusFile:async e=>{await d(e,y,`utf8`)},workflowStatusFile:process.env[v]},se={readStdin:k,readStatusFile:A,writeStdout:e=>{process.stdout.write(e)},writeStatusFile:async e=>{await d(e,y,`utf8`)},workflowStatusFile:process.env[v]},ce={readStdin:k,readStatusFile:A,writeSessionBinding:async(e,t)=>{await d(e,`${JSON.stringify({sessionId:t})}\n`,`utf8`)},workflowStatusFile:process.env[v]};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=me(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 C(e=oe){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=he(n);if(!r||r.hook_event_name&&r.hook_event_name!==`Stop`||r.stop_hook_active&&!r.hasClaudeTaskRegistry)return;let i=E(await e.readStatusFile(t));if(!r.session_id||r.session_id!==i)return;let a=w(r.transcript_path);if(!a)return;let o=await pe(e,a),s=o.sessionMeta;if(!s||r.session_id&&s.id&&r.session_id!==s.id||D(s.source))return;let c=r.hasClaudeTaskRegistry&&(r.background_tasks?.length??0)>0,l=r.hasClaudeTaskRegistry&&(r.session_crons?.length??0)>0;if(c||!r.hasClaudeTaskRegistry&&o.hasPendingWork){e.writeStdout?.(de(`In-progress work is still running. Wait for spawned agents and running tool calls to finish before ending the turn.`));return}l||await e.writeStatusFile(t)}async function ue(e=se){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=ge(n);if(!r?.conversationId)return;let i=await e.readStatusFile(t);if(!(i===null||i.trim()===`YES`)){if(!r.fullyIdle){e.writeStdout?.(fe(`In-progress work is still running. Continue the execution loop until background tasks finish.`));return}T(r.error)||r.terminationReason!==`model_stop`||await e.writeStatusFile(t)}}function de(e){return`${JSON.stringify({decision:`block`,reason:e,suppressOutput:!0})}\n`}function fe(e){return`${JSON.stringify({decision:`continue`,reason:e})}\n`}async function pe(e,t){return e.readTranscriptState?e.readTranscriptState(t):e.readTranscriptHead===j?M(t):{sessionMeta:await e.readTranscriptHead(t),hasPendingWork:!1}}function me(e){try{let t=JSON.parse(e);return O(t)?{session_id:typeof t.session_id==`string`?t.session_id:void 0}:null}catch{return null}}function he(e){try{let t=JSON.parse(e);if(!O(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:w(t.transcript_path),stop_hook_active:t.stop_hook_active===!0,background_tasks:n,session_crons:r,hasClaudeTaskRegistry:n!==void 0||r!==void 0}}catch{return null}}function ge(e){try{let t=JSON.parse(e);return O(t)?{conversationId:typeof t.conversationId==`string`?t.conversationId:void 0,transcriptPath:w(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 w(e){return typeof e==`string`&&e.trim().length>0?e:null}function T(e){return typeof e==`string`&&e.trim().length>0}function E(e){if(!e)return null;let t=e.trim();if(!t||t===`YES`)return null;try{let e=JSON.parse(t);return typeof e.sessionId==`string`&&e.sessionId.length>0?e.sessionId:null}catch{return null}}function D(e){return typeof e==`string`?e.startsWith(`subagent_`)||e.startsWith(`internal_`):O(e)?ae.some(t=>t in e):!1}function O(e){return typeof e==`object`&&!!e}async function k(){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 A(e){try{return await u(e,`utf8`)}catch{return null}}async function j(e){return(await M(e)).sessionMeta}async function M(e){let t=te(e,{encoding:`utf8`}),n=f({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=N(t);n&&(a||=P(n),F(n,r),I(n,i))}}finally{n.close(),t.destroy()}return{sessionMeta:a,hasPendingWork:r.size>0||Array.from(i.values()).some(z)}}function N(e){try{let t=JSON.parse(e);return O(t)?t:null}catch{return null}}function P(e){return e.type===b?e.payload??null:e.item?.type===b?e.item.payload??null:null}function F(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||!S.has(i)||(z(n.status)?t.add(r):t.delete(r))}function I(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(!O(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&&x.has(t)}function B(e,t){let n=e;for(let e of t){if(!O(n))return null;n=n[e]}return O(n)?n:null}function V(e){return typeof e==`string`&&e.length>0?e:void 0}const _e=new p(`codex-session-start-hook`).description(`Codex session-start hook: binds ${v} to the first workflow Codex session id`).action(async()=>{await le()}),ve=new p(`codex-stop-hook`).description(`Codex stop hook: reads stop-hook JSON on stdin and marks ${v} when the root session ends`).action(async()=>{await C()}),ye=new p(`claude-stop-hook`).description(`Claude Code stop hook: reads Stop hook JSON on stdin and marks ${v} when the root session is complete`).action(async()=>{await C()}),be=new p(`antigravity-stop-hook`).description(`Antigravity stop hook: reads Stop hook JSON on stdin and marks ${v} when the execution is fully idle`).action(async()=>{await ue()}),H=`list-crons`;function U(e={}){let t=e.createService??(()=>new i),n=e.exit??(e=>process.exit(e)),r=e.logError??((e,t)=>console.error(e,t)),a=e.writeStdout??(e=>process.stdout.write(e));return new p(H).description(`List cron jobs scheduled via workflow-mcp`).action(async()=>{try{let e=await t().list();a(`${JSON.stringify(e,null,2)}\n`),n(0)}catch(e){let t=new g(`Error listing cron jobs.`,`LIST_CRONS_COMMAND_FAILED`,{command:H},{cause:e});r(`${t.message} [${t.code}]`,t),n(1)}})}const xe=U(),W=`list-workflow-statuses`;function G(e){let t=Number(e);if(!Number.isInteger(t)||t<1)throw new m(`Expected a positive integer.`);return t}function Se(t={}){let n=t.createRegistry??(()=>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 p(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 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 g(`Error listing workflow statuses.`,`LIST_WORKFLOW_STATUSES_FAILED`,{command:W,workspace:e.workspace??`all`},{cause:t});i(`${n.message} [${n.code}]`,n),r(1)}})}const Ce=Se(),we=()=>{try{return c(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 Te(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 Ee(e){let t=we();if(!t)return async()=>{};let n=Te(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 De(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 Oe(e){try{await e()}catch{}}const ke=new p(`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 Ee(e.serviceName);try{let n=e.type.toLowerCase();n===`stdio`?await De(new s(r()),t):(console.error(`Unknown transport type: ${n}. Use: stdio`),process.exit(1))}catch(e){await Oe(t),console.error(`Failed to start MCP server:`,e),process.exit(1)}}),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 g(`Conflicting runner selectors.`,J,{cliAgent:e.cliAgent,command:q,runner:e.runner});return e.runner??e.cliAgent}function Ae(e,t){let n=process.argv[1];if(!n)throw new g(`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=l(process.execPath,r,{detached:!0,env:{...process.env,[Y]:`1`},stdio:`ignore`});return a.unref(),a.pid}async function je(e,t){if(process.env[Y]!==`1`)return async()=>{};let n=new ne(process.env.PROCESS_REGISTRY_PATH),r=ee(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 Me(e={}){let t=e.createService??(()=>new a),n=e.exit??(e=>process.exit(e)),r=e.launchBackgroundRun??Ae,i=e.registerBackgroundChild??je,o=e.logError??((e,t)=>console.error(e,t)),s=e.logInfo??(e=>process.stdout.write(`${e}\n`));return new p(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,a)=>{try{if(a.background){let t=r(e,a);s(`Started workflow in background${t?` (PID: ${t})`:``}`),n(0);return}let o=await i(e,a),c=t();try{let t=Q(a),r=await c.run({cliAgent:a.cliAgent,runner:t,workflowPath:e,job:a.job,inputs:Z(a.input),env:Z(a.env),secretFile:a.secretFile,dryRun:a.dryRun,continueOnError:a.continueOnError,keepWorktree:a.keepWorktree,prompt:a.prompt,name:a.name,workspace:a.workspace,skipLaunch:a.skipLaunch});await o(),n(r.exitCode)}catch(e){throw await o(),e}}catch(t){let r=t instanceof g?t:new g(`Error executing run-workflow.`,J,{background:!!a.background,command:q,workflow:e,workspace:a.workspace},{cause:t});o(`${r.message} [${r.code}]`,r),n(1)}})}const Ne=Me(),$=`schedule-cron`;function Pe(e={}){let n=e.createService??(()=>new i),r=e.exit??(e=>process.exit(e)),a=e.logError??((e,t)=>console.error(e,t)),s=e.writeStdout??(e=>process.stdout.write(`${e}\n`));return new p($).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`,o).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,i)=>{try{let a=t.parse({name:e,cwd:i.cwd??process.cwd(),cli:i.cli??`claude`,prompt:i.prompt,promptFile:i.promptFile,schedule:i.schedule,intervalMinutes:i.intervalMinutes?Number.parseInt(i.intervalMinutes,10):void 0}),o=await n().schedule(a);s(`Scheduled cron job "${o.name}" with schedule: ${o.schedule}`),s(`CLI: ${o.cli} | CWD: ${o.cwd}`),o.prompt&&s(`Prompt: ${o.prompt}`),r(0)}catch(t){let n=t instanceof g?t:new g(`Error scheduling cron job.`,`SCHEDULE_CRON_COMMAND_FAILED`,{command:$,name:e},{cause:t});a(`${n.message} [${n.code}]`,n),r(1)}})}const Fe=Pe();async function Ie(){let e=new p;e.name(`workflow-mcp`).description(`MCP server for running GitHub Actions workflows locally`).version(h),e.addCommand(xe),e.addCommand(Ce),e.addCommand(ie),e.addCommand(be),e.addCommand(ye),e.addCommand(_e),e.addCommand(ve),e.addCommand(ke),e.addCommand(Ne),e.addCommand(Fe),await e.parseAsync(process.argv)}Ie().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.1.8",
3
+ "version": "0.1.10",
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.8.4",
40
- "@agimon-ai/foundation-process-registry": "0.8.4",
41
- "@agimon-ai/foundation-validator": "0.5.4",
42
- "@agimon-ai/log-sink-mcp": "0.8.4"
39
+ "@agimon-ai/foundation-validator": "0.5.5",
40
+ "@agimon-ai/log-sink-mcp": "0.8.5",
41
+ "@agimon-ai/foundation-port-registry": "0.8.5",
42
+ "@agimon-ai/foundation-process-registry": "0.8.5"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/js-yaml": "4.0.9",