@runtypelabs/cli 2.59.1 → 2.59.2

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 (84) hide show
  1. package/dist/chunk-0-T9NWjH.js +3899 -0
  2. package/dist/chunk-0x7UKC1U.js +17 -0
  3. package/dist/chunk-2ySxXtcg.js +418 -0
  4. package/dist/chunk-3An_BRHv.js +501 -0
  5. package/dist/chunk-6uTxgdiY.js +16218 -0
  6. package/dist/chunk-87nRnNRG.js +1363 -0
  7. package/dist/chunk-B3wo8uRx.js +322 -0
  8. package/dist/chunk-BGp3ZdBy.js +560 -0
  9. package/dist/chunk-BJQv9Elp.js +107 -0
  10. package/dist/chunk-BOpO6xE-.js +6053 -0
  11. package/dist/chunk-BQXXCL8B.js +102 -0
  12. package/dist/chunk-BV-8isOd.js +34 -0
  13. package/dist/chunk-BVMhjGVe.js +826 -0
  14. package/dist/chunk-BW2fg46T.js +141 -0
  15. package/dist/chunk-BWJdY5Ag.js +36 -0
  16. package/dist/chunk-BYJOrLhh.js +302 -0
  17. package/dist/chunk-BYab8EVL.js +127 -0
  18. package/dist/chunk-BfEKvp7h.js +82 -0
  19. package/dist/chunk-BhgLSfH6.js +1123 -0
  20. package/dist/chunk-BirUxTtH.js +296 -0
  21. package/dist/chunk-BmB6UVZ9.js +1559 -0
  22. package/dist/chunk-BnWhIqW1.js +109 -0
  23. package/dist/chunk-Bpfnfk0t.js +123 -0
  24. package/dist/chunk-BrhgdnM3.js +108 -0
  25. package/dist/chunk-Bt0XAgX5.js +327 -0
  26. package/dist/chunk-Bu0FJr6S.js +84 -0
  27. package/dist/chunk-BvEoKpN3.js +255 -0
  28. package/dist/chunk-BxNJgYbE.js +28 -0
  29. package/dist/chunk-C1OFhd7F.js +6131 -0
  30. package/dist/chunk-C7e7WwbU.js +331 -0
  31. package/dist/chunk-CDTYP6bN.js +558 -0
  32. package/dist/chunk-CGDQoqDC.js +24 -0
  33. package/dist/chunk-CJraEBVd.js +127 -0
  34. package/dist/chunk-CQrNhOtv.js +40 -0
  35. package/dist/chunk-CW6AzBH6.js +128 -0
  36. package/dist/chunk-CXgL4w-9.js +317 -0
  37. package/dist/chunk-CZrqoPyf.js +233 -0
  38. package/dist/chunk-CcFCyqbB.js +83 -0
  39. package/dist/chunk-CdaIvO-y.js +485 -0
  40. package/dist/chunk-CehSXtgK.js +624 -0
  41. package/dist/chunk-Ciqkop65.js +218 -0
  42. package/dist/chunk-CoAWdSiE.js +43 -0
  43. package/dist/chunk-Crb4y7t4.js +830 -0
  44. package/dist/chunk-CrlqIfmb.js +337 -0
  45. package/dist/chunk-Cs4pwCQ-.js +324 -0
  46. package/dist/chunk-CwM_9y01.js +519 -0
  47. package/dist/chunk-CyRbIJg8.js +440 -0
  48. package/dist/chunk-D0OHsyMy.js +212 -0
  49. package/dist/chunk-D3y2bURs.js +1347 -0
  50. package/dist/chunk-D4_mJk03.js +2338 -0
  51. package/dist/chunk-D6C8rned.js +578 -0
  52. package/dist/chunk-D9ZbUnks.js +11 -0
  53. package/dist/chunk-DEPmLshX.js +834 -0
  54. package/dist/chunk-DFj1UPB2.js +330 -0
  55. package/dist/chunk-DGdFdZVf.js +197 -0
  56. package/dist/chunk-DGv7X9S_.js +63 -0
  57. package/dist/chunk-DJQ47WYZ.js +664 -0
  58. package/dist/chunk-DJw3O61j.js +1974 -0
  59. package/dist/chunk-DK4FbWVK.js +100 -0
  60. package/dist/chunk-DSRYhUus.js +993 -0
  61. package/dist/chunk-DajZ9Eg4.js +299 -0
  62. package/dist/chunk-Df4dunvU.js +195 -0
  63. package/dist/chunk-DjNTPhfO.js +481 -0
  64. package/dist/chunk-Dn8eaJuu.js +1042 -0
  65. package/dist/chunk-DwDWqSxM.js +637 -0
  66. package/dist/chunk-Dz7cEtxl.js +84 -0
  67. package/dist/chunk-E_0bgjrS.js +359 -0
  68. package/dist/chunk-KPYN2vUp.js +776 -0
  69. package/dist/chunk-KcqPQJBi.js +86 -0
  70. package/dist/chunk-N9H0JnoG.js +349 -0
  71. package/dist/chunk-NONFcXkn.js +728 -0
  72. package/dist/chunk-PrFjcpmH.js +256 -0
  73. package/dist/chunk-QSfNYiw9.js +11 -0
  74. package/dist/chunk-R265bzj9.js +226 -0
  75. package/dist/chunk-X9V_r6jM.js +166 -0
  76. package/dist/chunk-ZBtPUtyA.js +55 -0
  77. package/dist/chunk-ZCTfmOs7.js +4939 -0
  78. package/dist/chunk-h6oavj0t.js +97 -0
  79. package/dist/chunk-i7DwkvvD.js +42 -0
  80. package/dist/chunk-mG-4eZ6S.js +203 -0
  81. package/dist/chunk-o0ygUbCf.js +313 -0
  82. package/dist/chunk-ucu5lrDZ.js +5715 -0
  83. package/dist/index.js +11367 -73789
  84. package/package.json +4 -4
