@workflow-manager/runner 0.1.0 → 0.3.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.
Files changed (40) hide show
  1. package/README.md +123 -12
  2. package/dist/adapters.d.ts +4 -0
  3. package/dist/adapters.js +11 -0
  4. package/dist/claudeCodeExecutor.d.ts +4 -0
  5. package/dist/claudeCodeExecutor.js +173 -0
  6. package/dist/cliRunRenderer.d.ts +36 -0
  7. package/dist/cliRunRenderer.js +286 -0
  8. package/dist/engine.d.ts +4 -2
  9. package/dist/engine.js +622 -44
  10. package/dist/events.d.ts +1 -1
  11. package/dist/events.js +4 -2
  12. package/dist/index.js +371 -41
  13. package/dist/manPage.d.ts +1 -0
  14. package/dist/manPage.js +147 -0
  15. package/dist/mockExecutor.d.ts +2 -2
  16. package/dist/mockExecutor.js +62 -13
  17. package/dist/opencodeExecutor.d.ts +2 -2
  18. package/dist/opencodeExecutor.js +101 -138
  19. package/dist/parser.js +98 -2
  20. package/dist/piAgentExecutor.d.ts +4 -0
  21. package/dist/piAgentExecutor.js +298 -0
  22. package/dist/remote/api.d.ts +1 -0
  23. package/dist/remote/commands.d.ts +2 -0
  24. package/dist/remote/commands.js +76 -4
  25. package/dist/runnerApi.d.ts +7 -0
  26. package/dist/runnerApi.js +221 -0
  27. package/dist/runnerSession.d.ts +62 -0
  28. package/dist/runnerSession.js +260 -0
  29. package/dist/runtimePreflight.d.ts +16 -0
  30. package/dist/runtimePreflight.js +189 -0
  31. package/dist/skillResolver.d.ts +6 -0
  32. package/dist/skillResolver.js +75 -0
  33. package/dist/types.d.ts +148 -2
  34. package/man/wfm.1 +54 -4
  35. package/package.json +28 -4
  36. package/skills/commit-discipline/SKILL.md +109 -0
  37. package/skills/doc-sync/SKILL.md +83 -0
  38. package/skills/repo-hygiene/SKILL.md +70 -0
  39. package/skills/spec-driven-development/SKILL.md +33 -0
  40. package/skills/workflow-manager-cli/SKILL.md +14 -9
