@smithers-orchestrator/cli 0.16.8 → 0.17.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.
@@ -10,7 +10,7 @@ import { diagnoseRunEffect, } from "../why-diagnosis.js";
10
10
  import { chatAttemptKey, parseChatAttemptMeta, parseNodeOutputEvent, selectChatAttempts, } from "../chat.js";
11
11
  import { WATCH_MIN_INTERVAL_MS } from "../watch.js";
12
12
  import { discoverWorkflows, resolveWorkflow } from "../workflows.js";
13
- import { mdxPlugin } from "smithers-orchestrator/mdx-plugin";
13
+ import { mdxPlugin } from "../mdx-plugin.js";
14
14
  import { approveNode, denyNode } from "@smithers-orchestrator/engine/approvals";
15
15
  import { runWorkflow } from "@smithers-orchestrator/engine";
16
16
  import { revertToAttempt } from "@smithers-orchestrator/time-travel/revert";
@@ -0,0 +1,6 @@
1
+ import { plugin } from "bun";
2
+ import mdx from "@mdx-js/esbuild";
3
+
4
+ export function mdxPlugin() {
5
+ plugin(mdx());
6
+ }
package/src/output.js CHANGED
@@ -8,6 +8,9 @@ import { NodeOutputRouteError } from "@smithers-orchestrator/server/gatewayRoute
8
8
  import { EXIT_OK } from "./util/exitCodes.js";
9
9
  import { formatCliErrorForStderr, getCliErrorMapping } from "./util/errorMessage.js";
10
10
 
