@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,330 @@
1
+ #!/usr/bin/env node
2
+ import { t as isTTY } from "./index.js";
3
+ import { ConfirmPrompt, DataList, 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, i as printDetail, n as buildListParams, s as printNextCursorHint } from "./chunk-CcFCyqbB.js";
7
+ import { n as renderMessageContentWithImages, r as detectImageProtocol, t as renderMessageContent } from "./chunk-CW6AzBH6.js";
8
+ import { Command } from "commander";
9
+ import chalk from "chalk";
10
+ // src/commands/conversations.ts
11
+ const TOOL_RESULT_PREVIEW_CHARS = 200;
12
+ /** One line per tool call or result a message carries, so a tool turn never prints as a blank line. */
13
+ function renderToolActivity(msg) {
14
+ const calls = (msg.toolCalls ?? []).map((call) => `-> ${call.toolName}(${JSON.stringify(call.args ?? {})})`);
15
+ const results = (msg.toolResults ?? []).map((result) => {
16
+ const text = JSON.stringify(result.result ?? null) ?? "null";
17
+ const preview = text.length > TOOL_RESULT_PREVIEW_CHARS ? `${text.slice(0, 199)}…` : text;
18
+ return `<- ${result.toolName}: ${preview}`;
19
+ });
20
+ return [...calls, ...results];
21
+ }
22
+ /**
23
+ * Write one message line, emitting inline image escape sequences for renderable
24
+ * image parts and falling back to the text marker (via {@link renderMessageContent})
25
+ * for everything else. Used only on the interactive, image-capable path; the
26
+ * images are written on their own lines below the text so they don't interleave.
27
+ */
28
+ function writeMessageWithImages(msg, rolePrefix, protocol) {
29
+ const out = process.stdout;
30
+ const { text, images } = renderMessageContentWithImages(msg.content, protocol);
31
+ out.write(`${rolePrefix}${text}\n`);
32
+ for (const seq of images) out.write(`${seq}\n`);
33
+ }
34
+ /**
35
+ * Print a conversation's detail + messages to stdout (the non-Ink path). When
36
+ * `imageProtocol` is set (interactive, image-capable terminal), image parts are
37
+ * rendered inline; otherwise every part collapses to a text marker.
38
+ */
39
+ function printConversationDetail(data, imageProtocol) {
40
+ printDetail("Conversation", [
41
+ {
42
+ label: "ID",
43
+ value: data.id
44
+ },
45
+ {
46
+ label: "Title",
47
+ value: data.title
48
+ },
49
+ {
50
+ label: "Model",
51
+ value: data.modelId
52
+ },
53
+ {
54
+ label: "System prompt",
55
+ value: data.systemPrompt
56
+ },
57
+ {
58
+ label: "Owner",
59
+ value: data.ownerId
60
+ },
61
+ {
62
+ label: "Source",
63
+ value: data.source
64
+ },
65
+ {
66
+ label: "Created",
67
+ value: data.createdAt
68
+ },
69
+ {
70
+ label: "Updated",
71
+ value: data.updatedAt
72
+ }
73
+ ]);
74
+ const messages = data.messages ?? [];
75
+ console.log(chalk.cyan(`\nMessages (${messages.length}):`));
76
+ if (messages.length === 0) {
77
+ console.log(chalk.gray(" No messages"));
78
+ return;
79
+ }
80
+ for (const msg of messages) {
81
+ const roleColor = msg.role === "user" ? chalk.blue : msg.role === "assistant" ? chalk.green : chalk.gray;
82
+ if (imageProtocol) writeMessageWithImages(msg, ` ${roleColor(msg.role)}: `, imageProtocol);
83
+ else console.log(` ${roleColor(msg.role)}: ${renderMessageContent(msg.content)}`);
84
+ for (const line of renderToolActivity(msg)) console.log(chalk.gray(` ${line}`));
85
+ if (msg.createdAt) console.log(chalk.dim(` ${msg.createdAt}`));
86
+ }
87
+ }
88
+ const conversationsCommand = new Command("conversations").description("Manage conversations");
89
+ conversationsCommand.command("list").description("List your conversations").option("--owner-id <id>", "Filter by owner key (the indexed owner_id column)").option("--limit <n>", "Limit results (defaults to the API page size)").option("--cursor <cursor>", "Pagination cursor for the next page").option("--json", "Output as JSON").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (options) => {
90
+ const apiKey = await ensureAuth();
91
+ if (!apiKey) return;
92
+ const client = createCliClient(apiKey);
93
+ const params = buildListParams(options, { ownerId: options.ownerId });
94
+ if (!isTTY(options) || options.json) {
95
+ try {
96
+ const data = await client.get("/conversations", params);
97
+ if (options.json) {
98
+ printJson(data);
99
+ return;
100
+ }
101
+ const conversations = data.conversations ?? [];
102
+ if (conversations.length === 0) {
103
+ console.log("No conversations found");
104
+ return;
105
+ }
106
+ console.log("Your Conversations:");
107
+ for (const conv of conversations) {
108
+ const count = conv.messageCount ?? 0;
109
+ const sourceTag = conv.source ? ` [${conv.source}]` : "";
110
+ console.log(` ${chalk.green(conv.id)} ${conv.title}${sourceTag}`);
111
+ console.log(` Messages: ${count}`);
112
+ if (conv.updatedAt) console.log(` Updated: ${conv.updatedAt}`);
113
+ }
114
+ console.log(chalk.dim(`\n Showing ${conversations.length} conversations`));
115
+ printNextCursorHint("runtype conversations list", data.pagination);
116
+ } catch (error) {
117
+ const message = error instanceof Error ? error.message : "Unknown error";
118
+ console.error(chalk.red(`Failed to fetch conversations: ${message}`));
119
+ process.exit(1);
120
+ }
121
+ return;
122
+ }
123
+ const App = () => {
124
+ const [items, setItems] = useState(null);
125
+ const [error, setError] = useState(null);
126
+ const [total, setTotal] = useState();
127
+ useEffect(() => {
128
+ client.get("/conversations", params).then((res) => {
129
+ const conversations = res.conversations ?? [];
130
+ setItems(conversations);
131
+ setTotal(conversations.length);
132
+ }).catch((err) => setError(err instanceof Error ? err : new Error(String(err))));
133
+ }, []);
134
+ return React.createElement(DataList, {
135
+ title: "Your Conversations",
136
+ items,
137
+ error,
138
+ loading: items === null && error === null,
139
+ total,
140
+ emptyMessage: "No conversations found",
141
+ renderCard: (item) => {
142
+ const conv = item;
143
+ return React.createElement(EntityCard, { fields: [
144
+ {
145
+ label: "ID",
146
+ value: conv.id,
147
+ color: "green"
148
+ },
149
+ {
150
+ label: "Title",
151
+ value: conv.title
152
+ },
153
+ {
154
+ label: "Source",
155
+ value: conv.source,
156
+ color: "gray"
157
+ },
158
+ {
159
+ label: "Messages",
160
+ value: conv.messageCount !== void 0 ? String(conv.messageCount) : void 0,
161
+ color: "gray"
162
+ },
163
+ {
164
+ label: "Updated",
165
+ value: conv.updatedAt
166
+ }
167
+ ] });
168
+ }
169
+ });
170
+ };
171
+ const { waitUntilExit } = render(React.createElement(App));
172
+ await waitUntilExit();
173
+ });
174
+ conversationsCommand.command("get <id>").description("Get conversation details including its messages").option("--json", "Output as JSON").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").option("--no-images", "Disable inline image rendering even in supported terminals").action(async (id, options) => {
175
+ const apiKey = await ensureAuth();
176
+ if (!apiKey) return;
177
+ const client = createCliClient(apiKey);
178
+ const interactive = isTTY(options) && !options.json;
179
+ const imageProtocol = interactive && options.images !== false ? detectImageProtocol() : null;
180
+ if (!interactive || imageProtocol) {
181
+ try {
182
+ const data = await client.get(`/conversations/${id}`);
183
+ if (options.json) {
184
+ printJson(data);
185
+ return;
186
+ }
187
+ printConversationDetail(data, imageProtocol);
188
+ } catch (error) {
189
+ const message = error instanceof Error ? error.message : "Unknown error";
190
+ console.error(chalk.red(`Failed to fetch conversation: ${message}`));
191
+ process.exit(1);
192
+ }
193
+ return;
194
+ }
195
+ const App = () => {
196
+ const [loading, setLoading] = useState(true);
197
+ const [success, setSuccess] = useState(null);
198
+ const [error, setError] = useState(null);
199
+ const [resultNode, setResultNode] = useState(void 0);
200
+ useEffect(() => {
201
+ const run = async () => {
202
+ try {
203
+ const data = await client.get(`/conversations/${id}`);
204
+ const messages = data.messages ?? [];
205
+ const messageSummary = messages.length === 0 ? "No messages" : messages.map((m) => [`${m.role}: ${renderMessageContent(m.content)}`, ...renderToolActivity(m)].join("\n ")).join("\n");
206
+ setResultNode(React.createElement(EntityCard, { fields: [
207
+ {
208
+ label: "ID",
209
+ value: data.id,
210
+ color: "green"
211
+ },
212
+ {
213
+ label: "Title",
214
+ value: data.title
215
+ },
216
+ {
217
+ label: "Model",
218
+ value: data.modelId
219
+ },
220
+ {
221
+ label: "System prompt",
222
+ value: data.systemPrompt
223
+ },
224
+ {
225
+ label: "Owner",
226
+ value: data.ownerId
227
+ },
228
+ {
229
+ label: "Source",
230
+ value: data.source,
231
+ color: "gray"
232
+ },
233
+ {
234
+ label: "Created",
235
+ value: data.createdAt
236
+ },
237
+ {
238
+ label: "Updated",
239
+ value: data.updatedAt
240
+ },
241
+ {
242
+ label: `Messages (${messages.length})`,
243
+ value: messageSummary
244
+ }
245
+ ] }));
246
+ setSuccess(true);
247
+ setLoading(false);
248
+ } catch (err) {
249
+ setError(err instanceof Error ? err : new Error(String(err)));
250
+ setSuccess(false);
251
+ setLoading(false);
252
+ }
253
+ };
254
+ run();
255
+ }, []);
256
+ return React.createElement(MutationResult, {
257
+ loading,
258
+ loadingLabel: "Fetching conversation...",
259
+ success,
260
+ successMessage: "Conversation",
261
+ error,
262
+ result: resultNode
263
+ });
264
+ };
265
+ const { waitUntilExit } = render(React.createElement(App));
266
+ await waitUntilExit();
267
+ });
268
+ conversationsCommand.command("delete <id>").description("Delete a conversation").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").option("--yes", "Skip confirmation").action(async (id, options) => {
269
+ const apiKey = await ensureAuth();
270
+ if (!apiKey) return;
271
+ const client = createCliClient(apiKey);
272
+ if (!isTTY(options)) {
273
+ try {
274
+ await client.conversations.delete(id);
275
+ console.log(JSON.stringify({
276
+ status: "deleted",
277
+ id
278
+ }, null, 2));
279
+ } catch (error) {
280
+ const message = error instanceof Error ? error.message : "Unknown error";
281
+ console.error(chalk.red(`Failed to delete conversation: ${message}`));
282
+ process.exit(1);
283
+ }
284
+ return;
285
+ }
286
+ const runDelete = () => {
287
+ const App = () => {
288
+ const [loading, setLoading] = useState(true);
289
+ const [success, setSuccess] = useState(null);
290
+ const [error, setError] = useState(null);
291
+ useEffect(() => {
292
+ client.delete(`/conversations/${id}`).then(() => {
293
+ setSuccess(true);
294
+ setLoading(false);
295
+ }).catch((err) => {
296
+ setError(err instanceof Error ? err : new Error(String(err)));
297
+ setSuccess(false);
298
+ setLoading(false);
299
+ });
300
+ }, []);
301
+ return React.createElement(MutationResult, {
302
+ loading,
303
+ loadingLabel: "Deleting conversation...",
304
+ success,
305
+ successMessage: "Conversation deleted",
306
+ error
307
+ });
308
+ };
309
+ return App;
310
+ };
311
+ if (options.yes) {
312
+ const { waitUntilExit } = render(React.createElement(runDelete()));
313
+ await waitUntilExit();
314
+ return;
315
+ }
316
+ if (!await new Promise((resolve) => {
317
+ const { unmount } = render(React.createElement(ConfirmPrompt, {
318
+ message: `Delete conversation ${id}?`,
319
+ defaultValue: false,
320
+ onConfirm: (result) => {
321
+ resolve(result);
322
+ unmount();
323
+ }
324
+ }));
325
+ })) return;
326
+ const { waitUntilExit } = render(React.createElement(runDelete()));
327
+ await waitUntilExit();
328
+ });
329
+ export { conversationsCommand };
330
+
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env node
2
+ import { c as TRACE_QUERY_DIMENSIONS, l as TRACE_SLICE_SPAN_KINDS } from "./chunk-ucu5lrDZ.js";
3
+ import { r as createCliClient } from "./chunk-BW2fg46T.js";
4
+ import { n as ensureAuth } from "./chunk-CJraEBVd.js";
5
+ import { a as printJson } from "./chunk-CcFCyqbB.js";
6
+ import { Command } from "commander";
7
+ import chalk from "chalk";
8
+ // src/commands/traces.ts
9
+ const tracesCommand = new Command("traces").description("Mine traces: aggregate runs, read a bounded slice of one trace, compare two runs or cohorts");
10
+ async function run(json, action, render) {
11
+ try {
12
+ const apiKey = await ensureAuth();
13
+ if (!apiKey) return;
14
+ const result = await action(createCliClient(apiKey));
15
+ if (json) printJson(result);
16
+ else for (const line of render(result)) console.log(line);
17
+ } catch (error) {
18
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
19
+ process.exitCode = 1;
20
+ }
21
+ }
22
+ function integer(value) {
23
+ const parsed = Number(value);
24
+ if (!Number.isInteger(parsed) || parsed < 0) throw new Error(`Expected a non-negative integer, got "${value}"`);
25
+ return parsed;
26
+ }
27
+ function list(value) {
28
+ if (value === void 0) return void 0;
29
+ const items = value.split(",").map((item) => item.trim()).filter(Boolean);
30
+ return items.length > 0 ? items : void 0;
31
+ }
32
+ function oneOf(value, allowed, flag) {
33
+ if (allowed.includes(value)) return value;
34
+ throw new Error(`${flag} accepts ${allowed.join(", ")}; got "${value}"`);
35
+ }
36
+ function buildFilters(options) {
37
+ const agentIds = list(options.agent);
38
+ const agentVersionIds = list(options.agentVersion);
39
+ const flowIds = list(options.flow);
40
+ return {
41
+ ...agentIds ? { agentIds } : {},
42
+ ...agentVersionIds ? { agentVersionIds } : {},
43
+ ...flowIds ? { flowIds } : {},
44
+ ...options.surface ? { surfaceId: options.surface } : {},
45
+ ...options.model ? { model: options.model } : {},
46
+ ...options.source ? { source: oneOf(options.source, ["hosted", "ingested"], "--source") } : {},
47
+ ...options.status ? { status: oneOf(options.status, [
48
+ "running",
49
+ "completed",
50
+ "failed"
51
+ ], "--status") } : {},
52
+ ...options.stopReason ? { stopReason: options.stopReason } : {},
53
+ ...options.signal ? { signal: options.signal } : {},
54
+ ...options.from ? { from: options.from } : {},
55
+ ...options.to ? { to: options.to } : {},
56
+ ...options.includeChildren ? { includeChildren: true } : {}
57
+ };
58
+ }
59
+ function buildTraceQueryRequest(options) {
60
+ const groupBy = list(options.groupBy)?.map((dimension) => oneOf(dimension, TRACE_QUERY_DIMENSIONS, "--group-by"));
61
+ if (groupBy && groupBy.length > 2) throw new Error("--group-by takes at most two dimensions");
62
+ return {
63
+ ...buildFilters(options),
64
+ ...groupBy ? { groupBy } : {},
65
+ ...options.signalRate ? { signalRate: options.signalRate } : {},
66
+ ...options.includeToolCalls ? { includeToolCalls: true } : {},
67
+ ...options.limit !== void 0 ? { limit: options.limit } : {},
68
+ ...options.cursor ? { cursor: options.cursor } : {}
69
+ };
70
+ }
71
+ function buildTraceSliceParams(options) {
72
+ if (options.aroundFirstFailure !== void 0 && (options.start !== void 0 || options.end !== void 0)) throw new Error("--around-first-failure does not combine with --start/--end");
73
+ return {
74
+ ...options.start !== void 0 ? { start: options.start } : {},
75
+ ...options.end !== void 0 ? { end: options.end } : {},
76
+ ...options.kind ? { kind: oneOf(options.kind, TRACE_SLICE_SPAN_KINDS, "--kind") } : {},
77
+ ...options.tool ? { toolName: options.tool } : {},
78
+ ...options.aroundFirstFailure !== void 0 ? { aroundFirstFailure: options.aroundFirstFailure } : {},
79
+ ...options.limit !== void 0 ? { limit: options.limit } : {},
80
+ ...options.includeContent ? { includeContent: true } : {},
81
+ ...options.maxContentBytes !== void 0 ? { maxContentBytes: options.maxContentBytes } : {}
82
+ };
83
+ }
84
+ function compareSide(name, executionId, filters) {
85
+ if (executionId && filters) throw new Error(`Pass --${name} or --${name}-filters, not both`);
86
+ if (executionId) return { executionId };
87
+ if (!filters) throw new Error(`Pass --${name} <executionId> or --${name}-filters '<json>'`);
88
+ let parsed;
89
+ try {
90
+ parsed = JSON.parse(filters);
91
+ } catch {
92
+ throw new Error(`--${name}-filters is not valid JSON`);
93
+ }
94
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`--${name}-filters must be a JSON object of trace filters`);
95
+ return { filters: parsed };
96
+ }
97
+ function buildTraceCompareRequest(options) {
98
+ return {
99
+ left: compareSide("left", options.left, options.leftFilters),
100
+ right: compareSide("right", options.right, options.rightFilters),
101
+ ...options.from ? { from: options.from } : {},
102
+ ...options.to ? { to: options.to } : {},
103
+ ...options.sampleSize !== void 0 ? { sampleSize: options.sampleSize } : {}
104
+ };
105
+ }
106
+ function pct(value) {
107
+ return value === null || value === void 0 ? "-" : `${(value * 100).toFixed(1)}%`;
108
+ }
109
+ function num(value) {
110
+ if (value === null || value === void 0) return "-";
111
+ return Number.isInteger(value) ? String(value) : value.toFixed(4);
112
+ }
113
+ function metricsLine(metrics) {
114
+ const parts = [
115
+ `runs ${metrics.count}`,
116
+ `failed ${metrics.failed} (${pct(metrics.failureRate)})`,
117
+ `tokens p50 ${num(metrics.tokens.p50)}`,
118
+ `cost ${num(metrics.cost.sum)}${metrics.cost.unpricedRuns > 0 ? ` (+${metrics.cost.unpricedRuns} unpriced)` : ""}`,
119
+ `duration p50/p95 ${num(metrics.durationMs.p50)}/${num(metrics.durationMs.p95)}ms`
120
+ ];
121
+ if (metrics.toolCalls) parts.push(`tool calls p50/p95 ${num(metrics.toolCalls.p50)}/${num(metrics.toolCalls.p95)}`);
122
+ if (metrics.signalRate) parts.push(`signal uniform ${pct(metrics.signalRate.uniform.rate)} (${metrics.signalRate.uniform.evaluated}), filtered ${pct(metrics.signalRate.filtered.rate)} (${metrics.signalRate.filtered.evaluated})`);
123
+ return parts.join(" · ");
124
+ }
125
+ function formatTraceQuery(result) {
126
+ const lines = [chalk.cyan(`Traces ${result.window.from} → ${result.window.to}`)];
127
+ for (const warning of result.warnings) lines.push(chalk.yellow(` Warning (${warning.code}): ${warning.message}`));
128
+ if (result.groupBy.length > 0) {
129
+ if (result.groups.length === 0) lines.push(chalk.gray(" No runs matched"));
130
+ for (const group of result.groups) {
131
+ const key = result.groupBy.map((dimension) => `${dimension}=${group.key[dimension] ?? "(none)"}`).join(" ");
132
+ lines.push(` ${chalk.bold(key)}`);
133
+ lines.push(` ${metricsLine(group)}`);
134
+ if (group.sampleExecutionIds.length > 0) lines.push(chalk.dim(` samples: ${group.sampleExecutionIds.join(", ")}`));
135
+ }
136
+ }
137
+ lines.push(` ${chalk.bold("Total")} ${metricsLine(result.totals)}`);
138
+ if (result.nextCursor) lines.push(chalk.yellow("\n More groups available — fetch the next page with:") + chalk.dim(`\n runtype traces query ... --cursor ${result.nextCursor}`));
139
+ return lines;
140
+ }
141
+ function formatTraceSlice(slice) {
142
+ const { selection } = slice;
143
+ const lines = [chalk.cyan(`Trace ${slice.executionId} (${slice.source})`), chalk.dim(` spans ${selection.from}-${selection.to} of ${slice.totalSpans} (${slice.matchedSpans} matched, ${selection.mode}${selection.firstFailedIndex !== null ? `, first failure at #${selection.firstFailedIndex}` : ""})`)];
144
+ for (const span of slice.spans) {
145
+ const status = span.status === "error" || span.error ? chalk.red(span.status) : chalk.green(span.status);
146
+ const label = span.toolName ? `${span.name} [${span.toolName}]` : span.name;
147
+ const detail = [
148
+ span.kind,
149
+ span.model,
150
+ span.durationMs !== null ? `${span.durationMs}ms` : null,
151
+ span.stopReason ? `stop=${span.stopReason}` : null
152
+ ].filter(Boolean);
153
+ lines.push(` #${span.index} ${status} ${label}${detail.length > 0 ? chalk.dim(` (${detail.join(", ")})`) : ""}`);
154
+ if (span.error) lines.push(chalk.red(` error: ${span.error}`));
155
+ if (span.inputPreview) lines.push(chalk.dim(` in: ${span.inputPreview}`));
156
+ if (span.outputPreview) lines.push(chalk.dim(` out: ${span.outputPreview}`));
157
+ for (const [field, value] of Object.entries(span.content ?? {})) {
158
+ if (value === null || value === void 0) continue;
159
+ const cut = slice.content.cut.some((entry) => entry.spanId === span.spanId && entry.field === field);
160
+ const text = typeof value === "string" ? value : JSON.stringify(value, null, 2);
161
+ lines.push(` ${field}${cut ? chalk.yellow(" (cut)") : ""}:`);
162
+ for (const line of text.split("\n")) lines.push(` ${line}`);
163
+ }
164
+ }
165
+ if (slice.truncated) lines.push(chalk.yellow(" More spans matched than were returned; raise --limit or narrow the range"));
166
+ if (slice.traceTruncated) lines.push(chalk.yellow(" The trace exceeded the span read ceiling"));
167
+ if (slice.content.loaded) lines.push(chalk.dim(` content ${slice.content.usedBytes}/${slice.content.limitBytes} bytes, ${slice.content.cut.length} cut`));
168
+ return lines;
169
+ }
170
+ function formatTraceCompare(compare) {
171
+ if (compare.mode === "executions") {
172
+ const side = (label, totals) => ` ${label} ${totals.executionId}: status ${totals.status ?? "-"}, stop ${totals.stopReason ?? "-"}, tool calls ${totals.toolCalls}, cost ${num(totals.cost)}, ${num(totals.durationMs)}ms`;
173
+ const lines = [
174
+ chalk.cyan("Execution comparison"),
175
+ side("left ", compare.left),
176
+ side("right", compare.right)
177
+ ];
178
+ const truncatedSides = ["left", "right"].filter((name) => compare.truncated[name]);
179
+ if (truncatedSides.length > 0) lines.push(chalk.yellow(` Only the first tool calls were compared on the ${truncatedSides.join(" and ")} side; later calls are not aligned`));
180
+ lines.push(compare.divergence ? chalk.yellow(` First divergence at step ${compare.divergence.alignmentIndex}: ${compare.divergence.description}`) : chalk.green(" Tool-call sequences align"));
181
+ for (const tool of compare.perTool) lines.push(` ${tool.tool}: calls ${tool.leftCalls} → ${tool.rightCalls}, errors ${tool.leftErrors} → ${tool.rightErrors}` + (tool.argDrift !== null ? `, arg drift ${num(tool.argDrift)}` : ""));
182
+ return lines;
183
+ }
184
+ const side = (label, cohort) => ` ${label} sampled ${cohort.sampled}/${cohort.sampleLimit}, failure ${pct(cohort.failureRate)}`;
185
+ const lines = [
186
+ chalk.cyan(`Cohort comparison ${compare.window.from} → ${compare.window.to}`),
187
+ side("left ", compare.left),
188
+ side("right", compare.right)
189
+ ];
190
+ for (const delta of compare.toolShareDelta) lines.push(` ${delta.tool}: share of runs ${pct(delta.left)} → ${pct(delta.right)} (${delta.delta >= 0 ? "+" : ""}${pct(delta.delta)})`);
191
+ return lines;
192
+ }
193
+ tracesCommand.command("query").description("Aggregate runs by up to two dimensions, with sample execution ids per group").option("--agent <ids>", "Comma-separated agent ids").option("--agent-version <ids>", "Comma-separated agent version ids (ingested runs only)").option("--flow <ids>", "Comma-separated flow ids").option("--surface <id>", "Surface id").option("--model <model>", "Runs with any call to this model (window capped at 7 days)").option("--source <source>", "hosted or ingested").option("--status <status>", "running, completed or failed").option("--stop-reason <reason>", "Only runs that stopped for this reason").option("--signal <signal>", "Only runs where a signal fired: key, or key:value").option("--signal-rate <signal>", "Report the fire rate of this signal per group").option("--group-by <dimensions>", `One or two of: ${TRACE_QUERY_DIMENSIONS.join(", ")}`).option("--from <iso>", "Window start (default: 7 days before --to)").option("--to <iso>", "Window end (default: now)").option("--include-children", "Count subagent and nested-flow runs too").option("--include-tool-calls", "Tool-call percentiles per group (window capped at 7 days)").option("--limit <n>", "Groups per page", integer).option("--cursor <cursor>", "Continue from a previous page").option("--json", "Output as JSON").action((options) => run(options.json, (client) => client.logs.queryTraces(buildTraceQueryRequest(options)), (res) => formatTraceQuery(res.data)));
194
+ tracesCommand.command("slice <executionId>").description("Read a bounded window of one trace (previews only unless --include-content)").option("--start <n>", "First span index to include", integer).option("--end <n>", "Span index to stop before", integer).option("--kind <kind>", `Only spans of this kind: ${TRACE_SLICE_SPAN_KINDS.join(", ")}`).option("--tool <name>", "Only spans of this tool").option("--around-first-failure <n>", "The first failed span plus n spans either side (0-50)", integer).option("--limit <n>", "Spans to return (default 25, max 100)", integer).option("--include-content", "Load full content for the selected spans under the byte budget").option("--max-content-bytes <n>", "Content byte budget (default 65536)", integer).option("--json", "Output as JSON").action((executionId, options) => run(options.json, (client) => client.logs.readTraceSlice(executionId, buildTraceSliceParams(options)), (res) => formatTraceSlice(res.data)));
195
+ tracesCommand.command("compare").description("Compare two executions, or two cohorts of runs selected by filters").option("--left <executionId>", "Left execution").option("--right <executionId>", "Right execution").option("--left-filters <json>", "Left cohort filters, e.g. '{\"agentIds\":[\"agt_1\"],\"status\":\"failed\"}'").option("--right-filters <json>", "Right cohort filters").option("--from <iso>", "Cohort window start").option("--to <iso>", "Cohort window end").option("--sample-size <n>", "Runs sampled per cohort (default 20, max 50)", integer).option("--json", "Output as JSON").action((options) => run(options.json, async (client) => client.logs.compareTrace(buildTraceCompareRequest(options)), (res) => formatTraceCompare(res.data)));
196
+ export { tracesCommand };
197
+
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ import chalk from "chalk";
3
+ import { readFileSync } from "fs";
4
+ // src/lib/file-utils.ts
5
+ /** Parse a string as a JSON object, exiting with a clear error on failure. */
6
+ function parseJsonObject(raw, label) {
7
+ let parsed;
8
+ try {
9
+ parsed = JSON.parse(raw);
10
+ } catch {
11
+ console.error(chalk.red(`Invalid JSON in ${label}`));
12
+ process.exit(1);
13
+ }
14
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
15
+ console.error(chalk.red(`${label} must be a JSON object`));
16
+ process.exit(1);
17
+ }
18
+ return parsed;
19
+ }
20
+ /**
21
+ * Read and parse a JSON object from a file or `-` for stdin, exiting with a clear error on
22
+ * failure. When `unwrapKey` is provided and present as a nested object, returns
23
+ * that nested object (e.g. an exported `{ "agent": { ... } }` envelope).
24
+ */
25
+ function readJsonFile(path, opts = {}) {
26
+ const label = opts.label ?? "file";
27
+ if (path === "-" && process.stdin.isTTY) {
28
+ console.error(chalk.red(`Pipe a JSON object into stdin for ${label}, or provide a file path`));
29
+ process.exit(1);
30
+ }
31
+ let raw;
32
+ try {
33
+ raw = readFileSync(path === "-" ? 0 : path, "utf-8");
34
+ } catch {
35
+ console.error(chalk.red(`Failed to read ${label}: ${path}`));
36
+ process.exit(1);
37
+ }
38
+ const obj = parseJsonObject(raw, label);
39
+ if (opts.unwrapKey) {
40
+ const nested = obj[opts.unwrapKey];
41
+ if (nested && typeof nested === "object" && !Array.isArray(nested)) return { ...nested };
42
+ }
43
+ return obj;
44
+ }
45
+ /**
46
+ * Resolve a JSON object payload from exactly one of an inline JSON string or a
47
+ * file path. Exits with a clear error if both or neither are provided, or if the
48
+ * resolved content is not a JSON object.
49
+ */
50
+ function resolveJsonPayload(inline, fromFile, label) {
51
+ if (inline !== void 0 && fromFile !== void 0) {
52
+ console.error(chalk.red(`Error: provide only one of inline JSON or --from-file for ${label}`));
53
+ process.exit(1);
54
+ }
55
+ if (inline === void 0 && fromFile === void 0) {
56
+ console.error(chalk.red(`Error: ${label} is required — pass inline JSON or --from-file <path>`));
57
+ process.exit(1);
58
+ }
59
+ if (fromFile !== void 0) return readJsonFile(fromFile, { label });
60
+ return parseJsonObject(inline, label);
61
+ }
62
+ export { resolveJsonPayload as n, readJsonFile as t };
63
+