@@ -0,0 +1,189 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { resolveTaskAdapter } from "./adapters.js";
4
+ import { shouldUseRealClaudeCode } from "./claudeCodeExecutor.js";
5
+ import { shouldUseRealOpencode } from "./opencodeExecutor.js";
6
+ import { DEFAULT_PI_COMMAND } from "./piAgentExecutor.js";
7
+ function asRecord(value) {
8
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
9
+ }
10
+ function isExecutable(filePath) {
11
+ try {
12
+ const stat = fs.statSync(filePath);
13
+ if (!stat.isFile()) {
14
+ return false;
15
+ }
16
+ fs.accessSync(filePath, fs.constants.X_OK);
17
+ return true;
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ }
23
+ function commandExists(command, env) {
24
+ if (command.includes("/") || command.includes("\\")) {
25
+ return isExecutable(path.resolve(command));
26
+ }
27
+ const pathValue = env.PATH ?? "";
28
+ const extensions = process.platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean) : [""];
29
+ for (const directory of pathValue.split(path.delimiter)) {
30
+ if (!directory) {
31
+ continue;
32
+ }
33
+ for (const extension of extensions) {
34
+ if (isExecutable(path.join(directory, `${command}${extension}`))) {
35
+ return true;
36
+ }
37
+ }
38
+ }
39
+ return false;
40
+ }
41
+ function piAgentCommand(step, env) {
42
+ const payload = asRecord(step.taskSpec?.payload);
43
+ if (typeof payload.command === "string" && payload.command.trim()) {
44
+ return payload.command;
45
+ }
46
+ if (env.WFM_PI_AGENT_COMMAND?.trim()) {
47
+ return env.WFM_PI_AGENT_COMMAND;
48
+ }
49
+ return DEFAULT_PI_COMMAND;
50
+ }
51
+ function requiredEnvFromModel(model) {
52
+ if (typeof model !== "string" || !model.trim()) {
53
+ return null;
54
+ }
55
+ const normalized = model.trim().toLowerCase();
56
+ if (normalized.startsWith("openrouter/")) {
57
+ return "OPENROUTER_API_KEY";
58
+ }
59
+ if (normalized.startsWith("openai/") || normalized.startsWith("gpt-") || normalized.includes("/gpt-")) {
60
+ return "OPENAI_API_KEY";
61
+ }
62
+ if (normalized.startsWith("anthropic/") || normalized.startsWith("claude-") || normalized.includes("/claude")) {
63
+ return "ANTHROPIC_API_KEY";
64
+ }
65
+ return null;
66
+ }
67
+ function explicitRequiredEnv(step) {
68
+ const payload = asRecord(step.taskSpec?.payload);
69
+ const required = new Set();
70
+ if (Array.isArray(payload.requiredEnv)) {
71
+ for (const value of payload.requiredEnv) {
72
+ if (typeof value === "string" && value.trim()) {
73
+ required.add(value.trim());
74
+ }
75
+ }
76
+ }
77
+ return [...required].sort();
78
+ }
79
+ function requiredEnvVars(step) {
80
+ const payload = asRecord(step.taskSpec?.payload);
81
+ const required = new Set(explicitRequiredEnv(step));
82
+ const fromInitModel = requiredEnvFromModel(step.taskSpec?.init?.model);
83
+ const fromPayloadModel = requiredEnvFromModel(payload.model);
84
+ if (fromInitModel)
85
+ required.add(fromInitModel);
86
+ if (fromPayloadModel)
87
+ required.add(fromPayloadModel);
88
+ return [...required].sort();
89
+ }
90
+ function runtimeRequirement(step, env) {
91
+ if (step.kind !== "task") {
92
+ return null;
93
+ }
94
+ const adapter = resolveTaskAdapter(step.taskSpec?.adapterKey);
95
+ const envVars = requiredEnvVars(step);
96
+ if (adapter === "pi-agent") {
97
+ // pi manages provider credentials in its own auth store, so only
98
+ // explicitly declared env vars are enforced for pi steps.
99
+ return { stepKey: step.key, adapter, command: piAgentCommand(step, env), envVars: explicitRequiredEnv(step) };
100
+ }
101
+ if (adapter === "opencode" && shouldUseRealOpencode(step)) {
102
+ return { stepKey: step.key, adapter, command: "opencode", envVars };
103
+ }
104
+ if (adapter === "claude-code" && shouldUseRealClaudeCode(step)) {
105
+ return { stepKey: step.key, adapter, command: "claude", envVars };
106
+ }
107
+ if (envVars.length > 0 && adapter !== "mock") {
108
+ return { stepKey: step.key, adapter, envVars };
109
+ }
110
+ return null;
111
+ }
112
+ export function validateRuntimeRequirements(definition, env = process.env) {
113
+ const errors = [];
114
+ for (const step of definition.steps) {
115
+ const requirement = runtimeRequirement(step, env);
116
+ if (!requirement) {
117
+ continue;
118
+ }
119
+ if (requirement.command && !commandExists(requirement.command, env)) {
120
+ errors.push(`Step ${requirement.stepKey} requires ${requirement.adapter} command "${requirement.command}", but it is not installed or not executable on this host`);
121
+ }
122
+ for (const envVar of requirement.envVars) {
123
+ if (!env[envVar]?.trim()) {
124
+ errors.push(`Step ${requirement.stepKey} requires ${envVar} for ${requirement.adapter} LLM access`);
125
+ }
126
+ }
127
+ }
128
+ return errors;
129
+ }
130
+ function commandCheck(key, label, command, required, env) {
131
+ const ok = commandExists(command, env);
132
+ return {
133
+ key,
134
+ label,
135
+ status: ok ? "ok" : "missing",
136
+ required,
137
+ detail: ok ? `${command} is executable` : `${command} is not installed or not executable on this host`,
138
+ };
139
+ }
140
+ function envCheck(key, label, envVar, env) {
141
+ const ok = !!env[envVar]?.trim();
142
+ return {
143
+ key,
144
+ label,
145
+ status: ok ? "ok" : "missing",
146
+ required: false,
147
+ detail: ok ? `${envVar} is set` : `${envVar} is not set`,
148
+ };
149
+ }
150
+ export function runtimeDoctorChecks(env = process.env) {
151
+ const piAgentStep = { key: "pi-agent", kind: "task", taskSpec: {} };
152
+ return [
153
+ commandCheck("pi-agent", "Pi command", piAgentCommand(piAgentStep, env), true, env),
154
+ commandCheck("opencode", "OpenCode command", "opencode", false, env),
155
+ commandCheck("claude", "Claude Code command", "claude", false, env),
156
+ envCheck("openrouter-key", "OpenRouter API key", "OPENROUTER_API_KEY", env),
157
+ envCheck("openai-key", "OpenAI API key", "OPENAI_API_KEY", env),
158
+ envCheck("anthropic-key", "Anthropic API key", "ANTHROPIC_API_KEY", env),
159
+ ];
160
+ }
161
+ export function adapterImplementationStatuses() {
162
+ return [
163
+ {
164
+ adapter: "pi-agent",
165
+ status: "real",
166
+ detail: "default host-backed adapter driving the pi coding agent CLI",
167
+ },
168
+ {
169
+ adapter: "mock",
170
+ status: "mock",
171
+ detail: "deterministic in-process simulator for tests and local authoring",
172
+ },
173
+ {
174
+ adapter: "opencode",
175
+ status: "partial",
176
+ detail: "mock-routed by default; real host smoke path only when useRealAdapter and opencodeSmokeTest are true",
177
+ },
178
+ {
179
+ adapter: "codex",
180
+ status: "mock",
181
+ detail: "currently mock-routed; real Codex executor is not implemented yet",
182
+ },
183
+ {
184
+ adapter: "claude-code",
185
+ status: "partial",
186
+ detail: "mock-routed by default; real host CLI path only when useRealAdapter is true",
187
+ },
188
+ ];
189
+ }
@@ -0,0 +1,6 @@
1
+ import type { WorkflowDefinition } from "./types.js";
2
+ export interface ResolvedSkill {
3
+ content: string;
4
+ origin: string;
5
+ }
6
+ export declare function resolveSkill(name: string, workflow: WorkflowDefinition, workflowFilePath: string): ResolvedSkill | null;
@@ -0,0 +1,75 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ function readFileSafe(filePath) {
5
+ try {
6
+ if (!fs.existsSync(filePath))
7
+ return null;
8
+ return fs.readFileSync(filePath, "utf-8");
9
+ }
10
+ catch {
11
+ return null;
12
+ }
13
+ }
14
+ function isSafeSkillName(name) {
15
+ if (!name || name.includes("..") || name.includes("/") || name.includes("\\"))
16
+ return false;
17
+ return /^[a-zA-Z0-9_.-]+$/.test(name);
18
+ }
19
+ function isAllowedLocalSkillSourcePath(source) {
20
+ if (!source || path.isAbsolute(source) || source.includes("\\") || source.includes(".."))
21
+ return false;
22
+ const normalized = path.posix.normalize(source);
23
+ const withoutDot = normalized.startsWith("./") ? normalized.slice(2) : normalized;
24
+ if (!withoutDot.startsWith("skills/"))
25
+ return false;
26
+ return withoutDot.endsWith("/SKILL.md");
27
+ }
28
+ function resolveAllowedLocalSkillSourcePath(workflowDir, source) {
29
+ if (!isAllowedLocalSkillSourcePath(source))
30
+ return null;
31
+ const sourcePath = path.resolve(workflowDir, source);
32
+ const allowedRoot = path.resolve(workflowDir, "skills");
33
+ if (!sourcePath.startsWith(`${allowedRoot}${path.sep}`))
34
+ return null;
35
+ if (path.basename(sourcePath) !== "SKILL.md")
36
+ return null;
37
+ return sourcePath;
38
+ }
39
+ function tryRead(...candidates) {
40
+ for (const candidate of candidates) {
41
+ const content = readFileSafe(candidate);
42
+ if (content && content.trim())
43
+ return content;
44
+ }
45
+ return null;
46
+ }
47
+ export function resolveSkill(name, workflow, workflowFilePath) {
48
+ const entry = workflow.skills?.[name];
49
+ if (entry?.content && entry.content.trim()) {
50
+ return { content: entry.content, origin: "embedded" };
51
+ }
52
+ if (entry?.source) {
53
+ const workflowDir = path.dirname(path.resolve(workflowFilePath));
54
+ const sourcePath = resolveAllowedLocalSkillSourcePath(workflowDir, entry.source);
55
+ if (!sourcePath)
56
+ return null;
57
+ const content = readFileSafe(sourcePath);
58
+ if (content && content.trim())
59
+ return { content, origin: "source" };
60
+ return null;
61
+ }
62
+ if (!isSafeSkillName(name))
63
+ return null;
64
+ const workflowDir = path.dirname(path.resolve(workflowFilePath));
65
+ const projectLocal = tryRead(path.join(workflowDir, "skills", name, "SKILL.md"));
66
+ if (projectLocal)
67
+ return { content: projectLocal, origin: "project-local" };
68
+ const userGlobal = tryRead(path.join(os.homedir(), ".workflow-manager", "skills", name, "SKILL.md"));
69
+ if (userGlobal)
70
+ return { content: userGlobal, origin: "user-global" };
71
+ const packaged = tryRead(path.join(workflowDir, "node_modules", "workflow-manager", "skills", name, "SKILL.md"), path.join(workflowDir, "..", "node_modules", "workflow-manager", "skills", name, "SKILL.md"));
72
+ if (packaged)
73
+ return { content: packaged, origin: "npm" };
74
+ return null;
75
+ }
package/dist/types.d.ts CHANGED
@@ -5,7 +5,7 @@ export type NodeType = "AGENT" | "HUMAN" | "SYSTEM";
5
5
  export type ExecutionStatus = "SUCCESS" | "QA_REJECTED" | "YIELD_EXTERNAL" | "FAILED";
