smithers-orchestrator 0.24.2 → 0.25.1

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 (34) hide show
  1. package/README.md +231 -0
  2. package/package.json +51 -24
  3. package/src/CreateSmithersOptions.ts +7 -0
  4. package/src/MigrateSmithersStoreOptions.ts +22 -0
  5. package/src/OpenSmithersBackendOptions.ts +13 -0
  6. package/src/ResolveSmithersBackendChoiceOptions.ts +7 -0
  7. package/src/SmithersBackendChoice.ts +9 -0
  8. package/src/SmithersMigrationResult.ts +15 -0
  9. package/src/bin/smithers-delegation.js +98 -0
  10. package/src/bin/smithers.js +7 -98
  11. package/src/create.js +23 -35
  12. package/src/external/create-external-smithers.js +1 -27
  13. package/src/index.d.ts +77 -3
  14. package/src/index.js +8 -2
  15. package/src/migrateSmithersStore.js +695 -0
  16. package/src/openSmithersBackend.js +89 -0
  17. package/src/prepareOutputSchemas.js +35 -0
  18. package/src/resolveSmithersBackendChoice.js +193 -0
  19. package/src/ide/SmithersIdeAskUserResult.ts +0 -7
  20. package/src/ide/SmithersIdeAvailability.ts +0 -17
  21. package/src/ide/SmithersIdeCommandBaseResult.ts +0 -7
  22. package/src/ide/SmithersIdeOpenDiffResult.ts +0 -5
  23. package/src/ide/SmithersIdeOpenFileResult.ts +0 -8
  24. package/src/ide/SmithersIdeOpenWebviewResult.ts +0 -7
  25. package/src/ide/SmithersIdeOverlayOptions.ts +0 -7
  26. package/src/ide/SmithersIdeOverlayResult.ts +0 -8
  27. package/src/ide/SmithersIdeOverlayType.ts +0 -1
  28. package/src/ide/SmithersIdeResolvedConfig.ts +0 -8
  29. package/src/ide/SmithersIdeRunTerminalResult.ts +0 -8
  30. package/src/ide/SmithersIdeService.js +0 -432
  31. package/src/ide/SmithersIdeServiceApi.ts +0 -35
  32. package/src/ide/SmithersIdeServiceConfig.ts +0 -8
  33. package/src/ide/index.js +0 -44
  34. package/src/ide/tools.js +0 -94
