skydive-cli 0.1.0-beta.106

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.
@@ -0,0 +1,745 @@
1
+ #!/usr/bin/env node
2
+ import { z } from "zod";
3
+ import { createParser } from "eventsource-parser";
4
+
5
+ //#region \0rolldown/runtime.js
6
+ var __defProp = Object.defineProperty;
7
+ var __exportAll = (all, no_symbols) => {
8
+ let target = {};
9
+ for (var name in all) {
10
+ __defProp(target, name, {
11
+ get: all[name],
12
+ enumerable: true
13
+ });
14
+ }
15
+ if (!no_symbols) {
16
+ __defProp(target, Symbol.toStringTag, { value: "Module" });
17
+ }
18
+ return target;
19
+ };
20
+
21
+ //#endregion
22
+ //#region src/chat/api/rest.ts
23
+ var HttpError = class extends Error {
24
+ constructor(status, body) {
25
+ super(`HTTP ${status}: ${body.slice(0, 200)}`);
26
+ this.status = status;
27
+ this.body = body;
28
+ this.name = "HttpError";
29
+ }
30
+ };
31
+ const MAX_STREAM_RECONNECTS = 5;
32
+ function createRestClient({ appUrl, sessionToken }) {
33
+ const baseHeaders = {
34
+ authorization: `Bearer ${sessionToken}`,
35
+ accept: "application/json"
36
+ };
37
+ async function get(path, schema) {
38
+ const res = await fetch(`${appUrl}${path}`, { headers: baseHeaders });
39
+ if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
40
+ return schema.parse(await res.json());
41
+ }
42
+ async function post(path, body, schema, method = "POST") {
43
+ const res = await fetch(`${appUrl}${path}`, {
44
+ method,
45
+ headers: {
46
+ ...baseHeaders,
47
+ "content-type": "application/json"
48
+ },
49
+ body: JSON.stringify(body)
50
+ });
51
+ if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
52
+ return schema.parse(await res.json());
53
+ }
54
+ async function del(path) {
55
+ const res = await fetch(`${appUrl}${path}`, {
56
+ method: "DELETE",
57
+ headers: baseHeaders
58
+ });
59
+ if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
60
+ }
61
+ return {
62
+ listAgents: async ({ scope, onPage }) => {
63
+ const all = [];
64
+ let cursor;
65
+ const maxAgents = 2e3;
66
+ do {
67
+ const params = new URLSearchParams({
68
+ limit: "100",
69
+ scope,
70
+ sort: "mine_first_usage",
71
+ includeStats: "false"
72
+ });
73
+ if (cursor) params.set("cursor", cursor);
74
+ const page = await get(`/api/v1/agents?${params.toString()}`, listAgentsResponseSchema);
75
+ all.push(...page.agents);
76
+ cursor = page.nextCursor ?? void 0;
77
+ onPage?.([...all]);
78
+ } while (cursor && all.length < maxAgents);
79
+ return all;
80
+ },
81
+ createAgent: async ({ name }) => {
82
+ const { agent } = await post("/api/v1/agents", { name }, createAgentResponseSchema);
83
+ return agent;
84
+ },
85
+ getConversation: async ({ conversationId }) => {
86
+ const { conversation } = await get(`/api/v1/conversations/${encodeURIComponent(conversationId)}`, getConversationResponseSchema);
87
+ return conversation;
88
+ },
89
+ listModels: async () => {
90
+ const { models } = await get("/api/v1/models", listModelsResponseSchema);
91
+ return models;
92
+ },
93
+ updateAgentModel: async ({ agentId, model }) => {
94
+ const { agent } = await post(`/api/v1/agents/${encodeURIComponent(agentId)}`, { model }, updateAgentResponseSchema, "PATCH");
95
+ return { model: agent.model ?? null };
96
+ },
97
+ listConversations: async ({ agentId, limit }) => {
98
+ const params = new URLSearchParams({
99
+ agentId,
100
+ includeTotal: "false"
101
+ });
102
+ if (limit) params.set("limit", String(limit));
103
+ const { conversations } = await get(`/api/v1/conversations?${params.toString()}`, listConversationsResponseSchema);
104
+ return conversations;
105
+ },
106
+ listMessages: async ({ conversationId }) => {
107
+ const { messages } = await get(`/api/v1/conversations/${encodeURIComponent(conversationId)}/messages`, listMessagesResponseSchema);
108
+ return messages;
109
+ },
110
+ getRecap: async ({ conversationId }) => {
111
+ const { recap } = await get(`/api/v1/conversations/${encodeURIComponent(conversationId)}/recap`, recapResponseSchema);
112
+ return recap?.text ?? null;
113
+ },
114
+ uploadAttachment: async ({ agentId, fileName, mediaType, data }) => {
115
+ const size = data.byteLength;
116
+ const presign = await post("/api/v1/attachments/presign", {
117
+ agentId,
118
+ fileName,
119
+ mediaType,
120
+ size
121
+ }, presignResponseSchema);
122
+ const putRes = await fetch(presign.uploadUrl, {
123
+ method: "PUT",
124
+ headers: { "content-type": mediaType },
125
+ body: new Uint8Array(data)
126
+ });
127
+ if (!putRes.ok) throw new HttpError(putRes.status, await putRes.text().catch(() => ""));
128
+ const finalized = await post(`/api/v1/attachments/${encodeURIComponent(presign.id)}/finalize`, {
129
+ agentId,
130
+ fileName: presign.fileName,
131
+ mediaType: presign.mediaType,
132
+ size
133
+ }, finalizeResponseSchema);
134
+ return {
135
+ id: presign.id,
136
+ fileName: finalized.fileName,
137
+ mediaType: finalized.mediaType,
138
+ sizeBytes: finalized.sizeBytes ?? size
139
+ };
140
+ },
141
+ deleteConversation: async ({ conversationId }) => {
142
+ await del(`/api/v1/conversations/${encodeURIComponent(conversationId)}`);
143
+ },
144
+ sendMessage: async ({ clientSurface, ...input }) => post("/api/v1/chat/send", {
145
+ ...input,
146
+ clientSurface
147
+ }, sendResultSchema),
148
+ activeRun: async ({ conversationId }) => {
149
+ const { run } = await get(`/api/v1/chat/active-run?${new URLSearchParams({ conversationId }).toString()}`, activeRunResponseSchema);
150
+ return run;
151
+ },
152
+ cancelRun: async ({ runId }) => {
153
+ await post(`/api/v1/chat/runs/${encodeURIComponent(runId)}/cancel`, {}, z.object({ ok: z.boolean() }));
154
+ },
155
+ cancelSteer: async ({ directiveId }) => {
156
+ await post(`/api/v1/chat/steer/${encodeURIComponent(directiveId)}/cancel`, {}, z.object({ ok: z.boolean() }));
157
+ },
158
+ oauthConnect: async (input) => {
159
+ const { connectLink } = await post("/api/v1/oauth/connect", input, oauthConnectResponseSchema);
160
+ return { connectLink };
161
+ },
162
+ externalOauthConnect: async (input) => {
163
+ const { authorizationUrl } = await post("/api/v1/external-oauth/connect", input, externalOauthConnectResponseSchema);
164
+ return { authorizationUrl: authorizationUrl ?? null };
165
+ },
166
+ fulfillCredential: async ({ url, body }) => {
167
+ const target = new URL(url, appUrl).toString();
168
+ const res = await fetch(target, {
169
+ method: "POST",
170
+ headers: {
171
+ ...baseHeaders,
172
+ "content-type": "application/json"
173
+ },
174
+ body: JSON.stringify(body)
175
+ });
176
+ if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
177
+ },
178
+ streamRun: async ({ runId, signal, onEvent }) => {
179
+ let lastEventId = null;
180
+ let finished = false;
181
+ let reconnects = 0;
182
+ for (;;) {
183
+ if (signal.aborted) return;
184
+ try {
185
+ const headers = {
186
+ authorization: `Bearer ${sessionToken}`,
187
+ accept: "text/event-stream"
188
+ };
189
+ if (lastEventId) headers["last-event-id"] = lastEventId;
190
+ const res = await fetch(`${appUrl}/api/v1/chat/runs/${encodeURIComponent(runId)}/stream`, {
191
+ headers,
192
+ signal
193
+ });
194
+ if (!res.ok || !res.body) throw new HttpError(res.status, await res.text().catch(() => ""));
195
+ reconnects = 0;
196
+ const parser = createParser({ onEvent: (message) => {
197
+ if (message.id) lastEventId = message.id;
198
+ if (message.event === "error") {
199
+ const { error } = streamErrorSchema.parse(JSON.parse(message.data));
200
+ throw new Error(error);
201
+ }
202
+ const event = runStreamEventSchema.parse(JSON.parse(message.data));
203
+ if (event.kind === "finished") finished = true;
204
+ onEvent(event.kind === "finished" ? {
205
+ ...event,
206
+ error: event.error ?? null
207
+ } : event);
208
+ } });
209
+ const decoder = new TextDecoder();
210
+ const reader = res.body.getReader();
211
+ try {
212
+ for (;;) {
213
+ const { done, value } = await reader.read();
214
+ if (done) break;
215
+ parser.feed(decoder.decode(value, { stream: true }));
216
+ if (finished) return;
217
+ }
218
+ } finally {
219
+ try {
220
+ await reader.cancel();
221
+ } catch (_error) {}
222
+ }
223
+ } catch (err) {
224
+ if (signal.aborted) return;
225
+ if (err instanceof HttpError && err.status >= 400 && err.status < 500) throw err;
226
+ reconnects += 1;
227
+ if (reconnects > MAX_STREAM_RECONNECTS) throw err;
228
+ await sleep(Math.min(500 * 2 ** reconnects, 5e3));
229
+ continue;
230
+ }
231
+ if (finished) return;
232
+ reconnects += 1;
233
+ if (reconnects > MAX_STREAM_RECONNECTS) throw new Error("run stream ended unexpectedly");
234
+ await sleep(Math.min(500 * 2 ** reconnects, 5e3));
235
+ }
236
+ }
237
+ };
238
+ }
239
+ function sleep(ms) {
240
+ return new Promise((resolve) => setTimeout(resolve, ms));
241
+ }
242
+ const agentSummarySchema = z.object({
243
+ id: z.string().uuid(),
244
+ name: z.string(),
245
+ slug: z.string().nullable().optional(),
246
+ title: z.string().nullable().optional(),
247
+ description: z.string().nullable().optional(),
248
+ createdAt: z.string(),
249
+ creatorName: z.string().nullable().optional(),
250
+ model: z.string().nullable().optional(),
251
+ modelLocked: z.boolean().optional()
252
+ });
253
+ const platformModelSchema = z.object({
254
+ id: z.string(),
255
+ displayName: z.string(),
256
+ providerDisplay: z.string().optional(),
257
+ reasoning: z.boolean().optional(),
258
+ compliant: z.boolean().optional()
259
+ }).passthrough();
260
+ const listModelsResponseSchema = z.object({ models: z.array(platformModelSchema) });
261
+ const updateAgentResponseSchema = z.object({ agent: z.object({ model: z.string().nullable().optional() }).passthrough() });
262
+ const listAgentsResponseSchema = z.object({
263
+ agents: z.array(agentSummarySchema),
264
+ nextCursor: z.string().nullable().optional(),
265
+ totalCount: z.number().nullable().optional()
266
+ });
267
+ const createAgentResponseSchema = z.object({ agent: agentSummarySchema });
268
+ const conversationSummarySchema = z.object({
269
+ id: z.string().uuid(),
270
+ title: z.string().nullable(),
271
+ createdAt: z.string(),
272
+ updatedAt: z.string(),
273
+ preview: z.string().nullable(),
274
+ channel: z.string().nullable(),
275
+ channelLabel: z.string().nullable(),
276
+ agent: z.object({
277
+ id: z.string().uuid(),
278
+ name: z.string(),
279
+ slug: z.string().nullable().optional(),
280
+ title: z.string().nullable().optional()
281
+ })
282
+ });
283
+ const conversationTitleSchema = z.object({
284
+ id: z.string().uuid(),
285
+ title: z.string().nullable()
286
+ });
287
+ const getConversationResponseSchema = z.object({ conversation: conversationTitleSchema });
288
+ const listConversationsResponseSchema = z.object({
289
+ conversations: z.array(conversationSummarySchema),
290
+ nextCursor: z.string().nullable().optional(),
291
+ totalCount: z.number().optional()
292
+ });
293
+ const uiMessagePartSchema = z.union([
294
+ z.object({
295
+ type: z.literal("text"),
296
+ text: z.string()
297
+ }),
298
+ z.object({
299
+ type: z.literal("reasoning"),
300
+ text: z.string().optional()
301
+ }),
302
+ z.object({
303
+ type: z.literal("dynamic-tool"),
304
+ toolCallId: z.string(),
305
+ toolName: z.string(),
306
+ input: z.unknown().optional(),
307
+ output: z.unknown().optional(),
308
+ state: z.string().optional(),
309
+ errorText: z.string().optional()
310
+ }),
311
+ z.object({ type: z.string() }).passthrough()
312
+ ]);
313
+ const uiMessageSchema = z.object({
314
+ id: z.string(),
315
+ role: z.string(),
316
+ parts: z.array(uiMessagePartSchema)
317
+ });
318
+ const recapResponseSchema = z.object({ recap: z.object({ text: z.string() }).nullable() });
319
+ const listMessagesResponseSchema = z.object({ messages: z.array(uiMessageSchema) });
320
+ const sendResultSchema = z.object({
321
+ runId: z.string(),
322
+ conversationId: z.string().uuid(),
323
+ isNewConversation: z.boolean(),
324
+ steered: z.boolean().optional(),
325
+ directive: z.object({ id: z.string() }).passthrough().optional()
326
+ });
327
+ const presignResponseSchema = z.object({
328
+ id: z.string(),
329
+ uploadUrl: z.string(),
330
+ fileName: z.string(),
331
+ mediaType: z.string()
332
+ });
333
+ const finalizeResponseSchema = z.object({
334
+ fileName: z.string(),
335
+ mediaType: z.string(),
336
+ sizeBytes: z.number().nullable().optional()
337
+ });
338
+ const activeRunResponseSchema = z.object({ run: z.object({ runId: z.string() }).nullable() });
339
+ const oauthConnectResponseSchema = z.object({ connectLink: z.string() }).passthrough();
340
+ const externalOauthConnectResponseSchema = z.object({ authorizationUrl: z.string().optional() }).passthrough();
341
+ const runStreamEventSchema = z.union([z.object({
342
+ kind: z.literal("chunk"),
343
+ chunk: z.record(z.unknown())
344
+ }), z.object({
345
+ kind: z.literal("finished"),
346
+ status: z.string(),
347
+ error: z.string().nullish()
348
+ })]);
349
+ const streamErrorSchema = z.object({ error: z.string() });
350
+
351
+ //#endregion
352
+ //#region src/chat/util.ts
353
+ /** Narrowing helper for the many `unknown` payloads the chat stream and
354
+ * tool inputs/outputs carry. A type predicate (not an `as` cast), so call
355
+ * sites can read properties without asserting. */
356
+ function isRecord(value) {
357
+ return typeof value === "object" && value !== null && !Array.isArray(value);
358
+ }
359
+ /** Best-effort message from an unknown thrown value. */
360
+ function errorMessage(err) {
361
+ return err instanceof Error ? err.message : String(err);
362
+ }
363
+
364
+ //#endregion
365
+ //#region src/chat/tui/chat/card.ts
366
+ const urlActionKinds = [
367
+ "open_oauth",
368
+ "open_external_oauth",
369
+ "open_github_app",
370
+ "submit_credential"
371
+ ];
372
+ function isUrlActionKind(value) {
373
+ return typeof value === "string" && urlActionKinds.includes(value);
374
+ }
375
+ function optionalString(value) {
376
+ return typeof value === "string" && value ? value : null;
377
+ }
378
+ function bindStateKey(props) {
379
+ const value = props.value;
380
+ if (!isRecord(value)) return null;
381
+ const pointer = value.$bindState;
382
+ if (typeof pointer !== "string") return null;
383
+ return pointer.startsWith("/") ? pointer.slice(1) : pointer;
384
+ }
385
+ function parseButton(element) {
386
+ const props = isRecord(element.props) ? element.props : {};
387
+ const label = optionalString(props.label) ?? "Connect";
388
+ const on = isRecord(element.on) ? element.on : null;
389
+ const press = on && isRecord(on.press) ? on.press : null;
390
+ if (!press) return null;
391
+ const params = isRecord(press.params) ? press.params : {};
392
+ const primary = props.variant === "primary";
393
+ if (press.action === "approve_portal_access") {
394
+ const agentId = params.agentId;
395
+ if (typeof agentId !== "string" || !agentId) return null;
396
+ return {
397
+ label,
398
+ action: {
399
+ kind: "grant_portal",
400
+ agentId,
401
+ deviceId: typeof params.deviceId === "string" && params.deviceId ? params.deviceId : null
402
+ },
403
+ primary
404
+ };
405
+ }
406
+ if (!isUrlActionKind(press.action)) return null;
407
+ const url = params.url;
408
+ if (typeof url !== "string" || !url) return null;
409
+ return {
410
+ label,
411
+ action: {
412
+ kind: press.action,
413
+ url
414
+ },
415
+ primary
416
+ };
417
+ }
418
+ function parseConnectCard(spec) {
419
+ if (!isRecord(spec)) return null;
420
+ const { root, elements } = spec;
421
+ if (typeof root !== "string" || !isRecord(elements)) return null;
422
+ const rootEl = elements[root];
423
+ if (!isRecord(rootEl) || rootEl.type !== "Card") return null;
424
+ const rootProps = isRecord(rootEl.props) ? rootEl.props : {};
425
+ const title = optionalString(rootProps.title);
426
+ if (!title) return null;
427
+ const fields = [];
428
+ const buttons = [];
429
+ const children = Array.isArray(rootEl.children) ? rootEl.children : [];
430
+ for (const childId of children) {
431
+ if (typeof childId !== "string") continue;
432
+ const el = elements[childId];
433
+ if (!isRecord(el)) continue;
434
+ if (el.type === "TextInput" || el.type === "SecretInput") {
435
+ const props = isRecord(el.props) ? el.props : {};
436
+ const key = bindStateKey(props);
437
+ if (!key) continue;
438
+ fields.push({
439
+ key,
440
+ label: optionalString(props.label) ?? key,
441
+ placeholder: optionalString(props.placeholder),
442
+ secret: el.type === "SecretInput"
443
+ });
444
+ continue;
445
+ }
446
+ if (el.type === "Button") {
447
+ const button = parseButton(el);
448
+ if (button) buttons.push(button);
449
+ }
450
+ }
451
+ const preferred = buttons.find((b) => b.primary) ?? buttons[0] ?? null;
452
+ return {
453
+ title,
454
+ subtitle: optionalString(rootProps.subtitle),
455
+ description: optionalString(rootProps.description),
456
+ fields,
457
+ button: preferred ? {
458
+ label: preferred.label,
459
+ action: preferred.action
460
+ } : null,
461
+ state: isRecord(spec.state) ? spec.state : {}
462
+ };
463
+ }
464
+
465
+ //#endregion
466
+ //#region src/chat/tui/chat/card-actions.ts
467
+ function safeUrl(url) {
468
+ try {
469
+ return new URL(url, "http://localhost");
470
+ } catch (_error) {
471
+ return null;
472
+ }
473
+ }
474
+ /**
475
+ * Resolve a connect URL to an absolute one the OS can open in a browser.
476
+ *
477
+ * The server builds lazy connect links as server-relative paths (e.g.
478
+ * `/api/v1/oauth/start/slack?connect_session_token=...`). The web client
479
+ * resolves these against its own origin implicitly; the TUI runs outside a
480
+ * browser, so `open()` on a scheme-less path is a silent no-op — the browser
481
+ * never launches and the connect card sits in `launched` forever (the Slack
482
+ * channel-connect deadlock). Resolve against `appUrl` first, mirroring the
483
+ * `open_github_app` / `fulfillCredential` branches, so both the browser we
484
+ * launch and the URL shown on the card are absolute. An already-absolute URL
485
+ * passes through unchanged; if `appUrl` is missing we return the input as-is.
486
+ */
487
+ function resolveConnectUrl(url, appUrl) {
488
+ if (!appUrl) return url;
489
+ try {
490
+ return new URL(url, appUrl).toString();
491
+ } catch (_error) {
492
+ return url;
493
+ }
494
+ }
495
+ function parseOauthConnectParams(url) {
496
+ const parsed = safeUrl(url);
497
+ if (!parsed) return null;
498
+ const integrationKey = parsed.searchParams.get("integration");
499
+ const agentId = parsed.searchParams.get("agent_id");
500
+ const authConfigId = parsed.searchParams.get("auth_config_id");
501
+ const conversationId = parsed.searchParams.get("conversation_id");
502
+ if (!integrationKey || !agentId || !authConfigId || !conversationId) return null;
503
+ return {
504
+ integrationKey,
505
+ agentId,
506
+ authConfigId,
507
+ conversationId,
508
+ scopes: parsed.searchParams.get("scopes")
509
+ };
510
+ }
511
+ function parseExternalOauthConnectParams(url) {
512
+ const parsed = safeUrl(url);
513
+ if (!parsed) return null;
514
+ const agentId = parsed.searchParams.get("agent_id");
515
+ const conversationId = parsed.searchParams.get("conversation_id");
516
+ const serverUrl = parsed.searchParams.get("server_url");
517
+ if (!agentId || !conversationId || !serverUrl) return null;
518
+ return {
519
+ agentId,
520
+ conversationId,
521
+ serverUrl
522
+ };
523
+ }
524
+ /**
525
+ * A short human line for a failed card action. Fulfill/connect endpoints
526
+ * return `{ error: string }` bodies (e.g. "already fulfilled") — prefer that
527
+ * over the generic HttpError message.
528
+ */
529
+ function cardActionErrorMessage(err) {
530
+ if (err instanceof HttpError) {
531
+ try {
532
+ const parsed = JSON.parse(err.body);
533
+ if (isRecord(parsed) && typeof parsed.error === "string") return parsed.error;
534
+ } catch (_error) {}
535
+ return `request failed (HTTP ${err.status})`;
536
+ }
537
+ return errorMessage(err);
538
+ }
539
+ const MASK_CHAR = "•";
540
+ /**
541
+ * Recover the real secret from the masked input's displayed text. The input
542
+ * is controlled: after every edit we render bullets, which forces the cursor
543
+ * to the end, so the next edit is always a tail edit — the displayed text is
544
+ * some prefix of the old mask (kept characters) followed by newly typed or
545
+ * pasted plaintext. Characters beyond the retained bullets are the new tail.
546
+ */
547
+ function reconcileMaskedInput(previousValue, displayed) {
548
+ let kept = 0;
549
+ while (kept < displayed.length && kept < previousValue.length && displayed[kept] === MASK_CHAR) kept++;
550
+ return previousValue.slice(0, kept) + displayed.slice(kept);
551
+ }
552
+
553
+ //#endregion
554
+ //#region src/chat/connect-cards.ts
555
+ /**
556
+ * Turn a parsed ConnectCard into the headless summary, resolving any relative
557
+ * connect URL against the app origin so the emitted URL is directly openable.
558
+ */
559
+ function summarizeConnectCard(card, appUrl) {
560
+ const base = {
561
+ title: card.title,
562
+ subtitle: card.subtitle,
563
+ description: card.description
564
+ };
565
+ const button = card.button;
566
+ if (!button) return {
567
+ ...base,
568
+ action: { kind: "unsupported" }
569
+ };
570
+ const act = button.action;
571
+ switch (act.kind) {
572
+ case "open_oauth":
573
+ case "open_external_oauth":
574
+ case "open_github_app": return {
575
+ ...base,
576
+ action: {
577
+ kind: "open_url",
578
+ url: resolveConnectUrl(act.url, appUrl)
579
+ }
580
+ };
581
+ case "submit_credential": return {
582
+ ...base,
583
+ action: {
584
+ kind: "submit_credential",
585
+ url: resolveConnectUrl(act.url, appUrl),
586
+ fields: card.fields.map((f) => f.label)
587
+ }
588
+ };
589
+ case "grant_portal": return {
590
+ ...base,
591
+ action: {
592
+ kind: "approve_portal",
593
+ agentId: act.agentId
594
+ }
595
+ };
596
+ default: return {
597
+ ...base,
598
+ action: { kind: "unsupported" }
599
+ };
600
+ }
601
+ }
602
+ /**
603
+ * Parse a `data-anyone-render-spec` stream chunk into a connect-card summary,
604
+ * or null if the chunk isn't a connect card (other render specs — training,
605
+ * deep-learn, compute-request — parse to null, same as the TUI).
606
+ */
607
+ function connectCardFromChunk(chunk, appUrl) {
608
+ if (chunk["type"] !== "data-anyone-render-spec") return null;
609
+ const data = chunk["data"];
610
+ if (!isRecord(data)) return null;
611
+ const card = parseConnectCard(data["spec"]);
612
+ if (!card) return null;
613
+ return summarizeConnectCard(card, appUrl);
614
+ }
615
+ /** Render a connect-card summary as a human-readable action block. */
616
+ function formatConnectCard(card) {
617
+ const lines = [];
618
+ lines.push(`\n[action needed] ${card.title}`);
619
+ if (card.subtitle) lines.push(card.subtitle);
620
+ if (card.description) lines.push(card.description);
621
+ switch (card.action.kind) {
622
+ case "open_url":
623
+ lines.push(`Open to continue: ${card.action.url}`);
624
+ break;
625
+ case "submit_credential":
626
+ lines.push(`Provide credential (${card.action.fields.join(", ") || "value"}) at: ${card.action.url}`);
627
+ break;
628
+ case "approve_portal":
629
+ lines.push(`Approve local-machine access for agent ${card.action.agentId} in the TUI or web app.`);
630
+ break;
631
+ case "unsupported":
632
+ lines.push("Open this conversation in the web app to continue.");
633
+ break;
634
+ }
635
+ return lines.join("\n");
636
+ }
637
+
638
+ //#endregion
639
+ //#region src/chat/print.ts
640
+ var print_exports = /* @__PURE__ */ __exportAll({
641
+ readStdin: () => readStdin,
642
+ resolveAgent: () => resolveAgent,
643
+ runPrint: () => runPrint
644
+ });
645
+ /**
646
+ * Non-interactive chat, à la `claude -p`. Sends a single prompt to an
647
+ * agent, streams the run, and prints the assistant's reply to stdout
648
+ * before exiting. No OpenTUI, no Bun requirement — this rides the same
649
+ * Node-friendly REST client the TUI uses, so it runs anywhere the
650
+ * management commands do (CI, pipes, scripts).
651
+ *
652
+ * Resolution rules kept deliberately strict because there's no human to
653
+ * disambiguate: an `--agent` selector must match exactly one agent, and
654
+ * when it's omitted we only auto-pick if the account has exactly one.
655
+ */
656
+ async function runPrint({ appUrl, sessionToken, prompt, agentSelector, conversationId, json }) {
657
+ const client = createRestClient({
658
+ appUrl,
659
+ sessionToken
660
+ });
661
+ const agent = resolveAgent(await client.listAgents({
662
+ scope: "org",
663
+ onPage: null
664
+ }), agentSelector);
665
+ const send = await client.sendMessage({
666
+ agentId: agent.id,
667
+ conversationId,
668
+ content: prompt,
669
+ attachmentIds: [],
670
+ clientSurface: "cli"
671
+ });
672
+ let text = "";
673
+ const controller = new AbortController();
674
+ let streamError = null;
675
+ const connectCards = [];
676
+ await client.streamRun({
677
+ runId: send.runId,
678
+ signal: controller.signal,
679
+ onEvent: (event) => {
680
+ if (event.kind === "finished") {
681
+ if (event.error) streamError = event.error;
682
+ return;
683
+ }
684
+ const chunk = event.chunk;
685
+ if (chunk["type"] === "text-delta") {
686
+ const delta = typeof chunk["delta"] === "string" ? chunk["delta"] : typeof chunk["text"] === "string" ? chunk["text"] : "";
687
+ if (delta) {
688
+ text += delta;
689
+ if (!json) process.stdout.write(delta);
690
+ }
691
+ } else if (chunk["type"] === "error") streamError = typeof chunk["errorText"] === "string" ? chunk["errorText"] : "unknown error";
692
+ else {
693
+ const card = connectCardFromChunk(chunk, appUrl);
694
+ if (card) connectCards.push(card);
695
+ }
696
+ }
697
+ });
698
+ if (streamError) throw new Error(streamError);
699
+ if (!json && text && !text.endsWith("\n")) process.stdout.write("\n");
700
+ if (!json) for (const card of connectCards) process.stdout.write(`${formatConnectCard(card)}\n`);
701
+ return {
702
+ agentId: agent.id,
703
+ agentName: agent.name,
704
+ conversationId: send.conversationId,
705
+ isNewConversation: send.isNewConversation,
706
+ runId: send.runId,
707
+ text,
708
+ connectCards
709
+ };
710
+ }
711
+ /**
712
+ * Pick the target agent. With no selector, auto-pick only when the
713
+ * account has exactly one agent; otherwise the user must name one (there's
714
+ * no picker in non-interactive mode). A selector matches by id first, then
715
+ * a unique case-insensitive slug/name; ambiguous or missing matches throw
716
+ * with the candidate list so the caller knows what to pass.
717
+ */
718
+ function resolveAgent(agents, selector) {
719
+ if (!selector) {
720
+ const [only, ...rest] = agents;
721
+ if (!only) throw new Error("No agents on this account.");
722
+ if (rest.length === 0) return only;
723
+ throw new Error(`Multiple agents on this account — pass --agent <id|slug|name>. Candidates:\n${formatCandidates(agents)}`);
724
+ }
725
+ const byId = agents.find((a) => a.id === selector);
726
+ if (byId) return byId;
727
+ const needle = selector.toLowerCase();
728
+ const matches = agents.filter((a) => a.slug && a.slug.toLowerCase() === needle || a.name.toLowerCase() === needle);
729
+ const [firstMatch, ...restMatches] = matches;
730
+ if (firstMatch && restMatches.length === 0) return firstMatch;
731
+ if (restMatches.length > 0) throw new Error(`Multiple agents match "${selector}" — pass the id instead. Candidates:\n${formatCandidates(matches)}`);
732
+ throw new Error(`No agent matches "${selector}". Candidates:\n${formatCandidates(agents)}`);
733
+ }
734
+ function formatCandidates(agents) {
735
+ return agents.slice(0, 25).map((a) => ` ${a.id} ${a.slug ?? a.name}`).join("\n");
736
+ }
737
+ /** Read all of stdin as UTF-8. Used when `-p` is passed with no value. */
738
+ async function readStdin() {
739
+ const chunks = [];
740
+ for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
741
+ return Buffer.concat(chunks).toString("utf8");
742
+ }
743
+
744
+ //#endregion
745
+ export { parseExternalOauthConnectParams as a, resolveConnectUrl as c, isRecord as d, HttpError as f, cardActionErrorMessage as i, parseConnectCard as l, resolveAgent as n, parseOauthConnectParams as o, createRestClient as p, MASK_CHAR as r, reconcileMaskedInput as s, print_exports as t, errorMessage as u };