6
6
  export type QaAction = "PROCEED" | "RETRY_CURRENT" | "ROLLBACK_PREVIOUS" | "RESTART_ALL";
7
7
  export type ValidationMode = "none" | "human" | "external";
8
- export type AdapterKey = "mock" | "opencode" | "codex" | "claude-code";
8
+ export type AdapterKey = "pi-agent" | "mock" | "opencode" | "codex" | "claude-code";
9
9
  export interface RetryPolicy {
10
10
  maxAttempts?: number;
11
11
  }
@@ -43,6 +43,17 @@ export interface StepDefinition {
43
43
  validation?: ValidationSpec;
44
44
  };
45
45
  }
46
+ export interface SkillUpstream {
47
+ repo?: string;
48
+ ref?: string;
49
+ path?: string;
50
+ }
51
+ export interface SkillEntry {
52
+ source?: string;
53
+ content?: string;
54
+ upstream?: SkillUpstream;
55
+ contentSha256?: string;
56
+ }
46
57
  export interface WorkflowDefinition {
47
58
  key: string;
48
59
  title: string;
@@ -51,6 +62,7 @@ export interface WorkflowDefinition {
51
62
  inputSchema?: Record<string, unknown>;
52
63
  outputSchema?: Record<string, unknown>;
53
64
  defaultRetryPolicy?: RetryPolicy;
65
+ skills?: Record<string, SkillEntry>;
54
66
  steps: StepDefinition[];
55
67
  }
56
68
  export interface InputEnvelope {
@@ -96,6 +108,117 @@ export interface StepRun {
96
108
  confirmed: boolean;
97
109
  output?: Record<string, unknown>;
98
110
  }
111
+ export interface ApprovalReviewItem {
112
+ stepKey: string | null;
113
+ title: string;
114
+ summary: string;
115
+ source: "current_step" | "dependency" | "approval_gate";
116
+ status?: StepRunStatus;
117
+ }
118
+ export interface ApprovalPreview {
119
+ stepLabel: string;
120
+ objective: string | null;
121
+ summary: string;
122
+ items: ApprovalReviewItem[];
123
+ }
124
+ export interface WaitingForApprovalState {
125
+ stepKey: string;
126
+ reason: string;
127
+ validation?: ValidationMode;
128
+ preview?: ApprovalPreview | null;
129
+ }
130
+ export interface StepLastExecution {
131
+ executionStatus: ExecutionStatus | null;
132
+ qaAction: QaAction | null;
133
+ feedbackReason: string | null;
134
+ }
135
+ export interface ContextSummary {
136
+ type: "none" | "string" | "object";
137
+ length?: number;
138
+ keys?: string[];
139
+ }
140
+ export interface StepConfigSummary {
141
+ model: string | null;
142
+ skills: string[];
143
+ mcps: string[];
144
+ systemPrompts: string[];
145
+ contextSummary: ContextSummary;
146
+ }
147
+ export interface StepSnapshot {
148
+ stepKey: string;
149
+ status: StepRunStatus;
150
+ attempt: number;
151
+ confirmed: boolean;
152
+ adapter: AdapterKey | "approval";
153
+ startedAt: string | null;
154
+ updatedAt: string | null;
155
+ finishedAt: string | null;
156
+ }
157
+ export interface StepDetailSnapshot extends StepSnapshot {
158
+ kind: StepKind;
159
+ objective: string | null;
160
+ dependsOn: string[];
161
+ config: StepConfigSummary;
162
+ lastExecution: StepLastExecution;
163
+ }
164
+ export interface RunSnapshot {
165
+ runId: string;
166
+ workflowKey: string;
167
+ workflowTitle: string;
168
+ status: WorkflowRunStatus;
169
+ currentStepKey: string | null;
170
+ startedAt: string | null;
171
+ updatedAt: string | null;
172
+ endedAt: string | null;
173
+ objective: string;
174
+ objectives: string[];
175
+ waitingForApproval: WaitingForApprovalState | null;
176
+ steps: StepSnapshot[];
177
+ }
178
+ export interface RunnerLogChunk {
179
+ id: string;
180
+ runId: string;
181
+ stepKey?: string;
182
+ stream: "stdout" | "stderr";
183
+ text: string;
184
+ occurredAt: string;
185
+ }
186
+ export interface RunnerSessionInfo {
187
+ sessionId: string;
188
+ pid: number;
189
+ host: string;
190
+ port: number;
191
+ baseUrl: string;
192
+ attachToken: string;
193
+ startedAt: string;
194
+ run: {
195
+ runId: string;
196
+ workflowKey: string;
197
+ workflowTitle: string;
198
+ status: WorkflowRunStatus;
199
+ };
200
+ }
201
+ export type ApprovalDecision = "approved" | "cancelled";
202
+ export interface ApprovalRequest {
203
+ stepKey: string;
204
+ reason: string;
205
+ validation?: ValidationMode;
206
+ }
207
+ export interface ApprovalDecisionPayload {
208
+ decision: ApprovalDecision;
209
+ actor?: string;
210
+ note?: string;
211
+ source?: string;
212
+ }
213
+ export interface RunnerEventEnvelope {
214
+ id: string;
215
+ sequence: number;
216
+ type: RunEvent["type"];
217
+ runId: string;
218
+ stepKey?: string;
219
+ occurredAt: string;
220
+ data: Record<string, unknown>;
221
+ }
99
222
  export interface RunResult {
100
223
  runId: string;
101
224
  status: WorkflowRunStatus;
@@ -104,17 +227,40 @@ export interface RunResult {
104
227
  events: RunEvent[];
105
228
  }
106
229
  export interface RunOptions {
230
+ runId?: string;
107
231
  objective?: string;
108
232
  input?: Record<string, unknown>;
109
233
  actor?: string;
110
234
  confirmations?: string[];
111
235
  autoConfirmAll?: boolean;
236
+ interactive?: boolean;
237
+ workflowFilePath?: string;
238
+ approvalPrompt?: (request: ApprovalRequest & {
239
+ preview?: ApprovalPreview | null;
240
+ signal?: AbortSignal;
241
+ }) => Promise<ApprovalDecisionPayload | null>;
242
+ observer?: RunObserver;
243
+ controller?: RunController;
244
+ }
245
+ export interface StepExecutionHooks {
246
+ onStarted?: (payload?: Record<string, unknown>) => void;
247
+ onStdout?: (chunk: string) => void;
248
+ onStderr?: (chunk: string) => void;
249
+ onFinished?: (payload?: Record<string, unknown>) => void;
250
+ }
251
+ export interface RunController {
252
+ waitForDecision(request: ApprovalRequest): Promise<ApprovalDecisionPayload>;
253
+ }
254
+ export interface RunObserver {
255
+ onEvent(event: RunEvent): void;
256
+ onSnapshot(snapshot: RunSnapshot, stepDetails: StepDetailSnapshot[]): void;
257
+ onLog(log: RunnerLogChunk): void;
112
258
  }
113
259
  export interface RunEvent {
114
260
  id: string;
115
261
  runId: string;
116
262
  stepRunId?: string;
117
- type: "run.created" | "run.started" | "run.waiting_for_approval" | "step.runnable" | "step.claimed" | "step.execution_started" | "step.execution_finished" | "step.waiting_for_approval" | "approval.resolved" | "step.retried" | "step.confirmed" | "run.completed" | "run.failed" | "run.cancelled";
263
+ type: "run.created" | "run.started" | "run.waiting_for_approval" | "step.runnable" | "step.claimed" | "step.execution_started" | "step.execution_finished" | "step.waiting_for_approval" | "approval.resolved" | "step.retried" | "step.confirmed" | "agent.started" | "agent.stdout" | "agent.stderr" | "agent.finished" | "run.completed" | "run.failed" | "run.cancelled";
118
264
  sequenceNumber: number;
119
265
  occurredAt: string;
120
266
  actor: string;
package/man/wfm.1 CHANGED
@@ -12,8 +12,11 @@ it with deterministic in-memory orchestration.
12
12
  Workflow files can be Markdown with YAML frontmatter or JSON.
13
13
  .SH COMMANDS
14
14
  .TP
15
- .B questions
16
- Print discovery questions used to design a workflow.
15
+ .B doctor [workflow.md|workflow.json] [--json]
16
+ Inspect host adapter setup, LLM access keys, and current adapter implementation status. When a workflow path is provided, also validate schema and runtime requirements without executing steps.
17
+ .TP
18
+ .B agent [path] [--force]
19
+ Create WFM-focused agent rules. The default path is ./AGENTS.md. Existing files are not overwritten unless --force is passed.
17
20
  .TP
18
21
  .B scaffold [path] [--format markdown|json]
19
22
  Create a starter workflow file. Format defaults to markdown unless the output
@@ -22,8 +25,17 @@ path ends in .json.
22
25
  .B validate <workflow.md|workflow.json>
23
26
  Validate workflow structure and report schema errors.
24
27
  .TP
25
- .B run <workflow.md|workflow.json> [--input input.json] [--objective text] [--confirm list] [--auto-confirm-all]
26
- Run the workflow and print a JSON result with events and per-step outputs.
28
+ .B run <workflow.md|workflow.json> [--input input.json] [--objective text] [--confirm list] [--auto-confirm-all] [--port number] [--verbose] [--json]
29
+ Run the workflow with live CLI progress and optional JSON output.
30
+ .TP
31
+ .B approve [--url value] [--token value] [--run-id value] [--step value] [--actor value] [--note text]
32
+ Approve the current waiting runner step through the local attach API.
33
+ .TP
34
+ .B resume [--url value] [--token value] [--run-id value] [--step value] [--actor value] [--note text]
35
+ Alias for approve, intended for external resume flows.
36
+ .TP
37
+ .B cancel [--url value] [--token value] [--run-id value] [--step value] [--actor value] [--note text]
38
+ Cancel the current waiting runner step through the local attach API.
27
39
  .TP
28
40
  .B auth <login|whoami|logout> [--token value]
29
41
  Manage remote registry authentication for CLI publish and pull flows.
@@ -55,6 +67,35 @@ Provide explicit confirmations for steps that require validation.
55
67
  .TP
56
68
  .B --auto-confirm-all
57
69
  Bypass confirmation gating for all steps.
70
+ .TP
71
+ .B --port <number>
72
+ Bind the local attach API to a specific port. If omitted, the OS assigns a free port on 127.0.0.1.
73
+ .TP
74
+ .B --verbose
75
+ Stream per-step agent output and execution updates to stderr while the workflow runs.
76
+ .TP
77
+ .B --json
78
+ Print the final run result as JSON on stdout while keeping live progress on stderr.
79
+ .TP
80
+ Human approval steps in an interactive terminal show an inline review prompt so they can be approved or cancelled without a separate HTTP client.
81
+ .TP
82
+ .B --url <value>
83
+ Runner attach API base URL for approve, resume, or cancel commands.
84
+ .TP
85
+ .B --token <value>
86
+ Runner attach API bearer token for approve, resume, or cancel commands.
87
+ .TP
88
+ .B --run-id <value>
89
+ Runner id to control. If omitted, the CLI reads it from /session.
90
+ .TP
91
+ .B --step <value>
92
+ Optional step key when controlling a specific waiting step.
93
+ .TP
94
+ .B --actor <value>
95
+ Actor name recorded in approval audit events.
96
+ .TP
97
+ .B --note <text>
98
+ Optional approval or cancellation note recorded in the event payload.
58
99
  .SH EXAMPLES
59
100
  .TP
60
101
  Validate markdown workflow:
@@ -80,6 +121,15 @@ Search the remote registry:
80
121
  .TP
81
122
  Run with explicit confirmations:
82
123
  .B wfm run ./example-workflow.json --confirm discover:human,qa_gate:human
124
+ .TP
125
+ Inspect host setup:
126
+ .B wfm doctor
127
+ .TP
128
+ Check a workflow before running it:
129
+ .B wfm doctor ./example-workflow.json
130
+ .TP
131
+ Create agent rules:
132
+ .B wfm agent ./AGENTS.md
83
133
  .SH FILES
84
134
  .TP
85
135
  .B man/wfm.1
package/package.json CHANGED
@@ -1,11 +1,16 @@
1
1
  {
2
2
  "name": "@workflow-manager/runner",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "CLI runner for in-memory and markdown workflow orchestration using ATEP-like envelopes",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/navio/workflow-manager.git"
8
+ },
5
9
  "type": "module",
6
10
  "main": "dist/index.js",
7
11
  "bin": {
8
- "wfm": "dist/index.js"
12
+ "wfm": "dist/index.js",
13
+ "workflow-manager": "dist/index.js"
9
14
  },
10
15
  "publishConfig": {
11
16
  "access": "public"
@@ -23,21 +28,34 @@
23
28
  "build:bin:linux": "bun build ./src/index.ts --compile --target bun-linux-x64 --outfile ./dist/wfm-linux-x64",
24
29
  "build:bin:windows": "bun build ./src/index.ts --compile --target bun-windows-x64 --outfile ./dist/wfm-windows-x64.exe",
25
30
  "build:bin:all": "bun run build:bin:macos && bun run build:bin:linux && bun run build:bin:windows",
31
+ "lint": "biome lint --error-on-warnings",
32
+ "lint:ci": "biome lint --changed --since=origin/main --error-on-warnings --no-errors-on-unmatched",
33
+ "lint:fix": "biome lint --write --error-on-warnings",
34
+ "lint:staged": "lint-staged --relative --concurrent false",
26
35
  "man": "man ./man/wfm.1",
27
- "test:unit": "bun test tests/parser.test.ts tests/engine.test.ts tests/mockExecutor.test.ts tests/opencodeExecutor.test.ts tests/remote.test.ts tests/run-telemetry.test.ts tests/supabase-functions.test.ts tests/supabase-ops.test.ts tests/remote-registry-app.test.ts",
36
+ "test:unit": "bun test tests/parser.test.ts tests/engine.test.ts tests/runtimePreflight.test.ts tests/mockExecutor.test.ts tests/opencodeExecutor.test.ts tests/claudeCodeExecutor.test.ts tests/piAgentExecutor.test.ts tests/skillResolver.test.ts tests/publish-bundle.test.ts tests/remote.test.ts tests/installer.test.ts tests/run-telemetry.test.ts tests/runnerApi.test.ts tests/runnerCli.test.ts tests/supabase-functions.test.ts tests/supabase-ops.test.ts tests/remote-registry-app.test.ts",
37
+ "test:release": "bun test tests/parser.test.ts tests/engine.test.ts tests/runtimePreflight.test.ts tests/mockExecutor.test.ts tests/opencodeExecutor.test.ts tests/claudeCodeExecutor.test.ts tests/piAgentExecutor.test.ts tests/skillResolver.test.ts tests/publish-bundle.test.ts tests/remote.test.ts tests/run-telemetry.test.ts tests/runnerApi.test.ts tests/runnerCli.test.ts tests/supabase-functions.test.ts tests/supabase-ops.test.ts tests/remote-registry-app.test.ts tests/story-workflow.e2e.test.ts tests/opencode-real.e2e.test.ts",
28
38
  "test:e2e": "bun test tests/story-workflow.e2e.test.ts tests/opencode-real.e2e.test.ts",
29
39
  "test:e2e:real": "WORKFLOW_MANAGER_REAL_OPENCODE=1 bun test tests/opencode-real.e2e.test.ts",
30
40
  "test": "bun test",
31
41
  "dev": "bun run ./src/index.ts",
42
+ "netlify:build": "node ./scripts/netlify-build.mjs",
32
43
  "package:check": "npm pack --dry-run",
44
+ "prepare": "node ./scripts/install-git-hooks.mjs",
33
45
  "prepack": "bun run build",
34
46
  "supabase:start": "supabase start",
35
47
  "supabase:stop": "supabase stop",
36
48
  "supabase:status": "supabase status",
37
49
  "supabase:db:reset": "supabase db reset",
38
- "supabase:functions:deploy": "supabase functions deploy create-cli-token --project-ref whairnylpdvxxgbygbzu --use-api --no-verify-jwt && supabase functions deploy auth-whoami --project-ref whairnylpdvxxgbygbzu --use-api --no-verify-jwt && supabase functions deploy list-cli-tokens --project-ref whairnylpdvxxgbygbzu --use-api --no-verify-jwt && supabase functions deploy manage-workflow --project-ref whairnylpdvxxgbygbzu --use-api --no-verify-jwt && supabase functions deploy refresh-workflow-stats --project-ref whairnylpdvxxgbygbzu --use-api --no-verify-jwt && supabase functions deploy revoke-cli-token --project-ref whairnylpdvxxgbygbzu --use-api --no-verify-jwt && supabase functions deploy publish-workflow --project-ref whairnylpdvxxgbygbzu --use-api --no-verify-jwt && supabase functions deploy pull-workflow --project-ref whairnylpdvxxgbygbzu --use-api --no-verify-jwt && supabase functions deploy search-workflows --project-ref whairnylpdvxxgbygbzu --use-api --no-verify-jwt && supabase functions deploy workflow-analytics --project-ref whairnylpdvxxgbygbzu --use-api --no-verify-jwt && supabase functions deploy track-run-telemetry --project-ref whairnylpdvxxgbygbzu --use-api --no-verify-jwt && supabase functions deploy workflow-run-insights --project-ref whairnylpdvxxgbygbzu --use-api --no-verify-jwt",
50
+ "supabase:db:lint": "supabase db lint --local --fail-on error",
51
+ "supabase:test": "bun test tests/supabase-functions.test.ts tests/supabase-ops.test.ts",
52
+ "supabase:functions:deploy": "supabase functions deploy --project-ref whairnylpdvxxgbygbzu --use-api",
39
53
  "remote-registry:dev": "bun --cwd apps/remote-registry dev",
40
54
  "remote-registry:build": "bun --cwd apps/remote-registry build",
55
+ "remote-registry:test": "bun --cwd apps/remote-registry test:app",
56
+ "remote-registry:test:auth:local": "bun --cwd apps/remote-registry test:auth:local",
57
+ "remote-registry:test:publish:local": "bun --cwd apps/remote-registry test:publish:local",
58
+ "remote-registry:test:smoke:local": "bun --cwd apps/remote-registry test:smoke:local",
41
59
  "docs:dev": "vitepress dev doc",
42
60
  "docs:build": "vitepress build doc",
43
61
  "docs:preview": "vitepress preview doc"
@@ -57,9 +75,15 @@
57
75
  "gray-matter": "^4.0.3"
58
76
  },
59
77
  "devDependencies": {
78
+ "@biomejs/biome": "^2.4.14",
79
+ "@netlify/edge-functions": "^2.13.0",
60
80
  "@types/node": "^24.6.0",
61
81
  "bun-types": "^1.3.0",
82
+ "lint-staged": "^16.4.0",
62
83
  "typescript": "^5.9.2",
63
84
  "vitepress": "^1.5.0"
85
+ },
86
+ "lint-staged": {
87
+ "*.{js,cjs,mjs,jsx,ts,tsx,mts,cts,json,jsonc,css}": "biome lint --write --no-errors-on-unmatched"
64
88
  }
65
89
  }