rich-parallel-agents 0.1.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.
@@ -0,0 +1,4 @@
1
+ import type { CheckResult, ErrorClass } from "./types.js";
2
+ export declare function classifyFailure(results: CheckResult[], lastError?: string): ErrorClass;
3
+ export declare function shouldBlockWithoutRetry(errorClass: ErrorClass): boolean;
4
+ export declare function isFlakyClass(errorClass: ErrorClass): boolean;
@@ -0,0 +1,46 @@
1
+ const NO_RETRY = new Set([
2
+ "environment",
3
+ "permission",
4
+ "credential",
5
+ "ambiguous",
6
+ "unknown",
7
+ ]);
8
+ export function classifyFailure(results, lastError) {
9
+ const blob = [lastError, ...results.flatMap((r) => [r.name, r.command, r.stdout, r.stderr])]
10
+ .filter(Boolean)
11
+ .join("\n")
12
+ .toLowerCase();
13
+ if (results.some((r) => r.timedOut) || /timed out|timeout/.test(blob))
14
+ return "timeout";
15
+ if (/merge conflict|automatic merge failed|fix conflicts/.test(blob))
16
+ return "merge_conflict";
17
+ if (/eacces|eperm|permission denied|operation not permitted/.test(blob))
18
+ return "permission";
19
+ if (/credential|api[_ -]?key|unauthorized|401|403|eauth|auth(entication)? failed/.test(blob)) {
20
+ return "credential";
21
+ }
22
+ if (/enotfound|econnrefused|network|docker daemon|no such file|enoent|missing env|not installed/.test(blob)) {
23
+ return "environment";
24
+ }
25
+ if (/ambiguous|unclear requirement|need clarification/.test(blob))
26
+ return "ambiguous";
27
+ if (/segfault|sigabrt|sigsegv|panic:|fatal error/.test(blob))
28
+ return "crash";
29
+ if (/flaky|intermittent/.test(blob))
30
+ return "flaky";
31
+ if (/\btsc\b|typecheck|type error|ts\d{3,5}/.test(blob))
32
+ return "typecheck";
33
+ if (/\beslint\b|\blint\b|prettier/.test(blob))
34
+ return "lint";
35
+ if (/assert|expected|not to be|toequal|fail(ed)?\s+tests[/\\]|test failed/.test(blob) ||
36
+ results.some((r) => !r.passed && r.required)) {
37
+ return "assertion";
38
+ }
39
+ return "unknown";
40
+ }
41
+ export function shouldBlockWithoutRetry(errorClass) {
42
+ return NO_RETRY.has(errorClass);
43
+ }
44
+ export function isFlakyClass(errorClass) {
45
+ return errorClass === "flaky" || errorClass === "timeout";
46
+ }
@@ -0,0 +1,25 @@
1
+ import { type OrcaOpts } from "./orca.js";
2
+ import type { ResourceRecord, RunState } from "./types.js";
3
+ export type CleanOpts = {
4
+ storeDir: string;
5
+ run: RunState;
6
+ dry?: boolean;
7
+ worktrees?: boolean;
8
+ force?: boolean;
9
+ orca?: boolean;
10
+ orcaBin?: string;
11
+ orcaExec?: OrcaOpts["exec"];
12
+ };
13
+ export declare function cleanRun(opts: CleanOpts): Promise<{
14
+ expired: ResourceRecord[];
15
+ cleaned: ResourceRecord[];
16
+ skipped: ResourceRecord[];
17
+ dry: boolean;
18
+ }>;
19
+ export declare function assertOwned(resource: ResourceRecord): void;
20
+ export declare function formatCleanResult(result: {
21
+ expired: ResourceRecord[];
22
+ cleaned: ResourceRecord[];
23
+ skipped: ResourceRecord[];
24
+ dry: boolean;
25
+ }): string;
package/dist/clean.js ADDED
@@ -0,0 +1,64 @@
1
+ import { GateError } from "./errors.js";
2
+ import { removeOrcaWorktree } from "./orca.js";
3
+ import { expiredResources, liveResources, markCleaned } from "./resources.js";
4
+ import { persist } from "./store.js";
5
+ export async function cleanRun(opts) {
6
+ const { run } = opts;
7
+ const expired = expiredResources(run);
8
+ const skipped = [];
9
+ const cleaned = [];
10
+ const dryWorktrees = !opts.force;
11
+ const dry = Boolean(opts.dry);
12
+ run.status = "cleaning";
13
+ for (const resource of liveResources(run)) {
14
+ assertOwned(resource);
15
+ if (resource.kind === "worktree" && !opts.worktrees) {
16
+ skipped.push(resource);
17
+ continue;
18
+ }
19
+ const resourceDry = resource.kind === "worktree" ? dryWorktrees || dry : dry;
20
+ if (resourceDry) {
21
+ skipped.push(resource);
22
+ continue;
23
+ }
24
+ if (resource.kind === "worktree" && opts.orca) {
25
+ await removeOrcaWorktree(resource.selector, { orcaBin: opts.orcaBin, exec: opts.orcaExec });
26
+ }
27
+ const marked = markCleaned(run, resource.id);
28
+ if (marked)
29
+ cleaned.push(marked);
30
+ await persist(opts.storeDir, run, {
31
+ type: "RESOURCE_CLEANED",
32
+ taskId: resource.taskId,
33
+ payload: { id: resource.id, kind: resource.kind, selector: resource.selector },
34
+ });
35
+ }
36
+ const remaining = liveResources(run);
37
+ run.status = remaining.length === 0 ? "cleaned" : "partial";
38
+ await persist(opts.storeDir, run);
39
+ return { expired, cleaned, skipped, dry };
40
+ }
41
+ export function assertOwned(resource) {
42
+ if (resource.createdBy !== "rpa") {
43
+ throw new GateError(`refusing to clean non-RPA resource ${resource.id}`);
44
+ }
45
+ }
46
+ export function formatCleanResult(result) {
47
+ const lines = [
48
+ result.dry ? "clean dry-run" : "clean",
49
+ `cleaned ${result.cleaned.length} skipped ${result.skipped.length} expired-leases ${result.expired.length}`,
50
+ ];
51
+ if (result.expired.length) {
52
+ lines.push("expired leases (investigate, not deleted):");
53
+ for (const resource of result.expired) {
54
+ lines.push(` ${resource.kind} ${resource.selector} ${resource.leaseExpiresAt}`);
55
+ }
56
+ }
57
+ for (const resource of result.cleaned) {
58
+ lines.push(` cleaned ${resource.kind} ${resource.selector}`);
59
+ }
60
+ for (const resource of result.skipped) {
61
+ lines.push(` skipped ${resource.kind} ${resource.selector}`);
62
+ }
63
+ return lines.join("\n");
64
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function main(argv?: string[]): Promise<number>;
package/dist/cli.js ADDED
@@ -0,0 +1,449 @@
1
+ #!/usr/bin/env node
2
+ import { access } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { Command } from "commander";
5
+ import { commandAsCheck, DEFAULT_CONFIG, loadConfig } from "./config.js";
6
+ import { formatCleanResult, cleanRun } from "./clean.js";
7
+ import { GateError } from "./errors.js";
8
+ import { execCommand } from "./exec.js";
9
+ import { formatRun, formatTask, toJson } from "./format.js";
10
+ import { claimTask, refreshRunStatus, retryTask, verifyAll, verifyTask } from "./gate.js";
11
+ import { formatHistory, formatReport, loadHistory } from "./history.js";
12
+ import { integrateAccepted } from "./integrate.js";
13
+ import { startOrcaRetry } from "./orca.js";
14
+ import { startIntegrationRepair } from "./repair.js";
15
+ import { resumeRun } from "./resume.js";
16
+ import { prepareRun } from "./run.js";
17
+ import { installSkill, readSkill } from "./skill.js";
18
+ import { createRun, loadRun, persist, resolveStoreDir, StoreError } from "./store.js";
19
+ import { runChecks, verifyPassed } from "./verify.js";
20
+ export async function main(argv = process.argv) {
21
+ const program = new Command();
22
+ program
23
+ .name("rpa")
24
+ .description("Verification gate for orca workers. worker_done is a claim until verify passes.")
25
+ .version("0.1.0");
26
+ program
27
+ .command("init")
28
+ .description("Create a run and store verification config")
29
+ .option("--store <dir>", "state directory (default: $RPA_STORE or ./.rpa)")
30
+ .option("--repo <path>", "repository root", ".")
31
+ .option("--config <path>", "YAML config (verify + retry)")
32
+ .option("--id <id>", "run id")
33
+ .option("--json", "print JSON", false)
34
+ .action(async (opts) => {
35
+ const storeDir = resolveStoreDir(opts.store);
36
+ const config = await resolveConfigFile(opts.config, opts.repo ?? ".");
37
+ const run = await createRun(storeDir, {
38
+ repo: opts.repo ?? ".",
39
+ config,
40
+ id: opts.id,
41
+ });
42
+ writeOutput(opts.json, run, formatRun(run));
43
+ });
44
+ program
45
+ .command("run")
46
+ .description("Create a planned run from an objective or plan.json")
47
+ .argument("[objective...]", "run objective; numbered/bullet lines become tasks")
48
+ .option("--plan <file>", "plan.json")
49
+ .option("--planner <name>", "planner id or command")
50
+ .option("--plan-only", "write plan.json without dispatching", false)
51
+ .option("--dry", "plan only; print and stop", false)
52
+ .option("--orca", "dispatch via orca task-create/worker-start", false)
53
+ .option("--store <dir>", "state directory")
54
+ .option("--repo <path>", "repository root", ".")
55
+ .option("--config <path>", "YAML config")
56
+ .option("--id <id>", "run id")
57
+ .option("--json", "print JSON", false)
58
+ .action(async (objectiveParts, opts) => {
59
+ const objective = objectiveParts.join(" ").trim() || undefined;
60
+ const storeDir = resolveStoreDir(opts.store);
61
+ const config = await resolveConfigFile(opts.config, opts.repo ?? ".");
62
+ const run = await createRun(storeDir, {
63
+ repo: opts.repo ?? ".",
64
+ config,
65
+ id: opts.id,
66
+ objective,
67
+ planner: opts.planner,
68
+ });
69
+ try {
70
+ await prepareRun({
71
+ storeDir,
72
+ run,
73
+ objective,
74
+ planFile: opts.plan,
75
+ planner: opts.planner,
76
+ planOnly: opts.planOnly || opts.dry,
77
+ dry: opts.dry,
78
+ orca: opts.orca,
79
+ });
80
+ }
81
+ catch (err) {
82
+ run.status = "failed";
83
+ await persist(storeDir, run, {
84
+ type: "RUN_FAILED",
85
+ payload: {
86
+ phase: run.plan ? "dispatch" : "planning",
87
+ error: err instanceof Error ? err.message : String(err),
88
+ },
89
+ });
90
+ throw err;
91
+ }
92
+ writeOutput(opts.json, run, formatRun(run));
93
+ });
94
+ program
95
+ .command("claim")
96
+ .description("Record worker_done as a claim, not acceptance")
97
+ .requiredOption("--task <id>", "task id")
98
+ .requiredOption("--worktree <path>", "worker worktree")
99
+ .option("--outcome <outcome>", "worker_done outcome: succeeded|failed", "succeeded")
100
+ .option("--dispatch <id>", "orca dispatch id")
101
+ .option("--orca-task <id>", "orca task id")
102
+ .option("--agent <id>", "orca agent id")
103
+ .option("--store <dir>", "state directory")
104
+ .option("--repo <path>", "repository root", ".")
105
+ .option("--config <path>", "YAML config used if a run is created")
106
+ .option("--run <id>", "run id")
107
+ .option("--json", "print JSON", false)
108
+ .action(async (opts) => {
109
+ const outcome = parseOutcome(opts.outcome);
110
+ const storeDir = resolveStoreDir(opts.store);
111
+ const run = await loadOrCreate(storeDir, opts);
112
+ const task = await claimTask(run, {
113
+ taskId: opts.task,
114
+ worktree: opts.worktree,
115
+ outcome,
116
+ dispatchId: opts.dispatch,
117
+ orcaTaskId: opts.orcaTask,
118
+ agent: opts.agent,
119
+ });
120
+ await persist(storeDir, run, {
121
+ type: "TASK_CLAIMED",
122
+ taskId: task.id,
123
+ payload: { outcome: task.workerOutcome, dispatchId: task.dispatchId ?? null },
124
+ });
125
+ writeOutput(opts.json, { run, task }, formatTask(task));
126
+ });
127
+ program
128
+ .command("verify")
129
+ .description("Run task verify; pass becomes accepted")
130
+ .option("--task <id>", "task id (default: all claimed/retrying)")
131
+ .option("--command <cmd>", "override with a single required command")
132
+ .option("--cwd <path>", "working directory for --command without --task")
133
+ .option("--store <dir>", "state directory")
134
+ .option("--config <path>", "YAML config override")
135
+ .option("--run <id>", "run id")
136
+ .option("--json", "print JSON", false)
137
+ .action(async (opts) => {
138
+ if (opts.command && !opts.task) {
139
+ const cwd = path.resolve(opts.cwd ?? process.cwd());
140
+ const results = await runChecks([commandAsCheck(opts.command)], cwd, execCommand);
141
+ const ok = verifyPassed(results);
142
+ writeOutput(opts.json, { cwd, checks: results, passed: ok }, results
143
+ .map((check) => `${check.name} ${check.passed ? "pass" : "fail"}`)
144
+ .join("\n"));
145
+ if (!ok)
146
+ throw new GateError("verify failed");
147
+ return;
148
+ }
149
+ const storeDir = resolveStoreDir(opts.store);
150
+ const run = await loadRun(storeDir, opts.run);
151
+ const checks = opts.command
152
+ ? [commandAsCheck(opts.command)]
153
+ : opts.config
154
+ ? (await loadConfig(opts.config)).verify.task
155
+ : undefined;
156
+ const tasks = opts.task
157
+ ? [await verifyTask(run, opts.task, { exec: execCommand, checks })]
158
+ : await verifyAll(run, { exec: execCommand, checks });
159
+ if (!opts.task && tasks.length === 0) {
160
+ throw new GateError("no claimed/retrying tasks to verify");
161
+ }
162
+ for (const task of tasks) {
163
+ const last = task.attempts.at(-1);
164
+ await persist(storeDir, run, {
165
+ type: task.status === "accepted" ? "TASK_ACCEPTED" : "VERIFY_FAILED",
166
+ taskId: task.id,
167
+ attemptId: last ? String(last.attemptNo) : undefined,
168
+ payload: { status: task.status, errorClass: task.errorClass ?? null },
169
+ });
170
+ }
171
+ writeOutput(opts.json, { run, tasks }, opts.task ? formatTask(tasks[0]) : formatRun(run));
172
+ if (tasks.some((task) => task.status !== "accepted" && task.status !== "integrated")) {
173
+ throw new GateError("verify did not accept every selected task");
174
+ }
175
+ });
176
+ program
177
+ .command("retry")
178
+ .description("Mark a failed verify for retry; merge-blocked tasks must be re-claimed with a repaired SHA. Optionally start orca --retry-of")
179
+ .requiredOption("--task <id>", "task id")
180
+ .option("--orca", "call orca orchestration worker-start --retry-of", false)
181
+ .option("--store <dir>", "state directory")
182
+ .option("--run <id>", "run id")
183
+ .option("--json", "print JSON", false)
184
+ .action(async (opts) => {
185
+ const storeDir = resolveStoreDir(opts.store);
186
+ const run = await loadRun(storeDir, opts.run);
187
+ const task = retryTask(run, opts.task);
188
+ let orca;
189
+ const predecessorDispatchId = task.dispatchId;
190
+ if (opts.orca) {
191
+ task.dispatchPhase = "intent";
192
+ await persist(storeDir, run, {
193
+ type: "RETRY_REQUESTED",
194
+ taskId: task.id,
195
+ payload: {
196
+ phase: "intent",
197
+ orca: true,
198
+ predecessorDispatchId: predecessorDispatchId ?? null,
199
+ orcaTaskId: task.orcaTaskId ?? null,
200
+ },
201
+ });
202
+ try {
203
+ orca = await startOrcaRetry(task);
204
+ task.dispatchPhase = "worker-started";
205
+ }
206
+ catch (err) {
207
+ task.status = "blocked";
208
+ task.dispatchPhase = "failed";
209
+ task.errorClass = "environment";
210
+ task.lastError = `orca retry failed: ${err instanceof Error ? err.message : String(err)}`;
211
+ refreshRunStatus(run);
212
+ await persist(storeDir, run, {
213
+ type: "RETRY_REQUESTED",
214
+ taskId: task.id,
215
+ payload: {
216
+ phase: "failed",
217
+ orca: true,
218
+ predecessorDispatchId: predecessorDispatchId ?? null,
219
+ orcaTaskId: task.orcaTaskId ?? null,
220
+ error: task.lastError,
221
+ },
222
+ });
223
+ throw new GateError(task.lastError);
224
+ }
225
+ }
226
+ await persist(storeDir, run, {
227
+ type: "RETRY_REQUESTED",
228
+ taskId: task.id,
229
+ payload: {
230
+ phase: opts.orca ? "worker-started" : "requested",
231
+ orca: Boolean(opts.orca),
232
+ predecessorDispatchId: predecessorDispatchId ?? null,
233
+ dispatchId: task.dispatchId ?? null,
234
+ orcaTaskId: task.orcaTaskId ?? null,
235
+ },
236
+ });
237
+ writeOutput(opts.json, { run, task, orca }, orca ? `${formatTask(task)}\n\n${orca}` : formatTask(task));
238
+ });
239
+ program
240
+ .command("integrate")
241
+ .description("Merge accepted task SHAs only, then run integration verify")
242
+ .requiredOption("--into <path>", "integration worktree")
243
+ .option("--store <dir>", "state directory")
244
+ .option("--run <id>", "run id")
245
+ .option("--json", "print JSON", false)
246
+ .action(async (opts) => {
247
+ const storeDir = resolveStoreDir(opts.store);
248
+ const run = await loadRun(storeDir, opts.run);
249
+ await persist(storeDir, run, {
250
+ type: "INTEGRATION_STARTED",
251
+ payload: { into: opts.into },
252
+ });
253
+ await integrateAccepted(run, { into: opts.into, exec: execCommand });
254
+ const integration = run.integration;
255
+ const type = integration?.status === "passed"
256
+ ? "RUN_SUCCEEDED"
257
+ : integration?.status === "conflict"
258
+ ? "INTEGRATION_CONFLICT"
259
+ : "INTEGRATION_FAILED";
260
+ await persist(storeDir, run, {
261
+ type,
262
+ payload: { status: integration?.status ?? null, error: integration?.error ?? null },
263
+ });
264
+ writeOutput(opts.json, run, formatRun(run));
265
+ if (!integration || integration.status !== "passed") {
266
+ throw new GateError(integration?.error ?? "integration did not pass");
267
+ }
268
+ });
269
+ program
270
+ .command("repair")
271
+ .description("Open an integration_repair task from blocked merge/verify evidence")
272
+ .option("--from <ids>", "comma-separated task ids (default: all blocked)")
273
+ .option("--orca", "start an orca integration repair worker", false)
274
+ .option("--agent <id>", "agent for --orca")
275
+ .option("--store <dir>", "state directory")
276
+ .option("--run <id>", "run id")
277
+ .option("--json", "print JSON", false)
278
+ .action(async (opts) => {
279
+ const storeDir = resolveStoreDir(opts.store);
280
+ const run = await loadRun(storeDir, opts.run);
281
+ const fromIds = opts.from ? opts.from.split(",").map((id) => id.trim()).filter(Boolean) : undefined;
282
+ const result = await startIntegrationRepair(run, {
283
+ fromIds,
284
+ orca: opts.orca,
285
+ agent: opts.agent,
286
+ });
287
+ await persist(storeDir, run, {
288
+ type: "REPAIR_STARTED",
289
+ taskId: result.task.id,
290
+ payload: { from: result.task.escalatedFrom ?? [], orca: Boolean(opts.orca) },
291
+ });
292
+ writeOutput(opts.json, { run, task: result.task, orca: result.orca }, formatTask(result.task));
293
+ });
294
+ program
295
+ .command("resume")
296
+ .description("Reconcile journal/orca state and continue verify")
297
+ .argument("[run-id]", "run id (default: current)")
298
+ .option("--orca", "reconcile against orca worker-list", false)
299
+ .option("--store <dir>", "state directory")
300
+ .option("--json", "print JSON", false)
301
+ .action(async (runId, opts) => {
302
+ const storeDir = resolveStoreDir(opts.store);
303
+ const run = await loadRun(storeDir, runId ?? opts.run);
304
+ await resumeRun({ storeDir, run, exec: execCommand, orca: opts.orca });
305
+ writeOutput(opts.json, run, formatRun(run));
306
+ });
307
+ program
308
+ .command("clean")
309
+ .description("Clean RPA-owned resources only; expired leases are investigate signals")
310
+ .option("--worktrees", "include worktrees (dry unless --force)", false)
311
+ .option("--dry", "do not delete anything", false)
312
+ .option("--force", "actually remove worktrees recorded by RPA", false)
313
+ .option("--orca", "call orca worktree rm for recorded worktrees", false)
314
+ .option("--store <dir>", "state directory")
315
+ .option("--run <id>", "run id")
316
+ .option("--json", "print JSON", false)
317
+ .action(async (opts) => {
318
+ const storeDir = resolveStoreDir(opts.store);
319
+ const run = await loadRun(storeDir, opts.run);
320
+ const result = await cleanRun({
321
+ storeDir,
322
+ run,
323
+ dry: opts.dry,
324
+ worktrees: opts.worktrees,
325
+ force: opts.force,
326
+ orca: opts.orca,
327
+ });
328
+ writeOutput(opts.json, { run, ...result }, formatCleanResult(result));
329
+ });
330
+ program
331
+ .command("status")
332
+ .description("Show claim vs accepted vs integrated")
333
+ .option("--store <dir>", "state directory")
334
+ .option("--run <id>", "run id")
335
+ .option("--json", "print JSON", false)
336
+ .action(async (opts) => {
337
+ const storeDir = resolveStoreDir(opts.store);
338
+ const run = await loadRun(storeDir, opts.run);
339
+ writeOutput(opts.json, run, formatRun(run));
340
+ });
341
+ program
342
+ .command("report")
343
+ .description("Print a run report")
344
+ .option("--store <dir>", "state directory")
345
+ .option("--run <id>", "run id")
346
+ .option("--json", "print JSON", false)
347
+ .action(async (opts) => {
348
+ const storeDir = resolveStoreDir(opts.store);
349
+ const run = await loadRun(storeDir, opts.run);
350
+ writeOutput(opts.json, run, formatReport(run));
351
+ });
352
+ program
353
+ .command("skill")
354
+ .description("Print the agent skill, or install rpa onto PATH and agent skill dirs")
355
+ .option("--install", "symlink dist/cli.js to ~/.local/bin/rpa and SKILL.md into agent skill dirs", false)
356
+ .option("--json", "print JSON", false)
357
+ .action(async (opts) => {
358
+ if (opts.install) {
359
+ const result = await installSkill();
360
+ writeOutput(opts.json, result, [`installed bin ${result.bin}`, ...result.skills.map((skill) => `skill ${skill}`)].join("\n"));
361
+ return;
362
+ }
363
+ const text = await readSkill();
364
+ writeOutput(opts.json, { skill: text }, text.endsWith("\n") ? text.slice(0, -1) : text);
365
+ });
366
+ program
367
+ .command("history")
368
+ .description("List runs from the store journal")
369
+ .option("--stats", "aggregate success/retry stats", false)
370
+ .option("--by-type", "group by error class / status", false)
371
+ .option("--store <dir>", "state directory")
372
+ .option("--json", "print JSON", false)
373
+ .action(async (opts) => {
374
+ const storeDir = resolveStoreDir(opts.store);
375
+ const { rows, events } = await loadHistory(storeDir);
376
+ writeOutput(opts.json, { rows, events }, formatHistory(rows, opts.stats, opts.byType));
377
+ });
378
+ program.exitOverride();
379
+ try {
380
+ await program.parseAsync(argv);
381
+ return 0;
382
+ }
383
+ catch (err) {
384
+ if (isCommanderHelp(err))
385
+ return 0;
386
+ if (err instanceof GateError) {
387
+ process.stderr.write(`${err.message}\n`);
388
+ return err.exitCode;
389
+ }
390
+ const message = err instanceof Error ? err.message : String(err);
391
+ process.stderr.write(`${message}\n`);
392
+ return 1;
393
+ }
394
+ }
395
+ function parseOutcome(value) {
396
+ if (value === "succeeded" || value === "failed")
397
+ return value;
398
+ throw new GateError(`outcome must be succeeded or failed, got ${value}`);
399
+ }
400
+ async function resolveConfigFile(configPath, repo = ".") {
401
+ if (configPath)
402
+ return loadConfig(configPath);
403
+ const fallback = path.resolve(repo, "rpa.yaml");
404
+ try {
405
+ await access(fallback);
406
+ }
407
+ catch (err) {
408
+ if (err &&
409
+ typeof err === "object" &&
410
+ "code" in err &&
411
+ err.code === "ENOENT") {
412
+ return DEFAULT_CONFIG;
413
+ }
414
+ throw err;
415
+ }
416
+ return loadConfig(fallback);
417
+ }
418
+ async function loadOrCreate(storeDir, opts) {
419
+ try {
420
+ return await loadRun(storeDir, opts.run);
421
+ }
422
+ catch (err) {
423
+ if (opts.run || !(err instanceof StoreError) || err.kind !== "no-current")
424
+ throw err;
425
+ const config = await resolveConfigFile(opts.config, opts.repo ?? ".");
426
+ return createRun(storeDir, { repo: opts.repo ?? ".", config });
427
+ }
428
+ }
429
+ function writeOutput(json, value, text) {
430
+ if (json) {
431
+ process.stdout.write(toJson(value));
432
+ return;
433
+ }
434
+ process.stdout.write(`${text}\n`);
435
+ }
436
+ function isCommanderHelp(err) {
437
+ return Boolean(err &&
438
+ typeof err === "object" &&
439
+ "code" in err &&
440
+ (err.code === "commander.helpDisplayed" || err.code === "commander.version"));
441
+ }
442
+ const entry = process.argv[1];
443
+ if (entry &&
444
+ (entry.endsWith("/cli.ts") || entry.endsWith("/cli.js") || entry.endsWith("/rpa"))) {
445
+ main().then((code) => {
446
+ if (code !== 0)
447
+ process.exit(code);
448
+ });
449
+ }
@@ -0,0 +1,6 @@
1
+ import type { RpaConfig, VerifyCheck } from "./types.js";
2
+ export declare const DEFAULT_CONFIG: RpaConfig;
3
+ export declare function parseConfig(raw: string): RpaConfig;
4
+ export declare function loadConfig(path: string): Promise<RpaConfig>;
5
+ export declare function parseTimeout(value: unknown, fallback: number): number;
6
+ export declare function commandAsCheck(command: string): VerifyCheck;