@clemrig/factory 0.0.0-stage → 0.2.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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +187 -2
  3. package/dist/app/doctor.d.ts +18 -0
  4. package/dist/app/doctor.js +40 -0
  5. package/dist/app/doctor.js.map +1 -0
  6. package/dist/app/factory.d.ts +14 -0
  7. package/dist/app/factory.js +33 -0
  8. package/dist/app/factory.js.map +1 -0
  9. package/dist/app/init.d.ts +6 -0
  10. package/dist/app/init.js +15 -0
  11. package/dist/app/init.js.map +1 -0
  12. package/dist/app/options.d.ts +17 -0
  13. package/dist/app/options.js +78 -0
  14. package/dist/app/options.js.map +1 -0
  15. package/dist/app/run.d.ts +8 -0
  16. package/dist/app/run.js +29 -0
  17. package/dist/app/run.js.map +1 -0
  18. package/dist/app/runs.d.ts +4 -0
  19. package/dist/app/runs.js +6 -0
  20. package/dist/app/runs.js.map +1 -0
  21. package/dist/cli/application.d.ts +6 -0
  22. package/dist/cli/application.js +63 -0
  23. package/dist/cli/application.js.map +1 -0
  24. package/dist/cli/args.d.ts +12 -0
  25. package/dist/cli/args.js +45 -0
  26. package/dist/cli/args.js.map +1 -0
  27. package/dist/cli/doctor.d.ts +5 -0
  28. package/dist/cli/doctor.js +33 -0
  29. package/dist/cli/doctor.js.map +1 -0
  30. package/dist/cli/help.d.ts +1 -0
  31. package/dist/cli/help.js +17 -0
  32. package/dist/cli/help.js.map +1 -0
  33. package/dist/cli/init.d.ts +6 -0
  34. package/dist/cli/init.js +18 -0
  35. package/dist/cli/init.js.map +1 -0
  36. package/dist/cli/launcher.d.ts +5 -0
  37. package/dist/cli/launcher.js +55 -0
  38. package/dist/cli/launcher.js.map +1 -0
  39. package/dist/cli/reporter.d.ts +17 -0
  40. package/dist/cli/reporter.js +122 -0
  41. package/dist/cli/reporter.js.map +1 -0
  42. package/dist/cli/run.d.ts +5 -0
  43. package/dist/cli/run.js +52 -0
  44. package/dist/cli/run.js.map +1 -0
  45. package/dist/cli/status.d.ts +4 -0
  46. package/dist/cli/status.js +15 -0
  47. package/dist/cli/status.js.map +1 -0
  48. package/dist/cli/ui.d.ts +3 -0
  49. package/dist/cli/ui.js +25 -0
  50. package/dist/cli/ui.js.map +1 -0
  51. package/dist/index.d.ts +6 -0
  52. package/dist/index.js +7 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/main.d.ts +2 -0
  55. package/dist/main.js +4 -0
  56. package/dist/main.js.map +1 -0
  57. package/dist/system/environment.d.ts +2 -0
  58. package/dist/system/environment.js +13 -0
  59. package/dist/system/environment.js.map +1 -0
  60. package/dist/system/factory.d.ts +16 -0
  61. package/dist/system/factory.js +62 -0
  62. package/dist/system/factory.js.map +1 -0
  63. package/dist/system/templates.d.ts +8 -0
  64. package/dist/system/templates.js +30 -0
  65. package/dist/system/templates.js.map +1 -0
  66. package/package.json +42 -4
  67. package/templates/init/factory.ts +4 -0
  68. package/templates/init/gitignore +3 -0
  69. package/templates/init/tsconfig.json +13 -0
  70. package/templates/init/workflows/ask/index.ts +57 -0
  71. package/templates/init/workflows/ask/prompts/answer.md +7 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Clément CHESNEAU
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,188 @@
1
- # Temporary Holding Version
1
+ # @clemrig/factory
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Write agent workflows in TypeScript inside any repository, run them in the terminal and watch them in a local dashboard.
4
+
5
+ A workflow is an explicit graph of **CODE** stages (your code), **AGENT** stages (an agent run by [Claude Code](https://code.claude.com) or [Pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) and **GATE** stages that accept a result or send it back for correction, a bounded number of times. Workflows live in the repository's `.factory/` folder, are committed with it and run without a build step.
6
+
7
+ ## Requirements
8
+
9
+ - macOS or Linux, Node.js 24 or later, npm.
10
+ - For agents run by Claude Code: the `claude` CLI 2.1.296 or later in the `PATH`, logged in with a claude.ai subscription (`claude`, then `/login`) or given an API key (`ANTHROPIC_API_KEY`).
11
+ - For agents run by Pi: the `pi` CLI (`@earendil-works/pi-coding-agent` 1.1.0 or later) in the `PATH`, connected to the provider each Pi profile names (`pi`, then `/login`, or the provider's API key).
12
+
13
+ The factory never reads or stores credentials: each runtime uses its own connection, from its login or from an API key in the environment or `.factory/.env`. Claude Code prefers an API key to its login; Pi prefers a login stored for the provider to an API key. `factory doctor` and `factory run` show which connection each runtime uses.
14
+
15
+ ## Get started
16
+
17
+ ```sh
18
+ npm install -g @clemrig/factory
19
+ cd my-repository
20
+ factory init
21
+ factory doctor
22
+ factory run ask --question "What changed recently?"
23
+ ```
24
+
25
+ `factory init` creates `.factory/` in the current directory and installs it; it refuses to overwrite an existing `.factory/`. Commit `.factory/`: another machine only needs `npm ci --prefix .factory`.
26
+
27
+ ```
28
+ .factory/
29
+ package.json pins the exact factory version
30
+ factory.ts the repository's workflows
31
+ workflows/ask/
32
+ index.ts the example workflow
33
+ prompts/answer.md the prompt of its answer stage
34
+ tsconfig.json editor settings only
35
+ .gitignore node_modules/, runs/, .env
36
+ runs/ one folder per run (ignored)
37
+ .env optional secrets (ignored)
38
+ ```
39
+
40
+ The global `factory` command uses the nearest `.factory/` holding a `package.json`, in the current directory or a parent, and runs the factory version installed there: every machine runs the version pinned in the repository. Only `factory init` and `factory help` run with the global version. Without `.factory/` the command asks for `factory init`; without installed dependencies it gives the `npm ci --prefix .factory` command to run.
41
+
42
+ The example workflow `ask` reads the last 10 commits with `git log` (a CODE stage), asks a read-only Claude Code agent (`claude-sonnet-5-5`, effort `low`, tools `Read`, `Grep`, `Glob`) to answer the question, and checks the answer with a gate that sends it back at most 3 times. Copy its folder to start a new workflow.
43
+
44
+ ## Write a workflow
45
+
46
+ ```ts
47
+ // .factory/workflows/changelog/index.ts
48
+ import {
49
+ agent,
50
+ defineWorkflow,
51
+ END,
52
+ node,
53
+ pick,
54
+ retry,
55
+ runProcess,
56
+ schemaGate,
57
+ step,
58
+ z,
59
+ } from "@clemrig/factory";
60
+
61
+ const notes = z.strictObject({ entries: z.array(z.string().min(1)).min(1) });
62
+
63
+ export default defineWorkflow({
64
+ name: "changelog", // also the folder name: .factory/workflows/changelog/
65
+ input: z.object({
66
+ since: z.string().min(1),
67
+ limit: z.coerce.number().int().positive().optional(), // options are strings: coerce numbers
68
+ }),
69
+ initial: "log",
70
+ nodes: {
71
+ log: node(
72
+ step(async ({ dir, state }) => {
73
+ const log = await runProcess(
74
+ ["git", "log", "--oneline", `--since=${String(state.input.since)}`],
75
+ dir,
76
+ 30_000,
77
+ );
78
+ if (log.exitCode !== 0) throw new Error(log.stderr);
79
+ return { commits: log.stdout.trim().split("\n") };
80
+ }),
81
+ { ok: "write" },
82
+ ),
83
+ // AGENT: prompt in prompts/write.md, after the stage.
84
+ write: node(
85
+ agent({
86
+ profile: {
87
+ runtime: "claude-code",
88
+ model: "claude-sonnet-5-5",
89
+ thinking: "low",
90
+ tools: ["Read", "Grep", "Glob"],
91
+ // access: "read" by default; "write" allows tools that change the repository
92
+ },
93
+ schema: notes,
94
+ context: pick("input", "commits"),
95
+ }),
96
+ { ok: "check" },
97
+ ),
98
+ check: node(
99
+ schemaGate({
100
+ schema: notes,
101
+ into: "notes",
102
+ check: ({ entries }) =>
103
+ entries.length > 20 ? "Keep at most 20 entries" : undefined,
104
+ }),
105
+ { ok: END, invalid: retry("write", 3) },
106
+ ),
107
+ },
108
+ // Optional: without it, the result shows the run's validated data as JSON.
109
+ presentResult: (state) =>
110
+ (state.data.notes as { entries: string[] }).entries.map((entry) => `- ${entry}`),
111
+ });
112
+ ```
113
+
114
+ ```ts
115
+ // .factory/factory.ts
116
+ import ask from "./workflows/ask/index.ts";
117
+ import changelog from "./workflows/changelog/index.ts";
118
+
119
+ export default [ask, changelog];
120
+ ```
121
+
122
+ Workflows are TypeScript run directly by Node: use erasable syntax only (no `enum`, no parameter properties) and relative imports with their `.ts` extension. Import only from `@clemrig/factory`, which also exports `z` (Zod), so that workflows, the factory and its engine share one Zod instance.
123
+
124
+ **Graph.** Each stage declares its outcomes and the node routes every outcome to a stage, to `END` or to `retry(stage, max)`. A stage receives `{ state, agent, log, dir }`: the run (read-only), the agent runtime, the run journal and the repository directory. Stages and agents work in the repository (the folder that contains `.factory/`), which is also the process's working directory during a run, wherever the command was started.
125
+
126
+ | Primitive | Use |
127
+ |---|---|
128
+ | `step(run)`, `code(outcomes, run)` | CODE stage: returns data (merged into `state.data`) or a named outcome |
129
+ | `agent({ profile, schema, context, prompt?, session? })` | AGENT stage: one structured request run with its profile; its output waits in `state.output` for a gate |
130
+ | `gate(outcomes, run)`, `schemaGate({ schema, into, check? })` | GATE stage: `schemaGate` validates the agent output, runs `check`, then writes it into `state.data[into]` or sends a correction |
131
+ | `retry(stage, max)` | correction route, taken at most `max` times (1–100) per run, with the gate's reason as `feedback` for the next agent |
132
+ | `pick(...keys)` | agent context from `input` and data keys |
133
+ | `runProcess(argv, cwd, timeoutMs)` | runs a command without a shell; Ctrl-C stops it |
134
+
135
+ `defineWorkflow` refuses, when the workflow is loaded: a `.` in a stage or outcome name (a correction counter is named `<stage>.<outcome>`), an unrouted outcome, an unknown target, a retry maximum outside 1–100, a loop whose edges include no `retry` (with the loop's path), an invalid profile, a write tool in a read-only profile, a prompt or session name that cannot name a file, a `maxCostUsd` that is not a positive number, and an input that is not a `z.object`. Loading `factory.ts` also refuses an input whose name cannot be an option (`json`, `help`, `max-cost-usd`, or other than letters, digits, `-` and `_` after a letter). Every run is therefore bounded.
136
+
137
+ **Inputs.** Each field of `input` becomes a `--<name>` option of `factory run`; an option given without a value is `true` and only a boolean field accepts it: `z.boolean()`, possibly with `.optional()`, `.nullable()` or `.default(…)` (a transformed boolean or one with `.catch` takes no option alone). A missing, invalid or unknown option is refused with the list of problems, before any run is created. A workflow without `input` accepts no input option.
138
+
139
+ **Budget.** `maxCostUsd` (optional) fails a run once the cost its agents' runtimes announced exceeds it; `factory run … --max-cost-usd 2` sets it for one run, instead of the workflow's. It is checked before and after each agent call, failed or not, on the runtime's figures: a running call is not stopped and can go past it; once it is exceeded, no further call starts and the run fails at its next stage boundary, even if workflow code catches the error. A call that announced no cost fails a run that has a budget, since the budget can no longer be checked. With a subscription, the cost is an estimate.
140
+
141
+ **Profiles.** Each agent stage declares its complete profile; no machine setting changes it. A profile is a plain object: share one between stages with a constant, typed with `satisfies AgentProfile` (`AgentProfile` is exported by `@clemrig/factory`).
142
+
143
+ | Field | Claude Code (`runtime: "claude-code"`) | Pi (`runtime: "pi"`) |
144
+ |---|---|---|
145
+ | `access` | `"read"` (default) or `"write"` | `"read"` (default) or `"write"` |
146
+ | `provider` | — | the Pi provider, required (`openai`, `openai-codex`…) |
147
+ | `model` | model name or alias | model name |
148
+ | `thinking` | effort: `low` … `max` | `off` … `max` |
149
+ | `tools` | exact names (`Read`, `Grep`, `Bash`…) and `mcp__<server>__<tool>` | exact names (`read`, `bash`…); other names need an extension |
150
+ | other | `mcpServers: { name: { command, args? } }` | `skills`, `extensions` (paths) |
151
+ | `timeoutMs` | default 15 minutes | default 15 minutes |
152
+
153
+ A read-only profile may use only `Read`, `Grep`, `Glob`, `WebFetch`, `WebSearch` and declared MCP tools on Claude Code, and no `bash`, `powershell`, `edit` or `write` on Pi. Relative skill, extension and MCP command paths are relative to `.factory/`; a bare command is looked up in the `PATH`. Each Claude Code session holds only the profile's tools and MCP servers, without the operator's settings, plugins, skills or memory: a session with another tool, MCP server or plugin is refused, and another customization (skills, commands, output style, memory) is journaled as a warning. Pi runs without the machine's extensions, skills, context files and MCP servers. Runtimes are not sandboxes: listed tools act with your rights.
154
+
155
+ **Prompts.** An agent's prompt is `.factory/workflows/<workflow>/prompts/<name>.md`, where the name is the stage's unless `agent({ prompt })` names another. It is appended to the runtime's system prompt; the request (context and output JSON Schema) follows on standard input. Prompts are read when a run starts: an edit applies to the next run, without building anything.
156
+
157
+ **Sessions.** An agent stage keeps one conversation per run: a correction sent back to it resumes the conversation, with the gate's feedback. `session: "name"` shares a conversation between stages of the same runtime (sharing one across runtimes is refused); `session: (state) => name` opens one per value. A run's stage and session names form one namespace per runtime: a name returned by a function joins the conversation of that name on the same runtime. Session names start and end with a letter or digit.
158
+
159
+ ## Run and observe
160
+
161
+ ```sh
162
+ factory run changelog --since 2026-10-01 [--json]
163
+ factory status [RUN_ID] [--json]
164
+ factory ui [--port 4321]
165
+ factory doctor [--json]
166
+ ```
167
+
168
+ Before creating a run, `factory run` validates the input, reads the prompts and checks from the repository, where the agents will work, for each runtime the workflow uses, that its executable is in the `PATH`, at least the supported version, and connected; a missing piece fails the command with the stage, runtime and reason. The terminal then shows each runtime's version and connection. A model the account cannot use fails the run at its first call, with the runtime's message.
169
+
170
+ The run executes in the foreground. The terminal shows each stage's start and end, decisions and corrections with their counter (`[check.invalid 1/3]`), journal messages and, for an agent, the start and end of its call, each finished tool with its duration and isolation warnings; then the result, the duration, the agents' tokens and estimated cost, and the runtimes used. `--json` writes every event, then the result, as JSON lines on standard output. Exit codes: 0 completed, 1 error or failed run.
171
+
172
+ **Ctrl-C** stops the run's child processes and ends the run as `failed` with its cause before its next stage, or instead of completing; a second Ctrl-C kills the remaining child processes and quits at once (exit code 130, the run may then stay `running`). A failed or interrupted run is not resumed: start a new one.
173
+
174
+ `factory status` lists the repository's runs or shows one; while `factory.ts` cannot load, `status` and `ui` say so and still show the runs with the default rendering. `factory ui` serves a read-only dashboard of the repository's runs on `127.0.0.1` (port chosen by the system unless `--port` sets it, URL printed; Ctrl-C stops it): the runs with workflow, status, current stage, duration, estimated cost and last update, and for a run its runtimes, a timeline with one block per stage (status, duration, the agent's tools and tokens as they come, journal, decision), the correction counters and the result. It follows a running run as it goes. To watch a run on another machine, forward the same port through SSH: `ssh -L 4321:127.0.0.1:4321 machine`, run `factory ui --port 4321` there, and open `http://localhost:4321/`. `factory doctor` checks Node, loads the workflows, reads their prompts, lists each workflow's options and checks each runtime's executable, version and connection, without any provider request.
175
+
176
+ ## Data
177
+
178
+ Each run keeps `state.json` and `events.jsonl` in `.factory/runs/<RUN_ID>/`, plus its Pi sessions and Claude Code session markers; Claude Code keeps its transcripts in its own storage (`~/.claude/projects/…`). Runs hold inputs, agent outputs and the journal: they stay private and are ignored by Git. `.factory/.env` is loaded automatically, without overriding variables already set in the shell; runtimes, MCP servers and commands inherit the environment. `factory run` executes the code of `.factory/`: trust it as you trust `npm run`. The dashboard shows the same data to any local process that can reach `127.0.0.1`.
179
+
180
+ Tokens and costs are the figures each runtime announces, never recomputed: Claude Code announces at the end of a call its session's total, earlier calls included, and its tokens during a call are provisional; Pi announces each response with its cost. A run's total is the sum of its sessions; a call without figures makes it partial. The estimated cost is the runtime's public-price estimate, not a bill.
181
+
182
+ ## Limits
183
+
184
+ Not in this version: resuming a run, detached runs, logs `--follow` and stop, local settings or profile overrides, actions or graph drawing in the dashboard, runs of several repositories, `factory new`, a shared stage library, a test harness for workflows, CI, cron or Git hook triggers, notifications, Windows.
185
+
186
+ ## License
187
+
188
+ MIT
@@ -0,0 +1,18 @@
1
+ import { type AgentProblem, type Runtime, type RuntimeStatus, type Workflow } from "@clemrig/flow";
2
+ export type { RuntimeStatus } from "@clemrig/flow";
3
+ /** What `factory doctor` found, without any provider request. */
4
+ export interface Diagnosis {
5
+ ok: boolean;
6
+ node: RuntimeStatus;
7
+ factory: RuntimeStatus;
8
+ /** Each loaded workflow, with the reason its prompts cannot be read. */
9
+ workflows: {
10
+ workflow: Workflow;
11
+ error?: string;
12
+ }[];
13
+ runtimes: Partial<Record<Runtime, RuntimeStatus>>;
14
+ /** Missing runtimes, by `<workflow>.<stage>`. */
15
+ problems: AgentProblem[];
16
+ }
17
+ /** Node, the factory and its workflows' prompts, then each runtime in use. */
18
+ export declare function diagnose(factoryDir: string): Promise<Diagnosis>;
@@ -0,0 +1,40 @@
1
+ import { inspectNode, inspectRuntimes, } from "@clemrig/flow";
2
+ import { loadLocalEnvironment } from "../system/environment.js";
3
+ import { emptyFactory, loadPrompts } from "../system/factory.js";
4
+ import { loadWorkflows } from "./factory.js";
5
+ const message = (error) => error instanceof Error ? error.message : String(error);
6
+ /** Node, the factory and its workflows' prompts, then each runtime in use. */
7
+ export async function diagnose(factoryDir) {
8
+ loadLocalEnvironment(factoryDir);
9
+ const node = inspectNode();
10
+ let factory;
11
+ let loaded;
12
+ try {
13
+ factory = await loadWorkflows(factoryDir);
14
+ loaded = { ok: true, detail: `Factory ${factoryDir}` };
15
+ }
16
+ catch (error) {
17
+ loaded = { ok: false, detail: `Factory: ${message(error)}` };
18
+ }
19
+ const workflows = [];
20
+ const agents = {};
21
+ for (const workflow of factory?.workflows.values() ?? []) {
22
+ try {
23
+ await loadPrompts(factoryDir, workflow);
24
+ workflows.push({ workflow });
25
+ }
26
+ catch (error) {
27
+ workflows.push({ workflow, error: message(error) });
28
+ }
29
+ for (const [stage, declared] of Object.entries(workflow.agents))
30
+ agents[`${workflow.name}.${stage}`] = declared;
31
+ }
32
+ // Where the agents will work: the repository.
33
+ const { runtimes, problems } = await inspectRuntimes(agents, factory?.dir ?? emptyFactory(factoryDir).dir);
34
+ const ok = node.ok &&
35
+ loaded.ok &&
36
+ workflows.every(({ error }) => error === undefined) &&
37
+ problems.length === 0;
38
+ return { ok, node, factory: loaded, workflows, runtimes, problems };
39
+ }
40
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/app/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,WAAW,EACX,eAAe,GAIhB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,YAAY,EAAgB,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAgB7C,MAAM,OAAO,GAAG,CAAC,KAAc,EAAE,EAAE,CACjC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAEzD,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,UAAkB;IAC/C,oBAAoB,CAAC,UAAU,CAAC,CAAC;IACjC,MAAM,IAAI,GAAG,WAAW,EAAE,CAAC;IAC3B,IAAI,OAA4B,CAAC;IACjC,IAAI,MAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,aAAa,CAAC,UAAU,CAAC,CAAC;QAC1C,MAAM,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,UAAU,EAAE,EAAE,CAAC;IACzD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;IAC/D,CAAC;IACD,MAAM,SAAS,GAA2B,EAAE,CAAC;IAC7C,MAAM,MAAM,GAA+B,EAAE,CAAC;IAC9C,KAAK,MAAM,QAAQ,IAAI,OAAO,EAAE,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC;QACzD,IAAI,CAAC;YACH,MAAM,WAAW,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;YACxC,SAAS,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,SAAS,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,KAAK,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC7D,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,IAAI,KAAK,EAAE,CAAC,GAAG,QAAQ,CAAC;IACnD,CAAC;IACD,8CAA8C;IAC9C,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,MAAM,eAAe,CAClD,MAAM,EACN,OAAO,EAAE,GAAG,IAAI,YAAY,CAAC,UAAU,CAAC,CAAC,GAAG,CAC7C,CAAC;IACF,MAAM,EAAE,GACN,IAAI,CAAC,EAAE;QACP,MAAM,CAAC,EAAE;QACT,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC;QACnD,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC;IACxB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AACtE,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { type Factory } from "../system/factory.js";
2
+ export type { Factory } from "../system/factory.js";
3
+ /** The repository's workflows, each of whose inputs can be given as options of `factory run`. */
4
+ export declare function loadWorkflows(factoryDir: string): Promise<Factory>;
5
+ /** Loads `.factory/.env` without overriding the shell, then the repository's workflows. */
6
+ export declare function openFactory(factoryDir: string): Promise<Factory>;
7
+ /**
8
+ * The factory to read runs with: while `factory.ts` cannot load, its runs stay
9
+ * readable with the default rendering, and `error` says why.
10
+ */
11
+ export declare function openFactoryForReading(factoryDir: string): Promise<{
12
+ factory: Factory;
13
+ error?: string;
14
+ }>;
@@ -0,0 +1,33 @@
1
+ import { loadLocalEnvironment } from "../system/environment.js";
2
+ import { emptyFactory, loadFactory } from "../system/factory.js";
3
+ import { optionProblems } from "./options.js";
4
+ const message = (error) => error instanceof Error ? error.message : String(error);
5
+ /** The repository's workflows, each of whose inputs can be given as options of `factory run`. */
6
+ export async function loadWorkflows(factoryDir) {
7
+ const factory = await loadFactory(factoryDir);
8
+ for (const workflow of factory.workflows.values()) {
9
+ const problems = optionProblems(workflow);
10
+ if (problems.length)
11
+ throw new Error(`Invalid workflow ${workflow.name}: ${problems.join("; ")}`);
12
+ }
13
+ return factory;
14
+ }
15
+ /** Loads `.factory/.env` without overriding the shell, then the repository's workflows. */
16
+ export async function openFactory(factoryDir) {
17
+ loadLocalEnvironment(factoryDir);
18
+ return loadWorkflows(factoryDir);
19
+ }
20
+ /**
21
+ * The factory to read runs with: while `factory.ts` cannot load, its runs stay
22
+ * readable with the default rendering, and `error` says why.
23
+ */
24
+ export async function openFactoryForReading(factoryDir) {
25
+ loadLocalEnvironment(factoryDir);
26
+ try {
27
+ return { factory: await loadWorkflows(factoryDir) };
28
+ }
29
+ catch (error) {
30
+ return { factory: emptyFactory(factoryDir), error: message(error) };
31
+ }
32
+ }
33
+ //# sourceMappingURL=factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/app/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,YAAY,EAAgB,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAI9C,MAAM,OAAO,GAAG,CAAC,KAAc,EAAE,EAAE,CACjC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAEzD,iGAAiG;AACjG,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,UAAkB;IACpD,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,UAAU,CAAC,CAAC;IAC9C,KAAK,MAAM,QAAQ,IAAI,OAAO,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC;QAClD,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;QAC1C,IAAI,QAAQ,CAAC,MAAM;YACjB,MAAM,IAAI,KAAK,CACb,oBAAoB,QAAQ,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC5D,CAAC;IACN,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,2FAA2F;AAC3F,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,UAAkB;IAClD,oBAAoB,CAAC,UAAU,CAAC,CAAC;IACjC,OAAO,aAAa,CAAC,UAAU,CAAC,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,UAAkB;IAElB,oBAAoB,CAAC,UAAU,CAAC,CAAC;IACjC,IAAI,CAAC;QACH,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC,UAAU,CAAC,EAAE,CAAC;IACtD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;IACtE,CAAC;AACH,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { createFromTemplates as createFactory } from "../system/templates.js";
2
+ /** Installs `.factory/`'s dependencies; `output` explains a failure. */
3
+ export declare function installFactory(factoryDir: string): Promise<{
4
+ ok: boolean;
5
+ output: string;
6
+ }>;
@@ -0,0 +1,15 @@
1
+ import { runProcess } from "@clemrig/flow";
2
+ export { createFromTemplates as createFactory } from "../system/templates.js";
3
+ /** Installs `.factory/`'s dependencies; `output` explains a failure. */
4
+ export async function installFactory(factoryDir) {
5
+ const install = await runProcess(["npm", "install", "--no-audit", "--no-fund"], factoryDir, 600_000).catch((error) => ({
6
+ exitCode: 1,
7
+ stdout: "",
8
+ stderr: error instanceof Error ? error.message : String(error),
9
+ }));
10
+ return {
11
+ ok: install.exitCode === 0,
12
+ output: install.stderr || install.stdout,
13
+ };
14
+ }
15
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/app/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C,OAAO,EAAE,mBAAmB,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE9E,wEAAwE;AACxE,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,UAAkB;IAElB,MAAM,OAAO,GAAG,MAAM,UAAU,CAC9B,CAAC,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,CAAC,EAC7C,UAAU,EACV,OAAO,CACR,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,CAAC;QAC3B,QAAQ,EAAE,CAAC;QACX,MAAM,EAAE,EAAE;QACV,MAAM,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;KAC/D,CAAC,CAAC,CAAC;IACJ,OAAO;QACL,EAAE,EAAE,OAAO,CAAC,QAAQ,KAAK,CAAC;QAC1B,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM;KACzC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,17 @@
1
+ import { type Workflow, type z } from "@clemrig/flow";
2
+ /** What an option may be called, after its `--`. */
3
+ export declare const OPTION_NAME: RegExp;
4
+ /** Why some inputs of the workflow cannot be given as `--<name>` options of `factory run`. */
5
+ export declare function optionProblems(workflow: Pick<Workflow, "input">): string[];
6
+ /** Whether a field takes an option given alone: a boolean, possibly optional or defaulted. */
7
+ export declare function isFlag(schema: z.ZodType): boolean;
8
+ /**
9
+ * The input that `factory run` options give the workflow. An option given
10
+ * without a value is `true`, accepted only by a boolean field: otherwise it is
11
+ * refused with every other problem, found by validating the input once.
12
+ */
13
+ export declare function inputFromOptions(workflow: Pick<Workflow, "name" | "input">, options: Record<string, string | boolean>): Record<string, string | boolean>;
14
+ /** A refused input of `factory run`, worded as options; any other error as it is. */
15
+ export declare function asOptionsError(workflow: Pick<Workflow, "name">, options: Record<string, string | boolean>, error: unknown): unknown;
16
+ /** `--max-cost-usd`: the run's budget instead of the workflow's. */
17
+ export declare function parseBudget(value: string | boolean): number;
@@ -0,0 +1,78 @@
1
+ import { InvalidInputError, parseInput, } from "@clemrig/flow";
2
+ /** Options of every `factory run`: no input may take their names. */
3
+ const GLOBAL_OPTIONS = ["json", "help", "max-cost-usd"];
4
+ /** What an option may be called, after its `--`. */
5
+ export const OPTION_NAME = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
6
+ /** Why some inputs of the workflow cannot be given as `--<name>` options of `factory run`. */
7
+ export function optionProblems(workflow) {
8
+ return Object.keys(workflow.input?.shape ?? {}).flatMap((name) => GLOBAL_OPTIONS.includes(name)
9
+ ? [`input ${name} conflicts with the global --${name} option`]
10
+ : OPTION_NAME.test(name)
11
+ ? []
12
+ : [`input ${name} is not a valid option name`]);
13
+ }
14
+ /** Whether a field takes an option given alone: a boolean, possibly optional or defaulted. */
15
+ export function isFlag(schema) {
16
+ let def = schema.def;
17
+ while (def.innerType &&
18
+ ["optional", "nullable", "default"].includes(def.type))
19
+ def = def.innerType.def;
20
+ return def.type === "boolean";
21
+ }
22
+ /** Options given without a value to a field that is not a boolean: they have no value to give. */
23
+ function valueless(workflow, options) {
24
+ const shape = workflow.input?.shape ?? {};
25
+ return Object.keys(options).filter((name) => {
26
+ const field = Object.hasOwn(shape, name) ? shape[name] : undefined;
27
+ return options[name] === true && field !== undefined && !isFlag(field);
28
+ });
29
+ }
30
+ /** Every problem of `factory run` options, worded as options. */
31
+ function optionsError(workflow, options, missingValues, problems) {
32
+ const found = missingValues.map((name) => `--${name}: missing value`);
33
+ for (const { kind, field, message } of problems) {
34
+ const name = field.split(".")[0] ?? "";
35
+ if (kind === "unknown")
36
+ found.push(`unknown option --${field}`);
37
+ else if (!missingValues.includes(name))
38
+ found.push(Object.hasOwn(options, name)
39
+ ? `--${name}: ${message}`
40
+ : `missing option --${name}`);
41
+ }
42
+ return new Error(`Invalid input for ${workflow.name}: ${found.join("; ")}`);
43
+ }
44
+ /**
45
+ * The input that `factory run` options give the workflow. An option given
46
+ * without a value is `true`, accepted only by a boolean field: otherwise it is
47
+ * refused with every other problem, found by validating the input once.
48
+ */
49
+ export function inputFromOptions(workflow, options) {
50
+ const missingValues = valueless(workflow, options);
51
+ const input = Object.fromEntries(Object.entries(options).filter(([name]) => !missingValues.includes(name)));
52
+ if (!missingValues.length)
53
+ return input;
54
+ let problems = [];
55
+ try {
56
+ parseInput(workflow, input);
57
+ }
58
+ catch (error) {
59
+ if (!(error instanceof InvalidInputError))
60
+ throw error;
61
+ problems = error.problems;
62
+ }
63
+ throw optionsError(workflow, options, missingValues, problems);
64
+ }
65
+ /** A refused input of `factory run`, worded as options; any other error as it is. */
66
+ export function asOptionsError(workflow, options, error) {
67
+ return error instanceof InvalidInputError
68
+ ? optionsError(workflow, options, [], error.problems)
69
+ : error;
70
+ }
71
+ /** `--max-cost-usd`: the run's budget instead of the workflow's. */
72
+ export function parseBudget(value) {
73
+ const budget = Number(value);
74
+ if (typeof value !== "string" || !Number.isFinite(budget) || budget <= 0)
75
+ throw new Error(`Invalid --max-cost-usd ${String(value)}: expected a positive number of USD`);
76
+ return budget;
77
+ }
78
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/app/options.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { type RunHooks, type StartedRun } from "@clemrig/flow";
2
+ import { type Factory } from "../system/factory.js";
3
+ /**
4
+ * Starts a workflow of the factory from `factory run` options, with its prompts
5
+ * from `.factory/`; the run works in the repository that contains `.factory/`
6
+ * and is kept in its `runs/`.
7
+ */
8
+ export declare function startRun(factory: Factory, name: string, options: Record<string, string | boolean>, hooks: RunHooks): Promise<StartedRun>;
@@ -0,0 +1,29 @@
1
+ import { startRun as start, } from "@clemrig/flow";
2
+ import { loadPrompts, workflowOf } from "../system/factory.js";
3
+ import { asOptionsError, inputFromOptions, parseBudget } from "./options.js";
4
+ /**
5
+ * Starts a workflow of the factory from `factory run` options, with its prompts
6
+ * from `.factory/`; the run works in the repository that contains `.factory/`
7
+ * and is kept in its `runs/`.
8
+ */
9
+ export async function startRun(factory, name, options, hooks) {
10
+ const workflow = workflowOf(factory, name);
11
+ const { "max-cost-usd": budget, ...given } = options;
12
+ const maxCostUsd = budget === undefined ? undefined : parseBudget(budget);
13
+ const input = inputFromOptions(workflow, given);
14
+ try {
15
+ return await start({
16
+ workflow,
17
+ input,
18
+ maxCostUsd,
19
+ prompts: () => loadPrompts(factory.factoryDir, workflow),
20
+ dir: factory.dir,
21
+ configDir: factory.factoryDir,
22
+ runsDir: factory.runsDir,
23
+ }, hooks);
24
+ }
25
+ catch (error) {
26
+ throw asOptionsError(workflow, given, error);
27
+ }
28
+ }
29
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/app/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,QAAQ,IAAI,KAAK,GAClB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAgB,WAAW,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE7E;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,OAAgB,EAChB,IAAY,EACZ,OAAyC,EACzC,KAAe;IAEf,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC3C,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,GAAG,OAAO,CAAC;IACrD,MAAM,UAAU,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1E,MAAM,KAAK,GAAG,gBAAgB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAChD,IAAI,CAAC;QACH,OAAO,MAAM,KAAK,CAChB;YACE,QAAQ;YACR,KAAK;YACL,UAAU;YACV,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC;YACxD,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,SAAS,EAAE,OAAO,CAAC,UAAU;YAC7B,OAAO,EAAE,OAAO,CAAC,OAAO;SACzB,EACD,KAAK,CACN,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,cAAc,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { RunState } from "@clemrig/flow";
2
+ import type { Factory } from "../system/factory.js";
3
+ /** A run's result lines: its workflow's rendering, or its validated data when the workflow left `factory.ts`. */
4
+ export declare function presentResult(factory: Factory, state: Readonly<RunState>): string[];
@@ -0,0 +1,6 @@
1
+ import * as flow from "@clemrig/flow";
2
+ /** A run's result lines: its workflow's rendering, or its validated data when the workflow left `factory.ts`. */
3
+ export function presentResult(factory, state) {
4
+ return flow.presentResult(factory.workflows.get(state.workflow), state);
5
+ }
6
+ //# sourceMappingURL=runs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runs.js","sourceRoot":"","sources":["../../src/app/runs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AAItC,iHAAiH;AACjH,MAAM,UAAU,aAAa,CAC3B,OAAgB,EAChB,KAAyB;IAEzB,OAAO,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC;AAC1E,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The command run by the factory installed in `.factory/`: the global `factory`
3
+ * calls it with the arguments it received and the `.factory/` it found. This
4
+ * contract only changes with a major version.
5
+ */
6
+ export declare function main(argv: string[], directory: string): Promise<number>;
@@ -0,0 +1,63 @@
1
+ import { resolve } from "node:path";
2
+ import { openFactory, openFactoryForReading } from "../app/factory.js";
3
+ import { presentResult } from "../app/runs.js";
4
+ import { noOptions, parseArgs } from "./args.js";
5
+ import { doctor } from "./doctor.js";
6
+ import { help } from "./help.js";
7
+ import { TerminalReporter } from "./reporter.js";
8
+ import { run } from "./run.js";
9
+ import { status } from "./status.js";
10
+ import { ui } from "./ui.js";
11
+ /** `--port`: a fixed port, so that an SSH tunnel can forward the same one. */
12
+ function parsePort(value) {
13
+ const port = Number(value);
14
+ if (value === true || !/^\d+$/.test(value) || port < 1 || port > 65535)
15
+ throw new Error(`Invalid --port ${value === true ? "" : value}: expected a port from 1 to 65535`);
16
+ return port;
17
+ }
18
+ /**
19
+ * The command run by the factory installed in `.factory/`: the global `factory`
20
+ * calls it with the arguments it received and the `.factory/` it found. This
21
+ * contract only changes with a major version.
22
+ */
23
+ export async function main(argv, directory) {
24
+ // A run changes the working directory: every path must already be absolute.
25
+ const factoryDir = resolve(directory);
26
+ try {
27
+ const args = parseArgs(argv);
28
+ if (args.help || args.command === "help") {
29
+ process.stdout.write(help);
30
+ return 0;
31
+ }
32
+ if (args.command === "doctor") {
33
+ noOptions(args);
34
+ return await doctor(factoryDir, args.json);
35
+ }
36
+ if (args.command === "run") {
37
+ const factory = await openFactory(factoryDir);
38
+ const reporter = new TerminalReporter((state) => presentResult(factory, state), args.json);
39
+ return await run(factory, args, reporter);
40
+ }
41
+ if (!["status", "ui"].includes(args.command))
42
+ throw new Error(`Unknown command ${args.command}; see factory help`);
43
+ const { port, ...others } = args.options;
44
+ const dashboardPort = args.command === "ui" && port !== undefined ? parsePort(port) : undefined;
45
+ noOptions({
46
+ ...args,
47
+ options: args.command === "ui" ? others : args.options,
48
+ });
49
+ // Runs stay readable while factory.ts is being fixed.
50
+ const { factory, error } = await openFactoryForReading(factoryDir);
51
+ if (error)
52
+ process.stderr.write(`factory: factory.ts cannot be loaded, results use the default rendering: ${error}\n`);
53
+ if (args.command === "ui")
54
+ return await ui(factory, dashboardPort);
55
+ const reporter = new TerminalReporter((state) => presentResult(factory, state), args.json);
56
+ return await status(factory, reporter, args.positional[0]);
57
+ }
58
+ catch (error) {
59
+ process.stderr.write(`factory: ${error instanceof Error ? error.message : String(error)}\n`);
60
+ return 1;
61
+ }
62
+ }
63
+ //# sourceMappingURL=application.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"application.js","sourceRoot":"","sources":["../../src/cli/application.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export interface Arguments {
2
+ command: string;
3
+ positional: string[];
4
+ /** Command options; for `run`, the workflow's input. */
5
+ options: Record<string, string | true>;
6
+ json: boolean;
7
+ help: boolean;
8
+ }
9
+ /** `--name value`, or `--name` alone for true; --json and --help never take a value. */
10
+ export declare function parseArgs(argv: string[]): Arguments;
11
+ /** Commands other than `run` take no option of their own. */
12
+ export declare function noOptions(args: Arguments): void;