skydive-cli 0.1.0-beta.109 → 0.1.0-beta.122

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.
package/dist/js/bin.mjs CHANGED
@@ -11,9 +11,27 @@ import { spawnSync } from "node:child_process";
11
11
  import { createHash } from "node:crypto";
12
12
  import fs from "node:fs";
13
13
  import zlib from "node:zlib";
14
+ import { createParser } from "eventsource-parser";
14
15
 
16
+ //#region \0rolldown/runtime.js
17
+ var __defProp = Object.defineProperty;
18
+ var __exportAll = (all, no_symbols) => {
19
+ let target = {};
20
+ for (var name in all) {
21
+ __defProp(target, name, {
22
+ get: all[name],
23
+ enumerable: true
24
+ });
25
+ }
26
+ if (!no_symbols) {
27
+ __defProp(target, Symbol.toStringTag, { value: "Module" });
28
+ }
29
+ return target;
30
+ };
31
+
32
+ //#endregion
15
33
  //#region package.json
16
- var version$1 = "0.1.0-beta.109";
34
+ var version$1 = "0.1.0-beta.122";
17
35
 
18
36
  //#endregion
19
37
  //#region src/types.ts
@@ -553,7 +571,7 @@ async function loginWithDevice({ appUrl, openBrowser = true }) {
553
571
  let intervalMs = (code.interval ?? 5) * 1e3 || DEFAULT_INTERVAL_MS;
554
572
  const expiresAt = Date.now() + code.expires_in * 1e3;
555
573
  while (Date.now() < expiresAt) {
556
- await sleep(intervalMs);
574
+ await sleep$1(intervalMs);
557
575
  const result = await pollDeviceToken({
558
576
  appUrl,
559
577
  deviceCode: code.device_code,
@@ -601,7 +619,7 @@ function formatUserCode(code) {
601
619
  if (code.length !== 8) return code;
602
620
  return `${code.slice(0, 4)}-${code.slice(4)}`;
603
621
  }
604
- function sleep(ms) {
622
+ function sleep$1(ms) {
605
623
  return new Promise((resolve) => setTimeout(resolve, ms));
606
624
  }
607
625
 
@@ -794,7 +812,7 @@ function requireClient$2(argv) {
794
812
  }
795
813
  return new SkydiveApiClient(result.value);
796
814
  }
797
- const listCommand$3 = {
815
+ const listCommand$4 = {
798
816
  command: "list",
799
817
  describe: "List agents",
800
818
  builder: (y) => y.option("limit", {
@@ -938,7 +956,7 @@ const createCommand$1 = {
938
956
  const agentsCommand = {
939
957
  command: "agents",
940
958
  describe: "Manage agents",
941
- builder: (y) => y.command(listCommand$3).command(getCommand).command(createCommand$1).demandCommand(1, "Specify a subcommand: list, get, create"),
959
+ builder: (y) => y.command(listCommand$4).command(getCommand).command(createCommand$1).demandCommand(1, "Specify a subcommand: list, get, create"),
942
960
  handler: () => {}
943
961
  };
944
962
 
@@ -952,7 +970,7 @@ function requireClient$1(argv) {
952
970
  }
953
971
  return new SkydiveApiClient(result.value);
954
972
  }
955
- const listCommand$2 = {
973
+ const listCommand$3 = {
956
974
  command: "list",
957
975
  describe: "List API keys for an agent",
958
976
  handler: async (argv) => {
@@ -1041,7 +1059,7 @@ const keysCommand = {
1041
1059
  type: "string",
1042
1060
  demandOption: true,
1043
1061
  describe: "Agent ID"
1044
- }).command(listCommand$2).command(createCommand).command(revokeCommand).demandCommand(1, "Specify a subcommand: list, create, revoke"),
1062
+ }).command(listCommand$3).command(createCommand).command(revokeCommand).demandCommand(1, "Specify a subcommand: list, create, revoke"),
1045
1063
  handler: () => {}
1046
1064
  };
1047
1065
 
@@ -1056,12 +1074,12 @@ function requireClient(argv) {
1056
1074
  return new SkydiveApiClient(result.value);
1057
1075
  }
1058
1076
  /** Read all of stdin as UTF-8, trimming a single trailing newline. */
1059
- async function readStdin() {
1077
+ async function readStdin$1() {
1060
1078
  const chunks = [];
1061
1079
  for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1062
1080
  return Buffer.concat(chunks).toString("utf8").replace(/\n$/, "");
1063
1081
  }
1064
- const listCommand$1 = {
1082
+ const listCommand$2 = {
1065
1083
  command: "list",
1066
1084
  describe: "List secret names for an agent (values are never shown)",
1067
1085
  handler: async (argv) => {
@@ -1100,7 +1118,7 @@ const setCommand = {
1100
1118
  printError("No value given. Pass it as an argument or pipe it on stdin, e.g. `echo -n \"$TOKEN\" | skydive secrets set MY_KEY --agent-id <id>`.");
1101
1119
  process.exit(1);
1102
1120
  }
1103
- value = await readStdin();
1121
+ value = await readStdin$1();
1104
1122
  }
1105
1123
  if (!value) {
1106
1124
  printError("Empty secret value.");
@@ -1147,7 +1165,7 @@ const secretsCommand = {
1147
1165
  type: "string",
1148
1166
  demandOption: true,
1149
1167
  describe: "Agent ID"
1150
- }).command(listCommand$1).command(setCommand).command(rmCommand).demandCommand(1, "Specify a subcommand: list, set, rm"),
1168
+ }).command(listCommand$2).command(setCommand).command(rmCommand).demandCommand(1, "Specify a subcommand: list, set, rm"),
1151
1169
  handler: () => {}
1152
1170
  };
1153
1171
 
@@ -2134,7 +2152,7 @@ const chatCommand = {
2134
2152
  printError(`Unknown theme "${themeId}". Valid themes: ${themes.map((t) => t.id).join(", ")}`);
2135
2153
  process.exit(1);
2136
2154
  }
2137
- const { runChat } = await import("./boot-DRWra2vY.mjs");
2155
+ const { runChat } = await import("./boot-BvkqTAxQ.mjs");
2138
2156
  await runChat({
2139
2157
  appUrl,
2140
2158
  sessionToken: session.value.sessionToken,
@@ -2152,7 +2170,7 @@ async function runPrintMode({ argv, appUrl }) {
2152
2170
  printError(`${session.error.message} For non-interactive use, run \`skydive auth login --web\` first, or set SKYDIVE_SESSION_TOKEN.`);
2153
2171
  process.exit(1);
2154
2172
  }
2155
- const { runPrint, readStdin } = await import("./print-CQ8b7JUC.mjs").then((n) => n.t);
2173
+ const { runPrint, readStdin } = await Promise.resolve().then(() => print_exports);
2156
2174
  let prompt = (argv.print ?? "").trim();
2157
2175
  if (!prompt) {
2158
2176
  if (process.stdin.isTTY) {
@@ -2182,15 +2200,875 @@ async function runPrintMode({ argv, appUrl }) {
2182
2200
  }
2183
2201
 
2184
2202
  //#endregion
2185
- //#region src/commands/workspace.ts
2203
+ //#region src/chat/api/rest.ts
2204
+ var HttpError = class extends Error {
2205
+ constructor(status, body) {
2206
+ super(`HTTP ${status}: ${body.slice(0, 200)}`);
2207
+ this.status = status;
2208
+ this.body = body;
2209
+ this.name = "HttpError";
2210
+ }
2211
+ };
2212
+ const MAX_STREAM_RECONNECTS = 5;
2213
+ function createRestClient({ appUrl, sessionToken }) {
2214
+ const baseHeaders = {
2215
+ authorization: `Bearer ${sessionToken}`,
2216
+ accept: "application/json"
2217
+ };
2218
+ async function get(path, schema) {
2219
+ const res = await fetch(`${appUrl}${path}`, { headers: baseHeaders });
2220
+ if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
2221
+ return schema.parse(await res.json());
2222
+ }
2223
+ async function post(path, body, schema, method = "POST") {
2224
+ const res = await fetch(`${appUrl}${path}`, {
2225
+ method,
2226
+ headers: {
2227
+ ...baseHeaders,
2228
+ "content-type": "application/json"
2229
+ },
2230
+ body: JSON.stringify(body)
2231
+ });
2232
+ if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
2233
+ return schema.parse(await res.json());
2234
+ }
2235
+ async function del(path) {
2236
+ const res = await fetch(`${appUrl}${path}`, {
2237
+ method: "DELETE",
2238
+ headers: baseHeaders
2239
+ });
2240
+ if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
2241
+ }
2242
+ return {
2243
+ listAgents: async ({ scope, onPage }) => {
2244
+ const all = [];
2245
+ let cursor;
2246
+ const maxAgents = 2e3;
2247
+ do {
2248
+ const params = new URLSearchParams({
2249
+ limit: "100",
2250
+ scope,
2251
+ sort: "mine_first_usage",
2252
+ includeStats: "false"
2253
+ });
2254
+ if (cursor) params.set("cursor", cursor);
2255
+ const page = await get(`/api/v1/agents?${params.toString()}`, listAgentsResponseSchema);
2256
+ all.push(...page.agents);
2257
+ cursor = page.nextCursor ?? void 0;
2258
+ onPage?.([...all]);
2259
+ } while (cursor && all.length < maxAgents);
2260
+ return all;
2261
+ },
2262
+ createAgent: async ({ name }) => {
2263
+ const { agent } = await post("/api/v1/agents", { name }, createAgentResponseSchema);
2264
+ return agent;
2265
+ },
2266
+ getConversation: async ({ conversationId }) => {
2267
+ const { conversation } = await get(`/api/v1/conversations/${encodeURIComponent(conversationId)}`, getConversationResponseSchema);
2268
+ return conversation;
2269
+ },
2270
+ listModels: async () => {
2271
+ const { models } = await get("/api/v1/models", listModelsResponseSchema);
2272
+ return models;
2273
+ },
2274
+ updateAgentModel: async ({ agentId, model }) => {
2275
+ const { agent } = await post(`/api/v1/agents/${encodeURIComponent(agentId)}`, { model }, updateAgentResponseSchema, "PATCH");
2276
+ return { model: agent.model ?? null };
2277
+ },
2278
+ listConversations: async ({ agentId, limit }) => {
2279
+ const all = [];
2280
+ const maxConversations = limit ?? 5e3;
2281
+ let cursor;
2282
+ do {
2283
+ const remaining = maxConversations - all.length;
2284
+ const params = new URLSearchParams({
2285
+ agentId,
2286
+ includeTotal: "false"
2287
+ });
2288
+ params.set("limit", String(Math.min(remaining, 100)));
2289
+ if (cursor) params.set("cursor", cursor);
2290
+ const page = await get(`/api/v1/conversations?${params.toString()}`, listConversationsResponseSchema);
2291
+ all.push(...page.conversations);
2292
+ cursor = page.nextCursor ?? void 0;
2293
+ } while (cursor && all.length < maxConversations);
2294
+ return limit ? all.slice(0, limit) : all;
2295
+ },
2296
+ listMessages: async ({ conversationId }) => {
2297
+ const { messages } = await get(`/api/v1/conversations/${encodeURIComponent(conversationId)}/messages`, listMessagesResponseSchema);
2298
+ return messages;
2299
+ },
2300
+ getRecap: async ({ conversationId }) => {
2301
+ const { recap } = await get(`/api/v1/conversations/${encodeURIComponent(conversationId)}/recap`, recapResponseSchema);
2302
+ return recap?.text ?? null;
2303
+ },
2304
+ uploadAttachment: async ({ agentId, fileName, mediaType, data }) => {
2305
+ const size = data.byteLength;
2306
+ const presign = await post("/api/v1/attachments/presign", {
2307
+ agentId,
2308
+ fileName,
2309
+ mediaType,
2310
+ size
2311
+ }, presignResponseSchema);
2312
+ const putRes = await fetch(presign.uploadUrl, {
2313
+ method: "PUT",
2314
+ headers: { "content-type": mediaType },
2315
+ body: new Uint8Array(data)
2316
+ });
2317
+ if (!putRes.ok) throw new HttpError(putRes.status, await putRes.text().catch(() => ""));
2318
+ const finalized = await post(`/api/v1/attachments/${encodeURIComponent(presign.id)}/finalize`, {
2319
+ agentId,
2320
+ fileName: presign.fileName,
2321
+ mediaType: presign.mediaType,
2322
+ size
2323
+ }, finalizeResponseSchema);
2324
+ return {
2325
+ id: presign.id,
2326
+ fileName: finalized.fileName,
2327
+ mediaType: finalized.mediaType,
2328
+ sizeBytes: finalized.sizeBytes ?? size
2329
+ };
2330
+ },
2331
+ deleteConversation: async ({ conversationId }) => {
2332
+ await del(`/api/v1/conversations/${encodeURIComponent(conversationId)}`);
2333
+ },
2334
+ sendMessage: async ({ clientSurface, ...input }) => post("/api/v1/chat/send", {
2335
+ ...input,
2336
+ clientSurface
2337
+ }, sendResultSchema),
2338
+ activeRun: async ({ conversationId }) => {
2339
+ const { run } = await get(`/api/v1/chat/active-run?${new URLSearchParams({ conversationId }).toString()}`, activeRunResponseSchema);
2340
+ return run;
2341
+ },
2342
+ cancelRun: async ({ runId }) => {
2343
+ await post(`/api/v1/chat/runs/${encodeURIComponent(runId)}/cancel`, {}, z.object({ ok: z.boolean() }));
2344
+ },
2345
+ cancelSteer: async ({ directiveId }) => {
2346
+ await post(`/api/v1/chat/steer/${encodeURIComponent(directiveId)}/cancel`, {}, z.object({ ok: z.boolean() }));
2347
+ },
2348
+ oauthConnect: async (input) => {
2349
+ const { connectLink } = await post("/api/v1/oauth/connect", input, oauthConnectResponseSchema);
2350
+ return { connectLink };
2351
+ },
2352
+ externalOauthConnect: async (input) => {
2353
+ const { authorizationUrl } = await post("/api/v1/external-oauth/connect", input, externalOauthConnectResponseSchema);
2354
+ return { authorizationUrl: authorizationUrl ?? null };
2355
+ },
2356
+ fulfillCredential: async ({ url, body }) => {
2357
+ const target = new URL(url, appUrl).toString();
2358
+ const res = await fetch(target, {
2359
+ method: "POST",
2360
+ headers: {
2361
+ ...baseHeaders,
2362
+ "content-type": "application/json"
2363
+ },
2364
+ body: JSON.stringify(body)
2365
+ });
2366
+ if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
2367
+ },
2368
+ streamRun: async ({ runId, signal, onEvent }) => {
2369
+ let lastEventId = null;
2370
+ let finished = false;
2371
+ let reconnects = 0;
2372
+ for (;;) {
2373
+ if (signal.aborted) return;
2374
+ try {
2375
+ const headers = {
2376
+ authorization: `Bearer ${sessionToken}`,
2377
+ accept: "text/event-stream"
2378
+ };
2379
+ if (lastEventId) headers["last-event-id"] = lastEventId;
2380
+ const res = await fetch(`${appUrl}/api/v1/chat/runs/${encodeURIComponent(runId)}/stream`, {
2381
+ headers,
2382
+ signal
2383
+ });
2384
+ if (!res.ok || !res.body) throw new HttpError(res.status, await res.text().catch(() => ""));
2385
+ reconnects = 0;
2386
+ const parser = createParser({ onEvent: (message) => {
2387
+ if (message.id) lastEventId = message.id;
2388
+ if (message.event === "error") {
2389
+ const { error } = streamErrorSchema.parse(JSON.parse(message.data));
2390
+ throw new Error(error);
2391
+ }
2392
+ const event = runStreamEventSchema.parse(JSON.parse(message.data));
2393
+ if (event.kind === "finished") finished = true;
2394
+ onEvent(event.kind === "finished" ? {
2395
+ ...event,
2396
+ error: event.error ?? null
2397
+ } : event);
2398
+ } });
2399
+ const decoder = new TextDecoder();
2400
+ const reader = res.body.getReader();
2401
+ try {
2402
+ for (;;) {
2403
+ const { done, value } = await reader.read();
2404
+ if (done) break;
2405
+ parser.feed(decoder.decode(value, { stream: true }));
2406
+ if (finished) return;
2407
+ }
2408
+ } finally {
2409
+ try {
2410
+ await reader.cancel();
2411
+ } catch (_error) {}
2412
+ }
2413
+ } catch (err) {
2414
+ if (signal.aborted) return;
2415
+ if (err instanceof HttpError && err.status >= 400 && err.status < 500) throw err;
2416
+ reconnects += 1;
2417
+ if (reconnects > MAX_STREAM_RECONNECTS) throw err;
2418
+ await sleep(Math.min(500 * 2 ** reconnects, 5e3));
2419
+ continue;
2420
+ }
2421
+ if (finished) return;
2422
+ reconnects += 1;
2423
+ if (reconnects > MAX_STREAM_RECONNECTS) throw new Error("run stream ended unexpectedly");
2424
+ await sleep(Math.min(500 * 2 ** reconnects, 5e3));
2425
+ }
2426
+ }
2427
+ };
2428
+ }
2429
+ function sleep(ms) {
2430
+ return new Promise((resolve) => setTimeout(resolve, ms));
2431
+ }
2432
+ const agentSummarySchema = z.object({
2433
+ id: z.string().uuid(),
2434
+ name: z.string(),
2435
+ slug: z.string().nullable().optional(),
2436
+ title: z.string().nullable().optional(),
2437
+ description: z.string().nullable().optional(),
2438
+ createdAt: z.string(),
2439
+ creatorName: z.string().nullable().optional(),
2440
+ model: z.string().nullable().optional(),
2441
+ modelLocked: z.boolean().optional()
2442
+ });
2443
+ const platformModelSchema = z.object({
2444
+ id: z.string(),
2445
+ displayName: z.string(),
2446
+ providerDisplay: z.string().optional(),
2447
+ reasoning: z.boolean().optional(),
2448
+ compliant: z.boolean().optional()
2449
+ }).passthrough();
2450
+ const listModelsResponseSchema = z.object({ models: z.array(platformModelSchema) });
2451
+ const updateAgentResponseSchema = z.object({ agent: z.object({ model: z.string().nullable().optional() }).passthrough() });
2452
+ const listAgentsResponseSchema = z.object({
2453
+ agents: z.array(agentSummarySchema),
2454
+ nextCursor: z.string().nullable().optional(),
2455
+ totalCount: z.number().nullable().optional()
2456
+ });
2457
+ const createAgentResponseSchema = z.object({ agent: agentSummarySchema });
2458
+ const conversationSummarySchema = z.object({
2459
+ id: z.string().uuid(),
2460
+ title: z.string().nullable(),
2461
+ createdAt: z.string(),
2462
+ updatedAt: z.string(),
2463
+ preview: z.string().nullable(),
2464
+ channel: z.string().nullable(),
2465
+ channelLabel: z.string().nullable(),
2466
+ agent: z.object({
2467
+ id: z.string().uuid(),
2468
+ name: z.string(),
2469
+ slug: z.string().nullable().optional(),
2470
+ title: z.string().nullable().optional()
2471
+ })
2472
+ });
2473
+ const conversationTitleSchema = z.object({
2474
+ id: z.string().uuid(),
2475
+ title: z.string().nullable()
2476
+ });
2477
+ const getConversationResponseSchema = z.object({ conversation: conversationTitleSchema });
2478
+ const listConversationsResponseSchema = z.object({
2479
+ conversations: z.array(conversationSummarySchema),
2480
+ nextCursor: z.string().nullable().optional(),
2481
+ totalCount: z.number().optional()
2482
+ });
2483
+ const uiMessagePartSchema = z.union([
2484
+ z.object({
2485
+ type: z.literal("text"),
2486
+ text: z.string()
2487
+ }),
2488
+ z.object({
2489
+ type: z.literal("reasoning"),
2490
+ text: z.string().optional()
2491
+ }),
2492
+ z.object({
2493
+ type: z.literal("dynamic-tool"),
2494
+ toolCallId: z.string(),
2495
+ toolName: z.string(),
2496
+ input: z.unknown().optional(),
2497
+ output: z.unknown().optional(),
2498
+ state: z.string().optional(),
2499
+ errorText: z.string().optional()
2500
+ }),
2501
+ z.object({ type: z.string() }).passthrough()
2502
+ ]);
2503
+ const uiMessageSchema = z.object({
2504
+ id: z.string(),
2505
+ role: z.string(),
2506
+ parts: z.array(uiMessagePartSchema)
2507
+ });
2508
+ const recapResponseSchema = z.object({ recap: z.object({ text: z.string() }).nullable() });
2509
+ const listMessagesResponseSchema = z.object({ messages: z.array(uiMessageSchema) });
2510
+ const sendResultSchema = z.object({
2511
+ runId: z.string(),
2512
+ conversationId: z.string().uuid(),
2513
+ isNewConversation: z.boolean(),
2514
+ steered: z.boolean().optional(),
2515
+ directive: z.object({ id: z.string() }).passthrough().optional()
2516
+ });
2517
+ const presignResponseSchema = z.object({
2518
+ id: z.string(),
2519
+ uploadUrl: z.string(),
2520
+ fileName: z.string(),
2521
+ mediaType: z.string()
2522
+ });
2523
+ const finalizeResponseSchema = z.object({
2524
+ fileName: z.string(),
2525
+ mediaType: z.string(),
2526
+ sizeBytes: z.number().nullable().optional()
2527
+ });
2528
+ const activeRunResponseSchema = z.object({ run: z.object({ runId: z.string() }).nullable() });
2529
+ const oauthConnectResponseSchema = z.object({ connectLink: z.string() }).passthrough();
2530
+ const externalOauthConnectResponseSchema = z.object({ authorizationUrl: z.string().optional() }).passthrough();
2531
+ const runStreamEventSchema = z.union([z.object({
2532
+ kind: z.literal("chunk"),
2533
+ chunk: z.record(z.unknown())
2534
+ }), z.object({
2535
+ kind: z.literal("finished"),
2536
+ status: z.string(),
2537
+ error: z.string().nullish()
2538
+ })]);
2539
+ const streamErrorSchema = z.object({ error: z.string() });
2540
+
2541
+ //#endregion
2542
+ //#region src/chat/util.ts
2543
+ /** Narrowing helper for the many `unknown` payloads the chat stream and
2544
+ * tool inputs/outputs carry. A type predicate (not an `as` cast), so call
2545
+ * sites can read properties without asserting. */
2546
+ function isRecord(value) {
2547
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2548
+ }
2549
+ /** Best-effort message from an unknown thrown value. */
2550
+ function errorMessage(err) {
2551
+ return err instanceof Error ? err.message : String(err);
2552
+ }
2553
+
2554
+ //#endregion
2555
+ //#region src/chat/tui/chat/card.ts
2556
+ const urlActionKinds = [
2557
+ "open_oauth",
2558
+ "open_external_oauth",
2559
+ "open_github_app",
2560
+ "submit_credential"
2561
+ ];
2562
+ function isUrlActionKind(value) {
2563
+ return typeof value === "string" && urlActionKinds.includes(value);
2564
+ }
2565
+ function optionalString(value) {
2566
+ return typeof value === "string" && value ? value : null;
2567
+ }
2568
+ function bindStateKey(props) {
2569
+ const value = props.value;
2570
+ if (!isRecord(value)) return null;
2571
+ const pointer = value.$bindState;
2572
+ if (typeof pointer !== "string") return null;
2573
+ return pointer.startsWith("/") ? pointer.slice(1) : pointer;
2574
+ }
2575
+ function parseButton(element) {
2576
+ const props = isRecord(element.props) ? element.props : {};
2577
+ const label = optionalString(props.label) ?? "Connect";
2578
+ const on = isRecord(element.on) ? element.on : null;
2579
+ const press = on && isRecord(on.press) ? on.press : null;
2580
+ if (!press) return null;
2581
+ const params = isRecord(press.params) ? press.params : {};
2582
+ const primary = props.variant === "primary";
2583
+ if (press.action === "approve_portal_access") {
2584
+ const agentId = params.agentId;
2585
+ if (typeof agentId !== "string" || !agentId) return null;
2586
+ return {
2587
+ label,
2588
+ action: {
2589
+ kind: "grant_portal",
2590
+ agentId,
2591
+ deviceId: typeof params.deviceId === "string" && params.deviceId ? params.deviceId : null
2592
+ },
2593
+ primary
2594
+ };
2595
+ }
2596
+ if (!isUrlActionKind(press.action)) return null;
2597
+ const url = params.url;
2598
+ if (typeof url !== "string" || !url) return null;
2599
+ return {
2600
+ label,
2601
+ action: {
2602
+ kind: press.action,
2603
+ url
2604
+ },
2605
+ primary
2606
+ };
2607
+ }
2608
+ function parseConnectCard(spec) {
2609
+ if (!isRecord(spec)) return null;
2610
+ const { root, elements } = spec;
2611
+ if (typeof root !== "string" || !isRecord(elements)) return null;
2612
+ const rootEl = elements[root];
2613
+ if (!isRecord(rootEl) || rootEl.type !== "Card") return null;
2614
+ const rootProps = isRecord(rootEl.props) ? rootEl.props : {};
2615
+ const title = optionalString(rootProps.title);
2616
+ if (!title) return null;
2617
+ const fields = [];
2618
+ const buttons = [];
2619
+ const children = Array.isArray(rootEl.children) ? rootEl.children : [];
2620
+ for (const childId of children) {
2621
+ if (typeof childId !== "string") continue;
2622
+ const el = elements[childId];
2623
+ if (!isRecord(el)) continue;
2624
+ if (el.type === "TextInput" || el.type === "SecretInput") {
2625
+ const props = isRecord(el.props) ? el.props : {};
2626
+ const key = bindStateKey(props);
2627
+ if (!key) continue;
2628
+ fields.push({
2629
+ key,
2630
+ label: optionalString(props.label) ?? key,
2631
+ placeholder: optionalString(props.placeholder),
2632
+ secret: el.type === "SecretInput"
2633
+ });
2634
+ continue;
2635
+ }
2636
+ if (el.type === "Button") {
2637
+ const button = parseButton(el);
2638
+ if (button) buttons.push(button);
2639
+ }
2640
+ }
2641
+ const preferred = buttons.find((b) => b.primary) ?? buttons[0] ?? null;
2642
+ return {
2643
+ title,
2644
+ subtitle: optionalString(rootProps.subtitle),
2645
+ description: optionalString(rootProps.description),
2646
+ fields,
2647
+ button: preferred ? {
2648
+ label: preferred.label,
2649
+ action: preferred.action
2650
+ } : null,
2651
+ state: isRecord(spec.state) ? spec.state : {}
2652
+ };
2653
+ }
2654
+
2655
+ //#endregion
2656
+ //#region src/chat/tui/chat/card-actions.ts
2657
+ function safeUrl(url) {
2658
+ try {
2659
+ return new URL(url, "http://localhost");
2660
+ } catch (_error) {
2661
+ return null;
2662
+ }
2663
+ }
2664
+ /**
2665
+ * Resolve a connect URL to an absolute one the OS can open in a browser.
2666
+ *
2667
+ * The server builds lazy connect links as server-relative paths (e.g.
2668
+ * `/api/v1/oauth/start/slack?connect_session_token=...`). The web client
2669
+ * resolves these against its own origin implicitly; the TUI runs outside a
2670
+ * browser, so `open()` on a scheme-less path is a silent no-op — the browser
2671
+ * never launches and the connect card sits in `launched` forever (the Slack
2672
+ * channel-connect deadlock). Resolve against `appUrl` first, mirroring the
2673
+ * `open_github_app` / `fulfillCredential` branches, so both the browser we
2674
+ * launch and the URL shown on the card are absolute. An already-absolute URL
2675
+ * passes through unchanged; if `appUrl` is missing we return the input as-is.
2676
+ */
2677
+ function resolveConnectUrl(url, appUrl) {
2678
+ if (!appUrl) return url;
2679
+ try {
2680
+ return new URL(url, appUrl).toString();
2681
+ } catch (_error) {
2682
+ return url;
2683
+ }
2684
+ }
2685
+ function parseOauthConnectParams(url) {
2686
+ const parsed = safeUrl(url);
2687
+ if (!parsed) return null;
2688
+ const integrationKey = parsed.searchParams.get("integration");
2689
+ const agentId = parsed.searchParams.get("agent_id");
2690
+ const authConfigId = parsed.searchParams.get("auth_config_id");
2691
+ const conversationId = parsed.searchParams.get("conversation_id");
2692
+ if (!integrationKey || !agentId || !authConfigId || !conversationId) return null;
2693
+ return {
2694
+ integrationKey,
2695
+ agentId,
2696
+ authConfigId,
2697
+ conversationId,
2698
+ scopes: parsed.searchParams.get("scopes")
2699
+ };
2700
+ }
2701
+ function parseExternalOauthConnectParams(url) {
2702
+ const parsed = safeUrl(url);
2703
+ if (!parsed) return null;
2704
+ const agentId = parsed.searchParams.get("agent_id");
2705
+ const conversationId = parsed.searchParams.get("conversation_id");
2706
+ const serverUrl = parsed.searchParams.get("server_url");
2707
+ if (!agentId || !conversationId || !serverUrl) return null;
2708
+ return {
2709
+ agentId,
2710
+ conversationId,
2711
+ serverUrl
2712
+ };
2713
+ }
2714
+ /**
2715
+ * A short human line for a failed card action. Fulfill/connect endpoints
2716
+ * return `{ error: string }` bodies (e.g. "already fulfilled") — prefer that
2717
+ * over the generic HttpError message.
2718
+ */
2719
+ function cardActionErrorMessage(err) {
2720
+ if (err instanceof HttpError) {
2721
+ try {
2722
+ const parsed = JSON.parse(err.body);
2723
+ if (isRecord(parsed) && typeof parsed.error === "string") return parsed.error;
2724
+ } catch (_error) {}
2725
+ return `request failed (HTTP ${err.status})`;
2726
+ }
2727
+ return errorMessage(err);
2728
+ }
2729
+ const MASK_CHAR = "•";
2730
+ /**
2731
+ * Recover the real secret from the masked input's displayed text. The input
2732
+ * is controlled: after every edit we render bullets, which forces the cursor
2733
+ * to the end, so the next edit is always a tail edit — the displayed text is
2734
+ * some prefix of the old mask (kept characters) followed by newly typed or
2735
+ * pasted plaintext. Characters beyond the retained bullets are the new tail.
2736
+ */
2737
+ function reconcileMaskedInput(previousValue, displayed) {
2738
+ let kept = 0;
2739
+ while (kept < displayed.length && kept < previousValue.length && displayed[kept] === MASK_CHAR) kept++;
2740
+ return previousValue.slice(0, kept) + displayed.slice(kept);
2741
+ }
2742
+
2743
+ //#endregion
2744
+ //#region src/chat/connect-cards.ts
2745
+ /**
2746
+ * Turn a parsed ConnectCard into the headless summary, resolving any relative
2747
+ * connect URL against the app origin so the emitted URL is directly openable.
2748
+ */
2749
+ function summarizeConnectCard(card, appUrl) {
2750
+ const base = {
2751
+ title: card.title,
2752
+ subtitle: card.subtitle,
2753
+ description: card.description
2754
+ };
2755
+ const button = card.button;
2756
+ if (!button) return {
2757
+ ...base,
2758
+ action: { kind: "unsupported" }
2759
+ };
2760
+ const act = button.action;
2761
+ switch (act.kind) {
2762
+ case "open_oauth":
2763
+ case "open_external_oauth":
2764
+ case "open_github_app": return {
2765
+ ...base,
2766
+ action: {
2767
+ kind: "open_url",
2768
+ url: resolveConnectUrl(act.url, appUrl)
2769
+ }
2770
+ };
2771
+ case "submit_credential": return {
2772
+ ...base,
2773
+ action: {
2774
+ kind: "submit_credential",
2775
+ url: resolveConnectUrl(act.url, appUrl),
2776
+ fields: card.fields.map((f) => f.label)
2777
+ }
2778
+ };
2779
+ case "grant_portal": return {
2780
+ ...base,
2781
+ action: {
2782
+ kind: "approve_portal",
2783
+ agentId: act.agentId
2784
+ }
2785
+ };
2786
+ default: return {
2787
+ ...base,
2788
+ action: { kind: "unsupported" }
2789
+ };
2790
+ }
2791
+ }
2792
+ /**
2793
+ * Parse a `data-anyone-render-spec` stream chunk into a connect-card summary,
2794
+ * or null if the chunk isn't a connect card (other render specs — training,
2795
+ * deep-learn, compute-request — parse to null, same as the TUI).
2796
+ */
2797
+ function connectCardFromChunk(chunk, appUrl) {
2798
+ if (chunk["type"] !== "data-anyone-render-spec") return null;
2799
+ const data = chunk["data"];
2800
+ if (!isRecord(data)) return null;
2801
+ const card = parseConnectCard(data["spec"]);
2802
+ if (!card) return null;
2803
+ return summarizeConnectCard(card, appUrl);
2804
+ }
2805
+ /** Render a connect-card summary as a human-readable action block. */
2806
+ function formatConnectCard(card) {
2807
+ const lines = [];
2808
+ lines.push(`\n[action needed] ${card.title}`);
2809
+ if (card.subtitle) lines.push(card.subtitle);
2810
+ if (card.description) lines.push(card.description);
2811
+ switch (card.action.kind) {
2812
+ case "open_url":
2813
+ lines.push(`Open to continue: ${card.action.url}`);
2814
+ break;
2815
+ case "submit_credential":
2816
+ lines.push(`Provide credential (${card.action.fields.join(", ") || "value"}) at: ${card.action.url}`);
2817
+ break;
2818
+ case "approve_portal":
2819
+ lines.push(`Approve local-machine access for agent ${card.action.agentId} in the TUI or web app.`);
2820
+ break;
2821
+ case "unsupported":
2822
+ lines.push("Open this conversation in the web app to continue.");
2823
+ break;
2824
+ }
2825
+ return lines.join("\n");
2826
+ }
2827
+
2828
+ //#endregion
2829
+ //#region src/chat/print.ts
2830
+ var print_exports = /* @__PURE__ */ __exportAll({
2831
+ readStdin: () => readStdin,
2832
+ resolveAgent: () => resolveAgent,
2833
+ runPrint: () => runPrint
2834
+ });
2835
+ /**
2836
+ * Non-interactive chat, à la `claude -p`. Sends a single prompt to an
2837
+ * agent, streams the run, and prints the assistant's reply to stdout
2838
+ * before exiting. No OpenTUI, no Bun requirement — this rides the same
2839
+ * Node-friendly REST client the TUI uses, so it runs anywhere the
2840
+ * management commands do (CI, pipes, scripts).
2841
+ *
2842
+ * Resolution rules kept deliberately strict because there's no human to
2843
+ * disambiguate: an `--agent` selector must match exactly one agent, and
2844
+ * when it's omitted we only auto-pick if the account has exactly one.
2845
+ */
2846
+ async function runPrint({ appUrl, sessionToken, prompt, agentSelector, conversationId, json }) {
2847
+ const client = createRestClient({
2848
+ appUrl,
2849
+ sessionToken
2850
+ });
2851
+ const agent = resolveAgent(await client.listAgents({
2852
+ scope: "org",
2853
+ onPage: null
2854
+ }), agentSelector);
2855
+ const send = await client.sendMessage({
2856
+ agentId: agent.id,
2857
+ conversationId,
2858
+ content: prompt,
2859
+ attachmentIds: [],
2860
+ clientSurface: "cli"
2861
+ });
2862
+ let text = "";
2863
+ const controller = new AbortController();
2864
+ let streamError = null;
2865
+ const connectCards = [];
2866
+ await client.streamRun({
2867
+ runId: send.runId,
2868
+ signal: controller.signal,
2869
+ onEvent: (event) => {
2870
+ if (event.kind === "finished") {
2871
+ if (event.error) streamError = event.error;
2872
+ return;
2873
+ }
2874
+ const chunk = event.chunk;
2875
+ if (chunk["type"] === "text-delta") {
2876
+ const delta = typeof chunk["delta"] === "string" ? chunk["delta"] : typeof chunk["text"] === "string" ? chunk["text"] : "";
2877
+ if (delta) {
2878
+ text += delta;
2879
+ if (!json) process.stdout.write(delta);
2880
+ }
2881
+ } else if (chunk["type"] === "error") streamError = typeof chunk["errorText"] === "string" ? chunk["errorText"] : "unknown error";
2882
+ else {
2883
+ const card = connectCardFromChunk(chunk, appUrl);
2884
+ if (card) connectCards.push(card);
2885
+ }
2886
+ }
2887
+ });
2888
+ if (streamError) throw new Error(streamError);
2889
+ if (!json && text && !text.endsWith("\n")) process.stdout.write("\n");
2890
+ if (!json) for (const card of connectCards) process.stdout.write(`${formatConnectCard(card)}\n`);
2891
+ return {
2892
+ agentId: agent.id,
2893
+ agentName: agent.name,
2894
+ conversationId: send.conversationId,
2895
+ isNewConversation: send.isNewConversation,
2896
+ runId: send.runId,
2897
+ text,
2898
+ connectCards
2899
+ };
2900
+ }
2901
+ /**
2902
+ * Pick the target agent. With no selector, auto-pick only when the
2903
+ * account has exactly one agent; otherwise the user must name one (there's
2904
+ * no picker in non-interactive mode). A selector matches by id first, then
2905
+ * a unique case-insensitive slug/name; ambiguous or missing matches throw
2906
+ * with the candidate list so the caller knows what to pass.
2907
+ */
2908
+ function resolveAgent(agents, selector) {
2909
+ if (!selector) {
2910
+ const [only, ...rest] = agents;
2911
+ if (!only) throw new Error("No agents on this account.");
2912
+ if (rest.length === 0) return only;
2913
+ throw new Error(`Multiple agents on this account — pass --agent <id|slug|name>. Candidates:\n${formatCandidates(agents)}`);
2914
+ }
2915
+ const byId = agents.find((a) => a.id === selector);
2916
+ if (byId) return byId;
2917
+ const needle = selector.toLowerCase();
2918
+ const matches = agents.filter((a) => a.slug && a.slug.toLowerCase() === needle || a.name.toLowerCase() === needle);
2919
+ const [firstMatch, ...restMatches] = matches;
2920
+ if (firstMatch && restMatches.length === 0) return firstMatch;
2921
+ if (restMatches.length > 0) throw new Error(`Multiple agents match "${selector}" — pass the id instead. Candidates:\n${formatCandidates(matches)}`);
2922
+ throw new Error(`No agent matches "${selector}". Candidates:\n${formatCandidates(agents)}`);
2923
+ }
2924
+ function formatCandidates(agents) {
2925
+ return agents.slice(0, 25).map((a) => ` ${a.id} ${a.slug ?? a.name}`).join("\n");
2926
+ }
2927
+ /** Read all of stdin as UTF-8. Used when `-p` is passed with no value. */
2928
+ async function readStdin() {
2929
+ const chunks = [];
2930
+ for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
2931
+ return Buffer.concat(chunks).toString("utf8");
2932
+ }
2933
+
2934
+ //#endregion
2935
+ //#region src/commands/session.ts
2936
+ /**
2937
+ * Resolve the signed-in chat session or exit with a friendly hint. Shared by
2938
+ * every command that talks to the authenticated REST API so the
2939
+ * resolve-or-exit block isn't copy-pasted per command.
2940
+ */
2186
2941
  function requireSession(argv) {
2187
- const session = resolveSession({ appUrl: argv["api-url"] });
2942
+ const session = resolveSession({ appUrl: resolveAppUrl({ appUrl: argv["api-url"] }) });
2188
2943
  if (session.isErr()) {
2189
- printError("Not signed in for chat. Run `skydive auth login --web` first.");
2944
+ printError(`${session.error.message} Run \`skydive auth login --web\` first, or set SKYDIVE_SESSION_TOKEN.`);
2190
2945
  process.exit(1);
2191
2946
  }
2192
2947
  return session.value;
2193
2948
  }
2949
+ /** Resolve the session (see {@link requireSession}) and build a REST client. */
2950
+ function requireRestClient(argv) {
2951
+ const session = requireSession(argv);
2952
+ return createRestClient({
2953
+ appUrl: session.appUrl,
2954
+ sessionToken: session.sessionToken
2955
+ });
2956
+ }
2957
+
2958
+ //#endregion
2959
+ //#region src/commands/conversations.ts
2960
+ /**
2961
+ * Flatten a UiMessage's parts into plain text for the non-JSON view. Text and
2962
+ * reasoning parts are concatenated; tool calls are summarized to one line so a
2963
+ * transcript stays readable without the rich TUI rendering. This is a
2964
+ * lossy-but-legible view; `--json` carries the full structured parts for a
2965
+ * driving agent that wants everything.
2966
+ */
2967
+ function messageToText(message) {
2968
+ const chunks = [];
2969
+ for (const part of message.parts) if (part.type === "text" && typeof part.text === "string") chunks.push(part.text);
2970
+ else if (part.type === "reasoning" && typeof part.text === "string") chunks.push(part.text);
2971
+ else if (part.type === "dynamic-tool" && "toolName" in part) {
2972
+ const toolName = typeof part.toolName === "string" ? part.toolName : "tool";
2973
+ chunks.push(`[tool: ${toolName}]`);
2974
+ }
2975
+ return chunks.join("\n").trim();
2976
+ }
2977
+ const listCommand$1 = {
2978
+ command: "list",
2979
+ describe: "List an agent's conversations",
2980
+ builder: (y) => y.option("agent", {
2981
+ type: "string",
2982
+ describe: "Agent whose conversations to list, by id, slug, or name. Optional when the account has exactly one agent."
2983
+ }).option("limit", {
2984
+ type: "number",
2985
+ describe: "Max conversations to return"
2986
+ }),
2987
+ handler: async (argv) => {
2988
+ const client = requireRestClient(argv);
2989
+ const agent = resolveAgent(await client.listAgents({
2990
+ scope: "org",
2991
+ onPage: null
2992
+ }), argv.agent ?? null);
2993
+ const conversations = await client.listConversations({
2994
+ agentId: agent.id,
2995
+ limit: argv.limit
2996
+ });
2997
+ if (argv.json) {
2998
+ output(argv, conversations);
2999
+ return;
3000
+ }
3001
+ if (conversations.length === 0) {
3002
+ console.log("No conversations found.");
3003
+ return;
3004
+ }
3005
+ printTable([
3006
+ "ID",
3007
+ "Title",
3008
+ "Updated",
3009
+ "Preview"
3010
+ ], conversations.map((c) => [
3011
+ c.id,
3012
+ c.title ?? "-",
3013
+ c.updatedAt,
3014
+ (c.preview ?? "").replace(/\s+/g, " ").slice(0, 48) || "-"
3015
+ ]));
3016
+ }
3017
+ };
3018
+ const showCommand = {
3019
+ command: "show <conversation-id>",
3020
+ describe: "Print a conversation transcript",
3021
+ builder: (y) => y.positional("conversation-id", {
3022
+ type: "string",
3023
+ demandOption: true,
3024
+ describe: "Conversation id"
3025
+ }).option("recap", {
3026
+ type: "boolean",
3027
+ default: false,
3028
+ describe: "Print the conversation recap instead of the full transcript"
3029
+ }),
3030
+ handler: async (argv) => {
3031
+ const client = requireRestClient(argv);
3032
+ const conversationId = argv["conversation-id"];
3033
+ if (argv.recap) {
3034
+ const recap = await client.getRecap({ conversationId });
3035
+ if (argv.json) {
3036
+ output(argv, {
3037
+ conversationId,
3038
+ recap
3039
+ });
3040
+ return;
3041
+ }
3042
+ console.log(recap ?? "(no recap available)");
3043
+ return;
3044
+ }
3045
+ const messages = await client.listMessages({ conversationId });
3046
+ if (argv.json) {
3047
+ output(argv, messages);
3048
+ return;
3049
+ }
3050
+ if (messages.length === 0) {
3051
+ console.log("No messages in this conversation.");
3052
+ return;
3053
+ }
3054
+ for (const message of messages) {
3055
+ const text = messageToText(message);
3056
+ if (!text) continue;
3057
+ console.log(`\n[${message.role}]`);
3058
+ console.log(text);
3059
+ }
3060
+ }
3061
+ };
3062
+ const conversationsCommand = {
3063
+ command: "conversations",
3064
+ aliases: ["conv"],
3065
+ describe: "List and read agent conversations",
3066
+ builder: (y) => y.command(listCommand$1).command(showCommand).demandCommand(1, "Specify a subcommand: list, show"),
3067
+ handler: () => {}
3068
+ };
3069
+
3070
+ //#endregion
3071
+ //#region src/commands/workspace.ts
2194
3072
  /**
2195
3073
  * List the account's workspaces, marking the active one. Shared by the `list`
2196
3074
  * subcommand and the bare `workspace` invocation. When `hint` is true (the bare
@@ -2251,7 +3129,7 @@ const switchCommand = {
2251
3129
  printError("The workspace picker needs the Bun runtime and it could not be set up automatically. Pass a workspace slug instead, or install Bun and retry.");
2252
3130
  process.exit(1);
2253
3131
  }
2254
- const { runWorkspacePicker } = await import("./boot-DRWra2vY.mjs");
3132
+ const { runWorkspacePicker } = await import("./boot-BvkqTAxQ.mjs");
2255
3133
  await runWorkspacePicker(session);
2256
3134
  return;
2257
3135
  }
@@ -2305,7 +3183,7 @@ function createCli(argv) {
2305
3183
  type: "string",
2306
3184
  global: true,
2307
3185
  describe: "Override API base URL"
2308
- }).command(authCommand).command(chatCommand).command(agentsCommand).command(keysCommand).command(secretsCommand).command(workspaceCommand).demandCommand(1, "Specify a command. Run --help for usage.").strict().wrap(null).version(version$1).alias("v", "version").alias("h", "help").help().fail((msg, err) => {
3186
+ }).command(authCommand).command(chatCommand).command(conversationsCommand).command(agentsCommand).command(keysCommand).command(secretsCommand).command(workspaceCommand).demandCommand(1, "Specify a command. Run --help for usage.").strict().wrap(null).version(version$1).alias("v", "version").alias("h", "help").help().fail((msg, err) => {
2309
3187
  printError(err ? err instanceof Error ? err.message : String(err) : msg ?? "Unknown error");
2310
3188
  process.exit(1);
2311
3189
  });
@@ -2378,4 +3256,4 @@ function resolveArgv(args, tty = {
2378
3256
  createCli(resolveArgv(hideBin(process.argv))).parse();
2379
3257
 
2380
3258
  //#endregion
2381
- export { getSavedTheme as _, noColorRequested as a, themeMode as c, themesForMode as d, getActiveWorkspaceId as f, DEFAULT_APP_URL as g, DEFAULT_API_URL as h, monoTheme as i, themeModeFromColorFgBg as l, setActiveWorkspace as m, applyTheme as n, theme as o, listWorkspaces as p, findTheme as r, themeForMode as s, DEFAULT_THEME_ID as t, themeVersion as u, resolveWebUrl as v, saveTheme as y };
3259
+ export { resolveWebUrl as A, themesForMode as C, DEFAULT_API_URL as D, setActiveWorkspace as E, DEFAULT_APP_URL as O, themeVersion as S, listWorkspaces as T, noColorRequested as _, parseOauthConnectParams as a, themeMode as b, parseConnectCard as c, HttpError as d, createRestClient as f, monoTheme as g, findTheme as h, parseExternalOauthConnectParams as i, saveTheme as j, getSavedTheme as k, errorMessage as l, applyTheme as m, MASK_CHAR as n, reconcileMaskedInput as o, DEFAULT_THEME_ID as p, cardActionErrorMessage as r, resolveConnectUrl as s, resolveAgent as t, isRecord as u, theme as v, getActiveWorkspaceId as w, themeModeFromColorFgBg as x, themeForMode as y };