@@ -0,0 +1,558 @@
1
+ #!/usr/bin/env node
2
+ import { t as isTTY } from "./index.js";
3
+ import { EntityCard, MutationResult, React, render, useEffect, useState } from "./chunk-CoAWdSiE.js";
4
+ import { r as createCliClient } from "./chunk-BW2fg46T.js";
5
+ import { n as ensureAuth } from "./chunk-CJraEBVd.js";
6
+ import { a as printJson } from "./chunk-CcFCyqbB.js";
7
+ import { Command, InvalidArgumentError } from "commander";
8
+ import chalk from "chalk";
9
+ import { readFileSync } from "fs";
10
+ // ../shared/dist/batch-execution-status.mjs
11
+ /**
12
+ * Lifecycle of a batch execution as the API writes it: `batch_executions.status` and the
13
+ * BatchExecutionDO state. Every SDK, CLI and MCP consumer classifies batch status through this list.
14
+ */
15
+ const BATCH_EXECUTION_STATUSES = [
16
+ "queued",
17
+ "processing",
18
+ "completed",
19
+ "failed",
20
+ "cancelled"
21
+ ];
22
+ const TERMINAL_BATCH_EXECUTION_STATUSES = [
23
+ "completed",
24
+ "failed",
25
+ "cancelled"
26
+ ];
27
+ function isBatchExecutionStatus(value) {
28
+ return BATCH_EXECUTION_STATUSES.includes(value);
29
+ }
30
+ /** False for an unknown status: a value this list does not name must never read as finished. */
31
+ function isTerminalBatchExecutionStatus(status) {
32
+ return TERMINAL_BATCH_EXECUTION_STATUSES.includes(status);
33
+ }
34
+ // src/lib/wait-for-status.ts
35
+ const MAX_WAIT_DURATION_MS = 2147483647;
36
+ /** Poll read-only status until terminal, timeout, or caller abort; never cancel the remote task. */
37
+ async function waitForStatus(options) {
38
+ for (const duration of [options.timeoutMs, options.intervalMs]) if (!Number.isInteger(duration) || duration <= 0 || duration > 2147483647) throw new RangeError(`Wait durations must be integers between 1 and ${MAX_WAIT_DURATION_MS} ms`);
39
+ options.signal?.throwIfAborted();
40
+ const controller = new AbortController();
41
+ const timeout = /* @__PURE__ */ new Error("Status wait timed out");
42
+ const abort = () => controller.abort(options.signal?.reason);
43
+ options.signal?.addEventListener("abort", abort, { once: true });
44
+ const deadlineTimer = setTimeout(() => controller.abort(timeout), options.timeoutMs);
45
+ let sleepTimer;
46
+ let value;
47
+ let rejectAborted = () => {};
48
+ const aborted = new Promise((_resolve, reject) => {
49
+ rejectAborted = () => reject(controller.signal.reason);
50
+ controller.signal.addEventListener("abort", rejectAborted, { once: true });
51
+ });
52
+ try {
53
+ for (;;) {
54
+ controller.signal.throwIfAborted();
55
+ value = await Promise.race([options.read(controller.signal), aborted]);
56
+ controller.signal.throwIfAborted();
57
+ const terminal = options.isTerminal(value);
58
+ options.onStatus?.(value);
59
+ if (terminal) return {
60
+ timedOut: false,
61
+ value
62
+ };
63
+ await Promise.race([new Promise((resolve) => {
64
+ sleepTimer = setTimeout(resolve, options.intervalMs);
65
+ }), aborted]);
66
+ }
67
+ } catch (error) {
68
+ if (controller.signal.aborted && controller.signal.reason === timeout) return {
69
+ timedOut: true,
70
+ value
71
+ };
72
+ throw error;
73
+ } finally {
74
+ clearTimeout(deadlineTimer);
75
+ clearTimeout(sleepTimer);
76
+ options.signal?.removeEventListener("abort", abort);
77
+ controller.signal.removeEventListener("abort", rejectAborted);
78
+ controller.abort();
79
+ }
80
+ }
81
+ // src/commands/batch.ts
82
+ function parseWatchDuration(value) {
83
+ const duration = Number(value);
84
+ if (!/^\d+$/.test(value) || !Number.isInteger(duration) || duration <= 0 || duration > 2147483647) throw new InvalidArgumentError(`Expected an integer between 1 and ${MAX_WAIT_DURATION_MS} ms`);
85
+ return duration;
86
+ }
87
+ /** Unknown statuses keep polling with one warning each, so a newer API state never ends a watch early. */
88
+ function createBatchTerminalCheck(warn) {
89
+ const warnedStatuses = /* @__PURE__ */ new Set();
90
+ return (data) => {
91
+ if (!isBatchExecutionStatus(data.status) && !warnedStatuses.has(data.status)) {
92
+ warnedStatuses.add(data.status);
93
+ warn(`Unknown batch status "${data.status}"; continuing to poll until a terminal status`);
94
+ }
95
+ return isTerminalBatchExecutionStatus(data.status);
96
+ };
97
+ }
98
+ function parseBatchStatusResponse(value) {
99
+ if (!value || typeof value !== "object" || !("status" in value) || typeof value.status !== "string") throw new Error("Invalid batch status response: expected an object with a status string");
100
+ for (const key of [
101
+ "processedRecords",
102
+ "totalRecords",
103
+ "failedRecords"
104
+ ]) {
105
+ if (!(key in value)) throw new Error(`Invalid batch status response: missing ${key}`);
106
+ const count = value[key];
107
+ if (typeof count !== "number" || !Number.isInteger(count) || count < 0) throw new Error(`Invalid batch status response: ${key} must be a non-negative integer`);
108
+ }
109
+ return value;
110
+ }
111
+ const batchCommand = new Command("batch").description("Manage batch operations");
112
+ batchCommand.command("submit").description("Submit a batch job").requiredOption("-f, --flow <id>", "Flow ID to execute").requiredOption("-r, --records <file>", "JSON file with record IDs").option("--json", "Output as JSON").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (options) => {
113
+ const apiKey = await ensureAuth();
114
+ if (!apiKey) return;
115
+ let recordIds;
116
+ try {
117
+ const content = readFileSync(options.records, "utf-8");
118
+ const parsed = JSON.parse(content);
119
+ recordIds = Array.isArray(parsed) ? parsed : parsed.recordIds || parsed.records || [];
120
+ } catch (error) {
121
+ const msg = error instanceof Error ? error.message : "Unknown error";
122
+ console.error(chalk.red(`Failed to read records file: ${msg}`));
123
+ process.exit(1);
124
+ }
125
+ const client = createCliClient(apiKey);
126
+ if (!isTTY(options) || options.json) {
127
+ try {
128
+ const data = await client.post("/batch/flow", {
129
+ flowId: options.flow,
130
+ recordIds
131
+ });
132
+ if (options.json) printJson(data);
133
+ else {
134
+ console.log(chalk.green("Batch submitted"));
135
+ console.log(` Batch ID: ${chalk.green(data.batchExecutionId)}`);
136
+ console.log(` Status: ${data.status}`);
137
+ }
138
+ } catch (error) {
139
+ const message = error instanceof Error ? error.message : "Unknown error";
140
+ console.error(chalk.red("Failed to submit batch"));
141
+ console.error(chalk.red(message));
142
+ process.exit(1);
143
+ }
144
+ return;
145
+ }
146
+ const App = () => {
147
+ const [loading, setLoading] = useState(true);
148
+ const [success, setSuccess] = useState(null);
149
+ const [error, setError] = useState(null);
150
+ const [resultNode, setResultNode] = useState(void 0);
151
+ useEffect(() => {
152
+ const run = async () => {
153
+ try {
154
+ const data = await client.post("/batch/flow", {
155
+ flowId: options.flow,
156
+ recordIds
157
+ });
158
+ setResultNode(React.createElement(EntityCard, { fields: [{
159
+ label: "Batch ID",
160
+ value: data.batchExecutionId,
161
+ color: "green"
162
+ }, {
163
+ label: "Status",
164
+ value: data.status
165
+ }] }));
166
+ setSuccess(true);
167
+ setLoading(false);
168
+ } catch (err) {
169
+ setError(err instanceof Error ? err : new Error(String(err)));
170
+ setSuccess(false);
171
+ setLoading(false);
172
+ }
173
+ };
174
+ run();
175
+ }, []);
176
+ return React.createElement(MutationResult, {
177
+ loading,
178
+ loadingLabel: `Submitting batch with ${recordIds.length} records...`,
179
+ success,
180
+ successMessage: "Batch submitted",
181
+ error,
182
+ result: resultNode
183
+ });
184
+ };
185
+ const { waitUntilExit } = render(React.createElement(App));
186
+ await waitUntilExit();
187
+ });
188
+ batchCommand.command("status <id>").description("Check batch job status").option("--watch", "Poll until terminal status; stop locally after the timeout without cancelling the batch").option("--timeout <ms>", "Watch deadline in ms (1–2147483647; default: 300000); requires --watch", parseWatchDuration).option("--interval <ms>", "Watch poll interval in ms (1–2147483647; default: 2000); requires --watch", parseWatchDuration).option("--json", "Output as JSON").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (batchId, options, command) => {
189
+ if (!options.watch && (options.timeout !== void 0 || options.interval !== void 0)) command.error("--timeout and --interval require --watch");
190
+ if (options.watch) {
191
+ try {
192
+ const apiKey = await ensureAuth({
193
+ exitOnFailure: false,
194
+ interactive: false
195
+ });
196
+ if (!apiKey) throw Object.assign(/* @__PURE__ */ new Error("Authentication required. Run \"runtype auth login\" first."), { code: "AUTH_REQUIRED" });
197
+ const client = createCliClient(apiKey);
198
+ const timeoutMs = options.timeout ?? 3e5;
199
+ const result = await waitForStatus({
200
+ read: async (signal) => {
201
+ return parseBatchStatusResponse(await (await client.requestStream(`/batch/status/${batchId}`, {
202
+ method: "GET",
203
+ signal
204
+ })).json());
205
+ },
206
+ isTerminal: createBatchTerminalCheck((message) => console.error(message)),
207
+ timeoutMs,
208
+ intervalMs: options.interval ?? 2e3,
209
+ onStatus: (data) => {
210
+ console.error(`Batch ${batchId}: ${data.status} (${data.processedRecords}/${data.totalRecords}, ${data.failedRecords} failed)`);
211
+ }
212
+ });
213
+ if (result.timedOut) {
214
+ const message = `Timed out watching batch ${batchId} after ${timeoutMs} ms. The server batch was not cancelled.`;
215
+ if (options.json) printJson({
216
+ success: false,
217
+ batchExecutionId: batchId,
218
+ status: "timeout",
219
+ lastStatus: result.value ?? null,
220
+ error: {
221
+ code: "BATCH_WATCH_TIMEOUT",
222
+ message
223
+ }
224
+ });
225
+ console.error(message);
226
+ process.exitCode = 1;
227
+ return;
228
+ }
229
+ const data = result.value;
230
+ if (options.json) printJson(data);
231
+ else console.log(`Batch Job: ${batchId}\n Status: ${data.status}\n Progress: ${data.processedRecords}/${data.totalRecords}\n Failed: ${data.failedRecords}`);
232
+ if (data.status !== "completed" || data.failedRecords > 0) process.exitCode = 1;
233
+ } catch (error) {
234
+ const message = error instanceof Error ? error.message : "Unknown error";
235
+ const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : "BATCH_WATCH_ERROR";
236
+ if (options.json) printJson({
237
+ success: false,
238
+ batchExecutionId: batchId,
239
+ error: {
240
+ code,
241
+ message
242
+ }
243
+ });
244
+ console.error(`Failed to watch batch status: ${message}`);
245
+ process.exitCode = 1;
246
+ }
247
+ return;
248
+ }
249
+ const apiKey = await ensureAuth();
250
+ if (!apiKey) return;
251
+ const client = createCliClient(apiKey);
252
+ if (!isTTY(options) || options.json) {
253
+ try {
254
+ const data = await client.get(`/batch/status/${batchId}`);
255
+ if (options.json) printJson(data);
256
+ else {
257
+ console.log(chalk.cyan(`Batch Job: ${batchId}`));
258
+ console.log(` Status: ${chalk.green(data.status)}`);
259
+ console.log(` Progress: ${data.processedRecords}/${data.totalRecords}`);
260
+ if (data.failedRecords > 0) console.log(` Failed: ${chalk.red(data.failedRecords)}`);
261
+ }
262
+ } catch (error) {
263
+ const message = error instanceof Error ? error.message : "Unknown error";
264
+ console.error(chalk.red("Failed to fetch batch status"));
265
+ console.error(chalk.red(message));
266
+ process.exit(1);
267
+ }
268
+ return;
269
+ }
270
+ const App = () => {
271
+ const [loading, setLoading] = useState(true);
272
+ const [success, setSuccess] = useState(null);
273
+ const [error, setError] = useState(null);
274
+ const [resultNode, setResultNode] = useState(void 0);
275
+ useEffect(() => {
276
+ const run = async () => {
277
+ try {
278
+ const data = await client.get(`/batch/status/${batchId}`);
279
+ const fields = [
280
+ {
281
+ label: "Batch ID",
282
+ value: batchId
283
+ },
284
+ {
285
+ label: "Status",
286
+ value: data.status,
287
+ color: data.status === "completed" ? "green" : "yellow"
288
+ },
289
+ {
290
+ label: "Progress",
291
+ value: `${data.processedRecords}/${data.totalRecords}`
292
+ }
293
+ ];
294
+ if (data.failedRecords > 0) fields.push({
295
+ label: "Failed",
296
+ value: data.failedRecords,
297
+ color: "red"
298
+ });
299
+ setResultNode(React.createElement(EntityCard, { fields }));
300
+ setSuccess(true);
301
+ setLoading(false);
302
+ } catch (err) {
303
+ setError(err instanceof Error ? err : new Error(String(err)));
304
+ setSuccess(false);
305
+ setLoading(false);
306
+ }
307
+ };
308
+ run();
309
+ }, []);
310
+ return React.createElement(MutationResult, {
311
+ loading,
312
+ loadingLabel: "Fetching batch status...",
313
+ success,
314
+ successMessage: "Batch Status",
315
+ error,
316
+ result: resultNode
317
+ });
318
+ };
319
+ const { waitUntilExit } = render(React.createElement(App));
320
+ await waitUntilExit();
321
+ });
322
+ batchCommand.command("cancel <id>").description("Cancel a batch job").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (batchId, options) => {
323
+ const apiKey = await ensureAuth();
324
+ if (!apiKey) return;
325
+ const client = createCliClient(apiKey);
326
+ if (!isTTY(options)) {
327
+ try {
328
+ await client.post(`/batch/cancel/${batchId}`);
329
+ console.log(chalk.green("Batch job cancelled"));
330
+ } catch (error) {
331
+ const message = error instanceof Error ? error.message : "Unknown error";
332
+ console.error(chalk.red("Failed to cancel batch job"));
333
+ console.error(chalk.red(message));
334
+ process.exit(1);
335
+ }
336
+ return;
337
+ }
338
+ const App = () => {
339
+ const [loading, setLoading] = useState(true);
340
+ const [success, setSuccess] = useState(null);
341
+ const [error, setError] = useState(null);
342
+ useEffect(() => {
343
+ const run = async () => {
344
+ try {
345
+ await client.post(`/batch/cancel/${batchId}`);
346
+ setSuccess(true);
347
+ setLoading(false);
348
+ } catch (err) {
349
+ setError(err instanceof Error ? err : new Error(String(err)));
350
+ setSuccess(false);
351
+ setLoading(false);
352
+ }
353
+ };
354
+ run();
355
+ }, []);
356
+ return React.createElement(MutationResult, {
357
+ loading,
358
+ loadingLabel: "Cancelling batch job...",
359
+ success,
360
+ successMessage: "Batch job cancelled",
361
+ error
362
+ });
363
+ };
364
+ const { waitUntilExit } = render(React.createElement(App));
365
+ await waitUntilExit();
366
+ });
367
+ batchCommand.command("summary").description("Show a summary of active and recently completed batch jobs").option("--json", "Output as JSON").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (options) => {
368
+ const apiKey = await ensureAuth();
369
+ if (!apiKey) return;
370
+ const client = createCliClient(apiKey);
371
+ if (!isTTY(options) || options.json) {
372
+ try {
373
+ const data = await client.get("/batch/summary");
374
+ if (options.json) printJson(data);
375
+ else {
376
+ console.log(chalk.cyan("Batch Summary"));
377
+ console.log(` Active: ${chalk.green(data.activeBatches)}`);
378
+ console.log(` Recently completed: ${data.recentlyCompleted}`);
379
+ if (data.batches.length > 0) {
380
+ console.log(chalk.cyan("\nActive Batches:"));
381
+ for (const b of data.batches) console.log(` ${chalk.green(b.id)} ${b.flowName ?? ""} — ${b.status} (${b.processedRecords}/${b.totalRecords}, ${b.progress}%)`);
382
+ }
383
+ if (data.recent.length > 0) {
384
+ console.log(chalk.cyan("\nRecently Completed:"));
385
+ for (const b of data.recent) {
386
+ const statusColor = b.status === "completed" ? chalk.green(b.status) : chalk.red(b.status);
387
+ console.log(` ${chalk.green(b.id)} ${b.flowName ?? ""} — ${statusColor} (${b.processedRecords}/${b.totalRecords})`);
388
+ }
389
+ }
390
+ if (data.batches.length === 0 && data.recent.length === 0) console.log(chalk.gray("\nNo active or recent batch jobs"));
391
+ }
392
+ } catch (error) {
393
+ const message = error instanceof Error ? error.message : "Unknown error";
394
+ console.error(chalk.red("Failed to fetch batch summary"));
395
+ console.error(chalk.red(message));
396
+ process.exit(1);
397
+ }
398
+ return;
399
+ }
400
+ const App = () => {
401
+ const [loading, setLoading] = useState(true);
402
+ const [success, setSuccess] = useState(null);
403
+ const [error, setError] = useState(null);
404
+ const [resultNode, setResultNode] = useState(void 0);
405
+ useEffect(() => {
406
+ const run = async () => {
407
+ try {
408
+ const data = await client.get("/batch/summary");
409
+ const fields = [{
410
+ label: "Active",
411
+ value: data.activeBatches,
412
+ color: "green"
413
+ }, {
414
+ label: "Recently completed",
415
+ value: data.recentlyCompleted
416
+ }];
417
+ for (const b of data.batches) fields.push({
418
+ label: b.id,
419
+ value: `${b.flowName ?? ""} — ${b.status} (${b.processedRecords}/${b.totalRecords}, ${b.progress}%)`,
420
+ color: "yellow"
421
+ });
422
+ for (const b of data.recent) fields.push({
423
+ label: b.id,
424
+ value: `${b.flowName ?? ""} — ${b.status} (${b.processedRecords}/${b.totalRecords})`,
425
+ color: b.status === "completed" ? "green" : "red"
426
+ });
427
+ setResultNode(React.createElement(EntityCard, { fields }));
428
+ setSuccess(true);
429
+ setLoading(false);
430
+ } catch (err) {
431
+ setError(err instanceof Error ? err : new Error(String(err)));
432
+ setSuccess(false);
433
+ setLoading(false);
434
+ }
435
+ };
436
+ run();
437
+ }, []);
438
+ return React.createElement(MutationResult, {
439
+ loading,
440
+ loadingLabel: "Fetching batch summary...",
441
+ success,
442
+ successMessage: "Batch Summary",
443
+ error,
444
+ result: resultNode
445
+ });
446
+ };
447
+ const { waitUntilExit } = render(React.createElement(App));
448
+ await waitUntilExit();
449
+ });
450
+ function printCostBreakdown(data) {
451
+ console.log(` Total (blended): ${chalk.green(`$${data.totalCostUsd.toFixed(6)}`)}`);
452
+ console.log(` Runtype-funded: ${data.billedCostUsd == null ? chalk.dim("unavailable") : chalk.green(`$${data.billedCostUsd.toFixed(6)}`)}`);
453
+ if (data.selfReportedCostUsd > 0) console.log(` Self-reported: ${chalk.yellow(`$${data.selfReportedCostUsd.toFixed(6)}`)}`);
454
+ if (data.unpricedExecutions > 0) console.log(` Unpriced executions: ${chalk.yellow(String(data.unpricedExecutions))}`);
455
+ }
456
+ batchCommand.command("cost <id>").description("Show the total cost of a batch job (or a single record with --record)").option("--record <recordId>", "Show cost for a specific record within the batch").option("--json", "Output as JSON").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (batchId, options) => {
457
+ const apiKey = await ensureAuth();
458
+ if (!apiKey) return;
459
+ const client = createCliClient(apiKey);
460
+ const recordId = options.record;
461
+ if (!isTTY(options) || options.json) {
462
+ try {
463
+ if (recordId) {
464
+ const data = await client.get(`/batch/${batchId}/records/${recordId}/cost`);
465
+ if (options.json) printJson(data);
466
+ else {
467
+ console.log(chalk.cyan(`Record Cost: ${batchId} / ${recordId}`));
468
+ printCostBreakdown(data);
469
+ }
470
+ } else {
471
+ const data = await client.get(`/batch/${batchId}/cost`);
472
+ if (options.json) printJson(data);
473
+ else {
474
+ console.log(chalk.cyan(`Batch Cost: ${batchId}`));
475
+ printCostBreakdown(data);
476
+ }
477
+ }
478
+ } catch (error) {
479
+ const message = error instanceof Error ? error.message : "Unknown error";
480
+ console.error(chalk.red("Failed to fetch batch cost"));
481
+ console.error(chalk.red(message));
482
+ process.exit(1);
483
+ }
484
+ return;
485
+ }
486
+ const App = () => {
487
+ const [loading, setLoading] = useState(true);
488
+ const [success, setSuccess] = useState(null);
489
+ const [error, setError] = useState(null);
490
+ const [resultNode, setResultNode] = useState(void 0);
491
+ useEffect(() => {
492
+ const run = async () => {
493
+ try {
494
+ if (recordId) {
495
+ const data = await client.get(`/batch/${batchId}/records/${recordId}/cost`);
496
+ setResultNode(React.createElement(EntityCard, { fields: [
497
+ {
498
+ label: "Batch ID",
499
+ value: data.batchExecutionId,
500
+ color: "green"
501
+ },
502
+ {
503
+ label: "Record ID",
504
+ value: data.recordId
505
+ },
506
+ {
507
+ label: "Total (blended)",
508
+ value: `$${data.totalCostUsd.toFixed(6)}`,
509
+ color: "green"
510
+ },
511
+ {
512
+ label: "Runtype-funded",
513
+ value: data.billedCostUsd == null ? "Unavailable" : `$${data.billedCostUsd.toFixed(6)}`
514
+ }
515
+ ] }));
516
+ } else {
517
+ const data = await client.get(`/batch/${batchId}/cost`);
518
+ setResultNode(React.createElement(EntityCard, { fields: [
519
+ {
520
+ label: "Batch ID",
521
+ value: data.batchExecutionId,
522
+ color: "green"
523
+ },
524
+ {
525
+ label: "Total (blended)",
526
+ value: `$${data.totalCostUsd.toFixed(6)}`,
527
+ color: "green"
528
+ },
529
+ {
530
+ label: "Runtype-funded",
531
+ value: data.billedCostUsd == null ? "Unavailable" : `$${data.billedCostUsd.toFixed(6)}`
532
+ }
533
+ ] }));
534
+ }
535
+ setSuccess(true);
536
+ setLoading(false);
537
+ } catch (err) {
538
+ setError(err instanceof Error ? err : new Error(String(err)));
539
+ setSuccess(false);
540
+ setLoading(false);
541
+ }
542
+ };
543
+ run();
544
+ }, []);
545
+ return React.createElement(MutationResult, {
546
+ loading,
547
+ loadingLabel: "Fetching batch cost...",
548
+ success,
549
+ successMessage: "Batch Cost",
550
+ error,
551
+ result: resultNode
552
+ });
553
+ };
554
+ const { waitUntilExit } = render(React.createElement(App));
555
+ await waitUntilExit();
556
+ });
557
+ export { batchCommand };
558
+
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import React from "react";
3
+ import { render } from "ink";
4
+ import SelectInput from "ink-select-input";
5
+ // src/ink/shared/account-picker.ts
6
+ /** Interactive picker over stored logins; resolves undefined if the user exits without choosing. */
7
+ async function promptForAccount(accounts) {
8
+ let chosen;
9
+ const { waitUntilExit, unmount } = render(React.createElement(SelectInput, {
10
+ items: accounts.map((account) => ({
11
+ key: account.id,
12
+ label: `${account.orgId ?? "Personal"} ${account.apiUrl}`,
13
+ value: account.id
14
+ })),
15
+ onSelect: (item) => {
16
+ chosen = accounts.find((account) => account.id === item.value);
17
+ unmount();
18
+ }
19
+ }));
20
+ await waitUntilExit();
21
+ return chosen;
22
+ }
23
+ export { promptForAccount };
24
+