11
+ const RUN_ID_PATTERN = /^[a-z0-9_-]{1,64}$/;
12
+ const NODE_ID_PATTERN = /^[a-zA-Z0-9:_-]{1,128}$/;
13
+
11
14
  /**
12
15
  * @param {any} response
13
16
  * @returns {string}
@@ -69,6 +72,52 @@ async function resolveLatestIteration(adapter, runId, nodeId) {
69
72
  }
70
73
  }
71
74
 
75
+ /**
76
+ * @param {Record<string, unknown> | null} row
77
+ * @returns {Record<string, unknown> | null}
78
+ */
79
+ function stripOutputKeyColumns(row) {
80
+ if (!row || typeof row !== "object") {
81
+ return null;
82
+ }
83
+ const result = { ...row };
84
+ delete result.run_id;
85
+ delete result.runId;
86
+ delete result.node_id;
87
+ delete result.nodeId;
88
+ delete result.iteration;
89
+ return result;
90
+ }
91
+
92
+ /**
93
+ * @param {RunOutputCommandInput} input
94
+ * @param {number} iteration
95
+ * @returns {Promise<RunOutputCommandResult>}
96
+ */
97
+ async function runRawJsonOutput(input, iteration) {
98
+ if (!RUN_ID_PATTERN.test(input.runId)) {
99
+ throw new NodeOutputRouteError("InvalidRunId", "runId must match /^[a-z0-9_-]{1,64}$/.");
100
+ }
101
+ if (!NODE_ID_PATTERN.test(input.nodeId)) {
102
+ throw new NodeOutputRouteError("InvalidNodeId", "nodeId must match /^[a-zA-Z0-9:_-]{1,128}$/.");
103
+ }
104
+ const run = await input.adapter.getRun(input.runId);
105
+ if (!run) {
106
+ throw new NodeOutputRouteError("RunNotFound", `Run not found: ${input.runId}`);
107
+ }
108
+ const node = await input.adapter.getNode(input.runId, input.nodeId, iteration);
109
+ if (!node) {
110
+ throw new NodeOutputRouteError("NodeNotFound", `Node not found: ${input.nodeId}`);
111
+ }
112
+ const outputTable = typeof node.outputTable === "string" ? node.outputTable.trim() : "";
113
+ if (!outputTable) {
114
+ throw new NodeOutputRouteError("NodeHasNoOutput", `Node ${input.nodeId} has no output table.`);
115
+ }
116
+ const row = await input.adapter.getRawNodeOutputForIteration(outputTable, input.runId, input.nodeId, iteration);
117
+ input.stdout.write(`${JSON.stringify(stripOutputKeyColumns(row))}\n`);
118
+ return { exitCode: EXIT_OK };
119
+ }
120
+
72
121
  /**
73
122
  * @param {RunOutputCommandInput} input
74
123
  * @returns {Promise<RunOutputCommandResult>}
@@ -80,6 +129,9 @@ export async function runOutputOnce(input) {
80
129
  iteration = latest ?? 0;
81
130
  }
82
131
  try {
132
+ if (input.json && !input.pretty) {
133
+ return await runRawJsonOutput(input, iteration);
134
+ }
83
135
  const response = await getNodeOutputRoute({
84
136
  runId: input.runId,
85
137
  nodeId: input.nodeId,
@@ -3,10 +3,11 @@ import { Cause, Effect, Exit, Layer, ManagedRuntime } from "effect";
3
3
  import { SchedulerLive, WorkflowSessionLive } from "@smithers-orchestrator/scheduler";
4
4
  import { CorrelationContextLive, MetricsServiceLive, TracingServiceLive, createSmithersRuntimeLayer, getCurrentSmithersTraceAnnotations, getCurrentSmithersTraceSpan, } from "@smithers-orchestrator/observability";
5
5
  import { toSmithersError } from "@smithers-orchestrator/errors/toSmithersError";
6
+ import { SmithersLoggerLayer } from "./util/logger.ts";
6
7
  const ObservabilityLayer = Layer.mergeAll(CorrelationContextLive, MetricsServiceLive, TracingServiceLive);
7
8
  const SmithersCoreLayer = Layer.mergeAll(ObservabilityLayer, SchedulerLive.pipe(Layer.provide(ObservabilityLayer)), WorkflowSessionLive);
8
9
  const SmithersWorkflowEngineLayer = Layer.suspend(() => WorkflowEngine.layerMemory);
9
- const SmithersRuntimeLayer = Layer.mergeAll(SmithersCoreLayer, SmithersWorkflowEngineLayer, createSmithersRuntimeLayer()).pipe(Layer.orDie);
10
+ const SmithersRuntimeLayer = Layer.mergeAll(SmithersLoggerLayer, SmithersCoreLayer, SmithersWorkflowEngineLayer, createSmithersRuntimeLayer()).pipe(Layer.orDie);
10
11
  const runtime = ManagedRuntime.make(SmithersRuntimeLayer);
11
12
  /**
12
13
  * @template A, E, R
@@ -0,0 +1,97 @@
1
+ import { format } from "node:util";
2
+ import { Logger } from "effect";
3
+
4
+ type JsonModeState = {
5
+ jsonMode: boolean;
6
+ consoleRoutingInstalled: boolean;
7
+ originalConsole?: {
8
+ debug: typeof console.debug;
9
+ error: typeof console.error;
10
+ info: typeof console.info;
11
+ log: typeof console.log;
12
+ trace: typeof console.trace;
13
+ warn: typeof console.warn;
14
+ };
15
+ };
16
+
17
+ const STATE_KEY = Symbol.for("smithers.cli.jsonMode");
18
+
19
+ function getState(): JsonModeState {
20
+ const globalState = globalThis as typeof globalThis & {
21
+ [STATE_KEY]?: JsonModeState;
22
+ };
23
+ globalState[STATE_KEY] ??= {
24
+ jsonMode: false,
25
+ consoleRoutingInstalled: false,
26
+ };
27
+ return globalState[STATE_KEY];
28
+ }
29
+
30
+ export function setJsonMode(enabled: boolean): void {
31
+ getState().jsonMode = enabled;
32
+ }
33
+
34
+ export function isJsonMode(): boolean {
35
+ return getState().jsonMode === true;
36
+ }
37
+
38
+ function writeConsoleArgsToStderr(args: ReadonlyArray<unknown>): void {
39
+ process.stderr.write(`${format(...args)}\n`);
40
+ }
41
+
42
+ export function installJsonModeConsoleRouting(): void {
43
+ const state = getState();
44
+ if (state.consoleRoutingInstalled) {
45
+ return;
46
+ }
47
+
48
+ const originalConsole = {
49
+ debug: console.debug.bind(console),
50
+ error: console.error.bind(console),
51
+ info: console.info.bind(console),
52
+ log: console.log.bind(console),
53
+ trace: console.trace.bind(console),
54
+ warn: console.warn.bind(console),
55
+ };
56
+
57
+ console.debug = (...args: unknown[]) => {
58
+ if (isJsonMode()) return writeConsoleArgsToStderr(args);
59
+ return originalConsole.debug(...args);
60
+ };
61
+ console.error = (...args: unknown[]) => {
62
+ if (isJsonMode()) return writeConsoleArgsToStderr(args);
63
+ return originalConsole.error(...args);
64
+ };
65
+ console.info = (...args: unknown[]) => {
66
+ if (isJsonMode()) return writeConsoleArgsToStderr(args);
67
+ return originalConsole.info(...args);
68
+ };
69
+ console.log = (...args: unknown[]) => {
70
+ if (isJsonMode()) return writeConsoleArgsToStderr(args);
71
+ return originalConsole.log(...args);
72
+ };
73
+ console.trace = (...args: unknown[]) => {
74
+ if (isJsonMode()) return writeConsoleArgsToStderr(args);
75
+ return originalConsole.trace(...args);
76
+ };
77
+ console.warn = (...args: unknown[]) => {
78
+ if (isJsonMode()) return writeConsoleArgsToStderr(args);
79
+ return originalConsole.warn(...args);
80
+ };
81
+
82
+ state.originalConsole = originalConsole;
83
+ state.consoleRoutingInstalled = true;
84
+ }
85
+
86
+ export const smithersEffectLogger = Logger.make<unknown, void>((options) => {
87
+ const line = Logger.stringLogger.log(options);
88
+ const stream = isJsonMode() ? process.stderr : process.stdout;
89
+ stream.write(`${line}\n`);
90
+ });
91
+
92
+ export const SmithersLoggerLayer = Logger.replace(
93
+ Logger.defaultLogger,
94
+ smithersEffectLogger,
95
+ );
96
+
97
+ installJsonModeConsoleRouting();
@@ -4,7 +4,7 @@ import { dirname, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { generateAgentsTs } from "./agent-detection.js";
6
6
  /**
7
- * @typedef {{ force?: boolean; rootDir?: string; skipInstall?: boolean; }} InitOptions
7
+ * @typedef {{ force?: boolean; rootDir?: string; skipInstall?: boolean; agentsOnly?: boolean; }} InitOptions
8
8
  */