package/README.md ADDED
@@ -0,0 +1,231 @@
1
+ # smithers-orchestrator
2
+
3
+ **Run long-horizon coding-agent work as durable workflows.**
4
+
5
+ [![npm](https://img.shields.io/npm/v/smithers-orchestrator?color=2563eb&label=npm)](https://www.npmjs.com/package/smithers-orchestrator)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-2563eb)](https://github.com/smithersai/smithers/blob/main/LICENSE)
7
+ [![Docs](https://img.shields.io/badge/docs-smithers.sh-2563eb)](https://smithers.sh)
8
+
9
+ Tell your coding agent to do real, multi-step work, then Smithers runs it for minutes or
10
+ days with crash recovery, retries, human approvals, and full observability. The same
11
+ workflow runs across Claude Code, Codex, Pi, AI SDK models, and remote sandboxes.
12
+
13
+ This package is the published Smithers runtime: the engine, the `smithers` CLI, the JSX
14
+ workflow primitives, the agent adapters, and the Gateway. Install it once and your coding
15
+ agent drives the rest.
16
+
17
+ ![Live workflow runs: some succeeded, some running, some paused on an approval gate, every run resumable and rewindable.](https://raw.githubusercontent.com/smithersai/smithers/main/marketing/0.22.0/assets/runs-live.gif)
18
+
19
+ *A workflow run is a list of steps you can watch, pause, approve, and rewind. The run above shows several in flight at once.*
20
+
21
+ ## What you get
22
+
23
+ - 🛡️ **Durable runs that survive crashes**: every completed step is persisted the moment it
24
+ finishes, so a run resumes from where it stopped instead of starting over.
25
+ - 🔌 **Any agent, any model**: Claude Code, Codex, Pi, Antigravity, and more, plus any model
26
+ through the AI SDK. Swap the harness without rewriting the workflow.
27
+ - 🛠️ **Higher-quality output**: review loops, human approvals, and evals give agents the
28
+ structure that real work demands.
29
+ - 🧩 **Dozens of ready-to-run workflows**: planning, implementation, review, debugging,
30
+ tickets, audits, and long-horizon missions. Your agent can author new ones.
31
+
32
+ ## When to use Smithers
33
+
34
+ Smithers is the durable runtime for *coding-agent* work: when the unit of work is an agent
35
+ editing a real repository over many steps, and you need that work to be inspectable,
36
+ approvable, and recoverable. Use it when order matters across multiple AI steps, you need
37
+ crash recovery, a human must approve or answer mid-run, or different tasks need different
38
+ models. For a single prompt → single response, call your model provider's SDK directly;
39
+ Smithers adds nothing there.
40
+
41
+ ## Get started
42
+
43
+ Smithers is driven by your coding agent, **not** a GUI you click. Your agent runs Smithers
44
+ on your behalf: it scaffolds workflows, kicks off runs, watches them, and handles
45
+ approvals.
46
+
47
+ One command sets everything up. From inside your project:
48
+
49
+ ```bash
50
+ bunx smithers-orchestrator init
51
+ ```
52
+
53
+ `init` does everything:
54
+
55
+ - **Installs the `smithers` skill** into the coding agents on your machine (Claude Code,
56
+ Pi, and more), so your agent knows how and when to use Smithers. No `mkdir`, no `curl`.
57
+ - **Scaffolds `.smithers/`** with ready-made workflows (`hello`, `implement`, `plan`,
58
+ `review`, `debug`, and more) your agent can pick from.
59
+
60
+ Then just ask:
61
+
62
+ > *"orchestrate an agent to add rate limiting and keep iterating until the tests pass."*
63
+
64
+ Your agent picks the right workflow, starts the run, and keeps going through retries and
65
+ review loops until the work is actually done.
66
+
67
+ > **Always run `bunx smithers-orchestrator`, never `bunx smithers`.** On npm, `smithers`
68
+ > is an unrelated package. The installed binary alias `smithers` is only safe inside a
69
+ > project that resolves `node_modules/.bin/smithers`.
70
+
71
+ To wire the MCP server into every detected agent too, run
72
+ `bunx smithers-orchestrator mcp add`. See [Agent Support](https://smithers.sh/agents/overview)
73
+ for the full per-agent matrix.
74
+
75
+ ## Drive it yourself
76
+
77
+ Prefer the CLI? The seeded `hello` workflow is the smallest possible run; its entire prompt
78
+ is an editable Markdown file at `.smithers/prompts/hello.mdx`:
79
+
80
+ ```bash
81
+ # run your first workflow (edit .smithers/prompts/hello.mdx to change it)
82
+ bunx smithers-orchestrator workflow run hello
83
+
84
+ # turn a request into a practical implementation plan
85
+ bunx smithers-orchestrator workflow run plan --prompt "add rate limiting, audit logging, and API key rotation"
86
+ ```
87
+
88
+ Watch what's happening, whether your agent started the run or you did:
89
+
90
+ ```bash
91
+ bunx smithers-orchestrator ps # list active, paused, and recently completed runs
92
+ bunx smithers-orchestrator inspect RUN_ID # steps, agents, approvals, and outputs for one run
93
+ bunx smithers-orchestrator logs RUN_ID # tail the event log
94
+ bunx smithers-orchestrator chat RUN_ID # read the agent's chat output
95
+ ```
96
+
97
+ Run `bunx smithers-orchestrator starters` to browse plain-English starters, and
98
+ `bunx smithers-orchestrator workflow list` to see what's installed.
99
+
100
+ ## Durable by default
101
+
102
+ Durability is the differentiator. Runs survive crashes, restarts, and flaky tools because
103
+ **every completed step is persisted to SQLite the moment it finishes**. The runtime always
104
+ knows what's done and what to run next. Approvals, human questions, retries, and replay are
105
+ first-class.
106
+
107
+ ```text
108
+ prompt → render workflow → run task → validate output → persist to SQLite → re-render → resume · inspect · replay
109
+ ```
110
+
111
+ That loop is the whole model: a task runs, its output is validated against a schema and
112
+ written down, then the workflow re-renders from persisted state to decide the next task. A
113
+ crash at any point resumes from the last write, not from the top.
114
+
115
+ ```bash
116
+ bunx smithers-orchestrator up workflow.tsx --input '{"description":"Fix bug"}'
117
+ bunx smithers-orchestrator up workflow.tsx --run-id abc123 --resume true # resume after a crash
118
+ bunx smithers-orchestrator rewind abc123 --frame 4 # time-travel to an earlier frame
119
+ bunx smithers-orchestrator fork abc123 # branch an alternate timeline
120
+ bunx smithers-orchestrator replay abc123 # replay from a checkpoint
121
+ ```
122
+
123
+ ## Author your own
124
+
125
+ The built-in workflows are normal Smithers TSX files: run them as-is, have your agent adapt
126
+ them to your repo, or have it write new ones from the same primitives. A workflow is a JSX
127
+ tree of tasks, each with a Zod-validated output:
128
+
129
+ ```tsx
130
+ import { createSmithers, Sequence } from "smithers-orchestrator";
131
+ import { z } from "zod";
132
+
133
+ const { Workflow, Task, smithers, outputs } = createSmithers({
134
+ analyze: z.object({
135
+ summary: z.string(),
136
+ severity: z.enum(["low", "medium", "high"]),
137
+ }),
138
+ fix: z.object({
139
+ patch: z.string(),
140
+ explanation: z.string(),
141
+ }),
142
+ });
143
+
144
+ export default smithers((ctx) => (
145
+ <Workflow name="bugfix">
146
+ <Sequence>
147
+ <Task id="analyze" output={outputs.analyze} agent={analyzer}>
148
+ {`Analyze the bug: ${ctx.input.description}`}
149
+ </Task>
150
+
151
+ <Task id="fix" output={outputs.fix} agent={fixer}>
152
+ {`Fix this issue: ${ctx.latest("analyze").summary}`}
153
+ </Task>
154
+ </Sequence>
155
+ </Workflow>
156
+ ));
157
+ ```
158
+
159
+ Each task output is validated against its Zod schema and persisted to SQLite. If the
160
+ process crashes, Smithers resumes from the last completed node without re-running finished
161
+ work.
162
+
163
+ | Component | Purpose |
164
+ | ------------ | ------------------------------------- |
165
+ | `<Workflow>` | Root container |
166
+ | `<Task>` | AI or static task node |
167
+ | `<Sequence>` | Ordered execution |
168
+ | `<Parallel>` | Concurrent execution |
169
+ | `<Branch>` | Conditional execution |
170
+ | `<Loop>` | Repeat tasks until a condition is met |
171
+
172
+ There are many more: approvals, merge queues, sub-workflows, signals, timers, sagas,
173
+ sandboxes, and composite patterns. See
174
+ [Components](https://smithers.sh/components/workflow).
175
+
176
+ ## Package entry points
177
+
178
+ The main entry re-exports the full toolkit, so most code only needs
179
+ `import { ... } from "smithers-orchestrator"`. Dedicated subpaths exist for focused imports:
180
+
181
+ | Import | What it gives you |
182
+ | --- | --- |
183
+ | `smithers-orchestrator` | `createSmithers`, the workflow components, agent adapters, errors, and the rest of the core API. |
184
+ | `smithers-orchestrator/tools` | The built-in agent tool sandbox. |
185
+ | `smithers-orchestrator/gateway-client` | Typed client for the Gateway RPC/WS control plane. |
186
+ | `smithers-orchestrator/gateway-react` | React hooks for live, multi-run state. |
187
+ | `smithers-orchestrator/sandbox` | The `<Sandbox>` primitive and `SandboxProvider` interface. |
188
+ | `smithers-orchestrator/control-plane` | Programmatic control-plane API for launching and steering runs. |
189
+ | `smithers-orchestrator/server` | The Gateway server (`startServer`, `createServeApp`). |
190
+ | `smithers-orchestrator/observability` | Prometheus metrics and OpenTelemetry tracing layers. |
191
+ | `smithers-orchestrator/memory` | Cross-run memory store and recall. |
192
+ | `smithers-orchestrator/scorers` | Eval scorers (LLM-judge, relevancy, faithfulness, …). |
193
+ | `smithers-orchestrator/openapi` | Generate AI SDK tools from OpenAPI specs. |
194
+
195
+ ## Any agent, any model
196
+
197
+ Point each task at whichever agent is best for the job, mix several in one workflow, and
198
+ switch freely. The workflow doesn't change when the model does, so a frontier model can
199
+ plan, a fast model can fan out, and a specialized harness can do the edits.
200
+
201
+ Agent adapters ship in the box, including `ClaudeCodeAgent`, `CodexAgent`, `PiAgent`,
202
+ `AntigravityAgent`, `GeminiAgent`, and `AnthropicAgent` / `OpenAIAgent` for any AI SDK
203
+ model (with tools, structured output, and MCP). The same `<Sandbox>` primitive runs an
204
+ agent locally (Bubblewrap or Docker), in a container, on a managed remote like
205
+ [Freestyle](https://freestyle.sh), or on any backend you implement against
206
+ `SandboxProvider`.
207
+
208
+ ## Requirements
209
+
210
+ - [Bun](https://bun.sh) ≥ 1.3
211
+ - TypeScript ≥ 5 (only for authoring TSX workflows)
212
+ - Model or provider credentials (e.g. `ANTHROPIC_API_KEY`)
213
+ - A version control system for snapshotting and isolating agent work:
214
+ [jj (Jujutsu)](https://github.com/jj-vcs/jj) or [git](https://git-scm.com). jj is
215
+ preferred and powers durability, time-travel, and per-task worktrees; the optional
216
+ `@smithers-orchestrator/jj-<platform>` package bundles a jj binary so a fresh install
217
+ works with no system jj.
218
+
219
+ ## Docs
220
+
221
+ Full documentation lives at **[smithers.sh](https://smithers.sh)**.
222
+
223
+ - [Introduction](https://smithers.sh/introduction) — what Smithers is and when to use it.
224
+ - [Quickstart](https://smithers.sh/quickstart) — scaffold and run a workflow in two commands.
225
+ - [Tour](https://smithers.sh/tour) — a guided walk through a real run.
226
+ - [How It Works](https://smithers.sh/how-it-works) — the durable execution model.
227
+ - [Components](https://smithers.sh/components/workflow) — the full primitive set.
228
+
229
+ ## License
230
+
231
+ MIT
package/package.json CHANGED
@@ -1,7 +1,32 @@
1
1
  {
2
2
  "name": "smithers-orchestrator",
3
- "version": "0.24.2",
3
+ "version": "0.25.1",
4
4
  "description": "Public Smithers facade for durable coding-agent workflows and Gateway operation",
5
+ "license": "MIT",
6
+ "homepage": "https://smithers.sh",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/smithersai/smithers.git",
10
+ "directory": "packages/smithers"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/smithersai/smithers/issues"
14
+ },
15
+ "keywords": [
16
+ "ai",
17
+ "agent",
18
+ "agents",
19
+ "coding-agent",
20
+ "orchestration",
21
+ "workflow",
22
+ "durable-execution",
23
+ "react",
24
+ "jsx",
25
+ "llm",
26
+ "claude",
27
+ "codex",
28
+ "bun"
29
+ ],
5
30
  "type": "module",
6
31
  "sideEffects": false,
7
32
  "exports": {
@@ -52,7 +77,8 @@
52
77
  }
53
78
  },
54
79
  "files": [
55
- "src/"
80
+ "src/",
81
+ "README.md"
56
82
  ],
57
83
  "bin": {
58
84
  "smithers": "./src/bin/smithers.js"
@@ -66,28 +92,28 @@
66
92
  "incur": "^0.4.1",
67
93
  "react": "^19.2.5",
68
94
  "zod": "^4.3.6",
69
- "@smithers-orchestrator/agents": "0.24.2",
70
- "@smithers-orchestrator/control-plane": "0.24.2",
71
- "@smithers-orchestrator/components": "0.24.2",
72
- "@smithers-orchestrator/db": "0.24.2",
73
- "@smithers-orchestrator/engine": "0.24.2",
74
- "@smithers-orchestrator/driver": "0.24.2",
75
- "@smithers-orchestrator/errors": "0.24.2",
76
- "@smithers-orchestrator/gateway-client": "0.24.2",
77
- "@smithers-orchestrator/gateway-react": "0.24.2",
78
- "@smithers-orchestrator/graph": "0.24.2",
79
- "@smithers-orchestrator/memory": "0.24.2",
80
- "@smithers-orchestrator/openapi": "0.24.2",
81
- "@smithers-orchestrator/react-reconciler": "0.24.2",
82
- "@smithers-orchestrator/sandbox": "0.24.2",
83
- "@smithers-orchestrator/observability": "0.24.2",
84
- "@smithers-orchestrator/scheduler": "0.24.2",
85
- "@smithers-orchestrator/scorers": "0.24.2",
86
- "@smithers-orchestrator/server": "0.24.2",
87
- "@smithers-orchestrator/time-travel": "0.24.2",
88
- "@smithers-orchestrator/vcs": "0.24.2",
89
- "@smithers-orchestrator/tool-context": "0.24.2",
90
- "@smithers-orchestrator/cli": "0.24.2"
95
+ "@smithers-orchestrator/agents": "0.25.1",
96
+ "@smithers-orchestrator/db": "0.25.1",
97
+ "@smithers-orchestrator/engine": "0.25.1",
98
+ "@smithers-orchestrator/errors": "0.25.1",
99
+ "@smithers-orchestrator/components": "0.25.1",
100
+ "@smithers-orchestrator/cli": "0.25.1",
101
+ "@smithers-orchestrator/control-plane": "0.25.1",
102
+ "@smithers-orchestrator/memory": "0.25.1",
103
+ "@smithers-orchestrator/gateway-client": "0.25.1",
104
+ "@smithers-orchestrator/observability": "0.25.1",
105
+ "@smithers-orchestrator/driver": "0.25.1",
106
+ "@smithers-orchestrator/openapi": "0.25.1",
107
+ "@smithers-orchestrator/react-reconciler": "0.25.1",
108
+ "@smithers-orchestrator/gateway-react": "0.25.1",
109
+ "@smithers-orchestrator/sandbox": "0.25.1",
110
+ "@smithers-orchestrator/scheduler": "0.25.1",
111
+ "@smithers-orchestrator/scorers": "0.25.1",
112
+ "@smithers-orchestrator/server": "0.25.1",
113
+ "@smithers-orchestrator/time-travel": "0.25.1",
114
+ "@smithers-orchestrator/tool-context": "0.25.1",
115
+ "@smithers-orchestrator/graph": "0.25.1",
116
+ "@smithers-orchestrator/vcs": "0.25.1"
91
117
  },
92
118
  "optionalDependencies": {
93
119
  "@electric-sql/pglite": "0.5.1",
@@ -99,6 +125,7 @@
99
125
  "typescript": "~5.9.3"
100
126
  },
101
127
  "scripts": {
128
+ "test": "bun test tests",
102
129
  "typecheck": "tsc -p tsconfig.json --noEmit",
103
130
  "build": "tsup --dts-only"
104
131
  }
@@ -6,4 +6,11 @@ export type CreateSmithersOptions = {
6
6
  alertPolicy?: SmithersAlertPolicy;
7
7
  dbPath?: string;
8
8
  journalMode?: string;
9
+ /**
10
+ * Backend the caller resolved this API to. The synchronous `createSmithers`
11
+ * only serves `"sqlite"`; `"pglite"`/`"postgres"` require the async
12
+ * `openSmithersBackend` factory and fail loud here rather than silently
13
+ * opening bun:sqlite.
14
+ */
15
+ backend?: "sqlite" | "pglite" | "postgres";
9
16
  };
@@ -0,0 +1,22 @@
1
+ export type MigrateSmithersStoreOptions = {
2
+ cwd?: string;
3
+ dbPath?: string;
4
+ to?: "pglite" | "postgres";
5
+ url?: string;
6
+ env?: Record<string, string | undefined>;
7
+ pgliteDataDir?: string;
8
+ keepSqlite?: boolean;
9
+ batchSize?: number;
10
+ onProgress?: (event:
11
+ | { type: "table-start"; table?: string; sourceRows?: number }
12
+ | {
13
+ type: "table-copied";
14
+ table?: string;
15
+ copiedRows?: number;
16
+ sourceRows?: number;
17
+ targetRows?: number;
18
+ durationMs?: number;
19
+ }
20
+ | { type: "done"; copiedRows?: number; tableCount?: number; durationMs?: number }
21
+ ) => void | Promise<void>;
22
+ };
@@ -0,0 +1,13 @@
1
+ import type { CreateSmithersOptions } from "./CreateSmithersOptions";
2
+
3
+ type SmithersBackend = "sqlite" | "pglite" | "postgres";
4
+
5
+ export type OpenSmithersBackendOptions = CreateSmithersOptions & {
6
+ backend?: SmithersBackend;
7
+ cwd?: string;
8
+ configPath?: string;
9
+ env?: Record<string, string | undefined>;
10
+ connectionString?: string;
11
+ connection?: object;
12
+ pgliteDataDir?: string;
13
+ };
@@ -0,0 +1,7 @@
1
+ export type ResolveSmithersBackendChoiceOptions = {
2
+ backend?: "sqlite" | "pglite" | "postgres";
3
+ cwd?: string;
4
+ dbPath?: string;
5
+ configPath?: string;
6
+ env?: Record<string, string | undefined>;
7
+ };
@@ -0,0 +1,9 @@
1
+ export type SmithersBackendChoice = {
2
+ backend: "sqlite" | "pglite" | "postgres";
3
+ source: "options" | "env" | "config" | "default";
4
+ dbPath: string;
5
+ workspaceRoot: string;
6
+ runCount: number;
7
+ schemaVersion: string;
8
+ migratedMarker: boolean;
9
+ };
@@ -0,0 +1,15 @@
1
+ export type SmithersMigrationResult = {
2
+ backend: "pglite" | "postgres";
3
+ dbPath: string;
4
+ markerPath: string;
5
+ target: {
6
+ backend: "pglite" | "postgres";
7
+ dataDir?: string;
8
+ url?: string;
9
+ };
10
+ runCount: number;
11
+ schemaVersion: string;
12
+ durationMs: number;
13
+ tables: Array<{ table: string; sourceRows: number; targetRows: number; durationMs: number }>;
14
+ sqliteRemoved: boolean;
15
+ };
@@ -0,0 +1,98 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, parse, resolve } from "node:path";
3
+
4
+ const WORKFLOW_PATH_COMMANDS = new Set([
5
+ "up",
6
+ "graph",
7
+ "fork",
8
+ "replay",
9
+ "revert",
10
+ "timetravel",
11
+ ]);
12
+ const WORKFLOW_EXTENSIONS = new Set([".js", ".jsx", ".ts", ".tsx", ".mjs", ".mts"]);
13
+
14
+ /**
15
+ * @param {string} value
16
+ */
17
+ export function isOptionLike(value) {
18
+ return value.startsWith("-");
19
+ }
20
+
21
+ /**
22
+ * @param {string} value
23
+ */
24
+ export function looksLikeWorkflowPath(value) {
25
+ if (isOptionLike(value))
26
+ return false;
27
+ return WORKFLOW_EXTENSIONS.has(parse(value).ext);
28
+ }
29
+
30
+ /**
31
+ * @param {string[]} args
32
+ */
33
+ export function getExplicitWorkflowPath(args) {
34
+ if (args.length === 0)
35
+ return null;
36
+ if (looksLikeWorkflowPath(args[0]))
37
+ return args[0];
38
+ for (let index = 0; index < args.length; index++) {
39
+ const arg = args[index];
40
+ if (!WORKFLOW_PATH_COMMANDS.has(arg))
41
+ continue;
42
+ for (let nextIndex = index + 1; nextIndex < args.length; nextIndex++) {
43
+ const candidate = args[nextIndex];
44
+ if (looksLikeWorkflowPath(candidate))
45
+ return candidate;
46
+ }
47
+ return null;
48
+ }
49
+ for (const arg of args) {
50
+ if (looksLikeWorkflowPath(arg))
51
+ return arg;
52
+ }
53
+ return null;
54
+ }
55
+
56
+ /**
57
+ * Resolve the local `smithers-orchestrator` package's bin JS file under
58
+ * `<directory>/node_modules/`. Going through `package.json` (instead of the
59
+ * `.bin/smithers` shell shim npm/pnpm generate) is the whole point: the shim
60
+ * is `#!/bin/sh` and re-execing it with `process.execPath` (bun) makes bun
61
+ * parse shell as JavaScript, which crashes with `Expected ")" but found
62
+ * "$(echo "`.
63
+ *
64
+ * @param {string} directory
65
+ */
66
+ export function resolveLocalSmithersBinJs(directory) {
67
+ const pkgJsonPath = resolve(directory, "node_modules/smithers-orchestrator/package.json");
68
+ if (!existsSync(pkgJsonPath))
69
+ return null;
70
+ let pkg;
71
+ try {
72
+ pkg = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
73
+ } catch {
74
+ return null;
75
+ }
76
+ const binEntry = typeof pkg?.bin === "string" ? pkg.bin : pkg?.bin?.smithers;
77
+ if (typeof binEntry !== "string" || binEntry.length === 0)
78
+ return null;
79
+ const binPath = resolve(dirname(pkgJsonPath), binEntry);
80
+ return existsSync(binPath) ? binPath : null;
81
+ }
82
+
83
+ /**
84
+ * @param {string} cwd
85
+ * @param {string} workflowPath
86
+ */
87
+ export function findNearestWorkflowLocalCli(cwd, workflowPath) {
88
+ let current = dirname(resolve(cwd, workflowPath));
89
+ while (true) {
90
+ const localBin = resolveLocalSmithersBinJs(current);
91
+ if (localBin)
92
+ return localBin;
93
+ const parent = dirname(current);
94
+ if (parent === current)
95
+ return null;
96
+ current = parent;
97
+ }
98
+ }
@@ -1,104 +1,13 @@
1
1
  #!/usr/bin/env bun
2
2
  import { spawn } from "node:child_process";
3
- import { existsSync, readFileSync, realpathSync } from "node:fs";
4
- import { dirname, parse, resolve } from "node:path";
3
+ import { realpathSync } from "node:fs";
4
+ import { resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
-
7
- const WORKFLOW_PATH_COMMANDS = new Set([
8
- "up",
9
- "graph",
10
- "fork",
11
- "replay",
12
- "revert",
13
- "timetravel",
14
- ]);
15
- const WORKFLOW_EXTENSIONS = new Set([".js", ".jsx", ".ts", ".tsx", ".mjs", ".mts"]);
16
-
17
- /**
18
- * @param {string} value
19
- */
20
- function isOptionLike(value) {
21
- return value.startsWith("-");
22
- }
23
-
24
- /**
25
- * @param {string} value
26
- */
27
- function looksLikeWorkflowPath(value) {
28
- if (isOptionLike(value))
29
- return false;
30
- return WORKFLOW_EXTENSIONS.has(parse(value).ext);
31
- }
32
-
33
- /**
34
- * @param {string[]} args
35
- */
36
- function getExplicitWorkflowPath(args) {
37
- if (args.length === 0)
38
- return null;
39
- if (looksLikeWorkflowPath(args[0]))
40
- return args[0];
41
- for (let index = 0; index < args.length; index++) {
42
- const arg = args[index];
43
- if (!WORKFLOW_PATH_COMMANDS.has(arg))
44
- continue;
45
- for (let nextIndex = index + 1; nextIndex < args.length; nextIndex++) {
46
- const candidate = args[nextIndex];
47
- if (looksLikeWorkflowPath(candidate))
48
- return candidate;
49
- }
50
- return null;
51
- }
52
- for (const arg of args) {
53
- if (looksLikeWorkflowPath(arg))
54
- return arg;
55
- }
56
- return null;
57
- }
58
-
59
- /**
60
- * Resolve the local `smithers-orchestrator` package's bin JS file under
61
- * `<directory>/node_modules/`. Going through `package.json` (instead of the
62
- * `.bin/smithers` shell shim npm/pnpm generate) is the whole point: the shim
63
- * is `#!/bin/sh` and re-execing it with `process.execPath` (bun) makes bun
64
- * parse shell as JavaScript, which crashes with `Expected ")" but found
65
- * "$(echo "`.
66
- *
67
- * @param {string} directory
68
- */
69
- function resolveLocalSmithersBinJs(directory) {
70
- const pkgJsonPath = resolve(directory, "node_modules/smithers-orchestrator/package.json");
71
- if (!existsSync(pkgJsonPath))
72
- return null;
73
- let pkg;
74
- try {
75
- pkg = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
76
- } catch {
77
- return null;
78
- }
79
- const binEntry = typeof pkg?.bin === "string" ? pkg.bin : pkg?.bin?.smithers;
80
- if (typeof binEntry !== "string" || binEntry.length === 0)
81
- return null;
82
- const binPath = resolve(dirname(pkgJsonPath), binEntry);
83
- return existsSync(binPath) ? binPath : null;
84
- }
85
-
86
- /**
87
- * @param {string} cwd
88
- * @param {string} workflowPath
89
- */
90
- function findNearestWorkflowLocalCli(cwd, workflowPath) {
91
- let current = dirname(resolve(cwd, workflowPath));
92
- while (true) {
93
- const localBin = resolveLocalSmithersBinJs(current);
94
- if (localBin)
95
- return localBin;
96
- const parent = dirname(current);
97
- if (parent === current)
98
- return null;
99
- current = parent;
100
- }
101
- }
6
+ import {
7
+ getExplicitWorkflowPath,
8
+ findNearestWorkflowLocalCli,
9
+ resolveLocalSmithersBinJs,
10
+ } from "./smithers-delegation.js";
102
11
 
103
12
  /**
104
13
  * When a workflow directory (`.smithers/`) exists in the user's cwd and it's