skydive-cli 0.1.0-beta.382 → 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.382";
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,170 +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
- * Where the chat TUI persists pending review comments — one JSON file per
142
- * conversation, so a pending comment survives conversation switches and
143
- * process death. Kept beside the config file like prompt history.
144
- */
145
- function getReviewStateDir() {
146
- return path.join(path.dirname(store.path), "review");
147
- }
148
- /**
149
- * Resolve the chat/auth origin. Precedence: `SKYDIVE_APP_URL` env > explicit
150
- * `--api-url` style override > stored value > `SKYDIVE_API_URL` env >
151
- * `DEFAULT_APP_URL`.
152
- *
153
- * The `SKYDIVE_API_URL` fallback matters for previews: the device/`--web`
154
- * flow and chat hit the same api service as the management API, so pointing
155
- * `SKYDIVE_API_URL` at a preview stack is enough — you don't also have to set
156
- * `SKYDIVE_APP_URL`. Otherwise auth would silently fall through to prod
157
- * (`DEFAULT_APP_URL`) and hand back a prod verification URL.
158
- */
159
- function resolveAppUrl(opts) {
160
- return process.env["SKYDIVE_APP_URL"] ?? opts.appUrl ?? store.get("appUrl") ?? process.env["SKYDIVE_API_URL"] ?? DEFAULT_APP_URL;
161
- }
162
- function resolveSession(opts) {
163
- const sessionToken = process.env["SKYDIVE_SESSION_TOKEN"] ?? store.get("sessionToken");
164
- const appUrl = resolveAppUrl(opts);
165
- if (!sessionToken) return err({ message: "Not signed in for chat. Run `skydive chat` to sign in." });
166
- return ok({
167
- sessionToken,
168
- appUrl
169
- });
170
- }
171
- function saveSession(session) {
172
- store.set("sessionToken", session.sessionToken);
173
- store.set("sessionObtainedAt", (/* @__PURE__ */ new Date()).toISOString());
174
- store.set("appUrl", session.appUrl);
175
- }
176
- function getSavedTheme(mode) {
177
- return store.get(mode === "dark" ? "themeDark" : "themeLight");
178
- }
179
- function saveTheme(mode, themeId) {
180
- store.set(mode === "dark" ? "themeDark" : "themeLight", themeId);
181
- }
182
- /**
183
- * Whether `skydive chat` should enable portal machine sharing on launch
184
- * without `--share-machine`. Set by hand-editing `shareMachineDefault` in
185
- * config.json — deliberately no CLI command (kept off the API surface).
186
- * Sharing only makes the machine reachable — agents still need a
187
- * (persistent) grant to run anything, so this default skips the per-session
188
- * enable step, not the consent step.
189
- */
190
- function getShareMachineDefault() {
191
- return store.get("shareMachineDefault") ?? false;
192
- }
193
-
194
36
  //#endregion
195
37
  //#region src/api-client.ts
196
- const USER_AGENT = `skydive-cli/${version$1}`;
38
+ const USER_AGENT = `skydive-cli/${version}`;
197
39
  /** Largest page `/v1/agents` will return, enforced server-side. */
198
40
  const MAX_AGENT_PAGE = 100;
