skydive-cli 0.1.0-beta.380 → 0.1.0-beta.389

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
@@ -1,22 +1,25 @@
1
1
  #!/usr/bin/env node
2
- import { t as __exportAll } from "./rolldown-runtime-Cz4Tg37Z.mjs";
2
+ import { A as getStoredApiKeyId, C as DEFAULT_WEB_URL, E as getPromptHistoryPath, F as resolveSession, L as saveConfig, M as resolveAppUrl, N as resolveConfig, P as resolveManagementAuth, R as saveSession, T as getConfigPath, _ as setActiveWorkspace, b as API_KEY_PREFIX, d as themes, g as listWorkspaces, h as getSessionIdentity, j as getUpdateCheckDisabled, k as getShareMachineDefault, m as getActiveWorkspaceId, p as ensureActiveOrganization, v as API_KEYS_URL, w as deleteConfig, x as DEFAULT_API_URL, y as API_KEY_FAMILY_PREFIX } from "./theme-DRuLtrTy.mjs";
3
+ import { n as createRestClient } from "./rest-DTlkPko_.mjs";
4
+ import { i as resolveAgent } from "./print-BhfjRrxI.mjs";
5
+ import { a as registerPortalDevice, c as machineIdentity, n as findThisDevice, o as revokePortalAccess, r as grantPortalAccess, t as fetchPortalDevices } from "./api-DRpbKHz6.mjs";
6
+ import { t as SandboxStream } from "./client-BVOAwU8M.mjs";
3
7
  import { hideBin } from "yargs/helpers";
4
8
  import yargs from "yargs";
5
- import os, { hostname } from "node:os";
9
+ import { hostname } from "node:os";
6
10
  import path from "node:path";
7
- import Conf from "conf";
8
11
  import { err, ok } from "neverthrow";
9
12
  import { z } from "zod";
10
13
  import open from "open";
11
- import { createParser } from "eventsource-parser";
12
- import { spawnSync } from "node:child_process";
14
+ import { spawn, spawnSync } from "node:child_process";
13
15
  import { createHash } from "node:crypto";
14
16
  import fs from "node:fs";
15
17
  import zlib from "node:zlib";
16
- import { WebSocket } from "ws";
18
+ import semver from "semver";
17
19
 
18
20
  //#region package.json
19
- var version$1 = "0.1.0-beta.380";
21
+ var name = "skydive-cli";
22
+ var version = "0.1.0-beta.389";
20
23
 
21
24
  //#endregion
22
25
  //#region src/types.ts
@@ -30,162 +33,9 @@ function isNonInteractive() {
30
33
  return NON_INTERACTIVE_ENV_VARS.some((key) => process.env[key]);
31
34
  }
32
35
 
33
- //#endregion
34
- //#region src/config.ts
35
- /** Default host for the public management API (`/v1`, API-key auth). */
36
- const DEFAULT_API_URL = "https://api.skydive.com";
37
- /**
38
- * Default origin for the interactive chat client (`skydive chat`).
39
- *
40
- * The API host that serves better-auth (`/api/auth/*`) and the internal tRPC
41
- * API (`/api/v1/trpc`) that chat streams over. We target the API host
42
- * directly (not the web front door) because chat opens a WebSocket and
43
- * authenticates with a bearer token on the upgrade request. Same host as
44
- * `DEFAULT_API_URL`; override with `--api-url` / `SKYDIVE_APP_URL` for local
45
- * dev or while the DNS record is still being provisioned.
46
- */
47
- const DEFAULT_APP_URL = "https://api.skydive.com";
48
- /** Web front door, for pages opened in the user's browser. */
49
- const DEFAULT_WEB_URL = "https://skydive.com";
50
- /**
51
- * Origin for browser-facing links (e.g. opening a conversation's web page).
52
- * The app origin is the API host, which serves no web UI in production, so
53
- * map the default to the web front door. Overridden origins (local dev,
54
- * previews) serve both and pass through unchanged.
55
- */
56
- function resolveWebUrl(appUrl) {
57
- return appUrl === DEFAULT_APP_URL ? DEFAULT_WEB_URL : appUrl;
58
- }
59
- /** Prefix on workspace-scoped Skydive API keys. Kept in sync with the API's
60
- * `API_KEY_PREFIX` (`apps/anyone/api/src/lib/api-key.ts`); the CLI is a
61
- * standalone published package so it can't import the backend constant. */
62
- const API_KEY_PREFIX = "sky_live_";
63
- /**
64
- * Common prefix across all Skydive API key kinds — `sky_live_…` workspace
65
- * keys today, `sky_user_…` account keys when ANY-5105 lands. The CLI only
66
- * sanity-checks the family on `--api-key`; the server authoritatively rejects
67
- * a kind that can't drive a given route, with a clearer message than the
68
- * client could produce.
69
- */
70
- const API_KEY_FAMILY_PREFIX = "sky_";
71
- /** Where users mint and copy API keys. Shown in the login prompt. */
72
- const API_KEYS_URL = "skydive.com/settings/account";
73
- const store = new Conf({
74
- projectName: process.env["SKYDIVE_CONFIG_NAME"] ?? "skydive",
75
- projectSuffix: "",
76
- configFileMode: 384
77
- });
78
- function resolveConfig(opts) {
79
- const apiKey = process.env["SKYDIVE_API_KEY"] ?? store.get("apiKey");
80
- const apiUrl = process.env["SKYDIVE_API_URL"] ?? opts.apiUrl ?? store.get("apiUrl") ?? DEFAULT_API_URL;
81
- if (!apiKey) return err({ message: "Not authenticated. Run `skydive auth login` first." });
82
- return ok({
83
- apiKey,
84
- apiUrl
85
- });
86
- }
87
- /**
88
- * Resolve the bearer credential for the management API (`agents` / `keys` /
89
- * `secrets`). The server's `/v1` gate accepts either an API key or the
90
- * device-flow session bearer, so both work — but only one of them tracks the
91
- * active workspace.
92
- *
93
- * An API key is pinned server-side to the organization that minted it and
94
- * ignores the workspace header by design, so it can never follow `skydive
95
- * workspace switch`. A key is also a strictly narrower credential than its
96
- * owner's session. So the session wins whenever there is one, and a key is
97
- * what's left for machines that never ran an interactive login.
98
- *
99
- * `SKYDIVE_SESSION_TOKEN=` (empty) suppresses session auth for one
100
- * invocation, to drive a specific organization's key while signed in.
101
- */
102
- function resolveManagementAuth(opts) {
103
- const session = resolveSession({ appUrl: opts.apiUrl });
104
- if (session.isOk()) return ok({
105
- token: session.value.sessionToken,
106
- apiUrl: session.value.appUrl,
107
- kind: "session"
108
- });
109
- const apiKey = process.env["SKYDIVE_API_KEY"] ?? store.get("apiKey");
110
- if (apiKey) return ok({
111
- token: apiKey,
112
- apiUrl: process.env["SKYDIVE_API_URL"] ?? opts.apiUrl ?? store.get("apiUrl") ?? DEFAULT_API_URL,
113
- kind: "api-key"
114
- });
115
- return err({ message: "Not authenticated. Run `skydive auth login`." });
116
- }
117
- function saveConfig(config) {
118
- store.set("apiKey", config.apiKey);
119
- store.set("apiUrl", config.apiUrl);
120
- if (config.apiKeyId) store.set("apiKeyId", config.apiKeyId);
121
- else store.delete("apiKeyId");
122
- }
123
- /** Server-side id of the auto-minted key, if login minted one. */
124
- function getStoredApiKeyId() {
125
- return store.get("apiKeyId") ?? null;
126
- }
127
- function deleteConfig() {
128
- store.clear();
129
- }
130
- function getConfigPath() {
131
- return store.path;
132
- }
133
- /**
134
- * Where the chat TUI persists its prompt history (up-arrow recall). Kept
135
- * beside the config file so all CLI state lives in one directory.
136
- */
137
- function getPromptHistoryPath() {
138
- return path.join(path.dirname(store.path), "prompt-history.jsonl");
139
- }
140
- /**
141
- * Resolve the chat/auth origin. Precedence: `SKYDIVE_APP_URL` env > explicit
142
- * `--api-url` style override > stored value > `SKYDIVE_API_URL` env >
143
- * `DEFAULT_APP_URL`.
144
- *
145
- * The `SKYDIVE_API_URL` fallback matters for previews: the device/`--web`
146
- * flow and chat hit the same api service as the management API, so pointing
147
- * `SKYDIVE_API_URL` at a preview stack is enough — you don't also have to set
148
- * `SKYDIVE_APP_URL`. Otherwise auth would silently fall through to prod
149
- * (`DEFAULT_APP_URL`) and hand back a prod verification URL.
150
- */
151
- function resolveAppUrl(opts) {
152
- return process.env["SKYDIVE_APP_URL"] ?? opts.appUrl ?? store.get("appUrl") ?? process.env["SKYDIVE_API_URL"] ?? DEFAULT_APP_URL;
153
- }
154
- function resolveSession(opts) {
155
- const sessionToken = process.env["SKYDIVE_SESSION_TOKEN"] ?? store.get("sessionToken");
156
- const appUrl = resolveAppUrl(opts);
157
- if (!sessionToken) return err({ message: "Not signed in for chat. Run `skydive chat` to sign in." });
158
- return ok({
159
- sessionToken,
160
- appUrl
161
- });
162
- }
163
- function saveSession(session) {
164
- store.set("sessionToken", session.sessionToken);
165
- store.set("sessionObtainedAt", (/* @__PURE__ */ new Date()).toISOString());
166
- store.set("appUrl", session.appUrl);
167
- }
168
- function getSavedTheme(mode) {
169
- return store.get(mode === "dark" ? "themeDark" : "themeLight");
170
- }
171
- function saveTheme(mode, themeId) {
172
- store.set(mode === "dark" ? "themeDark" : "themeLight", themeId);
173
- }
174
- /**
175
- * Whether `skydive chat` should enable portal machine sharing on launch
176
- * without `--share-machine`. Set by hand-editing `shareMachineDefault` in
177
- * config.json — deliberately no CLI command (kept off the API surface).
178
- * Sharing only makes the machine reachable — agents still need a
179
- * (persistent) grant to run anything, so this default skips the per-session
180
- * enable step, not the consent step.
181
- */
182
- function getShareMachineDefault() {
183
- return store.get("shareMachineDefault") ?? false;
184
- }
185
-
186
36
  //#endregion
187
37
  //#region src/api-client.ts
188
- const USER_AGENT = `skydive-cli/${version$1}`;
38
+ const USER_AGENT = `skydive-cli/${version}`;
189
39
  /** Largest page `/v1/agents` will return, enforced server-side. */
190
40
  const MAX_AGENT_PAGE = 100;