9
9
  /**
10
10
  * @typedef {{ status: "ok" | "skipped" | "failed"; reason?: string; }} InitInstallResult
@@ -15,6 +15,9 @@ import { generateAgentsTs } from "./agent-detection.js";
15
15
  /**
16
16
  * @typedef {{ command: string; description: string; }} WorkflowCta
17
17
  */
18
+ /**
19
+ * @typedef {{ path: string; contents: string; preserveExisting?: boolean; }} TemplateFile
20
+ */
18
21
 
19
22
  /**
20
23
  * @param {string} path
@@ -78,6 +81,7 @@ const BUNDLED_VERSION_PINS = {
78
81
  reactTypes: "19.2.14",
79
82
  reactDomTypes: "19.2.3",
80
83
  mdxTypes: "2.0.13",
84
+ nodeTypes: "25.6.0",
81
85
  };
82
86
  /**
83
87
  * @returns {DependencyVersions}
@@ -90,6 +94,7 @@ function readDependencyVersions() {
90
94
  reactTypesVersion: resolveDependencyVersion("@types/react", BUNDLED_VERSION_PINS.reactTypes),
91
95
  reactDomTypesVersion: resolveDependencyVersion("@types/react-dom", BUNDLED_VERSION_PINS.reactDomTypes),
92
96
  mdxTypesVersion: resolveDependencyVersion("@types/mdx", BUNDLED_VERSION_PINS.mdxTypes),
97
+ nodeTypesVersion: resolveDependencyVersion("@types/node", BUNDLED_VERSION_PINS.nodeTypes),
93
98
  };
94
99
  }
95
100
  /**
@@ -116,6 +121,7 @@ function renderPackageJson(versions) {
116
121
  "@types/react": versions.reactTypesVersion,
117
122
  "@types/react-dom": versions.reactDomTypesVersion,
118
123
  "@types/mdx": versions.mdxTypesVersion,
124
+ "@types/node": versions.nodeTypesVersion,
119
125
  },
120
126
  }, null, 2) + "\n";
121
127
  }
@@ -143,6 +149,104 @@ function renderTsconfig() {
143
149
  exclude: ["./executions/**/*"],
144
150
  }, null, 2) + "\n";
145
151
  }
