skydive-cli 0.5.0-beta.2 → 0.5.0-beta.21

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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { c as LOCAL_PROTOCOL_VERSION, d as encodeLine, f as makeLineParser, n as ensureDaemonRunning, p as parseDaemonMessage, u as daemonPaths } from "./daemon-Bq93vOIk.mjs";
2
+ import { c as LOCAL_PROTOCOL_VERSION, d as encodeLine, f as makeLineParser, n as ensureDaemonRunning, p as parseDaemonMessage, u as daemonPaths } from "./daemon-Dj9tGT12.mjs";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { connect } from "node:net";
5
5
 
@@ -12,7 +12,7 @@ var PortalDaemonClient = class {
12
12
  lastBind = null;
13
13
  fallbackCwd = null;
14
14
  wantEnabled = false;
15
- pendingGrants = /* @__PURE__ */ new Set();
15
+ pendingGrants = /* @__PURE__ */ new Map();
16
16
  pendingDeclines = /* @__PURE__ */ new Map();
17
17
  grantedAgentIds = /* @__PURE__ */ new Set();
18
18
  constructor(opts) {
@@ -50,9 +50,10 @@ var PortalDaemonClient = class {
50
50
  conversationId: this.lastBind.conversationId,
51
51
  cwd: this.lastBind.cwd
52
52
  });
53
- for (const agentId of this.pendingGrants) this.send({
53
+ for (const [agentId, conversationId] of this.pendingGrants) this.send({
54
54
  t: "grant",
55
- agentId
55
+ agentId,
56
+ conversationId
56
57
  });
57
58
  for (const [agentId, declined] of this.pendingDeclines) this.send({
58
59
  t: "decline",
@@ -157,13 +158,16 @@ var PortalDaemonClient = class {
157
158
  * Authorize one agent to run commands on this machine. Resolves once the
158
159
  * request is sent to the daemon (the daemon performs the grant and pushes the
159
160
  * updated state); kept async so it's a drop-in for the old in-process client's
160
- * awaited `grantAgent`.
161
+ * awaited `grantAgent`. `conversationId` names the conversation whose run
162
+ * asked, so the grant can wake the waiting agent; null when the grant is not
163
+ * answering an in-chat request.
161
164
  */
162
- grantAgent(agentId) {
163
- this.pendingGrants.add(agentId);
165
+ grantAgent(agentId, conversationId) {
166
+ this.pendingGrants.set(agentId, conversationId);
164
167
  this.send({
165
168
  t: "grant",
166
- agentId
169
+ agentId,
170
+ conversationId
167
171
  });
168
172
  return Promise.resolve();
169
173
  }
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import "./client-DbqRBquD.mjs";
3
+ import "./daemon-Dj9tGT12.mjs";
4
+ import "./api-DG5W6iwx.mjs";
5
+ import { t as PortalDaemonClient } from "./daemon-client-BNK77jX_.mjs";
6
+
7
+ export { PortalDaemonClient };
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ import { t as fetchForwardTarget } from "./api-DG5W6iwx.mjs";
3
+ import net from "node:net";
4
+ import { WebSocket, createWebSocketStream } from "ws";
5
+
6
+ //#region src/chat/portal/forward.ts
7
+ const TARGET_REFRESH_SAFETY_MS = 12e4;
8
+ /**
9
+ * The reverse portal's client half: listen on the local machine's loopback and
10
+ * pipe each TCP connection to the agent sandbox's daemon (`/portal/tcp`),
11
+ * which pipes to the sandbox's own loopback. The daemon is reached through the
12
+ * agent-webserver edge Worker (sandboxes have public ingress disabled; the
13
+ * Worker owns boot-resolution and injects the sandbox edge-auth token), so a
14
+ * sandbox recycle just costs the next connection a cold-start wait rather
15
+ * than invalidating the forward.
16
+ */
17
+ async function startForward({ auth, agentId, localPort, targetPort, log }) {
18
+ let target = await fetchForwardTarget(auth, agentId);
19
+ let mintedAt = Date.now();
20
+ async function freshTarget() {
21
+ const ttlMs = target.expiresInSeconds * 1e3;
22
+ if (Date.now() - mintedAt > ttlMs - TARGET_REFRESH_SAFETY_MS) {
23
+ target = await fetchForwardTarget(auth, agentId);
24
+ mintedAt = Date.now();
25
+ }
26
+ return target;
27
+ }
28
+ const server = net.createServer((sock) => {
29
+ sock.pause();
30
+ (async () => {
31
+ let resolved;
32
+ try {
33
+ resolved = await freshTarget();
34
+ } catch (err) {
35
+ log(`forward: token refresh failed: ${err instanceof Error ? err.message : String(err)}`);
36
+ sock.destroy();
37
+ return;
38
+ }
39
+ const ws = new WebSocket(`${resolved.daemonOrigin.replace(/^http/, "ws")}/portal/tcp?port=${targetPort}`, { headers: { authorization: `Bearer ${resolved.token}` } });
40
+ ws.on("open", () => {
41
+ const stream = createWebSocketStream(ws);
42
+ stream.on("error", () => sock.destroy());
43
+ sock.on("error", () => stream.destroy());
44
+ sock.pipe(stream).pipe(sock);
45
+ sock.resume();
46
+ });
47
+ ws.on("error", (err) => {
48
+ log(`forward: tunnel connect failed: ${err.message}`);
49
+ sock.destroy();
50
+ });
51
+ })();
52
+ });
53
+ await new Promise((resolve, reject) => {
54
+ server.once("error", reject);
55
+ server.listen(localPort, "127.0.0.1", () => {
56
+ server.removeListener("error", reject);
57
+ resolve();
58
+ });
59
+ });
60
+ const addr = server.address();
61
+ return {
62
+ port: addr && typeof addr === "object" ? addr.port : localPort,
63
+ close: () => new Promise((resolve) => server.close(() => resolve()))
64
+ };
65
+ }
66
+
67
+ //#endregion
68
+ export { startForward };
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-CbayCa87.mjs";
3
- import "./rest-BY2nADw5.mjs";
2
+ import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-OvYyj6Uk.mjs";
3
+ import "./rest-Dx6b-Nq_.mjs";
4
4
  import "./billing-blocked-2wju4gC_.mjs";
5
5
 
6
6
  export { messageGet, readStdin, resolveAgent, runPrint };
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
3
- import { a as isRecord, i as errorMessage, r as sendErrorMessage, t as createRestClient } from "./rest-BY2nADw5.mjs";
3
+ import { a as isRecord, i as errorMessage, r as sendErrorMessage, t as createRestClient } from "./rest-Dx6b-Nq_.mjs";
4
4
  import { i as billingBlockedOutcomeFromSendResponse, n as BillingBlockedError } from "./billing-blocked-2wju4gC_.mjs";
5
5
  import path from "node:path";
6
6
  import Conf from "conf";
@@ -9,7 +9,7 @@ import stableStringify from "safe-stable-stringify";
9
9
 
10
10
  //#region src/config.ts
11
11
  /** Default host for the public management API (`/v1`, API-key auth). */
12
- const DEFAULT_API_URL = "https://api.skydive.com";
12
+ const DEFAULT_API_URL = typeof SKYDIVE_BUILD_API_URL === "string" ? SKYDIVE_BUILD_API_URL : "https://api.skydive.com";
13
13
  /**
14
14
  * Default origin for the interactive chat client (`skydive chat`).
15
15
  *
@@ -20,9 +20,9 @@ const DEFAULT_API_URL = "https://api.skydive.com";
20
20
  * `DEFAULT_API_URL`; override with `--api-url` / `SKYDIVE_APP_URL` for local
21
21
  * dev or while the DNS record is still being provisioned.
22
22
  */
23
- const DEFAULT_APP_URL = "https://api.skydive.com";
23
+ const DEFAULT_APP_URL = DEFAULT_API_URL;
24
24
  /** Web front door, for pages opened in the user's browser. */
25
- const DEFAULT_WEB_URL = "https://skydive.com";
25
+ const DEFAULT_WEB_URL = typeof SKYDIVE_BUILD_WEB_URL === "string" ? SKYDIVE_BUILD_WEB_URL : "https://skydive.com";
26
26
  function resolveWebUrl(appUrl) {
27
27
  if (appUrl == null) return appUrl;
28
28
  return appUrl === DEFAULT_APP_URL ? DEFAULT_WEB_URL : appUrl;
@@ -213,6 +213,32 @@ function saveUpdateCheck(value) {
213
213
  }
214
214
  store.set("updateCheck", value);
215
215
  }
216
+ /**
217
+ * Agent a bare `skydive chat` (no --agent/--resume) opens a new conversation
218
+ * with. Any selector `--agent` accepts works: an id, or a unique
219
+ * case-insensitive slug/name, resolved against the active workspace's roster
220
+ * at launch. Null when unset (the launch falls back to the agent picker).
221
+ * A hand-edited blank value counts as unset rather than as a selector no
222
+ * agent could ever match.
223
+ */
224
+ function getDefaultAgent() {
225
+ const value = store.get("defaultAgent")?.trim();
226
+ return value ? value : null;
227
+ }
228
+ function saveDefaultAgent(value) {
229
+ store.set("defaultAgent", value);
230
+ }
231
+ /**
232
+ * Remember the agent the chat TUI just opened a conversation with, so the
233
+ * next bare `skydive chat` returns to it (last used wins). Records the id —
234
+ * stable across renames, unlike a slug/name selector. Skips the write when
235
+ * the value already matches: this runs on every chat-screen entry, and an
236
+ * unchanged default shouldn't rewrite config.json.
237
+ */
238
+ function recordDefaultAgent(agentId) {
239
+ if (getDefaultAgent() === agentId) return;
240
+ store.set("defaultAgent", agentId);
241
+ }
216
242
  function parseBoolean(raw) {
217
243
  const v = raw.trim().toLowerCase();
218
244
  if ([
@@ -229,25 +255,40 @@ function parseBoolean(raw) {
229
255
  ].includes(v)) return ok(false);
230
256
  return err(`Expected a boolean (true/false), got "${raw}".`);
231
257
  }
232
- const PREFERENCES = [{
233
- key: "shareMachineDefault",
234
- type: "boolean",
235
- describe: "Share this machine over the portal on `skydive chat` launch, as if --share-machine were passed. Default false.",
236
- read: () => getShareMachineDefault(),
237
- isSet: () => store.has("shareMachineDefault"),
238
- parse: parseBoolean,
239
- write: (value) => saveShareMachineDefault(value),
240
- clear: () => store.delete("shareMachineDefault")
241
- }, {
242
- key: "updateCheck",
243
- type: "boolean",
244
- describe: "Run the daily background update check and its \"Update available\" notice. Set false to disable. Default true.",
245
- read: () => !getUpdateCheckDisabled(),
246
- isSet: () => store.has("updateCheck"),
247
- parse: parseBoolean,
248
- write: (value) => saveUpdateCheck(value),
249
- clear: () => store.delete("updateCheck")
250
- }];
258
+ const PREFERENCES = [
259
+ {
260
+ key: "shareMachineDefault",
261
+ type: "boolean",
262
+ describe: "Share this machine over the portal on `skydive chat` launch, as if --share-machine were passed. Default false.",
263
+ read: () => getShareMachineDefault(),
264
+ isSet: () => store.has("shareMachineDefault"),
265
+ set: (raw) => parseBoolean(raw).map(saveShareMachineDefault),
266
+ clear: () => store.delete("shareMachineDefault")
267
+ },
268
+ {
269
+ key: "updateCheck",
270
+ type: "boolean",
271
+ describe: "Run the daily background update check and its \"Update available\" notice. Set false to disable. Default true.",
272
+ read: () => !getUpdateCheckDisabled(),
273
+ isSet: () => store.has("updateCheck"),
274
+ set: (raw) => parseBoolean(raw).map(saveUpdateCheck),
275
+ clear: () => store.delete("updateCheck")
276
+ },
277
+ {
278
+ key: "defaultAgent",
279
+ type: "string",
280
+ describe: "Agent (id, slug, or name) a bare `skydive chat` opens a new conversation with, skipping the agent picker. Unset by default.",
281
+ read: () => getDefaultAgent(),
282
+ isSet: () => getDefaultAgent() !== null,
283
+ set: (raw) => {
284
+ const value = raw.trim();
285
+ if (!value) return err("Expected an agent id, slug, or name (use `config unset defaultAgent` to clear it).");
286
+ saveDefaultAgent(value);
287
+ return ok(void 0);
288
+ },
289
+ clear: () => store.delete("defaultAgent")
290
+ }
291
+ ];
251
292
  function getPreference(key) {
252
293
  return PREFERENCES.find((p) => p.key === key);
253
294
  }
@@ -282,14 +323,15 @@ function parseButton(element) {
282
323
  const params = isRecord(press.params) ? press.params : {};
283
324
  const primary = props.variant === "primary";
284
325
  if (press.action === "approve_portal_access") {
285
- const agentId = params.agentId;
286
- if (typeof agentId !== "string" || !agentId) return null;
326
+ const agentId = optionalString(params.agentId);
327
+ if (!agentId) return null;
287
328
  return {
288
329
  label,
289
330
  action: {
290
331
  kind: "grant_portal",
291
332
  agentId,
292
- deviceId: typeof params.deviceId === "string" && params.deviceId ? params.deviceId : null
333
+ deviceId: optionalString(params.deviceId),
334
+ conversationId: optionalString(params.conversationId)
293
335
  },
294
336
  primary
295
337
  };
@@ -323,12 +365,46 @@ function parseButton(element) {
323
365
  function specKeyFor(spec) {
324
366
  return stableStringify(spec) ?? crypto.randomUUID();
325
367
  }
368
+ /**
369
+ * The `platform portal request` consent card. The server emits one
370
+ * DesktopHandoffCard spec for every surface (api routes/portal.ts): the web
371
+ * renders it as the "continue in the Skydive desktop app" handoff, but this
372
+ * terminal's own portal daemon can provide the machine, so here it maps onto
373
+ * the existing grant_portal approval instead of a pointer to the app. Without
374
+ * this the spec is not a Card, parses to null, and the request is silently
375
+ * dropped — `platform portal request` looks desktop-only from the CLI
376
+ * (ANY-6521).
377
+ */
378
+ function parseDesktopHandoffCard(rootEl) {
379
+ const props = isRecord(rootEl.props) ? rootEl.props : {};
380
+ const agentId = optionalString(props.agentId);
381
+ if (!agentId) return null;
382
+ const agentName = optionalString(props.agentName) ?? "This agent";
383
+ return {
384
+ title: `Let ${agentName} use your computer?`,
385
+ subtitle: null,
386
+ description: `Approving shares this machine with ${agentName} while you're signed in. Revoke anytime: skydive portal revoke --agent "${agentName}"`,
387
+ fields: [],
388
+ button: {
389
+ label: "Approve",
390
+ action: {
391
+ kind: "grant_portal",
392
+ agentId,
393
+ deviceId: optionalString(props.deviceId),
394
+ conversationId: optionalString(props.conversationId)
395
+ }
396
+ },
397
+ state: {}
398
+ };
399
+ }
326
400
  function parseConnectCard(spec) {
327
401
  if (!isRecord(spec)) return null;
328
402
  const { root, elements } = spec;
329
403
  if (typeof root !== "string" || !isRecord(elements)) return null;
330
404
  const rootEl = elements[root];
331
- if (!isRecord(rootEl) || rootEl.type !== "Card") return null;
405
+ if (!isRecord(rootEl)) return null;
406
+ if (rootEl.type === "DesktopHandoffCard") return parseDesktopHandoffCard(rootEl);
407
+ if (rootEl.type !== "Card") return null;
332
408
  const rootProps = isRecord(rootEl.props) ? rootEl.props : {};
333
409
  const title = optionalString(rootProps.title);
334
410
  if (!title) return null;
@@ -572,7 +648,7 @@ async function runPrint({ appUrl, sessionToken, prompt, agentSelector, conversat
572
648
  onPage: null
573
649
  }), agentSelector);
574
650
  if (machineShare) {
575
- if (!machineShare.isGranted(agent.id) && grantTargetAgent) await machineShare.grantAgent(agent.id);
651
+ if (!machineShare.isGranted(agent.id) && grantTargetAgent) await machineShare.grantAgent(agent.id, null);
576
652
  if (machineShare.isGranted(agent.id)) console.error(`portal: shared this machine with ${agent.name} for this run (grant persists until revoked)`);
577
653
  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}\`.`);
578
654
  }
@@ -657,7 +733,7 @@ async function collectRunText({ client, appUrl, target, onText, messageIdForHint
657
733
  const onEvent = (event) => {
658
734
  if (event.kind === "finished") {
659
735
  if (event.outcome) billingBlocked = event.outcome;
660
- if (event.error) streamError = event.error;
736
+ if (event.error && streamError === null) streamError = event.error;
661
737
  return;
662
738
  }
663
739
  const chunk = event.chunk;
@@ -760,4 +836,4 @@ async function readStdin() {
760
836
  }
761
837
 
762
838
  //#endregion
763
- export { getStoredApiKeyWorkspaceName as A, saveTheme as B, getLastSeenVersion as C, getSavedTheme as D, getReviewStateDir as E, resolveManagementAuth as F, resolveSession as I, resolveWebUrl as L, resolveAppUrl as M, resolveChatAuth as N, getShareMachineDefault as O, resolveConfig as P, saveConfig as R, getConfigPath as S, getPromptHistoryPath as T, setLastSeenVersion as V, API_KEY_PREFIX as _, runPrint as a, PREFERENCES as b, cardActionErrorMessage as c, reconcileMaskedInput as d, resolveConnectUrl as f, API_KEY_FAMILY_PREFIX as g, API_KEYS_URL as h, resolveAgent as i, getUpdateCheckDisabled as j, getStoredApiKeyId as k, parseExternalOauthConnectParams as l, specKeyFor as m, messageGet as n, toPrintError as o, parseConnectCard as p, readStdin as r, MASK_CHAR as s, collectRunText as t, parseOauthConnectParams as u, DEFAULT_API_URL as v, getPreference as w, deleteConfig as x, DEFAULT_APP_URL as y, saveSession as z };
839
+ export { getStoredApiKeyId as A, saveConfig as B, getDefaultAgent as C, getReviewStateDir as D, getPromptHistoryPath as E, resolveChatAuth as F, saveTheme as H, resolveConfig as I, resolveManagementAuth as L, getUpdateCheckDisabled as M, recordDefaultAgent as N, getSavedTheme as O, resolveAppUrl as P, resolveSession as R, getConfigPath as S, getPreference as T, setLastSeenVersion as U, saveSession as V, API_KEY_PREFIX as _, runPrint as a, PREFERENCES as b, cardActionErrorMessage as c, reconcileMaskedInput as d, resolveConnectUrl as f, API_KEY_FAMILY_PREFIX as g, API_KEYS_URL as h, resolveAgent as i, getStoredApiKeyWorkspaceName as j, getShareMachineDefault as k, parseExternalOauthConnectParams as l, specKeyFor as m, messageGet as n, toPrintError as o, parseConnectCard as p, readStdin as r, MASK_CHAR as s, collectRunText as t, parseOauthConnectParams as u, DEFAULT_API_URL as v, getLastSeenVersion as w, deleteConfig as x, DEFAULT_APP_URL as y, resolveWebUrl as z };
@@ -14,7 +14,7 @@ import { n as printError } from "./output-DYzzdXYV.mjs";
14
14
  * the reply (and to --json).
15
15
  */
16
16
  async function connectMachineShare({ appUrl, sessionToken, timeoutHint }) {
17
- const { PortalClient } = await import("./client-CKBQ8pft.mjs");
17
+ const { PortalClient } = await import("./client-hH2PJL8y.mjs");
18
18
  let signalConnected;
19
19
  const connected = new Promise((resolve) => {
20
20
  signalConnected = resolve;
@@ -26,6 +26,8 @@ async function connectMachineShare({ appUrl, sessionToken, timeoutHint }) {
26
26
  deviceToken: null
27
27
  }),
28
28
  resolveCwd: () => process.cwd(),
29
+ persistedMachineName: null,
30
+ onMachineName: () => {},
29
31
  onState: (state) => {
30
32
  if (state.status === "connected") signalConnected();
31
33
  if (state.status === "error") console.error(`portal: connection error: ${state.error ?? "unknown"}; retrying`);
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { S as getConfigPath } from "./print-CbayCa87.mjs";
2
+ import { S as getConfigPath } from "./print-OvYyj6Uk.mjs";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { err, ok } from "neverthrow";
@@ -8,7 +8,7 @@ import fs from "node:fs";
8
8
 
9
9
  //#region package.json
10
10
  var name = "skydive-cli";
11
- var version$1 = "0.5.0-beta.2";
11
+ var version$1 = "0.5.0-beta.21";
12
12
 
13
13
  //#endregion
14
14
  //#region src/auth/organization.ts
@@ -125,6 +125,30 @@ async function ensureActiveOrganization({ appUrl, sessionToken }) {
125
125
  });
126
126
  }
127
127
 
128
+ //#endregion
129
+ //#region src/chat/import-seed.ts
130
+ /** Map `process.platform` to the OS family used by the seed prompts. Every
131
+ * non-`win32` platform Node runs on (darwin, linux, the BSDs) is POSIX. */
132
+ function machineOsFromPlatform(platform) {
133
+ return platform === "win32" ? "windows" : "posix";
134
+ }
135
+ /** One clause describing the machine's shell/paths so the agent's discovery
136
+ * sweep uses the right conventions instead of guessing. */
137
+ function osHint(os) {
138
+ return os === "windows" ? "This machine is Windows, so use PowerShell and Windows paths (%USERPROFILE%, backslashes)." : "This machine is POSIX (macOS/Linux), so use a POSIX shell and paths (~, forward slashes).";
139
+ }
140
+ /**
141
+ * The first message of an explicit `skydive import` conversation, sent as the
142
+ * user. Frames the migration and points the agent at its import-config skill.
143
+ */
144
+ function buildImportSeedPrompt(projectDir, os) {
145
+ return [
146
+ "I'm migrating from another coding agent. Import my setup from this machine.",
147
+ "",
148
+ `Use your import-config skill. I ran this from \`${projectDir}\`, so start there and in my home directory. ${osHint(os)} Don't assume one tool — do the discovery sweep so you catch whatever I actually use (Claude Code, Cursor, Codex, Gemini CLI, Copilot, Windsurf, Cline, OpenCode, Aider, and any nested AGENTS.md). Show me the plan first: everything you found, what you'll bring over, where it lands in you, and anything you're leaving out (credentials especially). Then wait for my OK before committing anything.`
149
+ ].join("\n");
150
+ }
151
+
128
152
  //#endregion
129
153
  //#region src/chat/tui/theme.ts
130
154
  const tokyonight = {
@@ -907,6 +931,17 @@ const WORDMARK = [
907
931
  "███████║██║ ██╗ ██║ ██████╔╝██║ ╚████╔╝ ███████╗",
908
932
  "╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚══════╝"
909
933
  ];
934
+ /** Columns between the pinwheel and the wordmark. */
935
+ const SPLASH_GAP = 3;
936
+ /** Columns the full mark + wordmark occupies (no chrome). */
937
+ const SPLASH_WIDTH = MARK_WIDTH + SPLASH_GAP + WORDMARK.reduce((max, line) => Math.max(max, line.length), 0);
938
+ /** Extra columns around the splash: `skydive --help` indents by 2, the TUI
939
+ * app pads 1 on each side. Same number, so one threshold covers both. */
940
+ const SPLASH_CHROME = 2;
941
+ /** Whether the full splash fits on one line of `columns` without wrapping. */
942
+ function splashFitsWidth(columns) {
943
+ return columns >= SPLASH_WIDTH + SPLASH_CHROME;
944
+ }
910
945
  const RESET = "\x1B[0m";
911
946
  function hexToRgb(hex) {
912
947
  const n = Number.parseInt(hex.slice(1), 16);
@@ -933,7 +968,7 @@ function markLinesAnsi() {
933
968
  function brandHelpArt(stream = process.stdout) {
934
969
  if (!stream.isTTY) return "";
935
970
  const truecolor = (typeof stream.getColorDepth === "function" ? stream.getColorDepth() : 1) >= 24;
936
- if ((stream.columns ?? 80) < 66) return truecolor ? `\n ${sgr("✦", BRAND_ACCENT)} Skydive\n` : "\n ✦ Skydive\n";
971
+ if (!splashFitsWidth(stream.columns ?? 80)) return truecolor ? `\n ${sgr("✦", BRAND_ACCENT)} Skydive\n` : "\n ✦ Skydive\n";
937
972
  if (!truecolor) return `\n${WORDMARK.map((l) => ` ${l}`).join("\n")}\n`;
938
973
  const mark = markLinesAnsi();
939
974
  const word = [...WORDMARK];
@@ -1264,4 +1299,4 @@ function maybeStartProfiling(argv, cliVersion) {
1264
1299
  }
1265
1300
 
1266
1301
  //#endregion
1267
- export { ensureActiveOrganization as C, setActiveWorkspace as D, listWorkspaces as E, name as O, themesForMode as S, getSessionIdentity as T, themeForMode as _, installCrashHandler as a, themeVersion as b, MARK_CELLS as c, DEFAULT_THEME_ID as d, applyTheme as f, theme as g, noColorRequested as h, writeArtifact as i, version$1 as k, WORDMARK as l, monoTheme as m, profilingEnabled as n, buildCrashReport as o, findTheme as p, record as r, writeCrashReport as s, maybeStartProfiling as t, brandHelpArt as u, themeMode as v, getActiveWorkspaceId as w, themes as x, themeModeFromColorFgBg as y };
1302
+ export { setActiveWorkspace as A, themesForMode as C, getActiveWorkspaceId as D, ensureActiveOrganization as E, version$1 as M, getSessionIdentity as O, themes as S, machineOsFromPlatform as T, theme as _, installCrashHandler as a, themeModeFromColorFgBg as b, MARK_CELLS as c, splashFitsWidth as d, DEFAULT_THEME_ID as f, noColorRequested as g, monoTheme as h, writeArtifact as i, name as j, listWorkspaces as k, WORDMARK as l, findTheme as m, profilingEnabled as n, buildCrashReport as o, applyTheme as p, record as r, writeCrashReport as s, maybeStartProfiling as t, brandHelpArt as u, themeForMode as v, buildImportSeedPrompt as w, themeVersion as x, themeMode as y };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as SandboxStream } from "./client-Cn2af31H.mjs";
2
+ import { t as SandboxStream } from "./client-c4c5MmgN.mjs";
3
3
 
4
4
  //#region src/chat/sandbox/raw-pty.ts
5
5
  /**
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import "./client-c4c5MmgN.mjs";
3
+ import { t as runRawPtyPassthrough } from "./raw-pty-DY4KelZW.mjs";
4
+
5
+ export { runRawPtyPassthrough };
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
3
- import { n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-BY2nADw5.mjs";
3
+ import { n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-Dx6b-Nq_.mjs";
4
4
  import "./billing-blocked-2wju4gC_.mjs";
5
5
 
6
6
  export { createRestClient };
@@ -193,13 +193,14 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
193
193
  }
194
194
  };
195
195
  return {
196
- listAgents: async ({ scope, onPage }) => {
196
+ listAgents: async ({ scope, onPage, limit }) => {
197
197
  const all = [];
198
198
  let cursor;
199
- const maxAgents = 2e3;
199
+ const maxAgents = limit ?? 2e3;
200
+ const pageSize = Math.min(100, maxAgents);
200
201
  do {
201
202
  const params = new URLSearchParams({
202
- limit: "100",
203
+ limit: String(pageSize),
203
204
  scope,
204
205
  sort: "mine_first_usage",
205
206
  includeStats: "false"
@@ -213,16 +214,23 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
213
214
  return all;
214
215
  },
215
216
  createAgent: async ({ name }) => {
216
- const { agent } = await post("/api/v1/agents", { name }, createAgentResponseSchema);
217
+ const { agent } = await post("/api/v1/agents", name == null ? {} : { name }, createAgentResponseSchema);
217
218
  return agent;
218
219
  },
219
- getAgent: async ({ agentId }) => {
220
- const { agent } = await get(`/api/v1/agents/${encodeURIComponent(agentId)}`, getAgentResponseSchema);
220
+ createOnboardingConversation: async ({ agentId, projectDir, os }) => {
221
+ const result = await post(`/api/v1/agents/${encodeURIComponent(agentId)}/onboarding-conversation`, {
222
+ projectDir,
223
+ os
224
+ }, onboardingConversationResponseSchema);
221
225
  return {
222
- id: agent.id,
223
- name: agent.name
226
+ conversationId: result.conversationId,
227
+ runId: result.runId
224
228
  };
225
229
  },
230
+ getAgent: async ({ agentId }) => {
231
+ const { agent } = await get(`/api/v1/agents/${encodeURIComponent(agentId)}`, getAgentResponseSchema);
232
+ return agent;
233
+ },
226
234
  suggestAgentIdentity: async () => {
227
235
  const { suggestion } = await get("/api/v1/agents/suggest", suggestAgentResponseSchema);
228
236
  return suggestion;
@@ -331,6 +339,9 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
331
339
  setConversationArchived: async ({ conversationId, archived }) => {
332
340
  await post(`/api/v1/conversations/${encodeURIComponent(conversationId)}/archive`, { archived }, z.object({ archived: z.boolean() }));
333
341
  },
342
+ markConversationRead: async ({ conversationId }) => {
343
+ await post(`/api/v1/conversations/${encodeURIComponent(conversationId)}/read`, {}, z.object({ read: z.boolean() }));
344
+ },
334
345
  renameConversation: async ({ conversationId, title }) => {
335
346
  await post(`/api/v1/conversations/${encodeURIComponent(conversationId)}`, { title }, z.object({ conversation: z.object({ id: z.string() }) }), "PATCH");
336
347
  },
@@ -453,10 +464,11 @@ const listAgentsResponseSchema = z.object({
453
464
  totalCount: z.number().nullable().optional()
454
465
  });
455
466
  const createAgentResponseSchema = z.object({ agent: agentSummarySchema });
456
- const getAgentResponseSchema = z.object({ agent: z.object({
457
- id: z.string(),
458
- name: z.string()
459
- }).passthrough() });
467
+ const onboardingConversationResponseSchema = z.object({
468
+ conversationId: z.string(),
469
+ runId: z.string().nullable()
470
+ });
471
+ const getAgentResponseSchema = z.object({ agent: agentSummarySchema });
460
472
  const agentSuggestionSchema = z.object({ name: z.string() });
461
473
  const suggestAgentResponseSchema = z.object({ suggestion: agentSuggestionSchema.nullable() });
462
474
  const conversationAgentSchema = z.object({
@@ -474,6 +486,7 @@ const conversationSummarySchema = z.object({
474
486
  channel: z.string().nullable(),
475
487
  channelLabel: z.string().nullable(),
476
488
  viewerArchivedAt: z.string().nullable().optional(),
489
+ unread: z.boolean().optional(),
477
490
  agent: conversationAgentSchema,
478
491
  agents: z.array(conversationAgentSchema).optional()
479
492
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skydive-cli",
3
- "version": "0.5.0-beta.2",
3
+ "version": "0.5.0-beta.21",
4
4
  "description": "Skydive CLI — cloud agents from the command line",
5
5
  "homepage": "https://skydive.com",
6
6
  "license": "MIT",
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env node
2
- import { t as PortalClient } from "./client-Dd5sMXPv.mjs";
3
-
4
- export { PortalClient };
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env node
2
- import "./client-Dd5sMXPv.mjs";
3
- import "./daemon-Bq93vOIk.mjs";
4
- import { t as PortalDaemonClient } from "./daemon-client-Bewt98dE.mjs";
5
-
6
- export { PortalDaemonClient };
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env node
2
- import "./client-Cn2af31H.mjs";
3
- import { t as runRawPtyPassthrough } from "./raw-pty-B6mAroiI.mjs";
4
-
5
- export { runRawPtyPassthrough };