191
41
  const AgentSchema = z.object({
@@ -468,121 +318,6 @@ async function pollDeviceToken({ appUrl, deviceCode, currentIntervalMs, signal }
468
318
  }
469
319
  }
470
320
 
471
- //#endregion
472
- //#region src/auth/organization.ts
473
- const workspaceSchema = z.object({
474
- id: z.string(),
475
- name: z.string(),
476
- slug: z.string()
477
- });
478
- function authHeaders(sessionToken) {
479
- return { authorization: `Bearer ${sessionToken}` };
480
- }
481
- async function listWorkspaces({ appUrl, sessionToken }) {
482
- try {
483
- const res = await fetch(`${appUrl}/api/auth/organization/list`, { headers: authHeaders(sessionToken) });
484
- if (!res.ok) return err({ message: `failed to list workspaces (${res.status})` });
485
- const parsed = z.array(workspaceSchema).safeParse(await res.json());
486
- if (!parsed.success) return err({ message: "unexpected workspace list response" });
487
- return ok(parsed.data);
488
- } catch (e) {
489
- return err({ message: e instanceof Error ? e.message : String(e) });
490
- }
491
- }
492
- /**
493
- * Resolve who the current chat session belongs to: the signed-in user's
494
- * email/name and the active workspace. Used by `auth status` so a user running
495
- * multiple accounts can answer "am I logged into the right one?" without
496
- * another command. Best-effort: any failure yields nulls rather than throwing,
497
- * since `auth status` should still report the rest of the state.
498
- */
499
- async function getSessionIdentity({ appUrl, sessionToken }) {
500
- try {
501
- const res = await fetch(`${appUrl}/api/auth/get-session?disableCookieCache=true`, { headers: authHeaders(sessionToken) });
502
- if (!res.ok) return err({ message: `failed to read session (${res.status})` });
503
- const parsed = z.object({
504
- user: z.object({
505
- email: z.string().nullable().optional(),
506
- name: z.string().nullable().optional()
507
- }).nullable().optional(),
508
- session: z.object({ activeOrganizationId: z.string().nullable().optional() }).nullable().optional()
509
- }).nullable().safeParse(await res.json());
510
- if (!parsed.success) return err({ message: "unexpected session response" });
511
- const activeWorkspaceId = parsed.data?.session?.activeOrganizationId ?? null;
512
- let activeWorkspaceName = null;
513
- if (activeWorkspaceId) {
514
- const workspaces = await listWorkspaces({
515
- appUrl,
516
- sessionToken
517
- });
518
- if (workspaces.isOk()) activeWorkspaceName = workspaces.value.find((w) => w.id === activeWorkspaceId)?.name ?? null;
519
- }
520
- return ok({
521
- email: parsed.data?.user?.email ?? null,
522
- name: parsed.data?.user?.name ?? null,
523
- activeWorkspaceId,
524
- activeWorkspaceName
525
- });
526
- } catch (e) {
527
- return err({ message: e instanceof Error ? e.message : String(e) });
528
- }
529
- }
530
- /** The workspace bound to the current session, or `null` if none is set. */
531
- async function getActiveWorkspaceId({ appUrl, sessionToken }) {
532
- try {
533
- const res = await fetch(`${appUrl}/api/auth/get-session?disableCookieCache=true`, { headers: authHeaders(sessionToken) });
534
- if (!res.ok) return err({ message: `failed to read session (${res.status})` });
535
- const parsed = z.object({ session: z.object({ activeOrganizationId: z.string().nullable().optional() }).nullable().optional() }).nullable().safeParse(await res.json());
536
- if (!parsed.success) return err({ message: "unexpected session response" });
537
- return ok(parsed.data?.session?.activeOrganizationId ?? null);
538
- } catch (e) {
539
- return err({ message: e instanceof Error ? e.message : String(e) });
540
- }
541
- }
542
- async function setActiveWorkspace({ appUrl, sessionToken, organizationId }) {
543
- try {
544
- const res = await fetch(`${appUrl}/api/v1/workspaces/switch`, {
545
- method: "POST",
546
- headers: {
547
- ...authHeaders(sessionToken),
548
- "content-type": "application/json"
549
- },
550
- body: JSON.stringify({ organizationId })
551
- });
552
- if (!res.ok) return err({ message: `failed to switch workspace (${res.status})` });
553
- return ok(void 0);
554
- } catch (e) {
555
- return err({ message: e instanceof Error ? e.message : String(e) });
556
- }
557
- }
558
- /**
559
- * Ensures the session has an active workspace. The device flow issues a
560
- * session without one (unlike a normal web sign-in, which sets it), so the
561
- * internal API rejects every request with "no active organization" until we
562
- * set it. Picks the account's first workspace by join order — run `skydive
563
- * workspace list` + `skydive workspace switch` afterward if that's the wrong
564
- * one (e.g. a personal workspace joined before a shared team workspace).
565
- */
566
- async function ensureActiveOrganization({ appUrl, sessionToken }) {
567
- const workspaces = await listWorkspaces({
568
- appUrl,
569
- sessionToken
570
- });
571
- if (workspaces.isErr()) return err(workspaces.error);
572
- const workspace = workspaces.value[0];
573
- if (!workspace) return err({ message: "your account has no organization yet" });
574
- const setResult = await setActiveWorkspace({
575
- appUrl,
576
- sessionToken,
577
- organizationId: workspace.id
578
- });
579
- if (setResult.isErr()) return err(setResult.error);
580
- return ok({
581
- organizationId: workspace.id,
582
- name: workspace.name
583
- });
584
- }
585
-
586
321
  //#endregion
587
322
  //#region src/auth/device-login.ts
588
323
  const DEFAULT_INTERVAL_MS = 5e3;
@@ -609,7 +344,7 @@ async function loginWithDevice({ appUrl, openBrowser = true }) {
609
344
  let intervalMs = (code.interval ?? 5) * 1e3 || DEFAULT_INTERVAL_MS;
610
345
  const expiresAt = Date.now() + code.expires_in * 1e3;
611
346
  while (Date.now() < expiresAt) {
612
- await sleep$1(intervalMs);
347
+ await sleep(intervalMs);
613
348
  const result = await pollDeviceToken({
614
349
  appUrl,
615
350
  deviceCode: code.device_code,
@@ -657,7 +392,7 @@ function formatUserCode(code) {
657
392
  if (code.length !== 8) return code;
658
393
  return `${code.slice(0, 4)}-${code.slice(4)}`;
659
394
  }
660
- function sleep$1(ms) {
395
+ function sleep(ms) {
661
396
  return new Promise((resolve) => setTimeout(resolve, ms));
662
397
  }
663
398
 
@@ -999,436 +734,6 @@ const authCommand = {
999
734
  handler: () => {}
1000
735
  };
1001
736
 
1002
- //#endregion
1003
- //#region src/chat/api/rest.ts
1004
- var rest_exports = /* @__PURE__ */ __exportAll({
1005
- HttpError: () => HttpError,
1006
- createRestClient: () => createRestClient,
1007
- errorDetail: () => errorDetail
1008
- });
1009
- var HttpError = class extends Error {
1010
- constructor(status, body) {
1011
- super(`HTTP ${status}: ${body.slice(0, 200)}`);
1012
- this.status = status;
1013
- this.body = body;
1014
- this.name = "HttpError";
1015
- }
1016
- };
1017
- const ERROR_DETAIL_MAX_BODY = 2e3;
1018
- /**
1019
- * Fullest renderable text for a thrown value. `HttpError.message` clips the
1020
- * response body to 200 chars (it flows into logs and one-line UIs); the
1021
- * transcript renders errors collapsed to a single line, so it can afford the
1022
- * whole body — capped with an explicit marker, never cut silently.
1023
- */
1024
- function errorDetail(err) {
1025
- if (err instanceof HttpError) {
1026
- const body = err.body.length > ERROR_DETAIL_MAX_BODY ? `${err.body.slice(0, ERROR_DETAIL_MAX_BODY)}… (+${err.body.length - ERROR_DETAIL_MAX_BODY} chars)` : err.body;
1027
- return body ? `HTTP ${err.status}: ${body}` : `HTTP ${err.status}`;
1028
- }
1029
- return err instanceof Error ? err.message : String(err);
1030
- }
1031
- const MAX_STREAM_RECONNECTS = 5;
1032
- function createRestClient({ appUrl, sessionToken }) {
1033
- const baseHeaders = {
1034
- authorization: `Bearer ${sessionToken}`,
1035
- accept: "application/json"
1036
- };
1037
- async function get(path, schema) {
1038
- const res = await fetch(`${appUrl}${path}`, { headers: baseHeaders });
1039
- if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
1040
- return schema.parse(await res.json());
1041
- }
1042
- async function post(path, body, schema, method = "POST") {
1043
- const res = await fetch(`${appUrl}${path}`, {
1044
- method,
1045
- headers: {
1046
- ...baseHeaders,
1047
- "content-type": "application/json"
1048
- },
1049
- body: JSON.stringify(body)
1050
- });
1051
- if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
1052
- return schema.parse(await res.json());
1053
- }
1054
- async function del(path) {
1055
- const res = await fetch(`${appUrl}${path}`, {
1056
- method: "DELETE",
1057
- headers: baseHeaders
1058
- });
1059
- if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
1060
- }
1061
- const streamEvents = async ({ path, label, signal, onEvent }) => {
1062
- let lastEventId = null;
1063
- let finished = false;
1064
- let reconnects = 0;
1065
- for (;;) {
1066
- if (signal.aborted) return;
1067
- try {
1068
- const headers = {
1069
- authorization: `Bearer ${sessionToken}`,
1070
- accept: "text/event-stream"
1071
- };
1072
- if (lastEventId) headers["last-event-id"] = lastEventId;
1073
- const res = await fetch(`${appUrl}${path}`, {
1074
- headers,
1075
- signal
1076
- });
1077
- if (!res.ok || !res.body) throw new HttpError(res.status, await res.text().catch(() => ""));
1078
- reconnects = 0;
1079
- const parser = createParser({ onEvent: (message) => {
1080
- if (message.id) lastEventId = message.id;
1081
- if (message.event === "error") {
1082
- const { error } = streamErrorSchema.parse(JSON.parse(message.data));
1083
- throw new Error(error);
1084
- }
1085
- const event = runStreamEventSchema.parse(JSON.parse(message.data));
1086
- if (event.kind === "finished") finished = true;
1087
- onEvent(event.kind === "finished" ? {
1088
- ...event,
1089
- error: event.error ?? null
1090
- } : event);
1091
- } });
1092
- const decoder = new TextDecoder();
1093
- const reader = res.body.getReader();
1094
- try {
1095
- for (;;) {
1096
- const { done, value } = await reader.read();
1097
- if (done) break;
1098
- parser.feed(decoder.decode(value, { stream: true }));
1099
- if (finished) return;
1100
- }
1101
- } finally {
1102
- try {
1103
- await reader.cancel();
1104
- } catch (_error) {}
1105
- }
1106
- } catch (err) {
1107
- if (signal.aborted) return;
1108
- if (err instanceof HttpError && err.status >= 400 && err.status < 500) throw err;
1109
- reconnects += 1;
1110
- if (reconnects > MAX_STREAM_RECONNECTS) throw err;
1111
- await sleep(Math.min(500 * 2 ** reconnects, 5e3));
1112
- continue;
1113
- }
1114
- if (finished) return;
1115
- reconnects += 1;
1116
- if (reconnects > MAX_STREAM_RECONNECTS) throw new Error(`${label} stream ended unexpectedly`);
1117
- await sleep(Math.min(500 * 2 ** reconnects, 5e3));
1118
- }
1119
- };
1120
- return {
1121
- listAgents: async ({ scope, onPage }) => {
1122
- const all = [];
1123
- let cursor;
1124
- const maxAgents = 2e3;
1125
- do {
1126
- const params = new URLSearchParams({
1127
- limit: "100",
1128
- scope,
1129
- sort: "mine_first_usage",
1130
- includeStats: "false"
1131
- });
1132
- if (cursor) params.set("cursor", cursor);
1133
- const page = await get(`/api/v1/agents?${params.toString()}`, listAgentsResponseSchema);
1134
- all.push(...page.agents);
1135
- cursor = page.nextCursor ?? void 0;
1136
- onPage?.([...all]);
1137
- } while (cursor && all.length < maxAgents);
1138
- return all;
1139
- },
1140
- createAgent: async ({ name }) => {
1141
- const { agent } = await post("/api/v1/agents", { name }, createAgentResponseSchema);
1142
- return agent;
1143
- },
1144
- getConversation: async ({ conversationId }) => {
1145
- const { conversation } = await get(`/api/v1/conversations/${encodeURIComponent(conversationId)}`, getConversationResponseSchema);
1146
- return conversation;
1147
- },
1148
- listModels: async () => {
1149
- const { models } = await get("/api/v1/models", listModelsResponseSchema);
1150
- return models;
1151
- },
1152
- updateAgentModel: async ({ agentId, model }) => {
1153
- const { agent } = await post(`/api/v1/agents/${encodeURIComponent(agentId)}`, { model }, updateAgentResponseSchema, "PATCH");
1154
- return { model: agent.model ?? null };
1155
- },
1156
- listConversations: async ({ agentId, limit, channels, onPage }) => {
1157
- const all = [];
1158
- const maxConversations = limit ?? 5e3;
1159
- let cursor;
1160
- do {
1161
- const remaining = maxConversations - all.length;
1162
- const params = new URLSearchParams({
1163
- agentId,
1164
- includeTotal: "false"
1165
- });
1166
- params.set("limit", String(Math.min(remaining, 100)));
1167
- if (cursor) params.set("cursor", cursor);
1168
- for (const channel of channels ?? []) params.append("channels", channel);
1169
- const page = await get(`/api/v1/conversations?${params.toString()}`, listConversationsResponseSchema);
1170
- all.push(...page.conversations);
1171
- cursor = page.nextCursor ?? void 0;
1172
- onPage?.(limit ? all.slice(0, limit) : [...all]);
1173
- } while (cursor && all.length < maxConversations);
1174
- return limit ? all.slice(0, limit) : all;
1175
- },
1176
- listMessages: async ({ conversationId }) => {
1177
- const { messages } = await get(`/api/v1/conversations/${encodeURIComponent(conversationId)}/messages`, listMessagesResponseSchema);
1178
- return messages;
1179
- },
1180
- getRecap: async ({ conversationId }) => {
1181
- const { recap } = await get(`/api/v1/conversations/${encodeURIComponent(conversationId)}/recap`, recapResponseSchema);
1182
- return recap?.text ?? null;
1183
- },
1184
- uploadAttachment: async ({ agentId, fileName, mediaType, data }) => {
1185
- const size = data.byteLength;
1186
- const presign = await post("/api/v1/attachments/presign", {
1187
- agentId,
1188
- fileName,
1189
- mediaType,
1190
- size
1191
- }, presignResponseSchema);
1192
- const putRes = await fetch(presign.uploadUrl, {
1193
- method: "PUT",
1194
- headers: { "content-type": mediaType },
1195
- body: new Uint8Array(data)
1196
- });
1197
- if (!putRes.ok) throw new HttpError(putRes.status, await putRes.text().catch(() => ""));
1198
- const finalized = await post(`/api/v1/attachments/${encodeURIComponent(presign.id)}/finalize`, {
1199
- agentId,
1200
- fileName: presign.fileName,
1201
- mediaType: presign.mediaType,
1202
- size
1203
- }, finalizeResponseSchema);
1204
- return {
1205
- id: presign.id,
1206
- fileName: finalized.fileName,
1207
- mediaType: finalized.mediaType,
1208
- sizeBytes: finalized.sizeBytes ?? size
1209
- };
1210
- },
1211
- deleteConversation: async ({ conversationId }) => {
1212
- await del(`/api/v1/conversations/${encodeURIComponent(conversationId)}`);
1213
- },
1214
- sendMessage: async ({ clientSurface, ...input }) => post("/api/v1/chat/send", {
1215
- ...input,
1216
- clientSurface
1217
- }, sendResultSchema),
1218
- cancelRun: async ({ runId }) => {
1219
- await post(`/api/v1/chat/runs/${encodeURIComponent(runId)}/cancel`, {}, z.object({ ok: z.boolean() }));
1220
- },
1221
- cancelSteer: async ({ directiveId }) => {
1222
- await post(`/api/v1/chat/steer/${encodeURIComponent(directiveId)}/cancel`, {}, z.object({ ok: z.boolean() }));
1223
- },
1224
- oauthConnect: async (input) => {
1225
- const { connectLink } = await post("/api/v1/oauth/connect", input, oauthConnectResponseSchema);
1226
- return { connectLink };
1227
- },
1228
- externalOauthConnect: async (input) => {
1229
- const { authorizationUrl } = await post("/api/v1/external-oauth/connect", input, externalOauthConnectResponseSchema);
1230
- return { authorizationUrl: authorizationUrl ?? null };
1231
- },
1232
- fulfillCredential: async ({ url, body }) => {
1233
- const target = new URL(url, appUrl).toString();
1234
- const res = await fetch(target, {
1235
- method: "POST",
1236
- headers: {
1237
- ...baseHeaders,
1238
- "content-type": "application/json"
1239
- },
1240
- body: JSON.stringify(body)
1241
- });
1242
- if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
1243
- },
1244
- streamRun: async ({ runId, signal, onEvent }) => streamEvents({
1245
- path: `/api/v1/chat/runs/${encodeURIComponent(runId)}/stream`,
1246
- label: "run",
1247
- signal,
1248
- onEvent
1249
- }),
1250
- streamMessage: async ({ messageId, signal, onEvent }) => streamEvents({
1251
- path: `/api/v1/chat/messages/${encodeURIComponent(messageId)}/stream`,
1252
- label: "message",
1253
- signal,
1254
- onEvent
1255
- }),
1256
- streamConversation: async ({ conversationId, signal, onEvent }) => {
1257
- let reconnects = 0;
1258
- for (;;) {
1259
- if (signal.aborted) return;
1260
- try {
1261
- const res = await fetch(`${appUrl}/api/v1/chat/conversations/${encodeURIComponent(conversationId)}/stream`, {
1262
- headers: {
1263
- authorization: `Bearer ${sessionToken}`,
1264
- accept: "text/event-stream"
1265
- },
1266
- signal
1267
- });
1268
- if (!res.ok || !res.body) throw new HttpError(res.status, await res.text().catch(() => ""));
1269
- reconnects = 0;
1270
- const parser = createParser({ onEvent: (message) => {
1271
- const parsed = conversationStreamEventSchema.safeParse(JSON.parse(message.data));
1272
- if (parsed.success) onEvent(parsed.data);
1273
- } });
1274
- const decoder = new TextDecoder();
1275
- const reader = res.body.getReader();
1276
- try {
1277
- for (;;) {
1278
- const { done, value } = await reader.read();
1279
- if (done) break;
1280
- parser.feed(decoder.decode(value, { stream: true }));
1281
- }
1282
- } finally {
1283
- try {
1284
- await reader.cancel();
1285
- } catch (_error) {}
1286
- }
1287
- } catch (err) {
1288
- if (signal.aborted) return;
1289
- if (err instanceof HttpError && err.status >= 400 && err.status < 500) throw err;
1290
- reconnects += 1;
1291
- if (reconnects > MAX_STREAM_RECONNECTS) throw err;
1292
- await sleep(Math.min(500 * 2 ** reconnects, 5e3));
1293
- continue;
1294
- }
1295
- if (signal.aborted) return;
1296
- reconnects += 1;
1297
- if (reconnects > MAX_STREAM_RECONNECTS) throw new Error("conversation stream ended unexpectedly");
1298
- await sleep(Math.min(500 * 2 ** reconnects, 5e3));
1299
- }
1300
- }
1301
- };
1302
- }
1303
- function sleep(ms) {
1304
- return new Promise((resolve) => setTimeout(resolve, ms));
1305
- }
1306
- const agentSummarySchema = z.object({
1307
- id: z.string().uuid(),
1308
- name: z.string(),
1309
- slug: z.string().nullable().optional(),
1310
- title: z.string().nullable().optional(),
1311
- description: z.string().nullable().optional(),
1312
- createdAt: z.string(),
1313
- creatorName: z.string().nullable().optional(),
1314
- model: z.string().nullable().optional(),
1315
- modelLocked: z.boolean().optional()
1316
- });
1317
- const platformModelSchema = z.object({
1318
- id: z.string(),
1319
- displayName: z.string(),
1320
- providerDisplay: z.string().optional(),
1321
- reasoning: z.boolean().optional(),
1322
- compliant: z.boolean().optional()
1323
- }).passthrough();
1324
- const listModelsResponseSchema = z.object({ models: z.array(platformModelSchema) });
1325
- const updateAgentResponseSchema = z.object({ agent: z.object({ model: z.string().nullable().optional() }).passthrough() });
1326
- const listAgentsResponseSchema = z.object({
1327
- agents: z.array(agentSummarySchema),
1328
- nextCursor: z.string().nullable().optional(),
1329
- totalCount: z.number().nullable().optional()
1330
- });
1331
- const createAgentResponseSchema = z.object({ agent: agentSummarySchema });
1332
- const conversationSummarySchema = z.object({
1333
- id: z.string().uuid(),
1334
- title: z.string().nullable(),
1335
- createdAt: z.string(),
1336
- updatedAt: z.string(),
1337
- preview: z.string().nullable(),
1338
- channel: z.string().nullable(),
1339
- channelLabel: z.string().nullable(),
1340
- agent: z.object({
1341
- id: z.string().uuid(),
1342
- name: z.string(),
1343
- slug: z.string().nullable().optional(),
1344
- title: z.string().nullable().optional()
1345
- })
1346
- });
1347
- const conversationDetailSchema = z.object({
1348
- id: z.string().uuid(),
1349
- title: z.string().nullable(),
1350
- agentId: z.string().uuid(),
1351
- createdAt: z.string(),
1352
- updatedAt: z.string()
1353
- });
1354
- const getConversationResponseSchema = z.object({ conversation: conversationDetailSchema });
1355
- const listConversationsResponseSchema = z.object({
1356
- conversations: z.array(conversationSummarySchema),
1357
- nextCursor: z.string().nullable().optional(),
1358
- totalCount: z.number().optional()
1359
- });
1360
- const uiMessagePartSchema = z.union([
1361
- z.object({
1362
- type: z.literal("text"),
1363
- text: z.string()
1364
- }),
1365
- z.object({
1366
- type: z.literal("reasoning"),
1367
- text: z.string().optional()
1368
- }),
1369
- z.object({
1370
- type: z.literal("dynamic-tool"),
1371
- toolCallId: z.string(),
1372
- toolName: z.string(),
1373
- input: z.unknown().optional(),
1374
- output: z.unknown().optional(),
1375
- state: z.string().optional(),
1376
- errorText: z.string().optional()
1377
- }),
1378
- z.object({ type: z.string() }).passthrough()
1379
- ]);
1380
- const uiMessageSchema = z.object({
1381
- id: z.string(),
1382
- role: z.string(),
1383
- parts: z.array(uiMessagePartSchema),
1384
- metadata: z.object({ custom: z.object({
1385
- agentId: z.string().nullish(),
1386
- agentName: z.string().nullish()
1387
- }).passthrough().optional() }).passthrough().optional()
1388
- });
1389
- const recapResponseSchema = z.object({ recap: z.object({ text: z.string() }).nullable() });
1390
- const listMessagesResponseSchema = z.object({ messages: z.array(uiMessageSchema) });
1391
- const sendResultSchema = z.object({
1392
- runId: z.string(),
1393
- messageId: z.string().uuid().nullish(),
1394
- conversationId: z.string().uuid(),
1395
- isNewConversation: z.boolean(),
1396
- agentId: z.string().nullish(),
1397
- agentName: z.string().nullish(),
1398
- steered: z.boolean().optional(),
1399
- directive: z.object({ id: z.string() }).passthrough().optional()
1400
- });
1401
- const presignResponseSchema = z.object({
1402
- id: z.string(),
1403
- uploadUrl: z.string(),
1404
- fileName: z.string(),
1405
- mediaType: z.string()
1406
- });
1407
- const finalizeResponseSchema = z.object({
1408
- fileName: z.string(),
1409
- mediaType: z.string(),
1410
- sizeBytes: z.number().nullable().optional()
1411
- });
1412
- const oauthConnectResponseSchema = z.object({ connectLink: z.string() }).passthrough();
1413
- const externalOauthConnectResponseSchema = z.object({ authorizationUrl: z.string().optional() }).passthrough();
1414
- const runStreamEventSchema = z.union([z.object({
1415
- kind: z.literal("chunk"),
1416
- chunk: z.record(z.unknown())
1417
- }), z.object({
1418
- kind: z.literal("finished"),
1419
- status: z.string(),
1420
- error: z.string().nullish()
1421
- })]);
1422
- const streamErrorSchema = z.object({ error: z.string() });
1423
- const conversationStreamEventSchema = z.discriminatedUnion("kind", [z.object({
1424
- kind: z.literal("conversation"),
1425
- id: z.string(),
1426
- title: z.string().nullable()
1427
- }), z.object({
1428
- kind: z.literal("run"),
1429
- runId: z.string()
1430
- })]);
1431
-
1432
737
  //#endregion
1433
738
  //#region src/commands/session.ts
1434
739
  /**
@@ -1715,7 +1020,7 @@ const keysCommand = {
1715
1020
  //#endregion
1716
1021
  //#region src/commands/secrets.ts
1717
1022
  /** Read all of stdin as UTF-8, trimming a single trailing newline. */
1718
- async function readStdin$1() {
1023
+ async function readStdin() {
1719
1024
  const chunks = [];
1720
1025
  for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1721
1026
  return Buffer.concat(chunks).toString("utf8").replace(/\n$/, "");
@@ -1759,7 +1064,7 @@ const setCommand = {
1759
1064
  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>`.");
1760
1065
  process.exit(1);
1761
1066
  }
1762
- value = await readStdin$1();
1067
+ value = await readStdin();
1763
1068
  }
1764
1069
  if (!value) {
1765
1070
  printError("Empty secret value.");
@@ -1810,688 +1115,6 @@ const secretsCommand = {
1810
1115
  handler: () => {}
1811
1116
  };
1812
1117
 
1813
- //#endregion
1814
- //#region src/chat/tui/theme.ts
1815
- const tokyonight = {
1816
- id: "tokyonight",
1817
- label: "Tokyo Night",
1818
- mode: "dark",
1819
- palette: {
1820
- fg: "#c0caf5",
1821
- muted: "#7a7a7a",
1822
- dim: "#565f89",
1823
- faint: "#3b4261",
1824
- surface: "#16161e",
1825
- accent: "#7aa2f7",
1826
- success: "#9ece6a",
1827
- warning: "#e0af68",
1828
- error: "#f7768e",
1829
- user: "#7aa2f7",
1830
- assistant: "#c0caf5",
1831
- tool: "#bb9af7",
1832
- reasoning: "#565f89",
1833
- syntaxBlue: "#7aa2f7",
1834
- syntaxCyan: "#7dcfff",
1835
- syntaxTeal: "#73daca",
1836
- syntaxGreen: "#9ece6a",
1837
- syntaxYellow: "#e0af68",
1838
- syntaxOrange: "#ff9e64",
1839
- syntaxRed: "#f7768e",
1840
- syntaxMagenta: "#bb9af7"
1841
- }
1842
- };
1843
- const tokyonightDay = {
1844
- id: "tokyonight-day",
1845
- label: "Tokyo Night Day",
1846
- mode: "light",
1847
- palette: {
1848
- fg: "#3760bf",
1849
- muted: "#848cb5",
1850
- dim: "#9da3c2",
1851
- faint: "#c4c8da",
1852
- surface: "#d0d1d8",
1853
- accent: "#2e7de9",
1854
- success: "#587539",
1855
- warning: "#8c6c3e",
1856
- error: "#f52a65",
1857
- user: "#2e7de9",
1858
- assistant: "#3760bf",
1859
- tool: "#9854f1",
1860
- reasoning: "#848cb5",
1861
- syntaxBlue: "#2e7de9",
1862
- syntaxCyan: "#007197",
1863
- syntaxTeal: "#118c74",
1864
- syntaxGreen: "#587539",
1865
- syntaxYellow: "#8c6c3e",
1866
- syntaxOrange: "#b15c00",
1867
- syntaxRed: "#f52a65",
1868
- syntaxMagenta: "#9854f1"
1869
- }
1870
- };
1871
- const catppuccinMocha = {
1872
- id: "catppuccin-mocha",
1873
- label: "Catppuccin Mocha",
1874
- mode: "dark",
1875
- palette: {
1876
- fg: "#cdd6f4",
1877
- muted: "#7f849c",
1878
- dim: "#6c7086",
1879
- faint: "#45475a",
1880
- surface: "#181825",
1881
- accent: "#89b4fa",
1882
- success: "#a6e3a1",
1883
- warning: "#f9e2af",
1884
- error: "#f38ba8",
1885
- user: "#89b4fa",
1886
- assistant: "#cdd6f4",
1887
- tool: "#cba6f7",
1888
- reasoning: "#6c7086",
1889
- syntaxBlue: "#89b4fa",
1890
- syntaxCyan: "#89dceb",
1891
- syntaxTeal: "#94e2d5",
1892
- syntaxGreen: "#a6e3a1",
1893
- syntaxYellow: "#f9e2af",
1894
- syntaxOrange: "#fab387",
1895
- syntaxRed: "#f38ba8",
1896
- syntaxMagenta: "#cba6f7"
1897
- }
1898
- };
1899
- const catppuccinLatte = {
1900
- id: "catppuccin-latte",
1901
- label: "Catppuccin Latte",
1902
- mode: "light",
1903
- palette: {
1904
- fg: "#4c4f69",
1905
- muted: "#8c8fa1",
1906
- dim: "#9ca0b0",
1907
- faint: "#bcc0cc",
1908
- surface: "#e6e9ef",
1909
- accent: "#1e66f5",
1910
- success: "#40a02b",
1911
- warning: "#df8e1d",
1912
- error: "#d20f39",
1913
- user: "#1e66f5",
1914
- assistant: "#4c4f69",
1915
- tool: "#8839ef",
1916
- reasoning: "#9ca0b0",
1917
- syntaxBlue: "#1e66f5",
1918
- syntaxCyan: "#04a5e5",
1919
- syntaxTeal: "#179299",
1920
- syntaxGreen: "#40a02b",
1921
- syntaxYellow: "#df8e1d",
1922
- syntaxOrange: "#fe640b",
1923
- syntaxRed: "#d20f39",
1924
- syntaxMagenta: "#8839ef"
1925
- }
1926
- };
1927
- const gruvboxDark = {
1928
- id: "gruvbox-dark",
1929
- label: "Gruvbox Dark",
1930
- mode: "dark",
1931
- palette: {
1932
- fg: "#ebdbb2",
1933
- muted: "#928374",
1934
- dim: "#7c6f64",
1935
- faint: "#504945",
1936
- surface: "#1d2021",
1937
- accent: "#83a598",
1938
- success: "#b8bb26",
1939
- warning: "#fabd2f",
1940
- error: "#fb4934",
1941
- user: "#83a598",
1942
- assistant: "#ebdbb2",
1943
- tool: "#d3869b",
1944
- reasoning: "#7c6f64",
1945
- syntaxBlue: "#83a598",
1946
- syntaxCyan: "#8ec07c",
1947
- syntaxTeal: "#8ec07c",
1948
- syntaxGreen: "#b8bb26",
1949
- syntaxYellow: "#fabd2f",
1950
- syntaxOrange: "#fe8019",
1951
- syntaxRed: "#fb4934",
1952
- syntaxMagenta: "#d3869b"
1953
- }
1954
- };
1955
- const gruvboxLight = {
1956
- id: "gruvbox-light",
1957
- label: "Gruvbox Light",
1958
- mode: "light",
1959
- palette: {
1960
- fg: "#3c3836",
1961
- muted: "#928374",
1962
- dim: "#a89984",
1963
- faint: "#d5c4a1",
1964
- surface: "#ebdbb2",
1965
- accent: "#076678",
1966
- success: "#79740e",
1967
- warning: "#b57614",
1968
- error: "#9d0006",
1969
- user: "#076678",
1970
- assistant: "#3c3836",
1971
- tool: "#8f3f71",
1972
- reasoning: "#a89984",
1973
- syntaxBlue: "#076678",
1974
- syntaxCyan: "#427b58",
1975
- syntaxTeal: "#427b58",
1976
- syntaxGreen: "#79740e",
1977
- syntaxYellow: "#b57614",
1978
- syntaxOrange: "#af3a03",
1979
- syntaxRed: "#9d0006",
1980
- syntaxMagenta: "#8f3f71"
1981
- }
1982
- };
1983
- const solarizedDark = {
1984
- id: "solarized-dark",
1985
- label: "Solarized Dark",
1986
- mode: "dark",
1987
- palette: {
1988
- fg: "#93a1a1",
1989
- muted: "#586e75",
1990
- dim: "#586e75",
1991
- faint: "#073642",
1992
- surface: "#00212b",
1993
- accent: "#268bd2",
1994
- success: "#859900",
1995
- warning: "#b58900",
1996
- error: "#dc322f",
1997
- user: "#268bd2",
1998
- assistant: "#93a1a1",
1999
- tool: "#6c71c4",
2000
- reasoning: "#586e75",
2001
- syntaxBlue: "#268bd2",
2002
- syntaxCyan: "#2aa198",
2003
- syntaxTeal: "#2aa198",
2004
- syntaxGreen: "#859900",
2005
- syntaxYellow: "#b58900",
2006
- syntaxOrange: "#cb4b16",
2007
- syntaxRed: "#dc322f",
2008
- syntaxMagenta: "#6c71c4"
2009
- }
2010
- };
2011
- const solarizedLight = {
2012
- id: "solarized-light",
2013
- label: "Solarized Light",
2014
- mode: "light",
2015
- palette: {
2016
- fg: "#657b83",
2017
- muted: "#839496",
2018
- dim: "#93a1a1",
2019
- faint: "#eee8d5",
2020
- surface: "#eee8d5",
2021
- accent: "#268bd2",
2022
- success: "#859900",
2023
- warning: "#b58900",
2024
- error: "#dc322f",
2025
- user: "#268bd2",
2026
- assistant: "#657b83",
2027
- tool: "#6c71c4",
2028
- reasoning: "#93a1a1",
2029
- syntaxBlue: "#268bd2",
2030
- syntaxCyan: "#2aa198",
2031
- syntaxTeal: "#2aa198",
2032
- syntaxGreen: "#859900",
2033
- syntaxYellow: "#b58900",
2034
- syntaxOrange: "#cb4b16",
2035
- syntaxRed: "#dc322f",
2036
- syntaxMagenta: "#6c71c4"
2037
- }
2038
- };
2039
- const nord = {
2040
- id: "nord",
2041
- label: "Nord",
2042
- mode: "dark",
2043
- palette: {
2044
- fg: "#d8dee9",
2045
- muted: "#616e88",
2046
- dim: "#4c566a",
2047
- faint: "#3b4252",
2048
- surface: "#272c36",
2049
- accent: "#88c0d0",
2050
- success: "#a3be8c",
2051
- warning: "#ebcb8b",
2052
- error: "#bf616a",
2053
- user: "#88c0d0",
2054
- assistant: "#d8dee9",
2055
- tool: "#b48ead",
2056
- reasoning: "#4c566a",
2057
- syntaxBlue: "#81a1c1",
2058
- syntaxCyan: "#88c0d0",
2059
- syntaxTeal: "#8fbcbb",
2060
- syntaxGreen: "#a3be8c",
2061
- syntaxYellow: "#ebcb8b",
2062
- syntaxOrange: "#d08770",
2063
- syntaxRed: "#bf616a",
2064
- syntaxMagenta: "#b48ead"
2065
- }
2066
- };
2067
- const dracula = {
2068
- id: "dracula",
2069
- label: "Dracula",
2070
- mode: "dark",
2071
- palette: {
2072
- fg: "#f8f8f2",
2073
- muted: "#6272a4",
2074
- dim: "#6272a4",
2075
- faint: "#44475a",
2076
- surface: "#21222c",
2077
- accent: "#bd93f9",
2078
- success: "#50fa7b",
2079
- warning: "#ffb86c",
2080
- error: "#ff5555",
2081
- user: "#bd93f9",
2082
- assistant: "#f8f8f2",
2083
- tool: "#ff79c6",
2084
- reasoning: "#6272a4",
2085
- syntaxBlue: "#bd93f9",
2086
- syntaxCyan: "#8be9fd",
2087
- syntaxTeal: "#8be9fd",
2088
- syntaxGreen: "#50fa7b",
2089
- syntaxYellow: "#f1fa8c",
2090
- syntaxOrange: "#ffb86c",
2091
- syntaxRed: "#ff5555",
2092
- syntaxMagenta: "#ff79c6"
2093
- }
2094
- };
2095
- const oneDark = {
2096
- id: "one-dark",
2097
- label: "One Dark",
2098
- mode: "dark",
2099
- palette: {
2100
- fg: "#abb2bf",
2101
- muted: "#5c6370",
2102
- dim: "#5c6370",
2103
- faint: "#3e4451",
2104
- surface: "#21252b",
2105
- accent: "#61afef",
2106
- success: "#98c379",
2107
- warning: "#e5c07b",
2108
- error: "#e06c75",
2109
- user: "#61afef",
2110
- assistant: "#abb2bf",
2111
- tool: "#c678dd",
2112
- reasoning: "#5c6370",
2113
- syntaxBlue: "#61afef",
2114
- syntaxCyan: "#56b6c2",
2115
- syntaxTeal: "#56b6c2",
2116
- syntaxGreen: "#98c379",
2117
- syntaxYellow: "#e5c07b",
2118
- syntaxOrange: "#d19a66",
2119
- syntaxRed: "#e06c75",
2120
- syntaxMagenta: "#c678dd"
2121
- }
2122
- };
2123
- const oneLight = {
2124
- id: "one-light",
2125
- label: "One Light",
2126
- mode: "light",
2127
- palette: {
2128
- fg: "#383a42",
2129
- muted: "#a0a1a7",
2130
- dim: "#a0a1a7",
2131
- faint: "#e5e5e6",
2132
- surface: "#f0f0f1",
2133
- accent: "#4078f2",
2134
- success: "#50a14f",
2135
- warning: "#c18401",
2136
- error: "#e45649",
2137
- user: "#4078f2",
2138
- assistant: "#383a42",
2139
- tool: "#a626a4",
2140
- reasoning: "#a0a1a7",
2141
- syntaxBlue: "#4078f2",
2142
- syntaxCyan: "#0184bc",
2143
- syntaxTeal: "#0184bc",
2144
- syntaxGreen: "#50a14f",
2145
- syntaxYellow: "#c18401",
2146
- syntaxOrange: "#986801",
2147
- syntaxRed: "#e45649",
2148
- syntaxMagenta: "#a626a4"
2149
- }
2150
- };
2151
- const rosePine = {
2152
- id: "rose-pine",
2153
- label: "Rosé Pine",
2154
- mode: "dark",
2155
- palette: {
2156
- fg: "#e0def4",
2157
- muted: "#908caa",
2158
- dim: "#6e6a86",
2159
- faint: "#403d52",
2160
- surface: "#16141f",
2161
- accent: "#c4a7e7",
2162
- success: "#9ccfd8",
2163
- warning: "#f6c177",
2164
- error: "#eb6f92",
2165
- user: "#c4a7e7",
2166
- assistant: "#e0def4",
2167
- tool: "#ebbcba",
2168
- reasoning: "#908caa",
2169
- syntaxBlue: "#9ccfd8",
2170
- syntaxCyan: "#9ccfd8",
2171
- syntaxTeal: "#31748f",
2172
- syntaxGreen: "#31748f",
2173
- syntaxYellow: "#f6c177",
2174
- syntaxOrange: "#ebbcba",
2175
- syntaxRed: "#eb6f92",
2176
- syntaxMagenta: "#c4a7e7"
2177
- }
2178
- };
2179
- const rosePineDawn = {
2180
- id: "rose-pine-dawn",
2181
- label: "Rosé Pine Dawn",
2182
- mode: "light",
2183
- palette: {
2184
- fg: "#575279",
2185
- muted: "#797593",
2186
- dim: "#9893a5",
2187
- faint: "#cecacd",
2188
- surface: "#f2e9e1",
2189
- accent: "#907aa9",
2190
- success: "#56949f",
2191
- warning: "#ea9d34",
2192
- error: "#b4637a",
2193
- user: "#907aa9",
2194
- assistant: "#575279",
2195
- tool: "#d7827e",
2196
- reasoning: "#9893a5",
2197
- syntaxBlue: "#56949f",
2198
- syntaxCyan: "#56949f",
2199
- syntaxTeal: "#286983",
2200
- syntaxGreen: "#286983",
2201
- syntaxYellow: "#ea9d34",
2202
- syntaxOrange: "#d7827e",
2203
- syntaxRed: "#b4637a",
2204
- syntaxMagenta: "#907aa9"
2205
- }
2206
- };
2207
- const everforestDark = {
2208
- id: "everforest-dark",
2209
- label: "Everforest Dark",
2210
- mode: "dark",
2211
- palette: {
2212
- fg: "#d3c6aa",
2213
- muted: "#859289",
2214
- dim: "#7a8478",
2215
- faint: "#414b50",
2216
- surface: "#232a2e",
2217
- accent: "#7fbbb3",
2218
- success: "#a7c080",
2219
- warning: "#dbbc7f",
2220
- error: "#e67e80",
2221
- user: "#7fbbb3",
2222
- assistant: "#d3c6aa",
2223
- tool: "#d699b6",
2224
- reasoning: "#7a8478",
2225
- syntaxBlue: "#7fbbb3",
2226
- syntaxCyan: "#83c092",
2227
- syntaxTeal: "#83c092",
2228
- syntaxGreen: "#a7c080",
2229
- syntaxYellow: "#dbbc7f",
2230
- syntaxOrange: "#e69875",
2231
- syntaxRed: "#e67e80",
2232
- syntaxMagenta: "#d699b6"
2233
- }
2234
- };
2235
- const everforestLight = {
2236
- id: "everforest-light",
2237
- label: "Everforest Light",
2238
- mode: "light",
2239
- palette: {
2240
- fg: "#5c6a72",
2241
- muted: "#939f91",
2242
- dim: "#a6b0a0",
2243
- faint: "#e0dcc7",
2244
- surface: "#f4f0d9",
2245
- accent: "#3a94c5",
2246
- success: "#8da101",
2247
- warning: "#dfa000",
2248
- error: "#f85552",
2249
- user: "#3a94c5",
2250
- assistant: "#5c6a72",
2251
- tool: "#df69ba",
2252
- reasoning: "#a6b0a0",
2253
- syntaxBlue: "#3a94c5",
2254
- syntaxCyan: "#35a77c",
2255
- syntaxTeal: "#35a77c",
2256
- syntaxGreen: "#8da101",
2257
- syntaxYellow: "#dfa000",
2258
- syntaxOrange: "#f57d26",
2259
- syntaxRed: "#f85552",
2260
- syntaxMagenta: "#df69ba"
2261
- }
2262
- };
2263
- const githubDark = {
2264
- id: "github-dark",
2265
- label: "GitHub Dark",
2266
- mode: "dark",
2267
- palette: {
2268
- fg: "#c9d1d9",
2269
- muted: "#8b949e",
2270
- dim: "#6e7681",
2271
- faint: "#30363d",
2272
- surface: "#161b22",
2273
- accent: "#58a6ff",
2274
- success: "#3fb950",
2275
- warning: "#d29922",
2276
- error: "#f85149",
2277
- user: "#58a6ff",
2278
- assistant: "#c9d1d9",
2279
- tool: "#bc8cff",
2280
- reasoning: "#6e7681",
2281
- syntaxBlue: "#58a6ff",
2282
- syntaxCyan: "#39c5cf",
2283
- syntaxTeal: "#39c5cf",
2284
- syntaxGreen: "#3fb950",
2285
- syntaxYellow: "#d29922",
2286
- syntaxOrange: "#db6d28",
2287
- syntaxRed: "#f85149",
2288
- syntaxMagenta: "#bc8cff"
2289
- }
2290
- };
2291
- const githubLight = {
2292
- id: "github-light",
2293
- label: "GitHub Light",
2294
- mode: "light",
2295
- palette: {
2296
- fg: "#24292f",
2297
- muted: "#57606a",
2298
- dim: "#8c959f",
2299
- faint: "#d0d7de",
2300
- surface: "#f6f8fa",
2301
- accent: "#0969da",
2302
- success: "#1a7f37",
2303
- warning: "#9a6700",
2304
- error: "#cf222e",
2305
- user: "#0969da",
2306
- assistant: "#24292f",
2307
- tool: "#8250df",
2308
- reasoning: "#8c959f",
2309
- syntaxBlue: "#0969da",
2310
- syntaxCyan: "#1b7c83",
2311
- syntaxTeal: "#1b7c83",
2312
- syntaxGreen: "#1a7f37",
2313
- syntaxYellow: "#9a6700",
2314
- syntaxOrange: "#bc4c00",
2315
- syntaxRed: "#cf222e",
2316
- syntaxMagenta: "#8250df"
2317
- }
2318
- };
2319
- const kanagawa = {
2320
- id: "kanagawa",
2321
- label: "Kanagawa",
2322
- mode: "dark",
2323
- palette: {
2324
- fg: "#dcd7ba",
2325
- muted: "#727169",
2326
- dim: "#54546d",
2327
- faint: "#363646",
2328
- surface: "#16161d",
2329
- accent: "#7e9cd8",
2330
- success: "#98bb6c",
2331
- warning: "#e6c384",
2332
- error: "#e46876",
2333
- user: "#7e9cd8",
2334
- assistant: "#dcd7ba",
2335
- tool: "#957fb8",
2336
- reasoning: "#727169",
2337
- syntaxBlue: "#7e9cd8",
2338
- syntaxCyan: "#7aa89f",
2339
- syntaxTeal: "#7aa89f",
2340
- syntaxGreen: "#98bb6c",
2341
- syntaxYellow: "#e6c384",
2342
- syntaxOrange: "#ffa066",
2343
- syntaxRed: "#e46876",
2344
- syntaxMagenta: "#957fb8"
2345
- }
2346
- };
2347
- const cursorDark = {
2348
- id: "cursor-dark",
2349
- label: "Cursor Dark",
2350
- mode: "dark",
2351
- palette: {
2352
- background: "#181818",
2353
- fg: "#d4d4d4",
2354
- muted: "#898989",
2355
- dim: "#636363",
2356
- faint: "#272727",
2357
- surface: "#141414",
2358
- accent: "#88c0d0",
2359
- success: "#70b489",
2360
- warning: "#f1b467",
2361
- error: "#fc6b83",
2362
- diffAdded: "#3fa266",
2363
- diffRemoved: "#b80049",
2364
- diffWeight: .2,
2365
- user: "#88c0d0",
2366
- assistant: "#d4d4d4",
2367
- tool: "#aaa0fa",
2368
- reasoning: "#636363",
2369
- syntaxBlue: "#ebc88d",
2370
- syntaxCyan: "#87c3ff",
2371
- syntaxTeal: "#aaa0fa",
2372
- syntaxGreen: "#e394dc",
2373
- syntaxYellow: "#d4d4d4",
2374
- syntaxOrange: "#f8c762",
2375
- syntaxRed: "#cc7c8a",
2376
- syntaxMagenta: "#82d2ce"
2377
- }
2378
- };
2379
- const themes = [
2380
- tokyonight,
2381
- tokyonightDay,
2382
- catppuccinMocha,
2383
- catppuccinLatte,
2384
- gruvboxDark,
2385
- gruvboxLight,
2386
- solarizedDark,
2387
- solarizedLight,
2388
- nord,
2389
- dracula,
2390
- oneDark,
2391
- oneLight,
2392
- rosePine,
2393
- rosePineDawn,
2394
- everforestDark,
2395
- everforestLight,
2396
- githubDark,
2397
- githubLight,
2398
- kanagawa,
2399
- cursorDark
2400
- ];
2401
- const DEFAULT_THEME_ID = {
2402
- dark: tokyonight.id,
2403
- light: tokyonightDay.id
2404
- };
2405
- /** All-undefined palette for NO_COLOR: every fg/bg falls back to the
2406
- * terminal's own defaults, so nothing emits color. Not listed in `themes` —
2407
- * it's forced, never picked. */
2408
- const monoTheme = {
2409
- id: "mono",
2410
- label: "No color",
2411
- mode: "dark",
2412
- palette: {
2413
- fg: void 0,
2414
- muted: void 0,
2415
- dim: void 0,
2416
- faint: void 0,
2417
- surface: void 0,
2418
- accent: void 0,
2419
- success: void 0,
2420
- warning: void 0,
2421
- error: void 0,
2422
- user: void 0,
2423
- assistant: void 0,
2424
- tool: void 0,
2425
- reasoning: void 0,
2426
- syntaxBlue: void 0,
2427
- syntaxCyan: void 0,
2428
- syntaxTeal: void 0,
2429
- syntaxGreen: void 0,
2430
- syntaxYellow: void 0,
2431
- syntaxOrange: void 0,
2432
- syntaxRed: void 0,
2433
- syntaxMagenta: void 0
2434
- }
2435
- };
2436
- function themesForMode(mode) {
2437
- return themes.filter((t) => t.mode === mode);
2438
- }
2439
- function findTheme(id) {
2440
- if (id === monoTheme.id) return monoTheme;
2441
- return themes.find((t) => t.id === id);
2442
- }
2443
- /**
2444
- * The saved theme to use for a mode: the persisted pick if it exists *and*
2445
- * still matches the mode (a stale/renamed id falls back), else the default.
2446
- */
2447
- function themeForMode(mode, savedId) {
2448
- if (savedId) {
2449
- const saved = findTheme(savedId);
2450
- if (saved && saved.mode === mode) return saved;
2451
- }
2452
- return findTheme(DEFAULT_THEME_ID[mode]) ?? tokyonight;
2453
- }
2454
- /** https://no-color.org — any non-empty value disables color output. */
2455
- function noColorRequested(env = process.env) {
2456
- const v = env["NO_COLOR"];
2457
- return v !== void 0 && v !== "";
2458
- }
2459
- /**
2460
- * Fallback light/dark sniff for terminals that never answer the OSC 10/11
2461
- * query: `COLORFGBG` is "<fg>;<bg>" (sometimes "<fg>;default;<bg>") with
2462
- * ANSI palette indexes. Background 7/15 (white/bright white) means a light
2463
- * terminal; anything else we call dark. Returns null when unset/unparsable.
2464
- */
2465
- function themeModeFromColorFgBg(env = process.env) {
2466
- const raw = env["COLORFGBG"];
2467
- if (!raw) return null;
2468
- const parts = raw.split(";");
2469
- const bg = parts[parts.length - 1];
2470
- if (bg === void 0 || !/^\d+$/.test(bg)) return null;
2471
- const idx = Number(bg);
2472
- return idx === 7 || idx === 15 ? "light" : "dark";
2473
- }
2474
- /** Single source of truth for colors. Mutated in place by `applyTheme` so
2475
- * existing `theme.fg`-style reads across the TUI stay valid. */
2476
- const theme = { ...tokyonight.palette };
2477
- let version = 0;
2478
- function themeVersion() {
2479
- return version;
2480
- }
2481
- let mode = tokyonight.mode;
2482
- function themeMode() {
2483
- return mode;
2484
- }
2485
- function applyTheme(def) {
2486
- Object.assign(theme, def.palette);
2487
- theme.background = def.palette.background;
2488
- theme.diffAdded = def.palette.diffAdded;
2489
- theme.diffRemoved = def.palette.diffRemoved;
2490
- theme.diffWeight = def.palette.diffWeight;
2491
- mode = def.mode;
2492
- version++;
2493
- }
2494
-
2495
1118
  //#endregion
2496
1119
  //#region src/chat/bun-runtime.ts
2497
1120
  /**
@@ -2793,7 +1416,7 @@ const chatCommand = {
2793
1416
  printError(`Unknown theme "${themeId}". Valid themes: ${themes.map((t) => t.id).join(", ")}`);
2794
1417
  process.exit(1);
2795
1418
  }
2796
- const { runChat } = await import("./boot-DML2xNb6.mjs");
1419
+ const { runChat } = await import("./boot-CMhIv1BG.mjs");
2797
1420
  await runChat({
2798
1421
  appUrl,
2799
1422
  sessionToken: session.value.sessionToken,
@@ -2820,7 +1443,7 @@ async function runPrintMode({ argv, appUrl }) {
2820
1443
  printError(`${session.error.message} For non-interactive use, run \`skydive auth login\` first, or set SKYDIVE_SESSION_TOKEN.`);
2821
1444
  process.exit(1);
2822
1445
  }
2823
- const { runPrint, readStdin } = await Promise.resolve().then(() => print_exports);
1446
+ const { runPrint, readStdin } = await import("./print-DFPzQQk8.mjs");
2824
1447
  let prompt = (argv.print ?? "").trim();
2825
1448
  if (!prompt) {
2826
1449
  if (process.stdin.isTTY) {
@@ -2835,7 +1458,7 @@ async function runPrintMode({ argv, appUrl }) {
2835
1458
  }
2836
1459
  let machineShare = null;
2837
1460
  if (resolveShareMachine(argv)) {
2838
- const { PortalClient } = await import("./client-XFsd0Wy9.mjs").then((n) => n.n);
1461
+ const { PortalClient } = await import("./client-C-s6b9Yu.mjs");
2839
1462
  let signalConnected;
2840
1463
  const connected = new Promise((resolve) => {
2841
1464
  signalConnected = resolve;
@@ -2908,7 +1531,7 @@ const getCommand = {
2908
1531
  printError(`${session.error.message} Run \`skydive auth login\` first, or set SKYDIVE_SESSION_TOKEN.`);
2909
1532
  process.exit(1);
2910
1533
  }
2911
- const { messageGet } = await Promise.resolve().then(() => print_exports);
1534
+ const { messageGet } = await import("./print-DFPzQQk8.mjs");
2912
1535
  try {
2913
1536
  const result = await messageGet({
2914
1537
  appUrl,
@@ -2930,488 +1553,6 @@ const messagesCommand = {
2930
1553
  handler: () => {}
2931
1554
  };
2932
1555
 
2933
- //#endregion
2934
- //#region src/chat/util.ts
2935
- /** Narrowing helper for the many `unknown` payloads the chat stream and
2936
- * tool inputs/outputs carry. A type predicate (not an `as` cast), so call
2937
- * sites can read properties without asserting. */
2938
- function isRecord(value) {
2939
- return typeof value === "object" && value !== null && !Array.isArray(value);
2940
- }
2941
- /** Best-effort message from an unknown thrown value. */
2942
- function errorMessage(err) {
2943
- return err instanceof Error ? err.message : String(err);
2944
- }
2945
-
2946
- //#endregion
2947
- //#region src/chat/tui/chat/card.ts
2948
- const urlActionKinds = [
2949
- "open_oauth",
2950
- "open_external_oauth",
2951
- "open_github_app",
2952
- "submit_credential"
2953
- ];
2954
- function isUrlActionKind(value) {
2955
- return typeof value === "string" && urlActionKinds.includes(value);
2956
- }
2957
- function optionalString(value) {
2958
- return typeof value === "string" && value ? value : null;
2959
- }
2960
- function bindStateKey(props) {
2961
- const value = props.value;
2962
- if (!isRecord(value)) return null;
2963
- const pointer = value.$bindState;
2964
- if (typeof pointer !== "string") return null;
2965
- return pointer.startsWith("/") ? pointer.slice(1) : pointer;
2966
- }
2967
- function parseButton(element) {
2968
- const props = isRecord(element.props) ? element.props : {};
2969
- const label = optionalString(props.label) ?? "Connect";
2970
- const on = isRecord(element.on) ? element.on : null;
2971
- const press = on && isRecord(on.press) ? on.press : null;
2972
- if (!press) return null;
2973
- const params = isRecord(press.params) ? press.params : {};
2974
- const primary = props.variant === "primary";
2975
- if (press.action === "approve_portal_access") {
2976
- const agentId = params.agentId;
2977
- if (typeof agentId !== "string" || !agentId) return null;
2978
- return {
2979
- label,
2980
- action: {
2981
- kind: "grant_portal",
2982
- agentId,
2983
- deviceId: typeof params.deviceId === "string" && params.deviceId ? params.deviceId : null
2984
- },
2985
- primary
2986
- };
2987
- }
2988
- if (!isUrlActionKind(press.action)) return null;
2989
- const url = params.url;
2990
- if (typeof url !== "string" || !url) return null;
2991
- return {
2992
- label,
2993
- action: {
2994
- kind: press.action,
2995
- url
2996
- },
2997
- primary
2998
- };
2999
- }
3000
- function parseConnectCard(spec) {
3001
- if (!isRecord(spec)) return null;
3002
- const { root, elements } = spec;
3003
- if (typeof root !== "string" || !isRecord(elements)) return null;
3004
- const rootEl = elements[root];
3005
- if (!isRecord(rootEl) || rootEl.type !== "Card") return null;
3006
- const rootProps = isRecord(rootEl.props) ? rootEl.props : {};
3007
- const title = optionalString(rootProps.title);
3008
- if (!title) return null;
3009
- const fields = [];
3010
- const buttons = [];
3011
- const children = Array.isArray(rootEl.children) ? rootEl.children : [];
3012
- for (const childId of children) {
3013
- if (typeof childId !== "string") continue;
3014
- const el = elements[childId];
3015
- if (!isRecord(el)) continue;
3016
- if (el.type === "TextInput" || el.type === "SecretInput") {
3017
- const props = isRecord(el.props) ? el.props : {};
3018
- const key = bindStateKey(props);
3019
- if (!key) continue;
3020
- fields.push({
3021
- key,
3022
- label: optionalString(props.label) ?? key,
3023
- placeholder: optionalString(props.placeholder),
3024
- secret: el.type === "SecretInput"
3025
- });
3026
- continue;
3027
- }
3028
- if (el.type === "Button") {
3029
- const button = parseButton(el);
3030
- if (button) buttons.push(button);
3031
- }
3032
- }
3033
- const preferred = buttons.find((b) => b.primary) ?? buttons[0] ?? null;
3034
- return {
3035
- title,
3036
- subtitle: optionalString(rootProps.subtitle),
3037
- description: optionalString(rootProps.description),
3038
- fields,
3039
- button: preferred ? {
3040
- label: preferred.label,
3041
- action: preferred.action
3042
- } : null,
3043
- state: isRecord(spec.state) ? spec.state : {}
3044
- };
3045
- }
3046
-
3047
- //#endregion
3048
- //#region src/chat/tui/chat/card-actions.ts
3049
- function safeUrl(url) {
3050
- try {
3051
- return new URL(url, "http://localhost");
3052
- } catch (_error) {
3053
- return null;
3054
- }
3055
- }
3056
- /**
3057
- * Resolve a connect URL to an absolute one the OS can open in a browser.
3058
- *
3059
- * The server builds lazy connect links as server-relative paths (e.g.
3060
- * `/api/v1/oauth/start/slack?connect_session_token=...`). The web client
3061
- * resolves these against its own origin implicitly; the TUI runs outside a
3062
- * browser, so `open()` on a scheme-less path is a silent no-op — the browser
3063
- * never launches and the connect card sits in `launched` forever (the Slack
3064
- * channel-connect deadlock). Resolve against `appUrl` first, mirroring the
3065
- * `open_github_app` / `fulfillCredential` branches, so both the browser we
3066
- * launch and the URL shown on the card are absolute. An already-absolute URL
3067
- * passes through unchanged; if `appUrl` is missing we return the input as-is.
3068
- */
3069
- function resolveConnectUrl(url, appUrl) {
3070
- if (!appUrl) return url;
3071
- try {
3072
- return new URL(url, appUrl).toString();
3073
- } catch (_error) {
3074
- return url;
3075
- }
3076
- }
3077
- function parseOauthConnectParams(url) {
3078
- const parsed = safeUrl(url);
3079
- if (!parsed) return null;
3080
- const integrationKey = parsed.searchParams.get("integration");
3081
- const agentId = parsed.searchParams.get("agent_id");
3082
- const authConfigId = parsed.searchParams.get("auth_config_id");
3083
- const conversationId = parsed.searchParams.get("conversation_id");
3084
- if (!integrationKey || !agentId || !authConfigId || !conversationId) return null;
3085
- return {
3086
- integrationKey,
3087
- agentId,
3088
- authConfigId,
3089
- conversationId,
3090
- scopes: parsed.searchParams.get("scopes")
3091
- };
3092
- }
3093
- function parseExternalOauthConnectParams(url) {
3094
- const parsed = safeUrl(url);
3095
- if (!parsed) return null;
3096
- const agentId = parsed.searchParams.get("agent_id");
3097
- const conversationId = parsed.searchParams.get("conversation_id");
3098
- const serverUrl = parsed.searchParams.get("server_url");
3099
- if (!agentId || !conversationId || !serverUrl) return null;
3100
- return {
3101
- agentId,
3102
- conversationId,
3103
- serverUrl
3104
- };
3105
- }
3106
- /**
3107
- * A short human line for a failed card action. Fulfill/connect endpoints
3108
- * return `{ error: string }` bodies (e.g. "already fulfilled") — prefer that
3109
- * over the generic HttpError message.
3110
- */
3111
- function cardActionErrorMessage(err) {
3112
- if (err instanceof HttpError) {
3113
- try {
3114
- const parsed = JSON.parse(err.body);
3115
- if (isRecord(parsed) && typeof parsed.error === "string") return parsed.error;
3116
- } catch (_error) {}
3117
- return `request failed (HTTP ${err.status})`;
3118
- }
3119
- return errorMessage(err);
3120
- }
3121
- const MASK_CHAR = "•";
3122
- /**
3123
- * Recover the real secret from the masked input's displayed text. The input
3124
- * is controlled: after every edit we render bullets, which forces the cursor
3125
- * to the end, so the next edit is always a tail edit — the displayed text is
3126
- * some prefix of the old mask (kept characters) followed by newly typed or
3127
- * pasted plaintext. Characters beyond the retained bullets are the new tail.
3128
- */
3129
- function reconcileMaskedInput(previousValue, displayed) {
3130
- let kept = 0;
3131
- while (kept < displayed.length && kept < previousValue.length && displayed[kept] === MASK_CHAR) kept++;
3132
- return previousValue.slice(0, kept) + displayed.slice(kept);
3133
- }
3134
-
3135
- //#endregion
3136
- //#region src/chat/connect-cards.ts
3137
- /**
3138
- * Turn a parsed ConnectCard into the headless summary, resolving any relative
3139
- * connect URL against the app origin so the emitted URL is directly openable.
3140
- */
3141
- function summarizeConnectCard(card, appUrl) {
3142
- const base = {
3143
- title: card.title,
3144
- subtitle: card.subtitle,
3145
- description: card.description
3146
- };
3147
- const button = card.button;
3148
- if (!button) return {
3149
- ...base,
3150
- action: { kind: "unsupported" }
3151
- };
3152
- const act = button.action;
3153
- switch (act.kind) {
3154
- case "open_oauth":
3155
- case "open_external_oauth":
3156
- case "open_github_app": return {
3157
- ...base,
3158
- action: {
3159
- kind: "open_url",
3160
- url: resolveConnectUrl(act.url, appUrl)
3161
- }
3162
- };
3163
- case "submit_credential": return {
3164
- ...base,
3165
- action: {
3166
- kind: "submit_credential",
3167
- url: resolveConnectUrl(act.url, appUrl),
3168
- fields: card.fields.map((f) => f.label)
3169
- }
3170
- };
3171
- case "grant_portal": return {
3172
- ...base,
3173
- action: {
3174
- kind: "approve_portal",
3175
- agentId: act.agentId
3176
- }
3177
- };
3178
- default: return {
3179
- ...base,
3180
- action: { kind: "unsupported" }
3181
- };
3182
- }
3183
- }
3184
- /**
3185
- * Parse a `data-anyone-render-spec` stream chunk into a connect-card summary,
3186
- * or null if the chunk isn't a connect card (other render specs — training,
3187
- * deep-learn, compute-request — parse to null, same as the TUI).
3188
- */
3189
- function connectCardFromChunk(chunk, appUrl) {
3190
- if (chunk["type"] !== "data-anyone-render-spec") return null;
3191
- const data = chunk["data"];
3192
- if (!isRecord(data)) return null;
3193
- const card = parseConnectCard(data["spec"]);
3194
- if (!card) return null;
3195
- return summarizeConnectCard(card, appUrl);
3196
- }
3197
- /** Render a connect-card summary as a human-readable action block. */
3198
- function formatConnectCard(card) {
3199
- const lines = [];
3200
- lines.push(`\n[action needed] ${card.title}`);
3201
- if (card.subtitle) lines.push(card.subtitle);
3202
- if (card.description) lines.push(card.description);
3203
- switch (card.action.kind) {
3204
- case "open_url":
3205
- lines.push(`Open to continue: ${card.action.url}`);
3206
- break;
3207
- case "submit_credential":
3208
- lines.push(`Provide credential (${card.action.fields.join(", ") || "value"}) at: ${card.action.url}`);
3209
- break;
3210
- case "approve_portal":
3211
- lines.push(`Approve local-machine access for agent ${card.action.agentId} in the TUI or web app.`);
3212
- break;
3213
- case "unsupported":
3214
- lines.push("Open this conversation in the web app to continue.");
3215
- break;
3216
- }
3217
- return lines.join("\n");
3218
- }
3219
-
3220
- //#endregion
3221
- //#region src/chat/print.ts
3222
- var print_exports = /* @__PURE__ */ __exportAll({
3223
- collectRunText: () => collectRunText,
3224
- messageGet: () => messageGet,
3225
- readStdin: () => readStdin,
3226
- resolveAgent: () => resolveAgent,
3227
- runPrint: () => runPrint,
3228
- toPrintError: () => toPrintError
3229
- });
3230
- /**
3231
- * Non-interactive chat, à la `claude -p`. Sends a single prompt to an
3232
- * agent, streams the run, and prints the assistant's reply to stdout
3233
- * before exiting. No OpenTUI, no Bun requirement — this rides the same
3234
- * Node-friendly REST client the TUI uses, so it runs anywhere the
3235
- * management commands do (CI, pipes, scripts).
3236
- *
3237
- * Resolution rules kept deliberately strict because there's no human to
3238
- * disambiguate: an `--agent` selector must match exactly one agent, and
3239
- * when it's omitted we only auto-pick if the account has exactly one.
3240
- */
3241
- async function runPrint({ appUrl, sessionToken, prompt, agentSelector, conversationId, json, machineShare, grantTargetAgent }) {
3242
- const client = createRestClient({
3243
- appUrl,
3244
- sessionToken
3245
- });
3246
- const agent = resolveAgent(await client.listAgents({
3247
- scope: "org",
3248
- onPage: null
3249
- }), agentSelector);
3250
- if (machineShare) {
3251
- if (!machineShare.isGranted(agent.id) && grantTargetAgent) await machineShare.grantAgent(agent.id);
3252
- if (machineShare.isGranted(agent.id)) console.error(`portal: shared this machine with ${agent.name} for this run (grant persists until revoked)`);
3253
- else console.error(`portal: this machine is shared (shareMachineDefault), but ${agent.name} has no grant — approve its request, or run \`skydive portal grant --agent ${agent.name}\`.`);
3254
- }
3255
- let send;
3256
- try {
3257
- send = await client.sendMessage({
3258
- agentId: agent.id,
3259
- conversationId,
3260
- content: prompt,
3261
- attachmentIds: [],
3262
- clientSurface: "cli"
3263
- });
3264
- } catch (err) {
3265
- throw toPrintError(err);
3266
- }
3267
- const { text, connectCards } = await collectRunText({
3268
- client,
3269
- appUrl,
3270
- target: {
3271
- kind: "run",
3272
- id: send.runId
3273
- },
3274
- onText: json ? null : (delta) => process.stdout.write(delta),
3275
- messageIdForHint: send.messageId ?? null
3276
- });
3277
- if (!json && text && !text.endsWith("\n")) process.stdout.write("\n");
3278
- if (!json) for (const card of connectCards) process.stdout.write(`${formatConnectCard(card)}\n`);
3279
- return {
3280
- agentId: agent.id,
3281
- agentName: agent.name,
3282
- conversationId: send.conversationId,
3283
- isNewConversation: send.isNewConversation,
3284
- messageId: send.messageId ?? null,
3285
- text,
3286
- connectCards
3287
- };
3288
- }
3289
- /**
3290
- * Turn a transport failure into an actionable CLI error. A Cloudflare edge
3291
- * 5xx (502/504) on a long run otherwise leaks a raw HTML/JSON error page to
3292
- * stdout, which is impossible to act on. When a messageId is known and
3293
- * recoverable we point the user at `skydive messages get <messageId>` rather
3294
- * than a blind retry (a retry re-executes an agent that may have write access).
3295
- */
3296
- function toPrintError(err, messageId) {
3297
- if (err instanceof HttpError && err.status >= 500) {
3298
- const recovery = messageId ? ` The run may still be completing server-side. Do NOT blindly retry (it would re-run the agent). Fetch the result with: skydive messages get ${messageId}` : "";
3299
- return /* @__PURE__ */ new Error(`The request to Skydive timed out at the edge (HTTP ${err.status}).${recovery}`);
3300
- }
3301
- return err instanceof Error ? err : new Error(String(err));
3302
- }
3303
- /**
3304
- * Stream a run to completion, folding text-delta chunks into the reply and
3305
- * collecting any connect cards (OAuth / MCP / credential requests) the run
3306
- * posts. Shared by `chat -p` (streaming the run it just created) and `messages
3307
- * get` (re-attaching by message id — the server replays a finished run from its
3308
- * persisted log, so this works whether the run is live or already done).
3309
- */
3310
- async function collectRunText({ client, appUrl, target, onText, messageIdForHint }) {
3311
- let text = "";
3312
- const controller = new AbortController();
3313
- let streamError = null;
3314
- const connectCards = [];
3315
- const onEvent = (event) => {
3316
- if (event.kind === "finished") {
3317
- if (event.error) streamError = event.error;
3318
- return;
3319
- }
3320
- const chunk = event.chunk;
3321
- if (chunk["type"] === "text-delta") {
3322
- const delta = typeof chunk["delta"] === "string" ? chunk["delta"] : typeof chunk["text"] === "string" ? chunk["text"] : "";
3323
- if (delta) {
3324
- text += delta;
3325
- if (onText) onText(delta);
3326
- }
3327
- } else if (chunk["type"] === "error") streamError = typeof chunk["errorText"] === "string" ? chunk["errorText"] : "unknown error";
3328
- else {
3329
- const card = connectCardFromChunk(chunk, appUrl);
3330
- if (card) connectCards.push(card);
3331
- }
3332
- };
3333
- try {
3334
- if (target.kind === "message") await client.streamMessage({
3335
- messageId: target.id,
3336
- signal: controller.signal,
3337
- onEvent
3338
- });
3339
- else await client.streamRun({
3340
- runId: target.id,
3341
- signal: controller.signal,
3342
- onEvent
3343
- });
3344
- } catch (err) {
3345
- throw toPrintError(err, messageIdForHint);
3346
- }
3347
- if (streamError) throw new Error(streamError);
3348
- return {
3349
- text,
3350
- connectCards
3351
- };
3352
- }
3353
- /**
3354
- * Re-attach to an exchange by message id and print its reply. Backs `skydive
3355
- * messages get <messageId>` — the recovery path when a `chat -p` stream dropped
3356
- * at the edge after the message was accepted. The server resolves the run
3357
- * behind the message and replays it from its persisted event log, so this
3358
- * returns the full reply whether the run is still live or already done.
3359
- */
3360
- async function messageGet({ appUrl, sessionToken, messageId, json }) {
3361
- const { text, connectCards } = await collectRunText({
3362
- client: createRestClient({
3363
- appUrl,
3364
- sessionToken
3365
- }),
3366
- appUrl,
3367
- target: {
3368
- kind: "message",
3369
- id: messageId
3370
- },
3371
- onText: json ? null : (delta) => process.stdout.write(delta),
3372
- messageIdForHint: null
3373
- });
3374
- if (!json && text && !text.endsWith("\n")) process.stdout.write("\n");
3375
- if (!json) for (const card of connectCards) process.stdout.write(`${formatConnectCard(card)}\n`);
3376
- return {
3377
- messageId,
3378
- text,
3379
- connectCards
3380
- };
3381
- }
3382
- /**
3383
- * Pick the target agent. With no selector, auto-pick only when the
3384
- * account has exactly one agent; otherwise the user must name one (there's
3385
- * no picker in non-interactive mode). A selector matches by id first, then
3386
- * a unique case-insensitive slug/name; ambiguous or missing matches throw
3387
- * with the candidate list so the caller knows what to pass.
3388
- */
3389
- function resolveAgent(agents, selector) {
3390
- if (!selector) {
3391
- const [only, ...rest] = agents;
3392
- if (!only) throw new Error("No agents on this account.");
3393
- if (rest.length === 0) return only;
3394
- throw new Error(`Multiple agents on this account — pass --agent <id|slug|name>. Candidates:\n${formatCandidates(agents)}`);
3395
- }
3396
- const byId = agents.find((a) => a.id === selector);
3397
- if (byId) return byId;
3398
- const needle = selector.toLowerCase();
3399
- const matches = agents.filter((a) => a.slug && a.slug.toLowerCase() === needle || a.name.toLowerCase() === needle);
3400
- const [firstMatch, ...restMatches] = matches;
3401
- if (firstMatch && restMatches.length === 0) return firstMatch;
3402
- if (restMatches.length > 0) throw new Error(`Multiple agents match "${selector}" — pass the id instead. Candidates:\n${formatCandidates(matches)}`);
3403
- throw new Error(`No agent matches "${selector}". Candidates:\n${formatCandidates(agents)}`);
3404
- }
3405
- function formatCandidates(agents) {
3406
- return agents.slice(0, 25).map((a) => ` ${a.id} ${a.slug ?? a.name}`).join("\n");
3407
- }
3408
- /** Read all of stdin as UTF-8. Used when `-p` is passed with no value. */
3409
- async function readStdin() {
3410
- const chunks = [];
3411
- for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
3412
- return Buffer.concat(chunks).toString("utf8");
3413
- }
3414
-
3415
1556
  //#endregion
3416
1557
  //#region src/commands/conversations.ts
3417
1558
  /**
@@ -3587,7 +1728,7 @@ const switchCommand = {
3587
1728
  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.");
3588
1729
  process.exit(1);
3589
1730
  }
3590
- const { runWorkspacePicker } = await import("./boot-DML2xNb6.mjs");
1731
+ const { runWorkspacePicker } = await import("./boot-CMhIv1BG.mjs");
3591
1732
  await runWorkspacePicker(session);
3592
1733
  return;
3593
1734
  }
@@ -3624,145 +1765,6 @@ const workspaceCommand = {
3624
1765
  handler: (argv) => runList(argv, { hint: true })
3625
1766
  };
3626
1767
 
3627
- //#endregion
3628
- //#region src/chat/portal/machine.ts
3629
- /**
3630
- * Identity this machine registers under when the CLI shares it via the portal.
3631
- *
3632
- * The `-cli` suffix / `(CLI)` label keep a CLI-shared machine a DISTINCT portal
3633
- * device from the same host's Skydive Desktop app. `portal_device` is unique on
3634
- * (org, user, machineName), and directives route to whichever socket holds the
3635
- * device — if the CLI and desktop registered the same name they'd share a
3636
- * device row and both execute every directive. Distinct names also make the
3637
- * grant UI unambiguous about which surface is being authorized.
3638
- */
3639
- function machineIdentity() {
3640
- const host = (os.hostname() || "machine").trim().replace(/\.local$/i, "") || "machine";
3641
- return {
3642
- machineName: `${host}-cli`,
3643
- friendlyName: `${host} (CLI)`
3644
- };
3645
- }
3646
- const INHERITED_ENV = [
3647
- "HOME",
3648
- "USER",
3649
- "LOGNAME",
3650
- "SHELL",
3651
- "LANG",
3652
- "LC_ALL",
3653
- "TMPDIR",
3654
- "TERM",
3655
- "PATH"
3656
- ];
3657
- function buildEnv(extra) {
3658
- const env = {};
3659
- for (const key of INHERITED_ENV) {
3660
- const value = process.env[key];
3661
- if (value !== void 0) env[key] = value;
3662
- }
3663
- if (extra) for (const [key, value] of Object.entries(extra)) env[key] = value;
3664
- return env;
3665
- }
3666
- /**
3667
- * Build the desktop-portal WebSocket URL from the chat origin. Mirrors the Rust
3668
- * desktop client: http→ws, https→wss, scheme-less defaults to wss, and the
3669
- * machine/label ride as query pairs (percent-encoded by URL).
3670
- */
3671
- function portalWsUrl(appUrl, machine, label) {
3672
- const base = appUrl.replace(/\/+$/, "");
3673
- let wsBase;
3674
- if (base.startsWith("https://")) wsBase = `wss://${base.slice(8)}`;
3675
- else if (base.startsWith("http://")) wsBase = `ws://${base.slice(7)}`;
3676
- else wsBase = `wss://${base}`;
3677
- const url = new URL(`${wsBase}/api/v1/portal/desktop`);
3678
- url.searchParams.set("machine", machine);
3679
- url.searchParams.set("label", label);
3680
- return url.toString();
3681
- }
3682
-
3683
- //#endregion
3684
- //#region src/chat/portal/api.ts
3685
- /**
3686
- * The portal's session-authed REST surface, shared by `PortalClient` (the
3687
- * TUI/`portal open` connection) and the `skydive portal` management
3688
- * commands, so the endpoint contracts and response schemas live in exactly
3689
- * one place.
3690
- */
3691
- const deviceSchema = z.object({
3692
- id: z.string(),
3693
- machineName: z.string(),
3694
- friendlyName: z.string(),
3695
- connected: z.boolean(),
3696
- lastSeen: z.string().nullable(),
3697
- grantedAgentIds: z.array(z.string())
3698
- });
3699
- const devicesResponseSchema = z.object({
3700
- devices: z.array(deviceSchema),
3701
- agents: z.array(z.object({
3702
- id: z.string(),
3703
- name: z.string()
3704
- }))
3705
- });
3706
- const deviceTokenSchema = z.object({ token: z.string().min(1) });
3707
- async function portalFetch(auth, path, init) {
3708
- const res = await fetch(`${auth.appUrl}${path}`, {
3709
- method: init.method,
3710
- headers: {
3711
- authorization: `Bearer ${auth.sessionToken}`,
3712
- accept: "application/json",
3713
- ...init.body ? { "content-type": "application/json" } : {}
3714
- },
3715
- ...init.body ? { body: init.body } : {}
3716
- });
3717
- if (!res.ok) {
3718
- const body = await res.text().catch(() => "");
3719
- throw new HttpError(res.status, body);
3720
- }
3721
- return res.json();
3722
- }
3723
- async function fetchPortalDevices(auth) {
3724
- const json = await portalFetch(auth, "/api/v1/portal/devices", { method: "GET" });
3725
- return devicesResponseSchema.parse(json);
3726
- }
3727
- const registerResponseSchema = z.object({ device: z.object({ id: z.string() }) });
3728
- /**
3729
- * Register this machine's device row without connecting. Connecting registers
3730
- * as a side effect; this covers granting an agent on a machine that has never
3731
- * shared yet (the grant references the device row).
3732
- */
3733
- async function registerPortalDevice(auth, { machineName, friendlyName }) {
3734
- const json = await portalFetch(auth, "/api/v1/portal/devices", {
3735
- method: "POST",
3736
- body: JSON.stringify({
3737
- machineName,
3738
- friendlyName
3739
- })
3740
- });
3741
- return registerResponseSchema.parse(json).device;
3742
- }
3743
- /** Short-lived token the machine presents when dialing the portal WebSocket. */
3744
- async function mintPortalDeviceToken(auth) {
3745
- const json = await portalFetch(auth, "/api/v1/portal/device-token", { method: "POST" });
3746
- return deviceTokenSchema.parse(json).token;
3747
- }
3748
- async function grantPortalAccess(auth, { deviceId, agentId }) {
3749
- await portalFetch(auth, `/api/v1/portal/devices/${encodeURIComponent(deviceId)}/grants`, {
3750
- method: "POST",
3751
- body: JSON.stringify({ agentId })
3752
- });
3753
- }
3754
- async function revokePortalAccess(auth, { deviceId, agentId }) {
3755
- await portalFetch(auth, `/api/v1/portal/devices/${encodeURIComponent(deviceId)}/grants/${encodeURIComponent(agentId)}`, { method: "DELETE" });
3756
- }
3757
- /**
3758
- * The device row for a given machine identity. Matching is by `machineName`
3759
- * equality — the stable per-surface handle (`<host>-cli` vs the desktop's
3760
- * `<host>`), not the display label.
3761
- */
3762
- function findThisDevice(devices, machineName) {
3763
- return devices.find((device) => device.machineName === machineName) ?? null;
3764
- }
3765
-
3766
1768
  //#endregion
3767
1769
  //#region src/commands/portal.ts
3768
1770
  /**
@@ -3804,7 +1806,7 @@ const openCommand = {
3804
1806
  const agent = argv.agent ? resolveAgent((await fetchPortalDevices(session)).agents, argv.agent) : null;
3805
1807
  const cwd = argv.cwd ? path.resolve(argv.cwd) : process.cwd();
3806
1808
  const { machineName } = machineIdentity();
3807
- const { PortalClient } = await import("./client-XFsd0Wy9.mjs").then((n) => n.n);
1809
+ const { PortalClient } = await import("./client-C-s6b9Yu.mjs");
3808
1810
  let lastLine = "";
3809
1811
  let signalConnected;
3810
1812
  const connected = new Promise((resolve) => {
@@ -3935,171 +1937,6 @@ const portalCommand = {
3935
1937
  handler: () => {}
3936
1938
  };
3937
1939
 
3938
- //#endregion
3939
- //#region ../sandbox-stream-protocol/src/index.ts
3940
- const SANDBOX_STREAM_PATH = "/api/v1/sandbox/stream";
3941
- const FRAME = {
3942
- DATA: 1,
3943
- EXIT: 2,
3944
- ERROR: 3,
3945
- INPUT: 16,
3946
- RESIZE: 17
3947
- };
3948
- const MAX_INPUT_BYTES = 1 * 1024 * 1024;
3949
- /** Query params for the upgrade URL, from a spec. Inverse of {@link parseStreamSpec}. */
3950
- function streamSpecToQuery(spec) {
3951
- if (spec.mode === "pty") return {
3952
- agentId: spec.agentId,
3953
- mode: "pty",
3954
- cols: String(spec.cols),
3955
- rows: String(spec.rows)
3956
- };
3957
- return {
3958
- agentId: spec.agentId,
3959
- mode: "exec",
3960
- command: spec.command
3961
- };
3962
- }
3963
- function withType(type, payload) {
3964
- const frame = new Uint8Array(1 + payload.length);
3965
- frame[0] = type;
3966
- frame.set(payload, 1);
3967
- return frame;
3968
- }
3969
- /** client → server: keystroke bytes for the pty stdin. */
3970
- function encodeInput(data) {
3971
- return withType(FRAME.INPUT, data);
3972
- }
3973
- /** client → server: the client terminal was resized. */
3974
- function encodeResize(cols, rows) {
3975
- const frame = new Uint8Array(5);
3976
- frame[0] = FRAME.RESIZE;
3977
- const view = new DataView(frame.buffer);
3978
- view.setUint16(1, cols & 65535);
3979
- view.setUint16(3, rows & 65535);
3980
- return frame;
3981
- }
3982
- const view = (frame) => new DataView(frame.buffer, frame.byteOffset, frame.byteLength);
3983
- /**
3984
- * Decode a frame the server sent. Returns null for an empty, unknown, or
3985
- * truncated frame — a peer speaking a newer protocol must not crash us.
3986
- */
3987
- function decodeServerFrame(frame) {
3988
- const payload = frame.subarray(1);
3989
- switch (frame[0]) {
3990
- case FRAME.DATA: return {
3991
- type: "data",
3992
- payload
3993
- };
3994
- case FRAME.EXIT: return {
3995
- type: "exit",
3996
- code: payload.length >= 4 ? view(frame).getInt32(1) : 0
3997
- };
3998
- case FRAME.ERROR: return {
3999
- type: "error",
4000
- message: new TextDecoder().decode(payload)
4001
- };
4002
- default: return null;
4003
- }
4004
- }
4005
-
4006
- //#endregion
4007
- //#region src/chat/sandbox/client.ts
4008
- function wsBase(appUrl) {
4009
- const base = appUrl.replace(/\/+$/, "");
4010
- if (base.startsWith("https://")) return `wss://${base.slice(8)}`;
4011
- if (base.startsWith("http://")) return `ws://${base.slice(7)}`;
4012
- return `wss://${base}`;
4013
- }
4014
- /**
4015
- * A connected sandbox-stream session. Construct via `openSandboxStream`. Carries
4016
- * the write side (keystrokes / resize for pty mode) and teardown.
4017
- */
4018
- var SandboxStream = class SandboxStream {
4019
- ws;
4020
- closed = false;
4021
- constructor(ws, onEvent) {
4022
- this.ws = ws;
4023
- let ended = false;
4024
- const emitEnd = (event) => {
4025
- if (ended) return;
4026
- ended = true;
4027
- onEvent(event);
4028
- };
4029
- ws.on("message", (data, isBinary) => {
4030
- if (!isBinary) return;
4031
- const frame = decodeServerFrame(toBuffer(data));
4032
- if (!frame) return;
4033
- switch (frame.type) {
4034
- case "data":
4035
- onEvent({
4036
- type: "data",
4037
- bytes: new Uint8Array(frame.payload)
4038
- });
4039
- break;
4040
- case "exit":
4041
- emitEnd({
4042
- type: "exit",
4043
- code: frame.code
4044
- });
4045
- break;
4046
- case "error":
4047
- emitEnd({
4048
- type: "error",
4049
- message: frame.message
4050
- });
4051
- break;
4052
- }
4053
- });
4054
- let failure = null;
4055
- ws.on("error", (err) => {
4056
- failure = err.message;
4057
- });
4058
- ws.on("close", () => {
4059
- this.closed = true;
4060
- emitEnd({
4061
- type: "close",
4062
- failure
4063
- });
4064
- });
4065
- }
4066
- /** Feed keystroke bytes to the pty stdin. */
4067
- sendInput(data) {
4068
- if (this.closed || this.ws.readyState !== WebSocket.OPEN) return;
4069
- this.ws.send(encodeInput(data));
4070
- }
4071
- /** Notify the pty of a terminal resize. */
4072
- resize(cols, rows) {
4073
- if (this.closed || this.ws.readyState !== WebSocket.OPEN) return;
4074
- this.ws.send(encodeResize(cols, rows));
4075
- }
4076
- close() {
4077
- this.closed = true;
4078
- this.ws.close();
4079
- }
4080
- /** Open a stream. `mode` is 'pty' (interactive) or 'exec' (one-shot). */
4081
- static open(opts) {
4082
- const spec = opts.mode === "pty" ? {
4083
- mode: "pty",
4084
- agentId: opts.agentId,
4085
- cols: opts.cols,
4086
- rows: opts.rows
4087
- } : {
4088
- mode: "exec",
4089
- agentId: opts.agentId,
4090
- command: opts.command
4091
- };
4092
- const url = new URL(`${wsBase(opts.appUrl)}${SANDBOX_STREAM_PATH}`);
4093
- for (const [key, value] of Object.entries(streamSpecToQuery(spec))) url.searchParams.set(key, value);
4094
- return new SandboxStream(new WebSocket(url.toString(), { headers: { authorization: `Bearer ${opts.sessionToken}` } }), opts.onEvent);
4095
- }
4096
- };
4097
- function toBuffer(data) {
4098
- if (Buffer.isBuffer(data)) return data;
4099
- if (Array.isArray(data)) return Buffer.concat(data);
4100
- return Buffer.from(data);
4101
- }
4102
-
4103
1940
  //#endregion
4104
1941
  //#region src/commands/sandbox.ts
4105
1942
  /** POSIX single-quote one word so the remote shell treats it as one token. */
@@ -4141,8 +1978,8 @@ const sandboxCommand = {
4141
1978
  }).example("skydive sandbox --agent grace", "Live terminal (Ctrl-] detaches)").example("skydive sandbox --agent grace -- tail -n 50 /tmp/harness.log", "One-shot command (use `--` so its flags reach the sandbox)").example("skydive sandbox --agent grace -- sh -c 'ls /tmp | wc -l'", "Shell features go through an explicit `sh -c`"),
4142
1979
  handler: async (argv) => {
4143
1980
  const session = requireSession(argv);
4144
- const { createRestClient } = await Promise.resolve().then(() => rest_exports);
4145
- const { resolveAgent } = await Promise.resolve().then(() => print_exports);
1981
+ const { createRestClient } = await import("./rest-DQruM5kj.mjs");
1982
+ const { resolveAgent } = await import("./print-DFPzQQk8.mjs");
4146
1983
  const client = createRestClient({
4147
1984
  appUrl: session.appUrl,
4148
1985
  sessionToken: session.sessionToken
@@ -4211,7 +2048,7 @@ async function runPty({ session, agentId, agentName }) {
4211
2048
  return 1;
4212
2049
  }
4213
2050
  console.error(`Connecting to ${agentName}'s sandbox… (Ctrl-] detaches)`);
4214
- const { runRawPtyPassthrough } = await import("./raw-pty-C1DXKms6.mjs").then((n) => n.t);
2051
+ const { runRawPtyPassthrough } = await import("./raw-pty-pspO57gT.mjs");
4215
2052
  const result = await runRawPtyPassthrough({
4216
2053
  stdin: process.stdin,
4217
2054
  stdout: process.stdout,
@@ -4240,7 +2077,7 @@ function createCli(argv) {
4240
2077
  type: "string",
4241
2078
  global: true,
4242
2079
  describe: "Override API base URL"
4243
- }).command(authCommand).command(chatCommand).command(messagesCommand).command(conversationsCommand).command(agentsCommand).command(keysCommand).command(secretsCommand).command(workspaceCommand).command(portalCommand).command(sandboxCommand).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) => {
2080
+ }).command(authCommand).command(chatCommand).command(messagesCommand).command(conversationsCommand).command(agentsCommand).command(keysCommand).command(secretsCommand).command(workspaceCommand).command(portalCommand).command(sandboxCommand).demandCommand(1, "Specify a command. Run --help for usage.").strict().wrap(null).version(version).alias("v", "version").alias("h", "help").help().fail((msg, err) => {
4244
2081
  printError(err ? err instanceof Error ? err.message : String(err) : msg ?? "Unknown error");
4245
2082
  process.exit(1);
4246
2083
  });
@@ -4309,9 +2146,250 @@ function resolveArgv(args, tty = {
4309
2146
  return shouldDefaultToChat(args, tty, nonInteractive) ? ["chat", ...args] : args;
4310
2147
  }
4311
2148
 
2149
+ //#endregion
2150
+ //#region src/update-check/cache.ts
2151
+ /**
2152
+ * The on-disk state behind the update check: a single small JSON file the
2153
+ * fast path reads synchronously on every command and the background worker
2154
+ * rewrites after a successful fetch. Every read tolerates a missing, torn,
2155
+ * or type-mangled file (each field validates independently), and a failed
2156
+ * write is silently skipped — the cache must never break a command.
2157
+ */
2158
+ const CHECK_INTERVAL_MS = 1440 * 60 * 1e3;
2159
+ const cacheSchema = z.object({
2160
+ lastCheckedAt: z.string().optional().catch(void 0),
2161
+ channel: z.string().optional().catch(void 0),
2162
+ latestVersion: z.string().optional().catch(void 0)
2163
+ });
2164
+ /** Beside config.json so all CLI state shares one directory (and the
2165
+ * `SKYDIVE_CONFIG_NAME` profile isolation). */
2166
+ function getUpdateCachePath() {
2167
+ return path.join(path.dirname(getConfigPath()), "update-check.json");
2168
+ }
2169
+ function readUpdateCache() {
2170
+ try {
2171
+ const raw = fs.readFileSync(getUpdateCachePath(), "utf8");
2172
+ return cacheSchema.parse(JSON.parse(raw));
2173
+ } catch (_error) {
2174
+ return {};
2175
+ }
2176
+ }
2177
+ function writeUpdateCache(cache) {
2178
+ try {
2179
+ const file = getUpdateCachePath();
2180
+ fs.mkdirSync(path.dirname(file), { recursive: true });
2181
+ fs.writeFileSync(file, `${JSON.stringify(cache, null, 2)}\n`, { mode: 384 });
2182
+ } catch (_error) {}
2183
+ }
2184
+ function isCheckDue(lastCheckedAt, now = Date.now()) {
2185
+ if (!lastCheckedAt) return true;
2186
+ const then = Date.parse(lastCheckedAt);
2187
+ if (Number.isNaN(then)) return true;
2188
+ if (then > now) return true;
2189
+ return now - then >= CHECK_INTERVAL_MS;
2190
+ }
2191
+
2192
+ //#endregion
2193
+ //#region src/update-check/versions.ts
2194
+ /** Canary builds carry a prerelease suffix (`X.Y.Z-beta.N`, synthesized in
2195
+ * CI); a plain semver is a stable release. An unparseable version defaults
2196
+ * to stable. */
2197
+ function resolveChannel(version) {
2198
+ return (semver.prerelease(version, { loose: true })?.length ?? 0) > 0 ? "canary" : "stable";
2199
+ }
2200
+ /** npm dist-tag for a channel (see release-skydive-cli.yml). */
2201
+ function distTagForChannel(channel) {
2202
+ return channel === "canary" ? "beta" : "latest";
2203
+ }
2204
+ /**
2205
+ * True when `candidate` is a strictly newer release than `current`, per
2206
+ * semver precedence (the `semver` package, including prerelease ordering).
2207
+ * Unparseable input is never newer, so garbage from the registry can't
2208
+ * produce a notice.
2209
+ */
2210
+ function isNewerVersion(candidate, current) {
2211
+ if (!semver.valid(candidate, { loose: true })) return false;
2212
+ if (!semver.valid(current, { loose: true })) return false;
2213
+ return semver.gt(candidate, current, { loose: true });
2214
+ }
2215
+
2216
+ //#endregion
2217
+ //#region src/update-check/notice.ts
2218
+ /** The install-mode-specific action line. A package-manager install is never
2219
+ * self-mutated — we only tell the user what to run. */
2220
+ function renderUpdateCommand(channel, source) {
2221
+ if (source === "binary") return `curl -fsSL ${DEFAULT_WEB_URL}/api/v1/cli/install.sh | ${channel === "canary" ? "SKYDIVE_CHANNEL=canary " : ""}sh`;
2222
+ return `npm install -g ${name}@${distTagForChannel(channel)}`;
2223
+ }
2224
+ function renderUpdateNotice(opts) {
2225
+ return `\nUpdate available: ${opts.currentVersion} \u2192 ${opts.latestVersion}\nRun ${renderUpdateCommand(opts.channel, opts.source)}\n`;
2226
+ }
2227
+ /**
2228
+ * Whether this invocation may print the notice. Pure so it's testable; the
2229
+ * inputs are raw pre-yargs argv (parsing hasn't happened when this runs) and
2230
+ * stderr's TTY-ness. `--json`/`--quiet` go to stdout, and the notice goes to
2231
+ * stderr — but scripts commonly capture 2>&1, so machine-readable modes
2232
+ * suppress it entirely rather than risk corrupting piped output.
2233
+ */
2234
+ function shouldNotify(opts) {
2235
+ if (!opts.stderrIsTTY) return false;
2236
+ if (opts.argv.includes("--json") || opts.argv.includes("--quiet")) return false;
2237
+ return true;
2238
+ }
2239
+
2240
+ //#endregion
2241
+ //#region src/update-check/sources.ts
2242
+ function resolveInstallSource() {
2243
+ return typeof SKYDIVE_CLI_INSTALL_SOURCE === "string" && SKYDIVE_CLI_INSTALL_SOURCE === "binary" ? "binary" : "package-manager";
2244
+ }
2245
+ const NPM_REGISTRY_URL = "https://registry.npmjs.org";
2246
+ const npmDistTagsSchema = z.record(z.string(), z.unknown());
2247
+ const binaryManifestSchema = z.object({ version: z.unknown().optional() });
2248
+ /** npm dist-tags for the published package (`skydive-cli`). */
2249
+ const npmReleaseSource = { async fetchLatestVersion(channel, { signal }) {
2250
+ const response = await fetch(`${NPM_REGISTRY_URL}/-/package/${name}/dist-tags`, {
2251
+ signal,
2252
+ headers: { accept: "application/json" }
2253
+ });
2254
+ if (!response.ok) return null;
2255
+ const tags = npmDistTagsSchema.safeParse(await response.json());
2256
+ if (!tags.success) return null;
2257
+ const version = tags.data[distTagForChannel(channel)];
2258
+ return typeof version === "string" ? version : null;
2259
+ } };
2260
+ /**
2261
+ * Release CDN (CloudFront over the releases bucket, infra: CliReleasesCdn).
2262
+ * Serves the channel pointers the release workflow uploads
2263
+ * (`channels/{stable,canary}.json`, cached max-age=60) alongside the
2264
+ * binaries. The daily poll goes here, not to the api, so a fleet of
2265
+ * installed binaries puts no load on — and takes no dependency on — the api.
2266
+ */
2267
+ const RELEASE_CDN_URL = "https://dl.skydive.com";
2268
+ /** Channel manifest on the release CDN, for compiled binaries. The same
2269
+ * document the api's channel route serves (see
2270
+ * apps/anyone/api/src/routes/cli-releases.ts, which reads it from the
2271
+ * bucket this CDN fronts). */
2272
+ const binaryReleaseSource = { async fetchLatestVersion(channel, { signal }) {
2273
+ const response = await fetch(`${RELEASE_CDN_URL}/channels/${channel}.json`, {
2274
+ signal,
2275
+ headers: { accept: "application/json" }
2276
+ });
2277
+ if (!response.ok) return null;
2278
+ const manifest = binaryManifestSchema.safeParse(await response.json());
2279
+ if (!manifest.success) return null;
2280
+ return typeof manifest.data.version === "string" ? manifest.data.version : null;
2281
+ } };
2282
+ function releaseSourceForInstall(source) {
2283
+ return source === "binary" ? binaryReleaseSource : npmReleaseSource;
2284
+ }
2285
+
2286
+ //#endregion
2287
+ //#region src/update-check/index.ts
2288
+ /**
2289
+ * Non-blocking update check, update-notifier style: a command never waits on
2290
+ * the network. Each invocation reads a small on-disk cache (./cache.ts) and,
2291
+ * when it records a newer version for this build's channel, prints a notice
2292
+ * (./notice.ts) to stderr at process exit. Separately, at most once per
2293
+ * check interval, it spawns a short-lived detached child (this same
2294
+ * executable with {@link UPDATE_WORKER_FLAG}) that fetches release metadata
2295
+ * (./sources.ts) and rewrites the cache for *future* invocations. So a
2296
+ * notice is always one check behind — the price of never delaying a command.
2297
+ *
2298
+ * Two install modes share everything except the metadata source and the
2299
+ * suggested action (see ./sources.ts):
2300
+ *
2301
+ * - package-manager (npm/Yarn/pnpm/Bun): compare against npm dist-tags,
2302
+ * print the install command, never self-mutate the install.
2303
+ * - compiled binary: compare against the release channel manifest served by
2304
+ * the api, print the installer one-liner; a real `skydive update`
2305
+ * self-updater can slot in behind the same boundary later.
2306
+ *
2307
+ * Failure policy: every path here is best-effort. Network errors, timeouts,
2308
+ * a torn cache file, an unwritable config dir — all silent. The check must
2309
+ * never break or slow a command.
2310
+ */
2311
+ /** Hidden argv sentinel that turns an invocation into the background refresh
2312
+ * worker (see bin.ts). Namespaced so it can never collide with a real flag. */
2313
+ const UPDATE_WORKER_FLAG = "--skydive-internal-update-check";
2314
+ const FETCH_TIMEOUT_MS = 1e4;
2315
+ function isCheckDisabled(env) {
2316
+ return Boolean(env["SKYDIVE_NO_UPDATE_CHECK"] || env["NO_UPDATE_NOTIFIER"] || env["CI"]) || getUpdateCheckDisabled();
2317
+ }
2318
+ /** A cached answer counts only if it's for this build's channel and strictly
2319
+ * newer than what's running. */
2320
+ function updateAvailable(cache, currentVersion, channel) {
2321
+ return Boolean(cache.latestVersion && cache.channel === channel && isNewerVersion(cache.latestVersion, currentVersion));
2322
+ }
2323
+ function registerExitNotice(notice) {
2324
+ process.once("exit", () => {
2325
+ process.stderr.write(notice);
2326
+ });
2327
+ }
2328
+ /** Stamp the claim before the worker spawns, so a crashing/offline worker
2329
+ * retries next interval instead of respawning on every command. */
2330
+ function claimCheckInterval(cache) {
2331
+ writeUpdateCache({
2332
+ ...cache,
2333
+ lastCheckedAt: (/* @__PURE__ */ new Date()).toISOString()
2334
+ });
2335
+ }
2336
+ function spawnUpdateCheckWorker() {
2337
+ const args = resolveInstallSource() === "binary" ? [UPDATE_WORKER_FLAG] : [...process.argv[1] ? [process.argv[1]] : [], UPDATE_WORKER_FLAG];
2338
+ spawn(process.execPath, args, {
2339
+ detached: true,
2340
+ stdio: "ignore"
2341
+ }).unref();
2342
+ }
2343
+ /**
2344
+ * The detached child's whole job: fetch the channel's current version and
2345
+ * rewrite the cache. Timeout-bounded and silent on failure by design.
2346
+ */
2347
+ async function runUpdateCheckWorker() {
2348
+ try {
2349
+ const channel = resolveChannel(version);
2350
+ const latestVersion = await releaseSourceForInstall(resolveInstallSource()).fetchLatestVersion(channel, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
2351
+ if (!latestVersion) return;
2352
+ writeUpdateCache({
2353
+ lastCheckedAt: (/* @__PURE__ */ new Date()).toISOString(),
2354
+ channel,
2355
+ latestVersion
2356
+ });
2357
+ } catch (_error) {}
2358
+ }
2359
+ /**
2360
+ * Called once from bin.ts before command dispatch. Synchronous — one small
2361
+ * file read; the network work happens in the detached worker.
2362
+ */
2363
+ function setupUpdateCheck(argv) {
2364
+ try {
2365
+ if (isCheckDisabled(process.env)) return;
2366
+ const currentVersion = version;
2367
+ const channel = resolveChannel(currentVersion);
2368
+ const cache = readUpdateCache();
2369
+ if (updateAvailable(cache, currentVersion, channel) && shouldNotify({
2370
+ argv,
2371
+ stderrIsTTY: Boolean(process.stderr.isTTY)
2372
+ })) registerExitNotice(renderUpdateNotice({
2373
+ currentVersion,
2374
+ latestVersion: cache.latestVersion,
2375
+ channel,
2376
+ source: resolveInstallSource()
2377
+ }));
2378
+ if (isCheckDue(cache.lastCheckedAt)) {
2379
+ claimCheckInterval(cache);
2380
+ spawnUpdateCheckWorker();
2381
+ }
2382
+ } catch (_error) {}
2383
+ }
2384
+
4312
2385
  //#endregion
4313
2386
  //#region src/bin.ts
2387
+ if (process.argv.includes(UPDATE_WORKER_FLAG)) {
2388
+ await runUpdateCheckWorker();
2389
+ process.exit(0);
2390
+ }
2391
+ setupUpdateCheck(hideBin(process.argv));
4314
2392
  createCli(resolveArgv(hideBin(process.argv))).parse();
4315
2393
 
4316
2394
  //#endregion
4317
- export { HttpError as A, resolveWebUrl as B, noColorRequested as C, themeModeFromColorFgBg as D, themeMode as E, setActiveWorkspace as F, DEFAULT_API_URL as I, DEFAULT_APP_URL as L, errorDetail as M, getActiveWorkspaceId as N, themeVersion as O, listWorkspaces as P, getConfigPath as R, monoTheme as S, themeForMode as T, saveTheme as V, errorMessage as _, mintPortalDeviceToken as a, applyTheme as b, portalWsUrl as c, cardActionErrorMessage as d, parseExternalOauthConnectParams as f, parseConnectCard as g, resolveConnectUrl as h, grantPortalAccess as i, createRestClient as j, themesForMode as k, resolveAgent as l, reconcileMaskedInput as m, fetchPortalDevices as n, buildEnv as o, parseOauthConnectParams as p, findThisDevice as r, machineIdentity as s, SandboxStream as t, MASK_CHAR as u, isRecord as v, theme as w, findTheme as x, DEFAULT_THEME_ID as y, getSavedTheme as z };
2395
+ export { };