152
+ /**
153
+ * @returns {TemplateFile[]}
154
+ */
155
+ function renderAgentScaffoldFiles() {
156
+ return [
157
+ {
158
+ path: ".smithers/agents/claude-code.ts",
159
+ preserveExisting: true,
160
+ contents: [
161
+ 'import { ClaudeCodeAgent as SmithersClaudeCodeAgent } from "smithers-orchestrator";',
162
+ "",
163
+ '// Built-in Claude Code CLI agent (cliEngine: "claude-code").',
164
+ "// Tweak `model`, `cwd`, or uncomment extra options below to match your setup.",
165
+ "export const ClaudeCodeAgent = new SmithersClaudeCodeAgent({",
166
+ ' model: "claude-opus-4-6",',
167
+ " cwd: process.cwd(),",
168
+ ' // systemPrompt: "Add shared instructions for every Claude run.",',
169
+ " // timeoutMs: 10 * 60 * 1000,",
170
+ " // dangerouslySkipPermissions: true,",
171
+ "});",
172
+ "",
173
+ ].join("\n"),
174
+ },
175
+ {
176
+ path: ".smithers/agents/codex.ts",
177
+ preserveExisting: true,
178
+ contents: [
179
+ 'import { CodexAgent as SmithersCodexAgent } from "smithers-orchestrator";',
180
+ "",
181
+ '// Built-in Codex CLI agent (cliEngine: "codex").',
182
+ "// Tweak `model`, `cwd`, or uncomment extra options below to match your setup.",
183
+ "export const CodexAgent = new SmithersCodexAgent({",
184
+ ' model: "gpt-5.3-codex",',
185
+ " cwd: process.cwd(),",
186
+ " skipGitRepoCheck: true,",
187
+ ' // systemPrompt: "Add shared instructions for every Codex run.",',
188
+ ' // sandbox: "workspace-write",',
189
+ " // fullAuto: true,",
190
+ "});",
191
+ "",
192
+ ].join("\n"),
193
+ },
194
+ {
195
+ path: ".smithers/agents/gemini.ts",
196
+ preserveExisting: true,
197
+ contents: [
198
+ 'import { GeminiAgent as SmithersGeminiAgent } from "smithers-orchestrator";',
199
+ "",
200
+ '// Built-in Gemini CLI agent (cliEngine: "gemini").',
201
+ "// Tweak `model`, `cwd`, or uncomment extra options below to match your setup.",
202
+ "export const GeminiAgent = new SmithersGeminiAgent({",
203
+ ' model: "gemini-3.1-pro-preview",',
204
+ " cwd: process.cwd(),",
205
+ ' // systemPrompt: "Add shared instructions for every Gemini run.",',
206
+ ' // approvalMode: "yolo",',
207
+ ' // allowedTools: ["read_file", "write_file"],',
208
+ "});",
209
+ "",
210
+ ].join("\n"),
211
+ },
212
+ {
213
+ path: ".smithers/agents/index.ts",
214
+ preserveExisting: true,
215
+ contents: [
216
+ 'export { ClaudeCodeAgent } from "./claude-code";',
217
+ 'export { CodexAgent } from "./codex";',
218
+ 'export { GeminiAgent } from "./gemini";',
219
+ "",
220
+ ].join("\n"),
221
+ },
222
+ {
223
+ path: ".smithers/agents/README.md",
224
+ preserveExisting: true,
225
+ contents: [
226
+ "# Agent Config",
227
+ "",
228
+ "These files export the configured agent instances used by your Smithers workflows.",
229
+ "",
230
+ "- `claude-code.ts`, `codex.ts`, and `gemini.ts` are user-owned config.",
231
+ "- Edit them to pin models, set `cwd`, add a shared `systemPrompt`, or enable engine-specific flags.",
232
+ "- `index.ts` re-exports all three so root-level files can import from `./agents`.",
233
+ "",
234
+ "Examples:",
235
+ "",
236
+ "```ts",
237
+ 'import { ClaudeCodeAgent } from "./agents";',
238
+ 'import { CodexAgent } from "./agents/codex";',
239
+ "```",
240
+ "",
241
+ "Inside `.smithers/workflows/*`, use `../agents` or `../agents/<name>` instead.",
242
+ "",
243
+ "`smithers init` and `smithers init --agents-only` only create missing files in this directory.",
244
+ "Existing files here are left alone so your custom agent config is preserved.",
245
+ "",
246
+ ].join("\n"),
247
+ },
248
+ ];
249
+ }
146
250
  /**
147
251
  * @returns {TemplateFile[]}
148
252
  */
@@ -2046,6 +2150,21 @@ function renderTemplateFiles(versions, env) {
2046
2150
  path: ".smithers/tsconfig.json",
2047
2151
  contents: renderTsconfig(),
2048
2152
  },
