@reasonlayer/sdk 0.0.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 (117) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +35 -0
  3. package/bin/rl.mjs +75 -0
  4. package/dist/builder.d.ts +38 -0
  5. package/dist/builder.js +41 -0
  6. package/dist/bundler/index.d.ts +32 -0
  7. package/dist/bundler/index.js +143 -0
  8. package/dist/bundler/lint.d.ts +13 -0
  9. package/dist/bundler/lint.js +74 -0
  10. package/dist/bundler/node.d.ts +10 -0
  11. package/dist/bundler/node.js +55 -0
  12. package/dist/bundler/smoke.d.ts +12 -0
  13. package/dist/bundler/smoke.js +70 -0
  14. package/dist/bundler/worker.d.ts +31 -0
  15. package/dist/bundler/worker.js +214 -0
  16. package/dist/cli/args.d.ts +9 -0
  17. package/dist/cli/args.js +71 -0
  18. package/dist/cli/config.d.ts +38 -0
  19. package/dist/cli/config.js +94 -0
  20. package/dist/cli/convex.d.ts +9 -0
  21. package/dist/cli/convex.js +25 -0
  22. package/dist/cli/format.d.ts +11 -0
  23. package/dist/cli/format.js +140 -0
  24. package/dist/cli/index.d.ts +1 -0
  25. package/dist/cli/index.js +721 -0
  26. package/dist/cli/keychain.d.ts +21 -0
  27. package/dist/cli/keychain.js +100 -0
  28. package/dist/cli/login-browser.d.ts +22 -0
  29. package/dist/cli/login-browser.js +106 -0
  30. package/dist/cli/mcp.d.ts +3 -0
  31. package/dist/cli/mcp.js +36 -0
  32. package/dist/cli/operations.d.ts +106 -0
  33. package/dist/cli/operations.js +623 -0
  34. package/dist/cli/sdk-package.d.ts +18 -0
  35. package/dist/cli/sdk-package.js +45 -0
  36. package/dist/cli/storage-upload.d.ts +9 -0
  37. package/dist/cli/storage-upload.js +21 -0
  38. package/dist/cli/watcher-compile.d.ts +1 -0
  39. package/dist/cli/watcher-compile.js +20 -0
  40. package/dist/cli/watcher-process.d.ts +1 -0
  41. package/dist/cli/watcher-process.js +312 -0
  42. package/dist/cli/worker-host.d.ts +10 -0
  43. package/dist/cli/worker-host.js +120 -0
  44. package/dist/cli/workspace-files.d.ts +12 -0
  45. package/dist/cli/workspace-files.js +91 -0
  46. package/dist/cli/workspace-local.d.ts +27 -0
  47. package/dist/cli/workspace-local.js +310 -0
  48. package/dist/cli/workspace-session.d.ts +13 -0
  49. package/dist/cli/workspace-session.js +12 -0
  50. package/dist/compile.d.ts +27 -0
  51. package/dist/compile.js +834 -0
  52. package/dist/index.d.ts +10 -0
  53. package/dist/index.js +5 -0
  54. package/dist/schedule-validation.d.ts +1 -0
  55. package/dist/schedule-validation.js +16 -0
  56. package/dist/selector-schema.d.ts +26 -0
  57. package/dist/selector-schema.js +780 -0
  58. package/dist/selectors.d.ts +25 -0
  59. package/dist/selectors.js +99 -0
  60. package/dist/testkit.d.ts +32 -0
  61. package/dist/testkit.js +161 -0
  62. package/dist/types.d.ts +184 -0
  63. package/dist/types.js +110 -0
  64. package/node_modules/@reasonlayer/integrations/dist/catalog.d.ts +4937 -0
  65. package/node_modules/@reasonlayer/integrations/dist/catalog.js +20 -0
  66. package/node_modules/@reasonlayer/integrations/dist/generated/slack.d.ts +3014 -0
  67. package/node_modules/@reasonlayer/integrations/dist/generated/slack.js +3101 -0
  68. package/node_modules/@reasonlayer/integrations/dist/index.d.ts +2 -0
  69. package/node_modules/@reasonlayer/integrations/dist/index.js +1 -0
  70. package/node_modules/@reasonlayer/integrations/dist/internal/definitions.d.ts +44 -0
  71. package/node_modules/@reasonlayer/integrations/dist/internal/definitions.js +52 -0
  72. package/node_modules/@reasonlayer/integrations/dist/internal/transport.d.ts +7 -0
  73. package/node_modules/@reasonlayer/integrations/dist/internal/transport.js +48 -0
  74. package/node_modules/@reasonlayer/integrations/dist/testkit.d.ts +1 -0
  75. package/node_modules/@reasonlayer/integrations/dist/testkit.js +1 -0
  76. package/node_modules/@reasonlayer/integrations/package.json +26 -0
  77. package/node_modules/@reasonlayer/protocol/dist/dispatch.d.ts +10 -0
  78. package/node_modules/@reasonlayer/protocol/dist/dispatch.js +58 -0
  79. package/node_modules/@reasonlayer/protocol/dist/envelope.d.ts +9 -0
  80. package/node_modules/@reasonlayer/protocol/dist/envelope.js +1 -0
  81. package/node_modules/@reasonlayer/protocol/dist/environments.d.ts +7 -0
  82. package/node_modules/@reasonlayer/protocol/dist/environments.js +7 -0
  83. package/node_modules/@reasonlayer/protocol/dist/events.d.ts +313 -0
  84. package/node_modules/@reasonlayer/protocol/dist/events.js +20 -0
  85. package/node_modules/@reasonlayer/protocol/dist/index.d.ts +18 -0
  86. package/node_modules/@reasonlayer/protocol/dist/index.js +9 -0
  87. package/node_modules/@reasonlayer/protocol/dist/ir.d.ts +252 -0
  88. package/node_modules/@reasonlayer/protocol/dist/ir.js +116 -0
  89. package/node_modules/@reasonlayer/protocol/dist/permissions.d.ts +5 -0
  90. package/node_modules/@reasonlayer/protocol/dist/permissions.js +30 -0
  91. package/node_modules/@reasonlayer/protocol/dist/readmodels.d.ts +617 -0
  92. package/node_modules/@reasonlayer/protocol/dist/readmodels.js +36 -0
  93. package/node_modules/@reasonlayer/protocol/dist/runtime.d.ts +67 -0
  94. package/node_modules/@reasonlayer/protocol/dist/runtime.js +66 -0
  95. package/node_modules/@reasonlayer/protocol/dist/scheduling.d.ts +69 -0
  96. package/node_modules/@reasonlayer/protocol/dist/scheduling.js +126 -0
  97. package/node_modules/@reasonlayer/protocol/dist/selector.d.ts +23 -0
  98. package/node_modules/@reasonlayer/protocol/dist/selector.js +77 -0
  99. package/node_modules/@reasonlayer/protocol/dist/tasks.d.ts +69 -0
  100. package/node_modules/@reasonlayer/protocol/dist/tasks.js +6 -0
  101. package/node_modules/@reasonlayer/protocol/dist/validate.d.ts +6 -0
  102. package/node_modules/@reasonlayer/protocol/dist/validate.js +18 -0
  103. package/node_modules/@reasonlayer/protocol/package.json +21 -0
  104. package/node_modules/@reasonlayer/runner/dist/environment-materializer.d.ts +13 -0
  105. package/node_modules/@reasonlayer/runner/dist/environment-materializer.js +92 -0
  106. package/node_modules/@reasonlayer/runner/dist/flue-transcript.d.ts +17 -0
  107. package/node_modules/@reasonlayer/runner/dist/flue-transcript.js +182 -0
  108. package/node_modules/@reasonlayer/runner/dist/index.d.ts +184 -0
  109. package/node_modules/@reasonlayer/runner/dist/index.js +884 -0
  110. package/node_modules/@reasonlayer/runner/dist/mcp-supervisor.d.ts +15 -0
  111. package/node_modules/@reasonlayer/runner/dist/mcp-supervisor.js +142 -0
  112. package/node_modules/@reasonlayer/runner/dist/sandbox-env.d.ts +36 -0
  113. package/node_modules/@reasonlayer/runner/dist/sandbox-env.js +171 -0
  114. package/node_modules/@reasonlayer/runner/dist/task-child.d.ts +1 -0
  115. package/node_modules/@reasonlayer/runner/dist/task-child.js +72 -0
  116. package/node_modules/@reasonlayer/runner/package.json +21 -0
  117. package/package.json +65 -0