199
41
  const AgentSchema = z.object({
@@ -476,121 +318,6 @@ async function pollDeviceToken({ appUrl, deviceCode, currentIntervalMs, signal }
476
318
  }
477
319
  }
478
320
 
479
- //#endregion
480
- //#region src/auth/organization.ts
481
- const workspaceSchema = z.object({
482
- id: z.string(),
483
- name: z.string(),
484
- slug: z.string()
485
- });
486
- function authHeaders(sessionToken) {
487
- return { authorization: `Bearer ${sessionToken}` };
488
- }
489
- async function listWorkspaces({ appUrl, sessionToken }) {
490
- try {
491
- const res = await fetch(`${appUrl}/api/auth/organization/list`, { headers: authHeaders(sessionToken) });
492
- if (!res.ok) return err({ message: `failed to list workspaces (${res.status})` });
493
- const parsed = z.array(workspaceSchema).safeParse(await res.json());
494
- if (!parsed.success) return err({ message: "unexpected workspace list response" });
495
- return ok(parsed.data);
496
- } catch (e) {
497
- return err({ message: e instanceof Error ? e.message : String(e) });
498
- }
499
- }
500
- /**
501
- * Resolve who the current chat session belongs to: the signed-in user's
502
- * email/name and the active workspace. Used by `auth status` so a user running
503
- * multiple accounts can answer "am I logged into the right one?" without
504
- * another command. Best-effort: any failure yields nulls rather than throwing,
505
- * since `auth status` should still report the rest of the state.
506
- */
507
- async function getSessionIdentity({ appUrl, sessionToken }) {
508
- try {
509
- const res = await fetch(`${appUrl}/api/auth/get-session?disableCookieCache=true`, { headers: authHeaders(sessionToken) });
510
- if (!res.ok) return err({ message: `failed to read session (${res.status})` });
511
- const parsed = z.object({
512
- user: z.object({
513
- email: z.string().nullable().optional(),
514
- name: z.string().nullable().optional()
515
- }).nullable().optional(),
516
- session: z.object({ activeOrganizationId: z.string().nullable().optional() }).nullable().optional()
517
- }).nullable().safeParse(await res.json());
518
- if (!parsed.success) return err({ message: "unexpected session response" });
519
- const activeWorkspaceId = parsed.data?.session?.activeOrganizationId ?? null;
520
- let activeWorkspaceName = null;
521
- if (activeWorkspaceId) {
522
- const workspaces = await listWorkspaces({
523
- appUrl,
524
- sessionToken
525
- });
526
- if (workspaces.isOk()) activeWorkspaceName = workspaces.value.find((w) => w.id === activeWorkspaceId)?.name ?? null;
527
- }
528
- return ok({
529
- email: parsed.data?.user?.email ?? null,
530
- name: parsed.data?.user?.name ?? null,
531
- activeWorkspaceId,
532
- activeWorkspaceName
533
- });
534
- } catch (e) {
535
- return err({ message: e instanceof Error ? e.message : String(e) });
536
- }
537
- }
538
- /** The workspace bound to the current session, or `null` if none is set. */
539
- async function getActiveWorkspaceId({ appUrl, sessionToken }) {
540
- try {
541
- const res = await fetch(`${appUrl}/api/auth/get-session?disableCookieCache=true`, { headers: authHeaders(sessionToken) });
542
- if (!res.ok) return err({ message: `failed to read session (${res.status})` });
543
- const parsed = z.object({ session: z.object({ activeOrganizationId: z.string().nullable().optional() }).nullable().optional() }).nullable().safeParse(await res.json());
544
- if (!parsed.success) return err({ message: "unexpected session response" });
545
- return ok(parsed.data?.session?.activeOrganizationId ?? null);
546
- } catch (e) {
547
- return err({ message: e instanceof Error ? e.message : String(e) });
548
- }
549
- }
550
- async function setActiveWorkspace({ appUrl, sessionToken, organizationId }) {
551
- try {
552
- const res = await fetch(`${appUrl}/api/v1/workspaces/switch`, {
553
- method: "POST",
554
- headers: {
555
- ...authHeaders(sessionToken),
556
- "content-type": "application/json"
557
- },
558
- body: JSON.stringify({ organizationId })
559
- });
560
- if (!res.ok) return err({ message: `failed to switch workspace (${res.status})` });
561
- return ok(void 0);
562
- } catch (e) {
563
- return err({ message: e instanceof Error ? e.message : String(e) });
564
- }
565
- }
566
- /**
567
- * Ensures the session has an active workspace. The device flow issues a
568
- * session without one (unlike a normal web sign-in, which sets it), so the
569
- * internal API rejects every request with "no active organization" until we
570
- * set it. Picks the account's first workspace by join order — run `skydive
571
- * workspace list` + `skydive workspace switch` afterward if that's the wrong
572
- * one (e.g. a personal workspace joined before a shared team workspace).
573
- */
574
- async function ensureActiveOrganization({ appUrl, sessionToken }) {
575
- const workspaces = await listWorkspaces({
576
- appUrl,
577
- sessionToken
578
- });
579
- if (workspaces.isErr()) return err(workspaces.error);
580
- const workspace = workspaces.value[0];
581
- if (!workspace) return err({ message: "your account has no organization yet" });
582
- const setResult = await setActiveWorkspace({
583
- appUrl,
584
- sessionToken,
585
- organizationId: workspace.id
586
- });
587
- if (setResult.isErr()) return err(setResult.error);
588
- return ok({
589
- organizationId: workspace.id,
590
- name: workspace.name
591
- });
592
- }
593
-
594
321
  //#endregion
595
322
  //#region src/auth/device-login.ts
596
323
  const DEFAULT_INTERVAL_MS = 5e3;
@@ -617,7 +344,7 @@ async function loginWithDevice({ appUrl, openBrowser = true }) {
617
344
  let intervalMs = (code.interval ?? 5) * 1e3 || DEFAULT_INTERVAL_MS;
618
345
  const expiresAt = Date.now() + code.expires_in * 1e3;
619
346
  while (Date.now() < expiresAt) {
620
- await sleep$1(intervalMs);
347
+ await sleep(intervalMs);
621
348
  const result = await pollDeviceToken({
622
349
  appUrl,
623
350
  deviceCode: code.device_code,
@@ -665,7 +392,7 @@ function formatUserCode(code) {
665
392
  if (code.length !== 8) return code;
666
393
  return `${code.slice(0, 4)}-${code.slice(4)}`;
667
394
  }
668
- function sleep$1(ms) {
395
+ function sleep(ms) {
669
396
  return new Promise((resolve) => setTimeout(resolve, ms));
670
397
  }
671
398
 
@@ -1007,463 +734,6 @@ const authCommand = {
1007
734
  handler: () => {}
1008
735
  };
1009
736
 
1010
- //#endregion
1011
- //#region src/chat/api/rest.ts
1012
- var rest_exports = /* @__PURE__ */ __exportAll({
1013
- HttpError: () => HttpError,
1014
- createRestClient: () => createRestClient,
1015
- errorDetail: () => errorDetail
1016
- });
1017
- var HttpError = class extends Error {
1018
- constructor(status, body) {
1019
- super(`HTTP ${status}: ${body.slice(0, 200)}`);
1020
- this.status = status;
1021
- this.body = body;
1022
- this.name = "HttpError";
1023
- }
1024
- };
1025
- const ERROR_DETAIL_MAX_BODY = 2e3;
1026
- /**
1027
- * Fullest renderable text for a thrown value. `HttpError.message` clips the
1028
- * response body to 200 chars (it flows into logs and one-line UIs); the
1029
- * transcript renders errors collapsed to a single line, so it can afford the
1030
- * whole body — capped with an explicit marker, never cut silently.
1031
- */
1032
- function errorDetail(err) {
1033
- if (err instanceof HttpError) {
1034
- 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;
1035
- return body ? `HTTP ${err.status}: ${body}` : `HTTP ${err.status}`;
1036
- }
1037
- return err instanceof Error ? err.message : String(err);
1038
- }
1039
- const MAX_STREAM_RECONNECTS = 5;
1040
- function createRestClient({ appUrl, sessionToken }) {
1041
- const baseHeaders = {
1042
- authorization: `Bearer ${sessionToken}`,
1043
- accept: "application/json"
1044
- };
1045
- async function get(path, schema) {
1046
- const res = await fetch(`${appUrl}${path}`, { headers: baseHeaders });
1047
- if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
1048
- return schema.parse(await res.json());
1049
- }
1050
- async function post(path, body, schema, method = "POST") {
1051
- const res = await fetch(`${appUrl}${path}`, {
1052
- method,
1053
- headers: {
1054
- ...baseHeaders,
1055
- "content-type": "application/json"
1056
- },
1057
- body: JSON.stringify(body)
1058
- });
1059
- if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
1060
- return schema.parse(await res.json());
1061
- }
1062
- async function del(path) {
1063
- const res = await fetch(`${appUrl}${path}`, {
1064
- method: "DELETE",
1065
- headers: baseHeaders
1066
- });
1067
- if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
1068
- }
1069
- const streamEvents = async ({ path, label, signal, onEvent }) => {
1070
- let lastEventId = null;
1071
- let finished = false;
1072
- let reconnects = 0;
1073
- for (;;) {
1074
- if (signal.aborted) return;
1075
- try {
1076
- const headers = {
1077
- authorization: `Bearer ${sessionToken}`,
1078
- accept: "text/event-stream"
1079
- };
1080
- if (lastEventId) headers["last-event-id"] = lastEventId;
1081
- const res = await fetch(`${appUrl}${path}`, {
1082
- headers,
1083
- signal
1084
- });
1085
- if (!res.ok || !res.body) throw new HttpError(res.status, await res.text().catch(() => ""));
1086
- reconnects = 0;
1087
- const parser = createParser({ onEvent: (message) => {
1088
- if (message.id) lastEventId = message.id;
1089
- if (message.event === "error") {
1090
- const { error } = streamErrorSchema.parse(JSON.parse(message.data));
1091
- throw new Error(error);
1092
- }
1093
- const event = runStreamEventSchema.parse(JSON.parse(message.data));
1094
- if (event.kind === "finished") finished = true;
1095
- onEvent(event.kind === "finished" ? {
1096
- ...event,
1097
- error: event.error ?? null
1098
- } : event);
1099
- } });
1100
- const decoder = new TextDecoder();
1101
- const reader = res.body.getReader();
1102
- try {
1103
- for (;;) {
1104
- const { done, value } = await reader.read();
1105
- if (done) break;
1106
- parser.feed(decoder.decode(value, { stream: true }));
1107
- if (finished) return;
1108
- }
1109
- } finally {
1110
- try {
1111
- await reader.cancel();
1112
- } catch (_error) {}
1113
- }
1114
- } catch (err) {
1115
- if (signal.aborted) return;
1116
- if (err instanceof HttpError && err.status >= 400 && err.status < 500) throw err;
1117
- reconnects += 1;
1118
- if (reconnects > MAX_STREAM_RECONNECTS) throw err;
1119
- await sleep(Math.min(500 * 2 ** reconnects, 5e3));
1120
- continue;
1121
- }
1122
- if (finished) return;
1123
- reconnects += 1;
1124
- if (reconnects > MAX_STREAM_RECONNECTS) throw new Error(`${label} stream ended unexpectedly`);
1125
- await sleep(Math.min(500 * 2 ** reconnects, 5e3));
1126
- }
1127
- };
1128
- return {
1129
- listAgents: async ({ scope, onPage }) => {
1130
- const all = [];
1131
- let cursor;
1132
- const maxAgents = 2e3;
1133
- do {
1134
- const params = new URLSearchParams({
1135
- limit: "100",
1136
- scope,
1137
- sort: "mine_first_usage",
1138
- includeStats: "false"
1139
- });
1140
- if (cursor) params.set("cursor", cursor);
1141
- const page = await get(`/api/v1/agents?${params.toString()}`, listAgentsResponseSchema);
1142
- all.push(...page.agents);
1143
- cursor = page.nextCursor ?? void 0;
1144
- onPage?.([...all]);
1145
- } while (cursor && all.length < maxAgents);
1146
- return all;
1147
- },
1148
- createAgent: async ({ name }) => {
1149
- const { agent } = await post("/api/v1/agents", { name }, createAgentResponseSchema);
1150
- return agent;
1151
- },
1152
- getConversation: async ({ conversationId }) => {
1153
- const { conversation } = await get(`/api/v1/conversations/${encodeURIComponent(conversationId)}`, getConversationResponseSchema);
1154
- return conversation;
1155
- },
1156
- listModels: async () => {
1157
- const { models } = await get("/api/v1/models", listModelsResponseSchema);
1158
- return models;
1159
- },
1160
- updateAgentModel: async ({ agentId, model }) => {
1161
- const { agent } = await post(`/api/v1/agents/${encodeURIComponent(agentId)}`, { model }, updateAgentResponseSchema, "PATCH");
1162
- return { model: agent.model ?? null };
1163
- },
1164
- listConversations: async ({ agentId, limit, channels, onPage }) => {
1165
- const all = [];
1166
- const maxConversations = limit ?? 5e3;
1167
- let cursor;
1168
- do {
1169
- const remaining = maxConversations - all.length;
1170
- const params = new URLSearchParams({
1171
- agentId,
1172
- includeTotal: "false"
1173
- });
1174
- params.set("limit", String(Math.min(remaining, 100)));
1175
- if (cursor) params.set("cursor", cursor);
1176
- for (const channel of channels ?? []) params.append("channels", channel);
1177
- const page = await get(`/api/v1/conversations?${params.toString()}`, listConversationsResponseSchema);
1178
- all.push(...page.conversations);
1179
- cursor = page.nextCursor ?? void 0;
1180
- onPage?.(limit ? all.slice(0, limit) : [...all]);
1181
- } while (cursor && all.length < maxConversations);
1182
- return limit ? all.slice(0, limit) : all;
1183
- },
1184
- listMessages: async ({ conversationId }) => {
1185
- const { messages } = await get(`/api/v1/conversations/${encodeURIComponent(conversationId)}/messages`, listMessagesResponseSchema);
1186
- return messages;
1187
- },
1188
- listWorkspaceFiles: async ({ agentId }) => {
1189
- const { files } = await get(`/api/v1/workspace-files?${new URLSearchParams({ agentId }).toString()}`, listWorkspaceFilesResponseSchema);
1190
- return files;
1191
- },
1192
- readWorkspaceFile: async ({ fileId, maxBytes = 512 * 1024 }) => {
1193
- const res = await fetch(`${appUrl}/api/v1/workspace-files/${encodeURIComponent(fileId)}/download?disposition=inline`, { headers: {
1194
- ...baseHeaders,
1195
- Range: `bytes=0-${maxBytes}`
1196
- } });
1197
- if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
1198
- const bytes = new Uint8Array(await res.arrayBuffer());
1199
- const truncated = bytes.byteLength > maxBytes;
1200
- return {
1201
- text: new TextDecoder().decode(bytes.subarray(0, maxBytes)),
1202
- truncated
1203
- };
1204
- },
1205
- getRecap: async ({ conversationId }) => {
1206
- const { recap } = await get(`/api/v1/conversations/${encodeURIComponent(conversationId)}/recap`, recapResponseSchema);
1207
- return recap?.text ?? null;
1208
- },
1209
- uploadAttachment: async ({ agentId, fileName, mediaType, data }) => {
1210
- const size = data.byteLength;
1211
- const presign = await post("/api/v1/attachments/presign", {
1212
- agentId,
1213
- fileName,
1214
- mediaType,
1215
- size
1216
- }, presignResponseSchema);
1217
- const putRes = await fetch(presign.uploadUrl, {
1218
- method: "PUT",
1219
- headers: { "content-type": mediaType },
1220
- body: new Uint8Array(data)
1221
- });
1222
- if (!putRes.ok) throw new HttpError(putRes.status, await putRes.text().catch(() => ""));
1223
- const finalized = await post(`/api/v1/attachments/${encodeURIComponent(presign.id)}/finalize`, {
1224
- agentId,
1225
- fileName: presign.fileName,
1226
- mediaType: presign.mediaType,
1227
- size
1228
- }, finalizeResponseSchema);
1229
- return {
1230
- id: presign.id,
1231
- fileName: finalized.fileName,
1232
- mediaType: finalized.mediaType,
1233
- sizeBytes: finalized.sizeBytes ?? size
1234
- };
1235
- },
1236
- deleteConversation: async ({ conversationId }) => {
1237
- await del(`/api/v1/conversations/${encodeURIComponent(conversationId)}`);
1238
- },
1239
- sendMessage: async ({ clientSurface, ...input }) => post("/api/v1/chat/send", {
1240
- ...input,
1241
- clientSurface
1242
- }, sendResultSchema),
1243
- cancelRun: async ({ runId }) => {
1244
- await post(`/api/v1/chat/runs/${encodeURIComponent(runId)}/cancel`, {}, z.object({ ok: z.boolean() }));
1245
- },
1246
- cancelSteer: async ({ directiveId }) => {
1247
- await post(`/api/v1/chat/steer/${encodeURIComponent(directiveId)}/cancel`, {}, z.object({ ok: z.boolean() }));
1248
- },
1249
- oauthConnect: async (input) => {
1250
- const { connectLink } = await post("/api/v1/oauth/connect", input, oauthConnectResponseSchema);
1251
- return { connectLink };
1252
- },
1253
- externalOauthConnect: async (input) => {
1254
- const { authorizationUrl } = await post("/api/v1/external-oauth/connect", input, externalOauthConnectResponseSchema);
1255
- return { authorizationUrl: authorizationUrl ?? null };
1256
- },
1257
- fulfillCredential: async ({ url, body }) => {
1258
- const target = new URL(url, appUrl).toString();
1259
- const res = await fetch(target, {
1260
- method: "POST",
1261
- headers: {
1262
- ...baseHeaders,
1263
- "content-type": "application/json"
1264
- },
1265
- body: JSON.stringify(body)
1266
- });
1267
- if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
1268
- },
1269
- streamRun: async ({ runId, signal, onEvent }) => streamEvents({
1270
- path: `/api/v1/chat/runs/${encodeURIComponent(runId)}/stream`,
1271
- label: "run",
1272
- signal,
1273
- onEvent
1274
- }),
1275
- streamMessage: async ({ messageId, signal, onEvent }) => streamEvents({
1276
- path: `/api/v1/chat/messages/${encodeURIComponent(messageId)}/stream`,
1277
- label: "message",
1278
- signal,
1279
- onEvent
1280
- }),
1281
- streamConversation: async ({ conversationId, signal, onEvent }) => {
1282
- let reconnects = 0;
1283
- for (;;) {
1284
- if (signal.aborted) return;
1285
- try {
1286
- const res = await fetch(`${appUrl}/api/v1/chat/conversations/${encodeURIComponent(conversationId)}/stream`, {
1287
- headers: {
1288
- authorization: `Bearer ${sessionToken}`,
1289
- accept: "text/event-stream"
1290
- },
1291
- signal
1292
- });
1293
- if (!res.ok || !res.body) throw new HttpError(res.status, await res.text().catch(() => ""));
1294
- reconnects = 0;
1295
- const parser = createParser({ onEvent: (message) => {
1296
- const parsed = conversationStreamEventSchema.safeParse(JSON.parse(message.data));
1297
- if (parsed.success) onEvent(parsed.data);
1298
- } });
1299
- const decoder = new TextDecoder();
1300
- const reader = res.body.getReader();
1301
- try {
1302
- for (;;) {
1303
- const { done, value } = await reader.read();
1304
- if (done) break;
1305
- parser.feed(decoder.decode(value, { stream: true }));
1306
- }
1307
- } finally {
1308
- try {
1309
- await reader.cancel();
1310
- } catch (_error) {}
1311
- }
1312
- } catch (err) {
1313
- if (signal.aborted) return;
1314
- if (err instanceof HttpError && err.status >= 400 && err.status < 500) throw err;
1315
- reconnects += 1;
1316
- if (reconnects > MAX_STREAM_RECONNECTS) throw err;
1317
- await sleep(Math.min(500 * 2 ** reconnects, 5e3));
1318
- continue;
1319
- }
1320
- if (signal.aborted) return;
1321
- reconnects += 1;
1322
- if (reconnects > MAX_STREAM_RECONNECTS) throw new Error("conversation stream ended unexpectedly");
1323
- await sleep(Math.min(500 * 2 ** reconnects, 5e3));
1324
- }
1325
- }
1326
- };
1327
- }
1328
- function sleep(ms) {
1329
- return new Promise((resolve) => setTimeout(resolve, ms));
1330
- }
1331
- const agentSummarySchema = z.object({
1332
- id: z.string().uuid(),
1333
- name: z.string(),
1334
- slug: z.string().nullable().optional(),
1335
- title: z.string().nullable().optional(),
1336
- description: z.string().nullable().optional(),
1337
- createdAt: z.string(),
1338
- creatorName: z.string().nullable().optional(),
1339
- model: z.string().nullable().optional(),
1340
- modelLocked: z.boolean().optional()
1341
- });
1342
- const platformModelSchema = z.object({
1343
- id: z.string(),
1344
- displayName: z.string(),
1345
- providerDisplay: z.string().optional(),
1346
- reasoning: z.boolean().optional(),
1347
- compliant: z.boolean().optional()
1348
- }).passthrough();
1349
- const listModelsResponseSchema = z.object({ models: z.array(platformModelSchema) });
1350
- const updateAgentResponseSchema = z.object({ agent: z.object({ model: z.string().nullable().optional() }).passthrough() });
1351
- const listAgentsResponseSchema = z.object({
1352
- agents: z.array(agentSummarySchema),
1353
- nextCursor: z.string().nullable().optional(),
1354
- totalCount: z.number().nullable().optional()
1355
- });
1356
- const createAgentResponseSchema = z.object({ agent: agentSummarySchema });
1357
- const conversationSummarySchema = z.object({
1358
- id: z.string().uuid(),
1359
- title: z.string().nullable(),
1360
- createdAt: z.string(),
1361
- updatedAt: z.string(),
1362
- preview: z.string().nullable(),
1363
- channel: z.string().nullable(),
1364
- channelLabel: z.string().nullable(),
1365
- agent: z.object({
1366
- id: z.string().uuid(),
1367
- name: z.string(),
1368
- slug: z.string().nullable().optional(),
1369
- title: z.string().nullable().optional()
1370
- })
1371
- });
1372
- const conversationDetailSchema = z.object({
1373
- id: z.string().uuid(),
1374
- title: z.string().nullable(),
1375
- agentId: z.string().uuid(),
1376
- createdAt: z.string(),
1377
- updatedAt: z.string()
1378
- });
1379
- const getConversationResponseSchema = z.object({ conversation: conversationDetailSchema });
1380
- const listConversationsResponseSchema = z.object({
1381
- conversations: z.array(conversationSummarySchema),
1382
- nextCursor: z.string().nullable().optional(),
1383
- totalCount: z.number().optional()
1384
- });
1385
- const uiMessagePartSchema = z.union([
1386
- z.object({
1387
- type: z.literal("text"),
1388
- text: z.string()
1389
- }),
1390
- z.object({
1391
- type: z.literal("reasoning"),
1392
- text: z.string().optional()
1393
- }),
1394
- z.object({
1395
- type: z.literal("dynamic-tool"),
1396
- toolCallId: z.string(),
1397
- toolName: z.string(),
1398
- input: z.unknown().optional(),
1399
- output: z.unknown().optional(),
1400
- state: z.string().optional(),
1401
- errorText: z.string().optional()
1402
- }),
1403
- z.object({ type: z.string() }).passthrough()
1404
- ]);
1405
- const uiMessageSchema = z.object({
1406
- id: z.string(),
1407
- role: z.string(),
1408
- parts: z.array(uiMessagePartSchema),
1409
- metadata: z.object({ custom: z.object({
1410
- agentId: z.string().nullish(),
1411
- agentName: z.string().nullish()
1412
- }).passthrough().optional() }).passthrough().optional()
1413
- });
1414
- const recapResponseSchema = z.object({ recap: z.object({ text: z.string() }).nullable() });
1415
- const listMessagesResponseSchema = z.object({ messages: z.array(uiMessageSchema) });
1416
- const workspaceFileSchema = z.object({
1417
- id: z.string(),
1418
- path: z.string(),
1419
- mediaType: z.string(),
1420
- sizeBytes: z.number(),
1421
- contentHash: z.string(),
1422
- updatedAt: z.string(),
1423
- shareUrl: z.string()
1424
- });
1425
- const listWorkspaceFilesResponseSchema = z.object({ files: z.array(workspaceFileSchema) });
1426
- const sendResultSchema = z.object({
1427
- runId: z.string(),
1428
- messageId: z.string().uuid().nullish(),
1429
- conversationId: z.string().uuid(),
1430
- isNewConversation: z.boolean(),
1431
- agentId: z.string().nullish(),
1432
- agentName: z.string().nullish(),
1433
- steered: z.boolean().optional(),
1434
- directive: z.object({ id: z.string() }).passthrough().optional()
1435
- });
1436
- const presignResponseSchema = z.object({
1437
- id: z.string(),
1438
- uploadUrl: z.string(),
1439
- fileName: z.string(),
1440
- mediaType: z.string()
1441
- });
1442
- const finalizeResponseSchema = z.object({
1443
- fileName: z.string(),
1444
- mediaType: z.string(),
1445
- sizeBytes: z.number().nullable().optional()
1446
- });
1447
- const oauthConnectResponseSchema = z.object({ connectLink: z.string() }).passthrough();
1448
- const externalOauthConnectResponseSchema = z.object({ authorizationUrl: z.string().optional() }).passthrough();
1449
- const runStreamEventSchema = z.union([z.object({
1450
- kind: z.literal("chunk"),
1451
- chunk: z.record(z.unknown())
1452
- }), z.object({
1453
- kind: z.literal("finished"),
1454
- status: z.string(),
1455
- error: z.string().nullish()
1456
- })]);
1457
- const streamErrorSchema = z.object({ error: z.string() });
1458
- const conversationStreamEventSchema = z.discriminatedUnion("kind", [z.object({
1459
- kind: z.literal("conversation"),
1460
- id: z.string(),
1461
- title: z.string().nullable()
1462
- }), z.object({
1463
- kind: z.literal("run"),
1464
- runId: z.string()
1465
- })]);
1466
-
1467
737
  //#endregion
1468
738
  //#region src/commands/session.ts
1469
739
  /**
@@ -1750,7 +1020,7 @@ const keysCommand = {
1750
1020
  //#endregion
1751
1021
  //#region src/commands/secrets.ts
1752
1022
  /** Read all of stdin as UTF-8, trimming a single trailing newline. */
1753
- async function readStdin$1() {
1023
+ async function readStdin() {
1754
1024
  const chunks = [];
1755
1025
  for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1756
1026
  return Buffer.concat(chunks).toString("utf8").replace(/\n$/, "");
@@ -1794,7 +1064,7 @@ const setCommand = {
1794
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>`.");
1795
1065
  process.exit(1);
1796
1066
  }
1797
- value = await readStdin$1();
1067
+ value = await readStdin();
1798
1068
  }
1799
1069
  if (!value) {
1800
1070
  printError("Empty secret value.");
@@ -1845,688 +1115,6 @@ const secretsCommand = {
1845
1115
  handler: () => {}
1846
1116
  };
1847
1117
 
1848
- //#endregion
1849
- //#region src/chat/tui/theme.ts
1850
- const tokyonight = {
1851
- id: "tokyonight",
1852
- label: "Tokyo Night",
1853
- mode: "dark",
1854
- palette: {
1855
- fg: "#c0caf5",
1856
- muted: "#7a7a7a",
1857
- dim: "#565f89",
1858
- faint: "#3b4261",
1859
- surface: "#16161e",
1860
- accent: "#7aa2f7",
1861
- success: "#9ece6a",
1862
- warning: "#e0af68",
1863
- error: "#f7768e",
1864
- user: "#7aa2f7",
1865
- assistant: "#c0caf5",
1866
- tool: "#bb9af7",
1867
- reasoning: "#565f89",
1868
- syntaxBlue: "#7aa2f7",
1869
- syntaxCyan: "#7dcfff",
1870
- syntaxTeal: "#73daca",
1871
- syntaxGreen: "#9ece6a",
1872
- syntaxYellow: "#e0af68",
1873
- syntaxOrange: "#ff9e64",
1874
- syntaxRed: "#f7768e",
1875
- syntaxMagenta: "#bb9af7"
1876
- }
1877
- };
1878
- const tokyonightDay = {
1879
- id: "tokyonight-day",
1880
- label: "Tokyo Night Day",
1881
- mode: "light",
1882
- palette: {
1883
- fg: "#3760bf",
1884
- muted: "#848cb5",
1885
- dim: "#9da3c2",
1886
- faint: "#c4c8da",
1887
- surface: "#d0d1d8",
1888
- accent: "#2e7de9",
1889
- success: "#587539",
1890
- warning: "#8c6c3e",
1891
- error: "#f52a65",
1892
- user: "#2e7de9",
1893
- assistant: "#3760bf",
1894
- tool: "#9854f1",
1895
- reasoning: "#848cb5",
1896
- syntaxBlue: "#2e7de9",
1897
- syntaxCyan: "#007197",
1898
- syntaxTeal: "#118c74",
1899
- syntaxGreen: "#587539",
1900
- syntaxYellow: "#8c6c3e",
1901
- syntaxOrange: "#b15c00",
1902
- syntaxRed: "#f52a65",
1903
- syntaxMagenta: "#9854f1"
1904
- }
1905
- };
1906
- const catppuccinMocha = {
1907
- id: "catppuccin-mocha",
1908
- label: "Catppuccin Mocha",
1909
- mode: "dark",
1910
- palette: {
1911
- fg: "#cdd6f4",
1912
- muted: "#7f849c",
1913
- dim: "#6c7086",
1914
- faint: "#45475a",
1915
- surface: "#181825",
1916
- accent: "#89b4fa",
1917
- success: "#a6e3a1",
1918
- warning: "#f9e2af",
1919
- error: "#f38ba8",
1920
- user: "#89b4fa",
1921
- assistant: "#cdd6f4",
1922
- tool: "#cba6f7",
1923
- reasoning: "#6c7086",
1924
- syntaxBlue: "#89b4fa",
1925
- syntaxCyan: "#89dceb",
1926
- syntaxTeal: "#94e2d5",
1927
- syntaxGreen: "#a6e3a1",
1928
- syntaxYellow: "#f9e2af",
1929
- syntaxOrange: "#fab387",
1930
- syntaxRed: "#f38ba8",
1931
- syntaxMagenta: "#cba6f7"
1932
- }
1933
- };
1934
- const catppuccinLatte = {
1935
- id: "catppuccin-latte",
1936
- label: "Catppuccin Latte",
1937
- mode: "light",
1938
- palette: {
1939
- fg: "#4c4f69",
1940
- muted: "#8c8fa1",
1941
- dim: "#9ca0b0",
1942
- faint: "#bcc0cc",
1943
- surface: "#e6e9ef",
1944
- accent: "#1e66f5",
1945
- success: "#40a02b",
1946
- warning: "#df8e1d",
1947
- error: "#d20f39",
1948
- user: "#1e66f5",
1949
- assistant: "#4c4f69",
1950
- tool: "#8839ef",
1951
- reasoning: "#9ca0b0",
1952
- syntaxBlue: "#1e66f5",
1953
- syntaxCyan: "#04a5e5",
1954
- syntaxTeal: "#179299",
1955
- syntaxGreen: "#40a02b",
1956
- syntaxYellow: "#df8e1d",
1957
- syntaxOrange: "#fe640b",
1958
- syntaxRed: "#d20f39",
1959
- syntaxMagenta: "#8839ef"
1960
- }
1961
- };
1962
- const gruvboxDark = {
1963
- id: "gruvbox-dark",
1964
- label: "Gruvbox Dark",
1965
- mode: "dark",
1966
- palette: {
1967
- fg: "#ebdbb2",
1968
- muted: "#928374",
1969
- dim: "#7c6f64",
1970
- faint: "#504945",
1971
- surface: "#1d2021",
1972
- accent: "#83a598",
1973
- success: "#b8bb26",
1974
- warning: "#fabd2f",
1975
- error: "#fb4934",
1976
- user: "#83a598",
1977
- assistant: "#ebdbb2",
1978
- tool: "#d3869b",
1979
- reasoning: "#7c6f64",
1980
- syntaxBlue: "#83a598",
1981
- syntaxCyan: "#8ec07c",
1982
- syntaxTeal: "#8ec07c",
1983
- syntaxGreen: "#b8bb26",
1984
- syntaxYellow: "#fabd2f",
1985
- syntaxOrange: "#fe8019",
1986
- syntaxRed: "#fb4934",
1987
- syntaxMagenta: "#d3869b"
1988
- }
1989
- };
1990
- const gruvboxLight = {
1991
- id: "gruvbox-light",
1992
- label: "Gruvbox Light",
1993
- mode: "light",
1994
- palette: {
1995
- fg: "#3c3836",
1996
- muted: "#928374",
1997
- dim: "#a89984",
1998
- faint: "#d5c4a1",
1999
- surface: "#ebdbb2",
2000
- accent: "#076678",
2001
- success: "#79740e",
2002
- warning: "#b57614",
2003
- error: "#9d0006",
2004
- user: "#076678",
2005
- assistant: "#3c3836",
2006
- tool: "#8f3f71",
2007
- reasoning: "#a89984",
2008
- syntaxBlue: "#076678",
2009
- syntaxCyan: "#427b58",
2010
- syntaxTeal: "#427b58",
2011
- syntaxGreen: "#79740e",
2012
- syntaxYellow: "#b57614",
2013
- syntaxOrange: "#af3a03",
2014
- syntaxRed: "#9d0006",
2015
- syntaxMagenta: "#8f3f71"
2016
- }
2017
- };
2018
- const solarizedDark = {
2019
- id: "solarized-dark",
2020
- label: "Solarized Dark",
2021
- mode: "dark",
2022
- palette: {
2023
- fg: "#93a1a1",
2024
- muted: "#586e75",
2025
- dim: "#586e75",
2026
- faint: "#073642",
2027
- surface: "#00212b",
2028
- accent: "#268bd2",
2029
- success: "#859900",
2030
- warning: "#b58900",
2031
- error: "#dc322f",
2032
- user: "#268bd2",
2033
- assistant: "#93a1a1",
2034
- tool: "#6c71c4",
2035
- reasoning: "#586e75",
2036
- syntaxBlue: "#268bd2",
2037
- syntaxCyan: "#2aa198",
2038
- syntaxTeal: "#2aa198",
2039
- syntaxGreen: "#859900",
2040
- syntaxYellow: "#b58900",
2041
- syntaxOrange: "#cb4b16",
2042
- syntaxRed: "#dc322f",
2043
- syntaxMagenta: "#6c71c4"
2044
- }
2045
- };
2046
- const solarizedLight = {
2047
- id: "solarized-light",
2048
- label: "Solarized Light",
2049
- mode: "light",
2050
- palette: {
2051
- fg: "#657b83",
2052
- muted: "#839496",
2053
- dim: "#93a1a1",
2054
- faint: "#eee8d5",
2055
- surface: "#eee8d5",
2056
- accent: "#268bd2",
2057
- success: "#859900",
2058
- warning: "#b58900",
2059
- error: "#dc322f",
2060
- user: "#268bd2",
2061
- assistant: "#657b83",
2062
- tool: "#6c71c4",
2063
- reasoning: "#93a1a1",
2064
- syntaxBlue: "#268bd2",
2065
- syntaxCyan: "#2aa198",
2066
- syntaxTeal: "#2aa198",
2067
- syntaxGreen: "#859900",
2068
- syntaxYellow: "#b58900",
2069
- syntaxOrange: "#cb4b16",
2070
- syntaxRed: "#dc322f",
2071
- syntaxMagenta: "#6c71c4"
2072
- }
2073
- };
2074
- const nord = {
2075
- id: "nord",
2076
- label: "Nord",
2077
- mode: "dark",
2078
- palette: {
2079
- fg: "#d8dee9",
2080
- muted: "#616e88",
2081
- dim: "#4c566a",
2082
- faint: "#3b4252",
2083
- surface: "#272c36",
2084
- accent: "#88c0d0",
2085
- success: "#a3be8c",
2086
- warning: "#ebcb8b",
2087
- error: "#bf616a",
2088
- user: "#88c0d0",
2089
- assistant: "#d8dee9",
2090
- tool: "#b48ead",
2091
- reasoning: "#4c566a",
2092
- syntaxBlue: "#81a1c1",
2093
- syntaxCyan: "#88c0d0",
2094
- syntaxTeal: "#8fbcbb",
2095
- syntaxGreen: "#a3be8c",
2096
- syntaxYellow: "#ebcb8b",
2097
- syntaxOrange: "#d08770",
2098
- syntaxRed: "#bf616a",
2099
- syntaxMagenta: "#b48ead"
2100
- }
2101
- };
2102
- const dracula = {
2103
- id: "dracula",
2104
- label: "Dracula",
2105
- mode: "dark",
2106
- palette: {
2107
- fg: "#f8f8f2",
2108
- muted: "#6272a4",
2109
- dim: "#6272a4",
2110
- faint: "#44475a",
2111
- surface: "#21222c",
2112
- accent: "#bd93f9",
2113
- success: "#50fa7b",
2114
- warning: "#ffb86c",
2115
- error: "#ff5555",
2116
- user: "#bd93f9",
2117
- assistant: "#f8f8f2",
2118
- tool: "#ff79c6",
2119
- reasoning: "#6272a4",
2120
- syntaxBlue: "#bd93f9",
2121
- syntaxCyan: "#8be9fd",
2122
- syntaxTeal: "#8be9fd",
2123
- syntaxGreen: "#50fa7b",
2124
- syntaxYellow: "#f1fa8c",
2125
- syntaxOrange: "#ffb86c",
2126
- syntaxRed: "#ff5555",
2127
- syntaxMagenta: "#ff79c6"
2128
- }
2129
- };
2130
- const oneDark = {
2131
- id: "one-dark",
2132
- label: "One Dark",
2133
- mode: "dark",
2134
- palette: {
2135
- fg: "#abb2bf",
2136
- muted: "#5c6370",
2137
- dim: "#5c6370",
2138
- faint: "#3e4451",
2139
- surface: "#21252b",
2140
- accent: "#61afef",
2141
- success: "#98c379",
2142
- warning: "#e5c07b",
2143
- error: "#e06c75",
2144
- user: "#61afef",
2145
- assistant: "#abb2bf",
2146
- tool: "#c678dd",
2147
- reasoning: "#5c6370",
2148
- syntaxBlue: "#61afef",
2149
- syntaxCyan: "#56b6c2",
2150
- syntaxTeal: "#56b6c2",
2151
- syntaxGreen: "#98c379",
2152
- syntaxYellow: "#e5c07b",
2153
- syntaxOrange: "#d19a66",
2154
- syntaxRed: "#e06c75",
2155
- syntaxMagenta: "#c678dd"
2156
- }
2157
- };
2158
- const oneLight = {
2159
- id: "one-light",
2160
- label: "One Light",
2161
- mode: "light",
2162
- palette: {
2163
- fg: "#383a42",
2164
- muted: "#a0a1a7",
2165
- dim: "#a0a1a7",
2166
- faint: "#e5e5e6",
2167
- surface: "#f0f0f1",
2168
- accent: "#4078f2",
2169
- success: "#50a14f",
2170
- warning: "#c18401",
2171
- error: "#e45649",
2172
- user: "#4078f2",
2173
- assistant: "#383a42",
2174
- tool: "#a626a4",
2175
- reasoning: "#a0a1a7",
2176
- syntaxBlue: "#4078f2",
2177
- syntaxCyan: "#0184bc",
2178
- syntaxTeal: "#0184bc",
2179
- syntaxGreen: "#50a14f",
2180
- syntaxYellow: "#c18401",
2181
- syntaxOrange: "#986801",
2182
- syntaxRed: "#e45649",
2183
- syntaxMagenta: "#a626a4"
2184
- }
2185
- };
2186
- const rosePine = {
2187
- id: "rose-pine",
2188
- label: "Rosé Pine",
2189
- mode: "dark",
2190
- palette: {
2191
- fg: "#e0def4",
2192
- muted: "#908caa",
2193
- dim: "#6e6a86",
2194
- faint: "#403d52",
2195
- surface: "#16141f",
2196
- accent: "#c4a7e7",
2197
- success: "#9ccfd8",
2198
- warning: "#f6c177",
2199
- error: "#eb6f92",
2200
- user: "#c4a7e7",
2201
- assistant: "#e0def4",
2202
- tool: "#ebbcba",
2203
- reasoning: "#908caa",
2204
- syntaxBlue: "#9ccfd8",
2205
- syntaxCyan: "#9ccfd8",
2206
- syntaxTeal: "#31748f",
2207
- syntaxGreen: "#31748f",
2208
- syntaxYellow: "#f6c177",
2209
- syntaxOrange: "#ebbcba",
2210
- syntaxRed: "#eb6f92",
2211
- syntaxMagenta: "#c4a7e7"
2212
- }
2213
- };
2214
- const rosePineDawn = {
2215
- id: "rose-pine-dawn",
2216
- label: "Rosé Pine Dawn",
2217
- mode: "light",
2218
- palette: {
2219
- fg: "#575279",
2220
- muted: "#797593",
2221
- dim: "#9893a5",
2222
- faint: "#cecacd",
2223
- surface: "#f2e9e1",
2224
- accent: "#907aa9",
2225
- success: "#56949f",
2226
- warning: "#ea9d34",
2227
- error: "#b4637a",
2228
- user: "#907aa9",
2229
- assistant: "#575279",
2230
- tool: "#d7827e",
2231
- reasoning: "#9893a5",
2232
- syntaxBlue: "#56949f",
2233
- syntaxCyan: "#56949f",
2234
- syntaxTeal: "#286983",
2235
- syntaxGreen: "#286983",
2236
- syntaxYellow: "#ea9d34",
2237
- syntaxOrange: "#d7827e",
2238
- syntaxRed: "#b4637a",
2239
- syntaxMagenta: "#907aa9"
2240
- }
2241
- };
2242
- const everforestDark = {
2243
- id: "everforest-dark",
2244
- label: "Everforest Dark",
2245
- mode: "dark",
2246
- palette: {
2247
- fg: "#d3c6aa",
2248
- muted: "#859289",
2249
- dim: "#7a8478",
2250
- faint: "#414b50",
2251
- surface: "#232a2e",
2252
- accent: "#7fbbb3",
2253
- success: "#a7c080",
2254
- warning: "#dbbc7f",
2255
- error: "#e67e80",
2256
- user: "#7fbbb3",
2257
- assistant: "#d3c6aa",
2258
- tool: "#d699b6",
2259
- reasoning: "#7a8478",
2260
- syntaxBlue: "#7fbbb3",
2261
- syntaxCyan: "#83c092",
2262
- syntaxTeal: "#83c092",
2263
- syntaxGreen: "#a7c080",
2264
- syntaxYellow: "#dbbc7f",
2265
- syntaxOrange: "#e69875",
2266
- syntaxRed: "#e67e80",
2267
- syntaxMagenta: "#d699b6"
2268
- }
2269
- };
2270
- const everforestLight = {
2271
- id: "everforest-light",
2272
- label: "Everforest Light",
2273
- mode: "light",
2274
- palette: {
2275
- fg: "#5c6a72",
2276
- muted: "#939f91",
2277
- dim: "#a6b0a0",
2278
- faint: "#e0dcc7",
2279
- surface: "#f4f0d9",
2280
- accent: "#3a94c5",
2281
- success: "#8da101",
2282
- warning: "#dfa000",
2283
- error: "#f85552",
2284
- user: "#3a94c5",
2285
- assistant: "#5c6a72",
2286
- tool: "#df69ba",
2287
- reasoning: "#a6b0a0",
2288
- syntaxBlue: "#3a94c5",
2289
- syntaxCyan: "#35a77c",
2290
- syntaxTeal: "#35a77c",
2291
- syntaxGreen: "#8da101",
2292
- syntaxYellow: "#dfa000",
2293
- syntaxOrange: "#f57d26",
2294
- syntaxRed: "#f85552",
2295
- syntaxMagenta: "#df69ba"
2296
- }
2297
- };
2298
- const githubDark = {
2299
- id: "github-dark",
2300
- label: "GitHub Dark",
2301
- mode: "dark",
2302
- palette: {
2303
- fg: "#c9d1d9",
2304
- muted: "#8b949e",
2305
- dim: "#6e7681",
2306
- faint: "#30363d",
2307
- surface: "#161b22",
2308
- accent: "#58a6ff",
2309
- success: "#3fb950",
2310
- warning: "#d29922",
2311
- error: "#f85149",
2312
- user: "#58a6ff",
2313
- assistant: "#c9d1d9",
2314
- tool: "#bc8cff",
2315
- reasoning: "#6e7681",
2316
- syntaxBlue: "#58a6ff",
2317
- syntaxCyan: "#39c5cf",
2318
- syntaxTeal: "#39c5cf",
2319
- syntaxGreen: "#3fb950",
2320
- syntaxYellow: "#d29922",
2321
- syntaxOrange: "#db6d28",
2322
- syntaxRed: "#f85149",
2323
- syntaxMagenta: "#bc8cff"
2324
- }
2325
- };
2326
- const githubLight = {
2327
- id: "github-light",
2328
- label: "GitHub Light",
2329
- mode: "light",
2330
- palette: {
2331
- fg: "#24292f",
2332
- muted: "#57606a",
2333
- dim: "#8c959f",
2334
- faint: "#d0d7de",
2335
- surface: "#f6f8fa",
2336
- accent: "#0969da",
2337
- success: "#1a7f37",
2338
- warning: "#9a6700",
2339
- error: "#cf222e",
2340
- user: "#0969da",
2341
- assistant: "#24292f",
2342
- tool: "#8250df",
2343
- reasoning: "#8c959f",
2344
- syntaxBlue: "#0969da",
2345
- syntaxCyan: "#1b7c83",
2346
- syntaxTeal: "#1b7c83",
2347
- syntaxGreen: "#1a7f37",
2348
- syntaxYellow: "#9a6700",
2349
- syntaxOrange: "#bc4c00",
2350
- syntaxRed: "#cf222e",
2351
- syntaxMagenta: "#8250df"
2352
- }
2353
- };
2354
- const kanagawa = {
2355
- id: "kanagawa",
2356
- label: "Kanagawa",
2357
- mode: "dark",
2358
- palette: {
2359
- fg: "#dcd7ba",
2360
- muted: "#727169",
2361
- dim: "#54546d",
2362
- faint: "#363646",
2363
- surface: "#16161d",
2364
- accent: "#7e9cd8",
2365
- success: "#98bb6c",
2366
- warning: "#e6c384",
2367
- error: "#e46876",
2368
- user: "#7e9cd8",
2369
- assistant: "#dcd7ba",
2370
- tool: "#957fb8",
2371
- reasoning: "#727169",
2372
- syntaxBlue: "#7e9cd8",
2373
- syntaxCyan: "#7aa89f",
2374
- syntaxTeal: "#7aa89f",
2375
- syntaxGreen: "#98bb6c",
2376
- syntaxYellow: "#e6c384",
2377
- syntaxOrange: "#ffa066",
2378
- syntaxRed: "#e46876",
2379
- syntaxMagenta: "#957fb8"
2380
- }
2381
- };
2382
- const cursorDark = {
2383
- id: "cursor-dark",
2384
- label: "Cursor Dark",
2385
- mode: "dark",
2386
- palette: {
2387
- background: "#181818",
2388
- fg: "#d4d4d4",
2389
- muted: "#898989",
2390
- dim: "#636363",
2391
- faint: "#272727",
2392
- surface: "#141414",
2393
- accent: "#88c0d0",
2394
- success: "#70b489",
2395
- warning: "#f1b467",
2396
- error: "#fc6b83",
2397
- diffAdded: "#3fa266",
2398
- diffRemoved: "#b80049",
2399
- diffWeight: .2,
2400
- user: "#88c0d0",
2401
- assistant: "#d4d4d4",
2402
- tool: "#aaa0fa",
2403
- reasoning: "#636363",
2404
- syntaxBlue: "#ebc88d",
2405
- syntaxCyan: "#87c3ff",
2406
- syntaxTeal: "#aaa0fa",
2407
- syntaxGreen: "#e394dc",
2408
- syntaxYellow: "#d4d4d4",
2409
- syntaxOrange: "#f8c762",
2410
- syntaxRed: "#cc7c8a",
2411
- syntaxMagenta: "#82d2ce"
2412
- }
2413
- };
2414
- const themes = [
2415
- tokyonight,
2416
- tokyonightDay,
2417
- catppuccinMocha,
2418
- catppuccinLatte,
2419
- gruvboxDark,
2420
- gruvboxLight,
2421
- solarizedDark,
2422
- solarizedLight,
2423
- nord,
2424
- dracula,
2425
- oneDark,
2426
- oneLight,
2427
- rosePine,
2428
- rosePineDawn,
2429
- everforestDark,
2430
- everforestLight,
2431
- githubDark,
2432
- githubLight,
2433
- kanagawa,
2434
- cursorDark
2435
- ];
2436
- const DEFAULT_THEME_ID = {
2437
- dark: tokyonight.id,
2438
- light: tokyonightDay.id
2439
- };
2440
- /** All-undefined palette for NO_COLOR: every fg/bg falls back to the
2441
- * terminal's own defaults, so nothing emits color. Not listed in `themes` —
2442
- * it's forced, never picked. */
2443
- const monoTheme = {
2444
- id: "mono",
2445
- label: "No color",
2446
- mode: "dark",
2447
- palette: {
2448
- fg: void 0,
2449
- muted: void 0,
2450
- dim: void 0,
2451
- faint: void 0,
2452
- surface: void 0,
2453
- accent: void 0,
2454
- success: void 0,
2455
- warning: void 0,
2456
- error: void 0,
2457
- user: void 0,
2458
- assistant: void 0,
2459
- tool: void 0,
2460
- reasoning: void 0,
2461
- syntaxBlue: void 0,
2462
- syntaxCyan: void 0,
2463
- syntaxTeal: void 0,
2464
- syntaxGreen: void 0,
2465
- syntaxYellow: void 0,
2466
- syntaxOrange: void 0,
2467
- syntaxRed: void 0,
2468
- syntaxMagenta: void 0
2469
- }
2470
- };
2471
- function themesForMode(mode) {
2472
- return themes.filter((t) => t.mode === mode);
2473
- }
2474
- function findTheme(id) {
2475
- if (id === monoTheme.id) return monoTheme;
2476
- return themes.find((t) => t.id === id);
2477
- }
2478
- /**
2479
- * The saved theme to use for a mode: the persisted pick if it exists *and*
2480
- * still matches the mode (a stale/renamed id falls back), else the default.
2481
- */
2482
- function themeForMode(mode, savedId) {
2483
- if (savedId) {
2484
- const saved = findTheme(savedId);
2485
- if (saved && saved.mode === mode) return saved;
2486
- }
2487
- return findTheme(DEFAULT_THEME_ID[mode]) ?? tokyonight;
2488
- }
2489
- /** https://no-color.org — any non-empty value disables color output. */
2490
- function noColorRequested(env = process.env) {
2491
- const v = env["NO_COLOR"];
2492
- return v !== void 0 && v !== "";
2493
- }
2494
- /**
2495
- * Fallback light/dark sniff for terminals that never answer the OSC 10/11
2496
- * query: `COLORFGBG` is "<fg>;<bg>" (sometimes "<fg>;default;<bg>") with
2497
- * ANSI palette indexes. Background 7/15 (white/bright white) means a light
2498
- * terminal; anything else we call dark. Returns null when unset/unparsable.
2499
- */
2500
- function themeModeFromColorFgBg(env = process.env) {
2501
- const raw = env["COLORFGBG"];
2502
- if (!raw) return null;
2503
- const parts = raw.split(";");
2504
- const bg = parts[parts.length - 1];
2505
- if (bg === void 0 || !/^\d+$/.test(bg)) return null;
2506
- const idx = Number(bg);
2507
- return idx === 7 || idx === 15 ? "light" : "dark";
2508
- }
2509
- /** Single source of truth for colors. Mutated in place by `applyTheme` so
2510
- * existing `theme.fg`-style reads across the TUI stay valid. */
2511
- const theme = { ...tokyonight.palette };
2512
- let version = 0;
2513
- function themeVersion() {
2514
- return version;
2515
- }
2516
- let mode = tokyonight.mode;
2517
- function themeMode() {
2518
- return mode;
2519
- }
2520
- function applyTheme(def) {
2521
- Object.assign(theme, def.palette);
2522
- theme.background = def.palette.background;
2523
- theme.diffAdded = def.palette.diffAdded;
2524
- theme.diffRemoved = def.palette.diffRemoved;
2525
- theme.diffWeight = def.palette.diffWeight;
2526
- mode = def.mode;
2527
- version++;
2528
- }
2529
-
2530
1118
  //#endregion
2531
1119
  //#region src/chat/bun-runtime.ts
2532
1120
  /**
@@ -2828,7 +1416,7 @@ const chatCommand = {
2828
1416
  printError(`Unknown theme "${themeId}". Valid themes: ${themes.map((t) => t.id).join(", ")}`);
2829
1417
  process.exit(1);
2830
1418
  }
2831
- const { runChat } = await import("./boot-B48ty_OV.mjs");
1419
+ const { runChat } = await import("./boot-CMhIv1BG.mjs");
2832
1420
  await runChat({
2833
1421
  appUrl,
2834
1422
  sessionToken: session.value.sessionToken,
@@ -2855,7 +1443,7 @@ async function runPrintMode({ argv, appUrl }) {
2855
1443
  printError(`${session.error.message} For non-interactive use, run \`skydive auth login\` first, or set SKYDIVE_SESSION_TOKEN.`);
2856
1444
  process.exit(1);
2857
1445
  }
2858
- const { runPrint, readStdin } = await Promise.resolve().then(() => print_exports);
1446
+ const { runPrint, readStdin } = await import("./print-DFPzQQk8.mjs");
2859
1447
  let prompt = (argv.print ?? "").trim();
2860
1448
  if (!prompt) {
2861
1449
  if (process.stdin.isTTY) {
@@ -2870,7 +1458,7 @@ async function runPrintMode({ argv, appUrl }) {
2870
1458
  }
2871
1459
  let machineShare = null;
2872
1460
  if (resolveShareMachine(argv)) {
2873
- const { PortalClient } = await import("./client-XFsd0Wy9.mjs").then((n) => n.n);
1461
+ const { PortalClient } = await import("./client-C-s6b9Yu.mjs");
2874
1462
  let signalConnected;
2875
1463
  const connected = new Promise((resolve) => {
2876
1464
  signalConnected = resolve;
@@ -2943,7 +1531,7 @@ const getCommand = {
2943
1531
  printError(`${session.error.message} Run \`skydive auth login\` first, or set SKYDIVE_SESSION_TOKEN.`);
2944
1532
  process.exit(1);
2945
1533
  }
2946
- const { messageGet } = await Promise.resolve().then(() => print_exports);
1534
+ const { messageGet } = await import("./print-DFPzQQk8.mjs");
2947
1535
  try {
2948
1536
  const result = await messageGet({
2949
1537
  appUrl,
@@ -2965,488 +1553,6 @@ const messagesCommand = {
2965
1553
  handler: () => {}
2966
1554
  };
2967
1555
 
2968
- //#endregion
2969
- //#region src/chat/util.ts
2970
- /** Narrowing helper for the many `unknown` payloads the chat stream and
2971
- * tool inputs/outputs carry. A type predicate (not an `as` cast), so call
2972
- * sites can read properties without asserting. */
2973
- function isRecord(value) {
2974
- return typeof value === "object" && value !== null && !Array.isArray(value);
2975
- }
2976
- /** Best-effort message from an unknown thrown value. */
2977
- function errorMessage(err) {
2978
- return err instanceof Error ? err.message : String(err);
2979
- }
2980
-
2981
- //#endregion
2982
- //#region src/chat/tui/chat/card.ts
2983
- const urlActionKinds = [
2984
- "open_oauth",
2985
- "open_external_oauth",
2986
- "open_github_app",
2987
- "submit_credential"
2988
- ];
2989
- function isUrlActionKind(value) {
2990
- return typeof value === "string" && urlActionKinds.includes(value);
2991
- }
2992
- function optionalString(value) {
2993
- return typeof value === "string" && value ? value : null;
2994
- }
2995
- function bindStateKey(props) {
2996
- const value = props.value;
2997
- if (!isRecord(value)) return null;
2998
- const pointer = value.$bindState;
2999
- if (typeof pointer !== "string") return null;
3000
- return pointer.startsWith("/") ? pointer.slice(1) : pointer;
3001
- }
3002
- function parseButton(element) {
3003
- const props = isRecord(element.props) ? element.props : {};
3004
- const label = optionalString(props.label) ?? "Connect";
3005
- const on = isRecord(element.on) ? element.on : null;
3006
- const press = on && isRecord(on.press) ? on.press : null;
3007
- if (!press) return null;
3008
- const params = isRecord(press.params) ? press.params : {};
3009
- const primary = props.variant === "primary";
3010
- if (press.action === "approve_portal_access") {
3011
- const agentId = params.agentId;
3012
- if (typeof agentId !== "string" || !agentId) return null;
3013
- return {
3014
- label,
3015
- action: {
3016
- kind: "grant_portal",
3017
- agentId,
3018
- deviceId: typeof params.deviceId === "string" && params.deviceId ? params.deviceId : null
3019
- },
3020
- primary
3021
- };
3022
- }
3023
- if (!isUrlActionKind(press.action)) return null;
3024
- const url = params.url;
3025
- if (typeof url !== "string" || !url) return null;
3026
- return {
3027
- label,
3028
- action: {
3029
- kind: press.action,
3030
- url
3031
- },
3032
- primary
3033
- };
3034
- }
3035
- function parseConnectCard(spec) {
3036
- if (!isRecord(spec)) return null;
3037
- const { root, elements } = spec;
3038
- if (typeof root !== "string" || !isRecord(elements)) return null;
3039
- const rootEl = elements[root];
3040
- if (!isRecord(rootEl) || rootEl.type !== "Card") return null;
3041
- const rootProps = isRecord(rootEl.props) ? rootEl.props : {};
3042
- const title = optionalString(rootProps.title);
3043
- if (!title) return null;
3044
- const fields = [];
3045
- const buttons = [];
3046
- const children = Array.isArray(rootEl.children) ? rootEl.children : [];
3047
- for (const childId of children) {
3048
- if (typeof childId !== "string") continue;
3049
- const el = elements[childId];
3050
- if (!isRecord(el)) continue;
3051
- if (el.type === "TextInput" || el.type === "SecretInput") {
3052
- const props = isRecord(el.props) ? el.props : {};
3053
- const key = bindStateKey(props);
3054
- if (!key) continue;
3055
- fields.push({
3056
- key,
3057
- label: optionalString(props.label) ?? key,
3058
- placeholder: optionalString(props.placeholder),
3059
- secret: el.type === "SecretInput"
3060
- });
3061
- continue;
3062
- }
3063
- if (el.type === "Button") {
3064
- const button = parseButton(el);
3065
- if (button) buttons.push(button);
3066
- }
3067
- }
3068
- const preferred = buttons.find((b) => b.primary) ?? buttons[0] ?? null;
3069
- return {
3070
- title,
3071
- subtitle: optionalString(rootProps.subtitle),
3072
- description: optionalString(rootProps.description),
3073
- fields,
3074
- button: preferred ? {
3075
- label: preferred.label,
3076
- action: preferred.action
3077
- } : null,
3078
- state: isRecord(spec.state) ? spec.state : {}
3079
- };
3080
- }
3081
-
3082
- //#endregion
3083
- //#region src/chat/tui/chat/card-actions.ts
3084
- function safeUrl(url) {
3085
- try {
3086
- return new URL(url, "http://localhost");
3087
- } catch (_error) {
3088
- return null;
3089
- }
3090
- }
3091
- /**
3092
- * Resolve a connect URL to an absolute one the OS can open in a browser.
3093
- *
3094
- * The server builds lazy connect links as server-relative paths (e.g.
3095
- * `/api/v1/oauth/start/slack?connect_session_token=...`). The web client
3096
- * resolves these against its own origin implicitly; the TUI runs outside a
3097
- * browser, so `open()` on a scheme-less path is a silent no-op — the browser
3098
- * never launches and the connect card sits in `launched` forever (the Slack
3099
- * channel-connect deadlock). Resolve against `appUrl` first, mirroring the
3100
- * `open_github_app` / `fulfillCredential` branches, so both the browser we
3101
- * launch and the URL shown on the card are absolute. An already-absolute URL
3102
- * passes through unchanged; if `appUrl` is missing we return the input as-is.
3103
- */
3104
- function resolveConnectUrl(url, appUrl) {
3105
- if (!appUrl) return url;
3106
- try {
3107
- return new URL(url, appUrl).toString();
3108
- } catch (_error) {
3109
- return url;
3110
- }
3111
- }
3112
- function parseOauthConnectParams(url) {
3113
- const parsed = safeUrl(url);
3114
- if (!parsed) return null;
3115
- const integrationKey = parsed.searchParams.get("integration");
3116
- const agentId = parsed.searchParams.get("agent_id");
3117
- const authConfigId = parsed.searchParams.get("auth_config_id");
3118
- const conversationId = parsed.searchParams.get("conversation_id");
3119
- if (!integrationKey || !agentId || !authConfigId || !conversationId) return null;
3120
- return {
3121
- integrationKey,
3122
- agentId,
3123
- authConfigId,
3124
- conversationId,
3125
- scopes: parsed.searchParams.get("scopes")
3126
- };
3127
- }
3128
- function parseExternalOauthConnectParams(url) {
3129
- const parsed = safeUrl(url);
3130
- if (!parsed) return null;
3131
- const agentId = parsed.searchParams.get("agent_id");
3132
- const conversationId = parsed.searchParams.get("conversation_id");
3133
- const serverUrl = parsed.searchParams.get("server_url");
3134
- if (!agentId || !conversationId || !serverUrl) return null;
3135
- return {
3136
- agentId,
3137
- conversationId,
3138
- serverUrl
3139
- };
3140
- }
3141
- /**
3142
- * A short human line for a failed card action. Fulfill/connect endpoints
3143
- * return `{ error: string }` bodies (e.g. "already fulfilled") — prefer that
3144
- * over the generic HttpError message.
3145
- */
3146
- function cardActionErrorMessage(err) {
3147
- if (err instanceof HttpError) {
3148
- try {
3149
- const parsed = JSON.parse(err.body);
3150
- if (isRecord(parsed) && typeof parsed.error === "string") return parsed.error;
3151
- } catch (_error) {}
3152
- return `request failed (HTTP ${err.status})`;
3153
- }
3154
- return errorMessage(err);
3155
- }
3156
- const MASK_CHAR = "•";
3157
- /**
3158
- * Recover the real secret from the masked input's displayed text. The input
3159
- * is controlled: after every edit we render bullets, which forces the cursor
3160
- * to the end, so the next edit is always a tail edit — the displayed text is
3161
- * some prefix of the old mask (kept characters) followed by newly typed or
3162
- * pasted plaintext. Characters beyond the retained bullets are the new tail.
3163
- */
3164
- function reconcileMaskedInput(previousValue, displayed) {
3165
- let kept = 0;
3166
- while (kept < displayed.length && kept < previousValue.length && displayed[kept] === MASK_CHAR) kept++;
3167
- return previousValue.slice(0, kept) + displayed.slice(kept);
3168
- }
3169
-
3170
- //#endregion
3171
- //#region src/chat/connect-cards.ts
3172
- /**
3173
- * Turn a parsed ConnectCard into the headless summary, resolving any relative
3174
- * connect URL against the app origin so the emitted URL is directly openable.
3175
- */
3176
- function summarizeConnectCard(card, appUrl) {
3177
- const base = {
3178
- title: card.title,
3179
- subtitle: card.subtitle,
3180
- description: card.description
3181
- };
3182
- const button = card.button;
3183
- if (!button) return {
3184
- ...base,
3185
- action: { kind: "unsupported" }
3186
- };
3187
- const act = button.action;
3188
- switch (act.kind) {
3189
- case "open_oauth":
3190
- case "open_external_oauth":
3191
- case "open_github_app": return {
3192
- ...base,
3193
- action: {
3194
- kind: "open_url",
3195
- url: resolveConnectUrl(act.url, appUrl)
3196
- }
3197
- };
3198
- case "submit_credential": return {
3199
- ...base,
3200
- action: {
3201
- kind: "submit_credential",
3202
- url: resolveConnectUrl(act.url, appUrl),
3203
- fields: card.fields.map((f) => f.label)
3204
- }
3205
- };
3206
- case "grant_portal": return {
3207
- ...base,
3208
- action: {
3209
- kind: "approve_portal",
3210
- agentId: act.agentId
3211
- }
3212
- };
3213
- default: return {
3214
- ...base,
3215
- action: { kind: "unsupported" }
3216
- };
3217
- }
3218
- }
3219
- /**
3220
- * Parse a `data-anyone-render-spec` stream chunk into a connect-card summary,
3221
- * or null if the chunk isn't a connect card (other render specs — training,
3222
- * deep-learn, compute-request — parse to null, same as the TUI).
3223
- */
3224
- function connectCardFromChunk(chunk, appUrl) {
3225
- if (chunk["type"] !== "data-anyone-render-spec") return null;
3226
- const data = chunk["data"];
3227
- if (!isRecord(data)) return null;
3228
- const card = parseConnectCard(data["spec"]);
3229
- if (!card) return null;
3230
- return summarizeConnectCard(card, appUrl);
3231
- }
3232
- /** Render a connect-card summary as a human-readable action block. */
3233
- function formatConnectCard(card) {
3234
- const lines = [];
3235
- lines.push(`\n[action needed] ${card.title}`);
3236
- if (card.subtitle) lines.push(card.subtitle);
3237
- if (card.description) lines.push(card.description);
3238
- switch (card.action.kind) {
3239
- case "open_url":
3240
- lines.push(`Open to continue: ${card.action.url}`);
3241
- break;
3242
- case "submit_credential":
3243
- lines.push(`Provide credential (${card.action.fields.join(", ") || "value"}) at: ${card.action.url}`);
3244
- break;
3245
- case "approve_portal":
3246
- lines.push(`Approve local-machine access for agent ${card.action.agentId} in the TUI or web app.`);
3247
- break;
3248
- case "unsupported":
3249
- lines.push("Open this conversation in the web app to continue.");
3250
- break;
3251
- }
3252
- return lines.join("\n");
3253
- }
3254
-
3255
- //#endregion
3256
- //#region src/chat/print.ts
3257
- var print_exports = /* @__PURE__ */ __exportAll({
3258
- collectRunText: () => collectRunText,
3259
- messageGet: () => messageGet,
3260
- readStdin: () => readStdin,
3261
- resolveAgent: () => resolveAgent,
3262
- runPrint: () => runPrint,
3263
- toPrintError: () => toPrintError
3264
- });
3265
- /**
3266
- * Non-interactive chat, à la `claude -p`. Sends a single prompt to an
3267
- * agent, streams the run, and prints the assistant's reply to stdout
3268
- * before exiting. No OpenTUI, no Bun requirement — this rides the same
3269
- * Node-friendly REST client the TUI uses, so it runs anywhere the
3270
- * management commands do (CI, pipes, scripts).
3271
- *
3272
- * Resolution rules kept deliberately strict because there's no human to
3273
- * disambiguate: an `--agent` selector must match exactly one agent, and
3274
- * when it's omitted we only auto-pick if the account has exactly one.
3275
- */
3276
- async function runPrint({ appUrl, sessionToken, prompt, agentSelector, conversationId, json, machineShare, grantTargetAgent }) {
3277
- const client = createRestClient({
3278
- appUrl,
3279
- sessionToken
3280
- });
3281
- const agent = resolveAgent(await client.listAgents({
3282
- scope: "org",
3283
- onPage: null
3284
- }), agentSelector);
3285
- if (machineShare) {
3286
- if (!machineShare.isGranted(agent.id) && grantTargetAgent) await machineShare.grantAgent(agent.id);
3287
- if (machineShare.isGranted(agent.id)) console.error(`portal: shared this machine with ${agent.name} for this run (grant persists until revoked)`);
3288
- 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}\`.`);
3289
- }
3290
- let send;
3291
- try {
3292
- send = await client.sendMessage({
3293
- agentId: agent.id,
3294
- conversationId,
3295
- content: prompt,
3296
- attachmentIds: [],
3297
- clientSurface: "cli"
3298
- });
3299
- } catch (err) {
3300
- throw toPrintError(err);
3301
- }
3302
- const { text, connectCards } = await collectRunText({
3303
- client,
3304
- appUrl,
3305
- target: {
3306
- kind: "run",
3307
- id: send.runId
3308
- },
3309
- onText: json ? null : (delta) => process.stdout.write(delta),
3310
- messageIdForHint: send.messageId ?? null
3311
- });
3312
- if (!json && text && !text.endsWith("\n")) process.stdout.write("\n");
3313
- if (!json) for (const card of connectCards) process.stdout.write(`${formatConnectCard(card)}\n`);
3314
- return {
3315
- agentId: agent.id,
3316
- agentName: agent.name,
3317
- conversationId: send.conversationId,
3318
- isNewConversation: send.isNewConversation,
3319
- messageId: send.messageId ?? null,
3320
- text,
3321
- connectCards
3322
- };
3323
- }
3324
- /**
3325
- * Turn a transport failure into an actionable CLI error. A Cloudflare edge
3326
- * 5xx (502/504) on a long run otherwise leaks a raw HTML/JSON error page to
3327
- * stdout, which is impossible to act on. When a messageId is known and
3328
- * recoverable we point the user at `skydive messages get <messageId>` rather
3329
- * than a blind retry (a retry re-executes an agent that may have write access).
3330
- */
3331
- function toPrintError(err, messageId) {
3332
- if (err instanceof HttpError && err.status >= 500) {
3333
- 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}` : "";
3334
- return /* @__PURE__ */ new Error(`The request to Skydive timed out at the edge (HTTP ${err.status}).${recovery}`);
3335
- }
3336
- return err instanceof Error ? err : new Error(String(err));
3337
- }
3338
- /**
3339
- * Stream a run to completion, folding text-delta chunks into the reply and
3340
- * collecting any connect cards (OAuth / MCP / credential requests) the run
3341
- * posts. Shared by `chat -p` (streaming the run it just created) and `messages
3342
- * get` (re-attaching by message id — the server replays a finished run from its
3343
- * persisted log, so this works whether the run is live or already done).
3344
- */
3345
- async function collectRunText({ client, appUrl, target, onText, messageIdForHint }) {
3346
- let text = "";
3347
- const controller = new AbortController();
3348
- let streamError = null;
3349
- const connectCards = [];
3350
- const onEvent = (event) => {
3351
- if (event.kind === "finished") {
3352
- if (event.error) streamError = event.error;
3353
- return;
3354
- }
3355
- const chunk = event.chunk;
3356
- if (chunk["type"] === "text-delta") {
3357
- const delta = typeof chunk["delta"] === "string" ? chunk["delta"] : typeof chunk["text"] === "string" ? chunk["text"] : "";
3358
- if (delta) {
3359
- text += delta;
3360
- if (onText) onText(delta);
3361
- }
3362
- } else if (chunk["type"] === "error") streamError = typeof chunk["errorText"] === "string" ? chunk["errorText"] : "unknown error";
3363
- else {
3364
- const card = connectCardFromChunk(chunk, appUrl);
3365
- if (card) connectCards.push(card);
3366
- }
3367
- };
3368
- try {
3369
- if (target.kind === "message") await client.streamMessage({
3370
- messageId: target.id,
3371
- signal: controller.signal,
3372
- onEvent
3373
- });
3374
- else await client.streamRun({
3375
- runId: target.id,
3376
- signal: controller.signal,
3377
- onEvent
3378
- });
3379
- } catch (err) {
3380
- throw toPrintError(err, messageIdForHint);
3381
- }
3382
- if (streamError) throw new Error(streamError);
3383
- return {
3384
- text,
3385
- connectCards
3386
- };
3387
- }
3388
- /**
3389
- * Re-attach to an exchange by message id and print its reply. Backs `skydive
3390
- * messages get <messageId>` — the recovery path when a `chat -p` stream dropped
3391
- * at the edge after the message was accepted. The server resolves the run
3392
- * behind the message and replays it from its persisted event log, so this
3393
- * returns the full reply whether the run is still live or already done.
3394
- */
3395
- async function messageGet({ appUrl, sessionToken, messageId, json }) {
3396
- const { text, connectCards } = await collectRunText({
3397
- client: createRestClient({
3398
- appUrl,
3399
- sessionToken
3400
- }),
3401
- appUrl,
3402
- target: {
3403
- kind: "message",
3404
- id: messageId
3405
- },
3406
- onText: json ? null : (delta) => process.stdout.write(delta),
3407
- messageIdForHint: null
3408
- });
3409
- if (!json && text && !text.endsWith("\n")) process.stdout.write("\n");
3410
- if (!json) for (const card of connectCards) process.stdout.write(`${formatConnectCard(card)}\n`);
3411
- return {
3412
- messageId,
3413
- text,
3414
- connectCards
3415
- };
3416
- }
3417
- /**
3418
- * Pick the target agent. With no selector, auto-pick only when the
3419
- * account has exactly one agent; otherwise the user must name one (there's
3420
- * no picker in non-interactive mode). A selector matches by id first, then
3421
- * a unique case-insensitive slug/name; ambiguous or missing matches throw
3422
- * with the candidate list so the caller knows what to pass.
3423
- */
3424
- function resolveAgent(agents, selector) {
3425
- if (!selector) {
3426
- const [only, ...rest] = agents;
3427
- if (!only) throw new Error("No agents on this account.");
3428
- if (rest.length === 0) return only;
3429
- throw new Error(`Multiple agents on this account — pass --agent <id|slug|name>. Candidates:\n${formatCandidates(agents)}`);
3430
- }
3431
- const byId = agents.find((a) => a.id === selector);
3432
- if (byId) return byId;
3433
- const needle = selector.toLowerCase();
3434
- const matches = agents.filter((a) => a.slug && a.slug.toLowerCase() === needle || a.name.toLowerCase() === needle);
3435
- const [firstMatch, ...restMatches] = matches;
3436
- if (firstMatch && restMatches.length === 0) return firstMatch;
3437
- if (restMatches.length > 0) throw new Error(`Multiple agents match "${selector}" — pass the id instead. Candidates:\n${formatCandidates(matches)}`);
3438
- throw new Error(`No agent matches "${selector}". Candidates:\n${formatCandidates(agents)}`);
3439
- }
3440
- function formatCandidates(agents) {
3441
- return agents.slice(0, 25).map((a) => ` ${a.id} ${a.slug ?? a.name}`).join("\n");
3442
- }
3443
- /** Read all of stdin as UTF-8. Used when `-p` is passed with no value. */
3444
- async function readStdin() {
3445
- const chunks = [];
3446
- for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
3447
- return Buffer.concat(chunks).toString("utf8");
3448
- }
3449
-
3450
1556
  //#endregion
3451
1557
  //#region src/commands/conversations.ts
3452
1558
  /**
@@ -3622,7 +1728,7 @@ const switchCommand = {
3622
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.");
3623
1729
  process.exit(1);
3624
1730
  }
3625
- const { runWorkspacePicker } = await import("./boot-B48ty_OV.mjs");
1731
+ const { runWorkspacePicker } = await import("./boot-CMhIv1BG.mjs");
3626
1732
  await runWorkspacePicker(session);
3627
1733
  return;
3628
1734
  }
@@ -3659,145 +1765,6 @@ const workspaceCommand = {
3659
1765
  handler: (argv) => runList(argv, { hint: true })
3660
1766
  };
3661
1767
 
3662
- //#endregion
3663
- //#region src/chat/portal/machine.ts
3664
- /**
3665
- * Identity this machine registers under when the CLI shares it via the portal.
3666
- *
3667
- * The `-cli` suffix / `(CLI)` label keep a CLI-shared machine a DISTINCT portal
3668
- * device from the same host's Skydive Desktop app. `portal_device` is unique on
3669
- * (org, user, machineName), and directives route to whichever socket holds the
3670
- * device — if the CLI and desktop registered the same name they'd share a
3671
- * device row and both execute every directive. Distinct names also make the
3672
- * grant UI unambiguous about which surface is being authorized.
3673
- */
3674
- function machineIdentity() {
3675
- const host = (os.hostname() || "machine").trim().replace(/\.local$/i, "") || "machine";
3676
- return {
3677
- machineName: `${host}-cli`,
3678
- friendlyName: `${host} (CLI)`
3679
- };
3680
- }
3681
- const INHERITED_ENV = [
3682
- "HOME",
3683
- "USER",
3684
- "LOGNAME",
3685
- "SHELL",
3686
- "LANG",
3687
- "LC_ALL",
3688
- "TMPDIR",
3689
- "TERM",
3690
- "PATH"
3691
- ];
3692
- function buildEnv(extra) {
3693
- const env = {};
3694
- for (const key of INHERITED_ENV) {
3695
- const value = process.env[key];
3696
- if (value !== void 0) env[key] = value;
3697
- }
3698
- if (extra) for (const [key, value] of Object.entries(extra)) env[key] = value;
3699
- return env;
3700
- }
3701
- /**
3702
- * Build the desktop-portal WebSocket URL from the chat origin. Mirrors the Rust
3703
- * desktop client: http→ws, https→wss, scheme-less defaults to wss, and the
3704
- * machine/label ride as query pairs (percent-encoded by URL).
3705
- */
3706
- function portalWsUrl(appUrl, machine, label) {
3707
- const base = appUrl.replace(/\/+$/, "");
3708
- let wsBase;
3709
- if (base.startsWith("https://")) wsBase = `wss://${base.slice(8)}`;
3710
- else if (base.startsWith("http://")) wsBase = `ws://${base.slice(7)}`;
3711
- else wsBase = `wss://${base}`;
3712
- const url = new URL(`${wsBase}/api/v1/portal/desktop`);
3713
- url.searchParams.set("machine", machine);
3714
- url.searchParams.set("label", label);
3715
- return url.toString();
3716
- }
3717
-
3718
- //#endregion
3719
- //#region src/chat/portal/api.ts
3720
- /**
3721
- * The portal's session-authed REST surface, shared by `PortalClient` (the
3722
- * TUI/`portal open` connection) and the `skydive portal` management
3723
- * commands, so the endpoint contracts and response schemas live in exactly
3724
- * one place.
3725
- */
3726
- const deviceSchema = z.object({
3727
- id: z.string(),
3728
- machineName: z.string(),
3729
- friendlyName: z.string(),
3730
- connected: z.boolean(),
3731
- lastSeen: z.string().nullable(),
3732
- grantedAgentIds: z.array(z.string())
3733
- });
3734
- const devicesResponseSchema = z.object({
3735
- devices: z.array(deviceSchema),
3736
- agents: z.array(z.object({
3737
- id: z.string(),
3738
- name: z.string()
3739
- }))
3740
- });
3741
- const deviceTokenSchema = z.object({ token: z.string().min(1) });
3742
- async function portalFetch(auth, path, init) {
3743
- const res = await fetch(`${auth.appUrl}${path}`, {
3744
- method: init.method,
3745
- headers: {
3746
- authorization: `Bearer ${auth.sessionToken}`,
3747
- accept: "application/json",
3748
- ...init.body ? { "content-type": "application/json" } : {}
3749
- },
3750
- ...init.body ? { body: init.body } : {}
3751
- });
3752
- if (!res.ok) {
3753
- const body = await res.text().catch(() => "");
3754
- throw new HttpError(res.status, body);
3755
- }
3756
- return res.json();
3757
- }
3758
- async function fetchPortalDevices(auth) {
3759
- const json = await portalFetch(auth, "/api/v1/portal/devices", { method: "GET" });
3760
- return devicesResponseSchema.parse(json);
3761
- }
3762
- const registerResponseSchema = z.object({ device: z.object({ id: z.string() }) });
3763
- /**
3764
- * Register this machine's device row without connecting. Connecting registers
3765
- * as a side effect; this covers granting an agent on a machine that has never
3766
- * shared yet (the grant references the device row).
3767
- */
3768
- async function registerPortalDevice(auth, { machineName, friendlyName }) {
3769
- const json = await portalFetch(auth, "/api/v1/portal/devices", {
3770
- method: "POST",
3771
- body: JSON.stringify({
3772
- machineName,
3773
- friendlyName
3774
- })
3775
- });
3776
- return registerResponseSchema.parse(json).device;
3777
- }
3778
- /** Short-lived token the machine presents when dialing the portal WebSocket. */
3779
- async function mintPortalDeviceToken(auth) {
3780
- const json = await portalFetch(auth, "/api/v1/portal/device-token", { method: "POST" });
3781
- return deviceTokenSchema.parse(json).token;
3782
- }
3783
- async function grantPortalAccess(auth, { deviceId, agentId }) {
3784
- await portalFetch(auth, `/api/v1/portal/devices/${encodeURIComponent(deviceId)}/grants`, {
3785
- method: "POST",
3786
- body: JSON.stringify({ agentId })
3787
- });
3788
- }
3789
- async function revokePortalAccess(auth, { deviceId, agentId }) {
3790
- await portalFetch(auth, `/api/v1/portal/devices/${encodeURIComponent(deviceId)}/grants/${encodeURIComponent(agentId)}`, { method: "DELETE" });
3791
- }
3792
- /**
3793
- * The device row for a given machine identity. Matching is by `machineName`
3794
- * equality — the stable per-surface handle (`<host>-cli` vs the desktop's
3795
- * `<host>`), not the display label.
3796
- */
3797
- function findThisDevice(devices, machineName) {
3798
- return devices.find((device) => device.machineName === machineName) ?? null;
3799
- }
3800
-
3801
1768
  //#endregion
3802
1769
  //#region src/commands/portal.ts
3803
1770
  /**
@@ -3839,7 +1806,7 @@ const openCommand = {
3839
1806
  const agent = argv.agent ? resolveAgent((await fetchPortalDevices(session)).agents, argv.agent) : null;
3840
1807
  const cwd = argv.cwd ? path.resolve(argv.cwd) : process.cwd();
3841
1808
  const { machineName } = machineIdentity();
3842
- const { PortalClient } = await import("./client-XFsd0Wy9.mjs").then((n) => n.n);
1809
+ const { PortalClient } = await import("./client-C-s6b9Yu.mjs");
3843
1810
  let lastLine = "";
3844
1811
  let signalConnected;
3845
1812
  const connected = new Promise((resolve) => {
@@ -3970,171 +1937,6 @@ const portalCommand = {
3970
1937
  handler: () => {}
3971
1938
  };
3972
1939
 
3973
- //#endregion
3974
- //#region ../sandbox-stream-protocol/src/index.ts
3975
- const SANDBOX_STREAM_PATH = "/api/v1/sandbox/stream";
3976
- const FRAME = {
3977
- DATA: 1,
3978
- EXIT: 2,
3979
- ERROR: 3,
3980
- INPUT: 16,
3981
- RESIZE: 17
3982
- };
3983
- const MAX_INPUT_BYTES = 1 * 1024 * 1024;
3984
- /** Query params for the upgrade URL, from a spec. Inverse of {@link parseStreamSpec}. */
3985
- function streamSpecToQuery(spec) {
3986
- if (spec.mode === "pty") return {
3987
- agentId: spec.agentId,
3988
- mode: "pty",
3989
- cols: String(spec.cols),
3990
- rows: String(spec.rows)
3991
- };
3992
- return {
3993
- agentId: spec.agentId,
3994
- mode: "exec",
3995
- command: spec.command
3996
- };
3997
- }
3998
- function withType(type, payload) {
3999
- const frame = new Uint8Array(1 + payload.length);
4000
- frame[0] = type;
4001
- frame.set(payload, 1);
4002
- return frame;
4003
- }
4004
- /** client → server: keystroke bytes for the pty stdin. */
4005
- function encodeInput(data) {
4006
- return withType(FRAME.INPUT, data);
4007
- }
4008
- /** client → server: the client terminal was resized. */
4009
- function encodeResize(cols, rows) {
4010
- const frame = new Uint8Array(5);
4011
- frame[0] = FRAME.RESIZE;
4012
- const view = new DataView(frame.buffer);
4013
- view.setUint16(1, cols & 65535);
4014
- view.setUint16(3, rows & 65535);
4015
- return frame;
4016
- }
4017
- const view = (frame) => new DataView(frame.buffer, frame.byteOffset, frame.byteLength);
4018
- /**
4019
- * Decode a frame the server sent. Returns null for an empty, unknown, or
4020
- * truncated frame — a peer speaking a newer protocol must not crash us.
4021
- */
4022
- function decodeServerFrame(frame) {
4023
- const payload = frame.subarray(1);
4024
- switch (frame[0]) {
4025
- case FRAME.DATA: return {
4026
- type: "data",
4027
- payload
4028
- };
4029
- case FRAME.EXIT: return {
4030
- type: "exit",
4031
- code: payload.length >= 4 ? view(frame).getInt32(1) : 0
4032
- };
4033
- case FRAME.ERROR: return {
4034
- type: "error",
4035
- message: new TextDecoder().decode(payload)
4036
- };
4037
- default: return null;
4038
- }
4039
- }
4040
-
4041
- //#endregion
4042
- //#region src/chat/sandbox/client.ts
4043
- function wsBase(appUrl) {
4044
- const base = appUrl.replace(/\/+$/, "");
4045
- if (base.startsWith("https://")) return `wss://${base.slice(8)}`;
4046
- if (base.startsWith("http://")) return `ws://${base.slice(7)}`;
4047
- return `wss://${base}`;
4048
- }
4049
- /**
4050
- * A connected sandbox-stream session. Construct via `openSandboxStream`. Carries
4051
- * the write side (keystrokes / resize for pty mode) and teardown.
4052
- */
4053
- var SandboxStream = class SandboxStream {
4054
- ws;
4055
- closed = false;
4056
- constructor(ws, onEvent) {
4057
- this.ws = ws;
4058
- let ended = false;
4059
- const emitEnd = (event) => {
4060
- if (ended) return;
4061
- ended = true;
4062
- onEvent(event);
4063
- };
4064
- ws.on("message", (data, isBinary) => {
4065
- if (!isBinary) return;
4066
- const frame = decodeServerFrame(toBuffer(data));
4067
- if (!frame) return;
4068
- switch (frame.type) {
4069
- case "data":
4070
- onEvent({
4071
- type: "data",
4072
- bytes: new Uint8Array(frame.payload)
4073
- });
4074
- break;
4075
- case "exit":
4076
- emitEnd({
4077
- type: "exit",
4078
- code: frame.code
4079
- });
4080
- break;
4081
- case "error":
4082
- emitEnd({
4083
- type: "error",
4084
- message: frame.message
4085
- });
4086
- break;
4087
- }
4088
- });
4089
- let failure = null;
4090
- ws.on("error", (err) => {
4091
- failure = err.message;
4092
- });
4093
- ws.on("close", () => {
4094
- this.closed = true;
4095
- emitEnd({
4096
- type: "close",
4097
- failure
4098
- });
4099
- });
4100
- }
4101
- /** Feed keystroke bytes to the pty stdin. */
4102
- sendInput(data) {
4103
- if (this.closed || this.ws.readyState !== WebSocket.OPEN) return;
4104
- this.ws.send(encodeInput(data));
4105
- }
4106
- /** Notify the pty of a terminal resize. */
4107
- resize(cols, rows) {
4108
- if (this.closed || this.ws.readyState !== WebSocket.OPEN) return;
4109
- this.ws.send(encodeResize(cols, rows));
4110
- }
4111
- close() {
4112
- this.closed = true;
4113
- this.ws.close();
4114
- }
4115
- /** Open a stream. `mode` is 'pty' (interactive) or 'exec' (one-shot). */
4116
- static open(opts) {
4117
- const spec = opts.mode === "pty" ? {
4118
- mode: "pty",
4119
- agentId: opts.agentId,
4120
- cols: opts.cols,
4121
- rows: opts.rows
4122
- } : {
4123
- mode: "exec",
4124
- agentId: opts.agentId,
4125
- command: opts.command
4126
- };
4127
- const url = new URL(`${wsBase(opts.appUrl)}${SANDBOX_STREAM_PATH}`);
4128
- for (const [key, value] of Object.entries(streamSpecToQuery(spec))) url.searchParams.set(key, value);
4129
- return new SandboxStream(new WebSocket(url.toString(), { headers: { authorization: `Bearer ${opts.sessionToken}` } }), opts.onEvent);
4130
- }
4131
- };
4132
- function toBuffer(data) {
4133
- if (Buffer.isBuffer(data)) return data;
4134
- if (Array.isArray(data)) return Buffer.concat(data);
4135
- return Buffer.from(data);
4136
- }
4137
-
4138
1940
  //#endregion
4139
1941
  //#region src/commands/sandbox.ts
4140
1942
  /** POSIX single-quote one word so the remote shell treats it as one token. */
@@ -4176,8 +1978,8 @@ const sandboxCommand = {
4176
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`"),
4177
1979
  handler: async (argv) => {
4178
1980
  const session = requireSession(argv);
4179
- const { createRestClient } = await Promise.resolve().then(() => rest_exports);
4180
- const { resolveAgent } = await Promise.resolve().then(() => print_exports);
1981
+ const { createRestClient } = await import("./rest-DQruM5kj.mjs");
1982
+ const { resolveAgent } = await import("./print-DFPzQQk8.mjs");
4181
1983
  const client = createRestClient({
4182
1984
  appUrl: session.appUrl,
4183
1985
  sessionToken: session.sessionToken
@@ -4246,7 +2048,7 @@ async function runPty({ session, agentId, agentName }) {
4246
2048
  return 1;
4247
2049
  }
4248
2050
  console.error(`Connecting to ${agentName}'s sandbox… (Ctrl-] detaches)`);
4249
- const { runRawPtyPassthrough } = await import("./raw-pty-C1DXKms6.mjs").then((n) => n.t);
2051
+ const { runRawPtyPassthrough } = await import("./raw-pty-pspO57gT.mjs");
4250
2052
  const result = await runRawPtyPassthrough({
4251
2053
  stdin: process.stdin,
4252
2054
  stdout: process.stdout,
@@ -4275,7 +2077,7 @@ function createCli(argv) {
4275
2077
  type: "string",
4276
2078
  global: true,
4277
2079
  describe: "Override API base URL"
4278
- }).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) => {
4279
2081
  printError(err ? err instanceof Error ? err.message : String(err) : msg ?? "Unknown error");
4280
2082
  process.exit(1);
4281
2083
  });
@@ -4344,9 +2146,250 @@ function resolveArgv(args, tty = {
4344
2146
  return shouldDefaultToChat(args, tty, nonInteractive) ? ["chat", ...args] : args;
4345
2147
  }
4346
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
+
4347
2385
  //#endregion
4348
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));
4349
2392
  createCli(resolveArgv(hideBin(process.argv))).parse();
4350
2393
 
4351
2394
  //#endregion
4352
- export { HttpError as A, getSavedTheme as B, noColorRequested as C, themeModeFromColorFgBg as D, themeMode as E, setActiveWorkspace as F, saveTheme as H, 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, resolveWebUrl 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, getReviewStateDir as z };
2395
+ export { };