2153
+ {
2154
+ path: ".smithers/types/assets.d.ts",
2155
+ contents: [
2156
+ 'declare module "*.md" {',
2157
+ " const Component: any;",
2158
+ " export default Component;",
2159
+ "}",
2160
+ "",
2161
+ 'declare module "*.mdx" {',
2162
+ " const Component: any;",
2163
+ " export default Component;",
2164
+ "}",
2165
+ "",
2166
+ ].join("\n"),
2167
+ },
2049
2168
  {
2050
2169
  path: ".smithers/bunfig.toml",
2051
2170
  contents: ['preload = ["./preload.ts"]', ""].join("\n"),
@@ -2054,6 +2173,7 @@ function renderTemplateFiles(versions, env) {
2054
2173
  path: ".smithers/preload.ts",
2055
2174
  contents: ['import { mdxPlugin } from "smithers-orchestrator";', "", "mdxPlugin();", ""].join("\n"),
2056
2175
  },
2176
+ ...renderAgentScaffoldFiles(),
2057
2177
  {
2058
2178
  path: ".smithers/agents.ts",
2059
2179
  contents: generateAgentsTs(env),
@@ -2085,35 +2205,50 @@ function renderTemplateFiles(versions, env) {
2085
2205
  * @returns {InitResult}
2086
2206
  */
2087
2207
  export function initWorkflowPack(options = {}) {
2088
- const rootDir = resolve(options.rootDir ?? process.cwd(), ".smithers");
2208
+ const projectRoot = options.rootDir ?? process.cwd();
2209
+ const rootDir = resolve(projectRoot, ".smithers");
2089
2210
  const writtenFiles = [];
2090
2211
  const skippedFiles = [];
2091
2212
  const preservedPaths = [];
2092
- const versions = readDependencyVersions();
2093
- const env = process.env;
2094
2213
  ensureDir(rootDir);
2095
- ensureDir(resolve(rootDir, "prompts"));
2096
- ensureDir(resolve(rootDir, "components"));
2097
- ensureDir(resolve(rootDir, "workflows"));
2098
- ensureDir(resolve(rootDir, "tickets"));
2099
- const executionsDir = resolve(rootDir, "executions");
2100
- if (existsSync(executionsDir)) {
2101
- preservedPaths.push(executionsDir);
2214
+ ensureDir(resolve(rootDir, "agents"));
2215
+ /** @type {TemplateFile[]} */
2216
+ let templateFiles;
2217
+ if (options.agentsOnly) {
2218
+ templateFiles = renderAgentScaffoldFiles();
2102
2219
  }
2103
2220
  else {
2104
- ensureDir(executionsDir);
2221
+ const versions = readDependencyVersions();
2222
+ const env = process.env;
2223
+ ensureDir(resolve(rootDir, "prompts"));
2224
+ ensureDir(resolve(rootDir, "components"));
2225
+ ensureDir(resolve(rootDir, "workflows"));
2226
+ ensureDir(resolve(rootDir, "tickets"));
2227
+ const executionsDir = resolve(rootDir, "executions");
2228
+ if (existsSync(executionsDir)) {
2229
+ preservedPaths.push(executionsDir);
2230
+ }
2231
+ else {
2232
+ ensureDir(executionsDir);
2233
+ }
2234
+ templateFiles = renderTemplateFiles(versions, env);
2105
2235
  }
2106
- for (const file of renderTemplateFiles(versions, env)) {
2107
- const absolutePath = resolve(options.rootDir ?? process.cwd(), file.path);
2236
+ for (const file of templateFiles) {
2237
+ const absolutePath = resolve(projectRoot, file.path);
2108
2238
  ensureParent(absolutePath);
2109
- if (existsSync(absolutePath) && !options.force) {
2239
+ if (existsSync(absolutePath) && (file.preserveExisting || !options.force)) {
2110
2240
  skippedFiles.push(absolutePath);
2241
+ if (file.preserveExisting) {
2242
+ process.stderr.write(`[smithers:init] ${file.path} skipped: already exists\n`);
2243
+ }
2111
2244
  continue;
2112
2245
  }
2113
2246
  writeFileSync(absolutePath, file.contents, "utf8");
2114
2247
  writtenFiles.push(absolutePath);
2115
2248
  }
2116
- const install = runBunInstall(rootDir, options.skipInstall ?? false);
2249
+ const install = options.agentsOnly
2250
+ ? { status: "skipped", reason: "agents-only" }
2251
+ : runBunInstall(rootDir, options.skipInstall ?? false);
2117
2252
  return {
2118
2253
  rootDir,
2119
2254
  writtenFiles,