@@ -0,0 +1,721 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { readFile } from "node:fs/promises";
3
+ import { isTerminalRunStatus } from "@reasonlayer/protocol";
4
+ import { hasFlag, numberFlag, parseCliArgs, stringFlag } from "./args.js";
5
+ import { clearLogin, loadConfig, resolvedConvexUrl, resolvedTargetLabel, saveLogin, TOKEN_ONBOARDING_HINT, } from "./config.js";
6
+ import { formatCompileErrors, formatInspect, formatVariableList, jsonText, parseVariableValue, } from "./format.js";
7
+ import { buildProject, createOperationRuntime, operationForSurfaceByName, runOperation, validateWorkflow, } from "./operations.js";
8
+ import { sdkProvenance } from "./sdk-package.js";
9
+ async function readStdin() {
10
+ const chunks = [];
11
+ for await (const chunk of process.stdin) {
12
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
13
+ }
14
+ return Buffer.concat(chunks).toString("utf8");
15
+ }
16
+ async function parseInputJson(raw) {
17
+ if (raw === undefined)
18
+ return {};
19
+ if (raw === "-")
20
+ return JSON.parse(await readStdin());
21
+ if (raw.startsWith("@"))
22
+ return JSON.parse(await readFile(raw.slice(1), "utf8"));
23
+ return JSON.parse(raw);
24
+ }
25
+ function output(value, asJson, plain) {
26
+ process.stdout.write(asJson ? jsonText(value) : (plain ?? jsonText(value)));
27
+ }
28
+ function cliOperation(name) {
29
+ const operation = operationForSurfaceByName("cli", name);
30
+ if (!operation)
31
+ throw new Error(`CLI operation "${name}" is not declared`);
32
+ return operation;
33
+ }
34
+ function sleep(ms) {
35
+ return new Promise((resolve) => setTimeout(resolve, ms));
36
+ }
37
+ function requirePositional(positionals, index, label) {
38
+ const value = positionals[index];
39
+ if (!value)
40
+ throw new Error(`missing ${label}`);
41
+ return value;
42
+ }
43
+ function commaSeparatedFlag(parsed, name) {
44
+ const raw = stringFlag(parsed, name);
45
+ if (!raw)
46
+ return undefined;
47
+ return raw.split(",").map((value) => value.trim()).filter(Boolean);
48
+ }
49
+ function rejectConvexUrlFlag(parsed) {
50
+ if (Object.hasOwn(parsed.flags, "convex-url")) {
51
+ throw new Error("--convex-url is not supported; set RL_CONVEX_URL to target a non-production deployment");
52
+ }
53
+ }
54
+ async function commandLogin(parsed, asJson) {
55
+ const convexUrl = resolvedConvexUrl();
56
+ const target = resolvedTargetLabel();
57
+ const bootstrap = hasFlag(parsed, "bootstrap");
58
+ const explicitToken = stringFlag(parsed, "token") ?? process.env["RL_TOKEN"];
59
+ let token;
60
+ let account = {};
61
+ if (bootstrap) {
62
+ // Dev-only self-provisioning: mint a bootstrap root token, create the org below.
63
+ token = explicitToken ?? `rl_${randomBytes(24).toString("hex")}`;
64
+ }
65
+ else if (explicitToken) {
66
+ token = explicitToken;
67
+ }
68
+ else {
69
+ throw new Error(`Browser login is not available yet. ${TOKEN_ONBOARDING_HINT}`);
70
+ }
71
+ const runtime = createOperationRuntime({ convexUrl, token });
72
+ if (bootstrap) {
73
+ await runOperation(cliOperation("bootstrap_dev_org"), { runtime }, {
74
+ name: stringFlag(parsed, "org") ?? "Reasonlayer Dev",
75
+ });
76
+ }
77
+ const identity = await identifyOrg({ runtime });
78
+ if (account.orgId === undefined)
79
+ account = { ...account, orgId: identity.orgId };
80
+ const saved = await saveLogin(convexUrl, token, account);
81
+ const result = { ...identity, convexUrl, target, configPath: saved.configPath };
82
+ output(result, asJson, `Logged in to ${convexUrl} (${target}) as ${identity.name} (${identity.orgId})\nToken stored in the OS keychain\nConfig: ${saved.configPath}\n`);
83
+ }
84
+ async function identifyOrg(ctx) {
85
+ return await runOperation(cliOperation("whoami"), ctx, {});
86
+ }
87
+ async function commandLogout(asJson) {
88
+ const cleared = await clearLogin(resolvedConvexUrl());
89
+ output(cleared, asJson, cleared.convexUrl ? `Logged out of ${cleared.convexUrl}\n` : "No stored login found\n");
90
+ }
91
+ async function commandWhoami(asJson) {
92
+ const config = await loadConfig();
93
+ const result = await identifyOrg({ runtime: createOperationRuntime(config) });
94
+ const provenance = await sdkProvenance();
95
+ const target = { convexUrl: config.convexUrl, label: resolvedTargetLabel() };
96
+ output({ ...result, cli: provenance, target }, asJson, `${provenance.label}\nTarget: ${target.convexUrl} (${target.label})\n${result.name} (${result.orgId})\n`);
97
+ }
98
+ async function commandValidate(parsed, asJson) {
99
+ const result = (await runOperation(cliOperation("validate"), {}, { workflow: requirePositional(parsed.positionals, 0, "workflow path") }));
100
+ output(result, asJson, result.ok ? `Valid workflow ${result.manifest.workflowId}\n` : `${formatCompileErrors(result)}\n`);
101
+ return result.ok ? 0 : 1;
102
+ }
103
+ async function commandBuild(parsed, asJson) {
104
+ const entry = requirePositional(parsed.positionals, 0, "workflow path");
105
+ const outDir = stringFlag(parsed, "out-dir") ?? "dist";
106
+ const result = (await runOperation(cliOperation("build"), {}, { workflow: entry, outDir }));
107
+ if (!result.ok) {
108
+ if (asJson)
109
+ output(result, true);
110
+ else
111
+ process.stderr.write(`${formatCompileErrors(result)}\n`);
112
+ process.exitCode = 1;
113
+ return;
114
+ }
115
+ output(result, asJson, `Built ${result.manifest.workflowId} to ${result.outDir}\nCode steps: ${result.manifest.codeStepIds.join(", ") || "(none)"}\nAgent steps: ${result.manifest.agentStepIds.join(", ") || "(none)"}\n`);
116
+ }
117
+ async function commandDeploy(parsed, asJson) {
118
+ const runtime = createOperationRuntime(await loadConfig());
119
+ if (parsed.positionals.length > 0) {
120
+ throw new Error("rl deploy takes no arguments; run it inside a Reasonlayer checkout");
121
+ }
122
+ const result = await runOperation(cliOperation("publish_workspace"), { runtime }, {});
123
+ const published = result;
124
+ output(result, asJson, `Published ${published.workflowId ?? "workflow"} version ${published.versionNumber ?? "?"}\nVersion: ${published.versionId ?? "?"}\n`);
125
+ }
126
+ async function commandStartWorkspace(parsed, asJson, mode) {
127
+ const workflowId = requirePositional(parsed.positionals, 0, "workflow name");
128
+ const result = await runOperation(cliOperation(mode === "init" ? "init_workspace" : "pull_workspace"), { runtime: createOperationRuntime(await loadConfig()) }, {
129
+ workflowId,
130
+ ...(stringFlag(parsed, "dir") ? { targetDir: stringFlag(parsed, "dir") } : {}),
131
+ });
132
+ const checkout = result;
133
+ output(result, asJson, `${mode === "init" ? "Initialized" : "Pulled"} ${workflowId}\nCheckout: ${checkout.checkoutPath}\nSession: ${checkout.sessionId}\nWatcher: ${checkout.daemonPid}\n`);
134
+ }
135
+ async function commandDone(asJson) {
136
+ const result = await runOperation(cliOperation("done_workspace"), { runtime: createOperationRuntime(await loadConfig()) }, {});
137
+ const done = result;
138
+ output(result, asJson, `Finished ${done.workflowId ?? "workspace"}\nRemoved: ${done.checkoutPath}\n`);
139
+ }
140
+ async function commandStatus(asJson) {
141
+ const result = await runOperation(cliOperation("workspace_status"), { runtime: createOperationRuntime(await loadConfig()) }, {});
142
+ const status = result;
143
+ output(result, asJson, `${status.workflowId ?? "workspace"}: ${status.state ?? "unknown"}\nCompile: ${status.compile?.ok === true ? "green" : status.compile?.ok === false ? "red" : "pending"}\n`);
144
+ }
145
+ async function commandRun(parsed, asJson) {
146
+ const workflowId = requirePositional(parsed.positionals, 0, "workflow id");
147
+ const runtime = createOperationRuntime(await loadConfig());
148
+ const result = await runOperation(cliOperation("run"), { runtime }, {
149
+ workflowId,
150
+ input: await parseInputJson(stringFlag(parsed, "input-json")),
151
+ ...(stringFlag(parsed, "run-key") ? { runKey: stringFlag(parsed, "run-key") } : {}),
152
+ wait: !hasFlag(parsed, "no-wait"),
153
+ pollIntervalMs: numberFlag(parsed, "poll-interval-ms"),
154
+ timeoutMs: numberFlag(parsed, "timeout-ms"),
155
+ });
156
+ const status = result.status;
157
+ const runId = result.runId;
158
+ output(result, asJson, `Run ${runId ?? "(created)"}${status ? ` ${status}` : ""}\n${jsonText(result)}`);
159
+ }
160
+ async function commandDev(parsed, asJson) {
161
+ const workflow = requirePositional(parsed.positionals, 0, "workflow path");
162
+ const runtime = createOperationRuntime(await loadConfig());
163
+ const controller = new AbortController();
164
+ const timeoutMs = numberFlag(parsed, "timeout-ms");
165
+ const timeout = timeoutMs !== undefined ? setTimeout(() => controller.abort(), timeoutMs) : undefined;
166
+ let interrupted = false;
167
+ const onSignal = () => {
168
+ if (interrupted)
169
+ process.exit(130);
170
+ interrupted = true;
171
+ controller.abort();
172
+ };
173
+ process.once("SIGINT", onSignal);
174
+ process.once("SIGTERM", onSignal);
175
+ let result;
176
+ try {
177
+ result = await runOperation(cliOperation("dev"), { runtime }, {
178
+ workflow,
179
+ input: await parseInputJson(stringFlag(parsed, "input-json")),
180
+ ...(stringFlag(parsed, "run-key") ? { runKey: stringFlag(parsed, "run-key") } : {}),
181
+ pollIntervalMs: numberFlag(parsed, "poll-interval-ms"),
182
+ timeoutMs,
183
+ concurrency: numberFlag(parsed, "concurrency"),
184
+ signal: controller.signal,
185
+ onRunCreated: (created) => {
186
+ // Single-line NDJSON: supervisors parse stderr line by line.
187
+ process.stderr.write(`${JSON.stringify({ event: "run.created", ...created })}\n`);
188
+ },
189
+ });
190
+ }
191
+ finally {
192
+ if (timeout)
193
+ clearTimeout(timeout);
194
+ process.off("SIGINT", onSignal);
195
+ process.off("SIGTERM", onSignal);
196
+ }
197
+ if (result.ok === false) {
198
+ if (asJson)
199
+ output(result, true);
200
+ else
201
+ process.stderr.write(`${formatCompileErrors(result)}\n`);
202
+ process.exitCode = 1;
203
+ return;
204
+ }
205
+ const status = result.status;
206
+ const runId = result.runId;
207
+ output(result, asJson, `Dev run ${runId ?? "(created)"}${status ? ` ${status}` : ""}\n${jsonText(result)}`);
208
+ }
209
+ async function commandInspect(parsed, asJson) {
210
+ const runtime = createOperationRuntime(await loadConfig());
211
+ const target = parsed.positionals[0];
212
+ if (target === "runs") {
213
+ const value = await runOperation(cliOperation("list_runs"), { runtime }, {
214
+ limit: numberFlag(parsed, "limit"),
215
+ });
216
+ output(value, asJson, jsonText(value));
217
+ return;
218
+ }
219
+ if (target === "deployment") {
220
+ const deploymentId = requirePositional(parsed.positionals, 1, "deployment id");
221
+ const value = await runOperation(cliOperation("get_deployment"), { runtime }, { deploymentId });
222
+ output(value, asJson, jsonText(value));
223
+ return;
224
+ }
225
+ if (target !== "run")
226
+ throw new Error(`unknown inspect target "${target ?? ""}"`);
227
+ const runId = requirePositional(parsed.positionals, 1, "run id");
228
+ if (!hasFlag(parsed, "watch")) {
229
+ const value = await runOperation(cliOperation("inspect"), { runtime }, {
230
+ runId,
231
+ transcriptLimit: numberFlag(parsed, "transcript-limit") ?? 100,
232
+ });
233
+ const transcript = value.transcript;
234
+ const rendered = {
235
+ ...value,
236
+ transcriptPage: transcript,
237
+ transcript: transcript.events ?? [],
238
+ };
239
+ output(rendered, asJson, formatInspect(rendered));
240
+ return;
241
+ }
242
+ const pollIntervalMs = numberFlag(parsed, "poll-interval-ms") ?? 1000;
243
+ let cursor;
244
+ for (;;) {
245
+ const value = await runOperation(cliOperation("inspect"), { runtime }, {
246
+ runId,
247
+ ...(cursor !== undefined ? { transcriptCursor: cursor } : {}),
248
+ transcriptLimit: numberFlag(parsed, "transcript-limit") ?? 100,
249
+ });
250
+ const transcript = value.transcript;
251
+ const transcriptPage = transcript;
252
+ if (transcriptPage.nextCursor)
253
+ cursor = transcriptPage.nextCursor;
254
+ const rendered = {
255
+ ...value,
256
+ transcriptPage,
257
+ transcript: transcriptPage.events ?? [],
258
+ };
259
+ output(rendered, asJson, formatInspect(rendered));
260
+ const status = value.run?.status;
261
+ if (typeof status === "string" && isTerminalRunStatus(status))
262
+ return;
263
+ await sleep(pollIntervalMs);
264
+ }
265
+ }
266
+ async function commandFromTable(group, parsed, asJson, table) {
267
+ const subcommand = requirePositional(parsed.positionals, 0, `${group} subcommand`);
268
+ const definition = table[subcommand];
269
+ if (!definition)
270
+ throw new Error(`unknown ${group} command "${subcommand}"`);
271
+ const runtime = createOperationRuntime(await loadConfig());
272
+ const result = await runOperation(cliOperation(definition.operation), { runtime }, await definition.args(parsed));
273
+ output(result, asJson, definition.format?.(result) ?? jsonText(result));
274
+ }
275
+ const RUN_SUBCOMMANDS = {
276
+ list: {
277
+ usage: "runs list [--limit <n>]",
278
+ operation: "list_runs",
279
+ args: (parsed) => ({ limit: numberFlag(parsed, "limit") }),
280
+ },
281
+ cancel: {
282
+ usage: "runs cancel <run-id>",
283
+ operation: "cancel_run",
284
+ args: (parsed) => ({ runId: requirePositional(parsed.positionals, 1, "run id") }),
285
+ },
286
+ "cancel-step": {
287
+ usage: "runs cancel-step <run-id> <step-id> [--invocation-key <key>]",
288
+ operation: "cancel_step",
289
+ args: (parsed) => {
290
+ const invocationKey = stringFlag(parsed, "invocation-key");
291
+ return {
292
+ runId: requirePositional(parsed.positionals, 1, "run id"),
293
+ stepId: requirePositional(parsed.positionals, 2, "step id"),
294
+ ...(invocationKey !== undefined ? { invocationKey } : {}),
295
+ };
296
+ },
297
+ },
298
+ retry: {
299
+ usage: "runs retry <run-id>",
300
+ operation: "retry_run",
301
+ args: (parsed) => ({ runId: requirePositional(parsed.positionals, 1, "run id") }),
302
+ },
303
+ };
304
+ const REVIEW_SUBCOMMANDS = {
305
+ list: {
306
+ usage: "reviews list [--status <status>] [--limit <n>]",
307
+ operation: "list_reviews",
308
+ args: (parsed) => {
309
+ const status = stringFlag(parsed, "status");
310
+ const limit = numberFlag(parsed, "limit");
311
+ return {
312
+ ...(status !== undefined ? { status } : {}),
313
+ ...(limit !== undefined ? { limit } : {}),
314
+ };
315
+ },
316
+ },
317
+ approve: {
318
+ usage: "reviews approve <approval-id> --decision-json <json|@file|->",
319
+ operation: "approve_review",
320
+ args: async (parsed) => {
321
+ const rawDecision = stringFlag(parsed, "decision-json");
322
+ if (rawDecision === undefined)
323
+ throw new Error("reviews approve requires --decision-json");
324
+ return {
325
+ approvalId: requirePositional(parsed.positionals, 1, "approval id"),
326
+ decision: await parseInputJson(rawDecision),
327
+ };
328
+ },
329
+ },
330
+ reject: {
331
+ usage: "reviews reject <approval-id> [--reason <reason>]",
332
+ operation: "reject_review",
333
+ args: (parsed) => {
334
+ const reason = stringFlag(parsed, "reason");
335
+ return {
336
+ approvalId: requirePositional(parsed.positionals, 1, "approval id"),
337
+ ...(reason !== undefined ? { reason } : {}),
338
+ };
339
+ },
340
+ },
341
+ };
342
+ const TRIGGER_SUBCOMMANDS = {
343
+ list: { usage: "triggers list", operation: "list_triggers", args: () => ({}) },
344
+ enable: {
345
+ usage: "triggers enable <trigger-id>",
346
+ operation: "set_trigger_enabled",
347
+ args: (parsed) => ({
348
+ triggerId: requirePositional(parsed.positionals, 1, "trigger id"),
349
+ enabled: true,
350
+ }),
351
+ },
352
+ disable: {
353
+ usage: "triggers disable <trigger-id>",
354
+ operation: "set_trigger_enabled",
355
+ args: (parsed) => ({
356
+ triggerId: requirePositional(parsed.positionals, 1, "trigger id"),
357
+ enabled: false,
358
+ }),
359
+ },
360
+ };
361
+ const VERSION_SUBCOMMANDS = {
362
+ list: {
363
+ usage: "versions list <workflow-id>",
364
+ operation: "list_workflow_versions",
365
+ args: (parsed) => ({
366
+ workflowId: requirePositional(parsed.positionals, 1, "workflow id"),
367
+ }),
368
+ },
369
+ publish: {
370
+ usage: "versions publish <workflow-id> <version-id>",
371
+ operation: "publish_workflow_version",
372
+ args: (parsed) => ({
373
+ workflowId: requirePositional(parsed.positionals, 1, "workflow id"),
374
+ versionId: requirePositional(parsed.positionals, 2, "version id"),
375
+ }),
376
+ },
377
+ };
378
+ const WORKFLOW_SUBCOMMANDS = {
379
+ delete: {
380
+ usage: "workflow delete <workflow-id>",
381
+ operation: "delete_workflow",
382
+ args: (parsed) => ({ workflowId: requirePositional(parsed.positionals, 1, "workflow id") }),
383
+ },
384
+ };
385
+ const TOKEN_SUBCOMMANDS = {
386
+ list: { usage: "tokens list", operation: "list_tokens", args: () => ({}) },
387
+ create: {
388
+ usage: "tokens create --label <label> --scopes <scope,...> [--workflow-ids <id,...>] [--expires-at <ms>]",
389
+ operation: "create_token",
390
+ args: (parsed) => {
391
+ const label = stringFlag(parsed, "label");
392
+ const scopes = commaSeparatedFlag(parsed, "scopes");
393
+ if (!label || !scopes?.length)
394
+ throw new Error("tokens create requires --label and --scopes");
395
+ const workflowIds = commaSeparatedFlag(parsed, "workflow-ids");
396
+ const expiresAt = numberFlag(parsed, "expires-at");
397
+ return {
398
+ label,
399
+ scopes,
400
+ ...(workflowIds !== undefined ? { workflowIds } : {}),
401
+ ...(expiresAt !== undefined ? { expiresAt } : {}),
402
+ };
403
+ },
404
+ },
405
+ revoke: {
406
+ usage: "tokens revoke <token-id>",
407
+ operation: "revoke_token",
408
+ args: (parsed) => ({ tokenId: requirePositional(parsed.positionals, 1, "token id") }),
409
+ },
410
+ };
411
+ const DEVICE_SUBCOMMANDS = {
412
+ list: { usage: "device list", operation: "list_devices", args: () => ({}) },
413
+ register: {
414
+ usage: "device register --name <name> [--settings-json <json|@file|->]",
415
+ operation: "register_device",
416
+ args: async (parsed) => {
417
+ const name = stringFlag(parsed, "name");
418
+ if (!name)
419
+ throw new Error("device register requires --name");
420
+ const rawSettings = stringFlag(parsed, "settings-json");
421
+ const settings = rawSettings !== undefined
422
+ ? (await parseInputJson(rawSettings))
423
+ : undefined;
424
+ return {
425
+ name,
426
+ ...(settings !== undefined ? { settings } : {}),
427
+ };
428
+ },
429
+ },
430
+ bind: {
431
+ usage: "device bind <handle> <device-id>",
432
+ operation: "bind_device_handle",
433
+ args: (parsed) => ({
434
+ handle: requirePositional(parsed.positionals, 1, "device handle"),
435
+ deviceId: requirePositional(parsed.positionals, 2, "device id"),
436
+ }),
437
+ },
438
+ update: {
439
+ usage: "device update <device-id> [--name <name>] [--settings-json <json|@file|->] [--enable|--disable]",
440
+ operation: "update_device",
441
+ args: async (parsed) => {
442
+ const deviceId = requirePositional(parsed.positionals, 1, "device id");
443
+ const enabled = hasFlag(parsed, "enable") ? true : hasFlag(parsed, "disable") ? false : undefined;
444
+ const name = stringFlag(parsed, "name");
445
+ const rawSettings = stringFlag(parsed, "settings-json");
446
+ const settings = rawSettings !== undefined
447
+ ? (await parseInputJson(rawSettings))
448
+ : undefined;
449
+ return {
450
+ deviceId,
451
+ ...(name !== undefined ? { name } : {}),
452
+ ...(enabled !== undefined ? { enabled } : {}),
453
+ ...(settings !== undefined ? { settings } : {}),
454
+ };
455
+ },
456
+ },
457
+ "rotate-secret": {
458
+ usage: "device rotate-secret <device-id>",
459
+ operation: "rotate_device_secret",
460
+ args: (parsed) => ({ deviceId: requirePositional(parsed.positionals, 1, "device id") }),
461
+ },
462
+ profiles: {
463
+ usage: "device profiles [device-id]",
464
+ operation: "list_device_profiles",
465
+ args: (parsed) => parsed.positionals[1] !== undefined ? { deviceId: parsed.positionals[1] } : {},
466
+ },
467
+ "profile-create": {
468
+ usage: "device profile-create <device-id> --profile <key> [--label <label>]",
469
+ operation: "create_device_profile",
470
+ args: (parsed) => {
471
+ const deviceId = requirePositional(parsed.positionals, 1, "device id");
472
+ const profileKey = stringFlag(parsed, "profile");
473
+ if (!profileKey)
474
+ throw new Error("device profile-create requires --profile");
475
+ const label = stringFlag(parsed, "label");
476
+ return {
477
+ deviceId,
478
+ profileKey,
479
+ ...(label !== undefined ? { label } : {}),
480
+ };
481
+ },
482
+ },
483
+ login: {
484
+ usage: "device login <profile-id> [--message <message>]",
485
+ operation: "request_device_profile_login",
486
+ args: (parsed) => {
487
+ const profileId = requirePositional(parsed.positionals, 1, "profile id");
488
+ const message = stringFlag(parsed, "message");
489
+ return {
490
+ profileId,
491
+ ...(message !== undefined ? { message } : {}),
492
+ };
493
+ },
494
+ },
495
+ };
496
+ const SECRET_SUBCOMMANDS = {
497
+ set: {
498
+ usage: "secrets set <name> --value <value|->",
499
+ operation: "set_secret",
500
+ args: async (parsed) => {
501
+ const name = requirePositional(parsed.positionals, 1, "secret name");
502
+ const rawValue = stringFlag(parsed, "value");
503
+ const value = hasFlag(parsed, "value-stdin") || rawValue === "-"
504
+ ? await readStdin()
505
+ : rawValue;
506
+ if (value === undefined)
507
+ throw new Error("secrets set requires --value or --value-stdin");
508
+ return { name, value };
509
+ },
510
+ },
511
+ list: { usage: "secrets list", operation: "list_secrets", args: () => ({}) },
512
+ remove: {
513
+ usage: "secrets remove <name>",
514
+ operation: "remove_secret",
515
+ args: (parsed) => ({ name: requirePositional(parsed.positionals, 1, "secret name") }),
516
+ },
517
+ };
518
+ const VARIABLE_SUBCOMMANDS = {
519
+ list: {
520
+ usage: "variables list <workflow>",
521
+ operation: "list_variables",
522
+ args: (parsed) => ({
523
+ workflowId: requirePositional(parsed.positionals, 1, "workflow id"),
524
+ }),
525
+ format: formatVariableList,
526
+ },
527
+ set: {
528
+ usage: "variables set <workflow> <name> <json-value>",
529
+ operation: "set_variable",
530
+ args: (parsed) => ({
531
+ workflowId: requirePositional(parsed.positionals, 1, "workflow id"),
532
+ name: requirePositional(parsed.positionals, 2, "variable name"),
533
+ value: parseVariableValue(requirePositional(parsed.positionals, 3, "variable value")),
534
+ }),
535
+ format: (result) => {
536
+ const value = result;
537
+ return `Set ${value.name} for ${value.workflowId}.\n`;
538
+ },
539
+ },
540
+ remove: {
541
+ usage: "variables remove <workflow> <name>",
542
+ operation: "remove_variable",
543
+ args: (parsed) => ({
544
+ workflowId: requirePositional(parsed.positionals, 1, "workflow id"),
545
+ name: requirePositional(parsed.positionals, 2, "variable name"),
546
+ }),
547
+ format: (result) => {
548
+ const value = result;
549
+ return `Removed ${value.name} override for ${value.workflowId}; the default now applies.\n`;
550
+ },
551
+ },
552
+ };
553
+ /** Help text for an operation-backed command comes from the operation table. */
554
+ function helpFor(usage, operation) {
555
+ return { usage, summary: cliOperation(operation).title };
556
+ }
557
+ function group(name, table) {
558
+ return {
559
+ help: Object.values(table).map((entry) => helpFor(entry.usage, entry.operation)),
560
+ run: async (parsed, asJson) => {
561
+ await commandFromTable(name, parsed, asJson, table);
562
+ return 0;
563
+ },
564
+ };
565
+ }
566
+ const COMMANDS = {
567
+ login: {
568
+ help: [
569
+ { usage: "login --token <token>", summary: "Store an org token in the OS keychain" },
570
+ { usage: "login --bootstrap [--org <name>]", summary: cliOperation("bootstrap_dev_org").title },
571
+ ],
572
+ run: async (parsed, asJson) => {
573
+ await commandLogin(parsed, asJson);
574
+ return process.exitCode === 1 ? 1 : 0;
575
+ },
576
+ },
577
+ logout: {
578
+ help: [{ usage: "logout", summary: "Forget the stored login for this deployment" }],
579
+ run: async (_parsed, asJson) => {
580
+ await commandLogout(asJson);
581
+ return 0;
582
+ },
583
+ },
584
+ whoami: {
585
+ help: [helpFor("whoami", "whoami")],
586
+ run: async (_parsed, asJson) => {
587
+ await commandWhoami(asJson);
588
+ return 0;
589
+ },
590
+ },
591
+ init: {
592
+ help: [helpFor("init <name> [--dir <checkout>]", "init_workspace")],
593
+ run: async (parsed, asJson) => {
594
+ await commandStartWorkspace(parsed, asJson, "init");
595
+ return 0;
596
+ },
597
+ },
598
+ pull: {
599
+ help: [helpFor("pull <name> [--dir <checkout>]", "pull_workspace")],
600
+ run: async (parsed, asJson) => {
601
+ await commandStartWorkspace(parsed, asJson, "pull");
602
+ return 0;
603
+ },
604
+ },
605
+ done: {
606
+ help: [helpFor("done", "done_workspace")],
607
+ run: async (_parsed, asJson) => {
608
+ await commandDone(asJson);
609
+ return 0;
610
+ },
611
+ },
612
+ status: {
613
+ help: [helpFor("status", "workspace_status")],
614
+ run: async (_parsed, asJson) => {
615
+ await commandStatus(asJson);
616
+ return 0;
617
+ },
618
+ },
619
+ validate: {
620
+ help: [helpFor("validate <workflow.ts>", "validate")],
621
+ run: commandValidate,
622
+ },
623
+ build: {
624
+ help: [helpFor("build <workflow.ts> [--out-dir <dist>]", "build")],
625
+ run: async (parsed, asJson) => {
626
+ await commandBuild(parsed, asJson);
627
+ return process.exitCode === 1 ? 1 : 0;
628
+ },
629
+ },
630
+ deploy: {
631
+ help: [helpFor("deploy", "publish_workspace")],
632
+ run: async (parsed, asJson) => {
633
+ await commandDeploy(parsed, asJson);
634
+ return 0;
635
+ },
636
+ },
637
+ run: {
638
+ help: [helpFor("run <workflow> [--input-json <json|@file|->] [--run-key <key>] [--no-wait]", "run")],
639
+ run: async (parsed, asJson) => {
640
+ await commandRun(parsed, asJson);
641
+ return 0;
642
+ },
643
+ },
644
+ dev: {
645
+ help: [helpFor("dev <workflow.ts> [--input-json <json|@file|->] [--run-key <key>] [--concurrency <n>]", "dev")],
646
+ run: async (parsed, asJson) => {
647
+ await commandDev(parsed, asJson);
648
+ return process.exitCode === 1 ? 1 : 0;
649
+ },
650
+ },
651
+ inspect: {
652
+ help: [
653
+ helpFor("inspect run <run-id> [--watch] [--transcript-limit <n>]", "inspect"),
654
+ helpFor("inspect runs [--limit <n>]", "list_runs"),
655
+ helpFor("inspect deployment <deployment-id>", "get_deployment"),
656
+ ],
657
+ run: async (parsed, asJson) => {
658
+ await commandInspect(parsed, asJson);
659
+ return 0;
660
+ },
661
+ },
662
+ runs: group("runs", RUN_SUBCOMMANDS),
663
+ reviews: group("reviews", REVIEW_SUBCOMMANDS),
664
+ triggers: group("triggers", TRIGGER_SUBCOMMANDS),
665
+ versions: group("versions", VERSION_SUBCOMMANDS),
666
+ workflow: group("workflow", WORKFLOW_SUBCOMMANDS),
667
+ tokens: group("tokens", TOKEN_SUBCOMMANDS),
668
+ secrets: group("secrets", SECRET_SUBCOMMANDS),
669
+ variables: group("variables", VARIABLE_SUBCOMMANDS),
670
+ device: group("device", DEVICE_SUBCOMMANDS),
671
+ };
672
+ const SUMMARY_COLUMN = 60;
673
+ function helpLine({ usage, summary }) {
674
+ if (usage.length > SUMMARY_COLUMN)
675
+ return ` ${usage}\n ${" ".repeat(SUMMARY_COLUMN)} ${summary}`;
676
+ return ` ${usage.padEnd(SUMMARY_COLUMN)} ${summary}`;
677
+ }
678
+ function helpText() {
679
+ const lines = Object.values(COMMANDS).flatMap((command) => command.help);
680
+ return `rl
681
+
682
+ Commands:
683
+ ${lines.map(helpLine).join("\n")}
684
+
685
+ Global:
686
+ --json
687
+ --version
688
+
689
+ Authentication:
690
+ Browser onboarding is not available yet.
691
+ ${TOKEN_ONBOARDING_HINT.replaceAll("\n", "\n ")}
692
+ `;
693
+ }
694
+ export async function main(argv) {
695
+ const parsed = parseCliArgs(argv);
696
+ const asJson = hasFlag(parsed, "json");
697
+ try {
698
+ rejectConvexUrlFlag(parsed);
699
+ if (hasFlag(parsed, "version") || parsed.command === "version") {
700
+ const provenance = await sdkProvenance();
701
+ output(provenance, asJson, `${provenance.label}\n`);
702
+ return 0;
703
+ }
704
+ if (!parsed.command || parsed.command === "help" || hasFlag(parsed, "help")) {
705
+ process.stdout.write(helpText());
706
+ return 0;
707
+ }
708
+ const command = COMMANDS[parsed.command];
709
+ if (!command)
710
+ throw new Error(`unknown command "${parsed.command}"`);
711
+ return await command.run(parsed, asJson);
712
+ }
713
+ catch (err) {
714
+ const message = err instanceof Error ? err.message : String(err);
715
+ if (asJson)
716
+ process.stderr.write(jsonText({ error: message }));
717
+ else
718
+ process.stderr.write(`${message}\n`);
719
+ return 1;
720
+ }
721
+ }