skydive-cli 0.6.0-beta.25 → 0.6.0-beta.251
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/CHANGELOG.md +11 -0
- package/dist/js/bin.mjs +874 -93
- package/dist/js/{boot-KM9tlCQv.mjs → boot-CubSTWDm.mjs} +14017 -12512
- package/dist/js/client-CORM9fve.mjs +5 -0
- package/dist/js/{client-pKI_QTmT.mjs → client-CdTp8y6C.mjs} +67 -8
- package/dist/js/{client-l6orYfqq.mjs → client-Cxk3khQu.mjs} +18 -4
- package/dist/js/{daemon-C8z0gjiS.mjs → daemon-BfYW-fdR.mjs} +3 -3
- package/dist/js/{daemon--k2fcWhI.mjs → daemon-HTYWqbIg.mjs} +118 -28
- package/dist/js/{daemon-client-BxNqcNzX.mjs → daemon-client-DfFGTVRZ.mjs} +29 -4
- package/dist/js/daemon-client-RX2L4IDx.mjs +8 -0
- package/dist/js/{dist-CRtjM7ba.mjs → dist-C-qLSjvr.mjs} +28 -15
- package/dist/js/{forward-C-f04uyE.mjs → forward-DxbobE7J.mjs} +1 -1
- package/dist/js/{install-cm5SdN8E.mjs → install-voEEy3Xf.mjs} +82 -10
- package/dist/js/{localhost-cert-Bn-UBUmj.mjs → localhost-cert-Dq2MRdXk.mjs} +1 -1
- package/dist/js/{print-BjWK1R7z.mjs → print-DKvuaK5F.mjs} +13 -12
- package/dist/js/{print-DxckoQq_.mjs → print-U2e-h3oT.mjs} +2 -2
- package/dist/js/{print-share-C9wWXtly.mjs → print-share-sCPoMUsk.mjs} +2 -2
- package/dist/js/{raw-pty-dndn1Kxc.mjs → raw-pty-B-WKaaE3.mjs} +1 -1
- package/dist/js/raw-pty-BJIs_xjQ.mjs +5 -0
- package/dist/js/{rest-D_Bz9P6h.mjs → rest-BYOeHxEw.mjs} +1 -1
- package/dist/js/{rest-DyrLMPnC.mjs → rest-DLAvazAr.mjs} +129 -15
- package/dist/js/{tls-cert-Rua2oV7n.mjs → tls-cert-8j8y2A31.mjs} +1 -1
- package/dist/js/tls-cert-BQDesyjU.mjs +4 -0
- package/package.json +11 -9
- package/dist/js/client-LsfcGY1X.mjs +0 -5
- package/dist/js/daemon-client-hu2vyzDR.mjs +0 -8
- package/dist/js/raw-pty-DRZuf1oW.mjs +0 -5
- package/dist/js/tls-cert-BpCaD5AT.mjs +0 -4
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { c as version$1 } from "./rest-
|
|
3
|
-
import {
|
|
2
|
+
import { c as version$1 } from "./rest-DLAvazAr.mjs";
|
|
3
|
+
import { D as getConfigPath } from "./print-DKvuaK5F.mjs";
|
|
4
4
|
import os from "node:os";
|
|
5
|
-
import path from "node:path";
|
|
5
|
+
import path, { delimiter, join } from "node:path";
|
|
6
6
|
import { err, ok } from "neverthrow";
|
|
7
7
|
import { z } from "zod";
|
|
8
|
-
import
|
|
8
|
+
import open from "open";
|
|
9
|
+
import fs, { accessSync, constants } from "node:fs";
|
|
9
10
|
|
|
10
11
|
//#region src/auth/organization.ts
|
|
11
12
|
const workspaceSchema = z.object({
|
|
@@ -147,6 +148,29 @@ async function ensureActiveOrganization({ appUrl, sessionToken }) {
|
|
|
147
148
|
});
|
|
148
149
|
}
|
|
149
150
|
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/open-url.ts
|
|
153
|
+
function isOnPath(command) {
|
|
154
|
+
return (process.env.PATH ?? "").split(delimiter).filter(Boolean).some((dir) => {
|
|
155
|
+
try {
|
|
156
|
+
accessSync(join(dir, command), constants.X_OK);
|
|
157
|
+
return true;
|
|
158
|
+
} catch {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Best-effort open of a URL or file in the OS default app. On Linux, skips
|
|
165
|
+
* silently when xdg-open is missing: `open` spawns it detached with no error
|
|
166
|
+
* handler, and under Bun the resulting ENOENT error event fires before the
|
|
167
|
+
* caller can attach a listener, killing the process.
|
|
168
|
+
*/
|
|
169
|
+
async function openBestEffort({ target }) {
|
|
170
|
+
if (process.platform === "linux" && !isOnPath("xdg-open")) return;
|
|
171
|
+
await open(target).catch(() => void 0);
|
|
172
|
+
}
|
|
173
|
+
|
|
150
174
|
//#endregion
|
|
151
175
|
//#region src/shell/shell-env.ts
|
|
152
176
|
/**
|
|
@@ -336,7 +360,7 @@ function aliasActivationHint(result) {
|
|
|
336
360
|
}
|
|
337
361
|
|
|
338
362
|
//#endregion
|
|
339
|
-
//#region src/
|
|
363
|
+
//#region ../agents/src/import-seed.ts
|
|
340
364
|
/** Map `process.platform` to the OS family used by the seed prompts. Every
|
|
341
365
|
* non-`win32` platform Node runs on (darwin, linux, the BSDs) is POSIX. */
|
|
342
366
|
function machineOsFromPlatform(platform) {
|
|
@@ -348,14 +372,16 @@ function osHint(os) {
|
|
|
348
372
|
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).";
|
|
349
373
|
}
|
|
350
374
|
/**
|
|
351
|
-
* The first message of an
|
|
352
|
-
*
|
|
375
|
+
* The first message of an import conversation, sent as the user. Frames the
|
|
376
|
+
* migration and points the agent at its import-config skill. The CLI anchors
|
|
377
|
+
* on the cwd it was run from; the desktop has no cwd, so it passes `null` and
|
|
378
|
+
* the agent starts in the user's home directory.
|
|
353
379
|
*/
|
|
354
|
-
function buildImportSeedPrompt(projectDir, os) {
|
|
380
|
+
function buildImportSeedPrompt({ projectDir, os }) {
|
|
355
381
|
return [
|
|
356
382
|
"I'm migrating from another coding agent. Import my setup from this machine.",
|
|
357
383
|
"",
|
|
358
|
-
`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).
|
|
384
|
+
`Use your import-config skill. ${projectDir ? `I ran this from \`${projectDir}\`, so start there and in my home directory.` : "Start 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). Then show me a short plan: what you'll bring over and what you're leaving out (credentials especially). Keep it skimmable; I'll ask if I want the full list. Wait for my OK before committing anything.`
|
|
359
385
|
].join("\n");
|
|
360
386
|
}
|
|
361
387
|
|
|
@@ -1547,6 +1573,51 @@ function formatMb(bytes) {
|
|
|
1547
1573
|
return `${(bytes / MB).toFixed(1)} MB`;
|
|
1548
1574
|
}
|
|
1549
1575
|
|
|
1576
|
+
//#endregion
|
|
1577
|
+
//#region src/chat/tui/autowrap.ts
|
|
1578
|
+
/**
|
|
1579
|
+
* Why the TUI turns the terminal's autowrap off for its lifetime.
|
|
1580
|
+
*
|
|
1581
|
+
* OpenTUI's incremental cell-diff emitter positions every run with an
|
|
1582
|
+
* absolute cursor move — it never relies on the terminal to advance the
|
|
1583
|
+
* cursor — but it leaves the terminal's autowrap (DECAWM) enabled. When a
|
|
1584
|
+
* run reaches the last column, the host terminal wraps its cursor to column
|
|
1585
|
+
* 1 of the next row and paints the run's tail there, into cells owned by a
|
|
1586
|
+
* different element. The diff buffer then records the *intended* cells, so
|
|
1587
|
+
* those stray bytes are never repainted and unrelated text smears together
|
|
1588
|
+
* until a full repaint (a resize). Filed upstream with a repro and root
|
|
1589
|
+
* cause: https://github.com/anomalyco/opentui/issues/1187 — unfixed there,
|
|
1590
|
+
* the consumer-side fix (verified in numux#115) is to disable DECAWM while
|
|
1591
|
+
* the renderer runs. Pane content still wraps, inside OpenTUI's own grid.
|
|
1592
|
+
*/
|
|
1593
|
+
const AUTOWRAP_OFF = "\x1B[?7l";
|
|
1594
|
+
const AUTOWRAP_ON = "\x1B[?7h";
|
|
1595
|
+
/** Escape hatch: if any surface turns out to depend on host autowrap. */
|
|
1596
|
+
const keepAutowrap = () => Boolean(process.env["SKYDIVE_TUI_AUTOWRAP"]);
|
|
1597
|
+
let disabledBy = false;
|
|
1598
|
+
/**
|
|
1599
|
+
* Disable host autowrap so the diff emitter's runs can never wrap onto the
|
|
1600
|
+
* next row. No-op when stdout is not a terminal (piped output, tests) and
|
|
1601
|
+
* under `SKYDIVE_TUI_AUTOWRAP`.
|
|
1602
|
+
*/
|
|
1603
|
+
function disableTerminalAutowrap(stdout = process.stdout) {
|
|
1604
|
+
if (!stdout.isTTY || keepAutowrap() || disabledBy) return;
|
|
1605
|
+
disabledBy = true;
|
|
1606
|
+
stdout.write(AUTOWRAP_OFF);
|
|
1607
|
+
}
|
|
1608
|
+
/**
|
|
1609
|
+
* Restore host autowrap. The terminal mode is a property of the host, so
|
|
1610
|
+
* every teardown path must run this even when the renderer cannot finish
|
|
1611
|
+
* its own teardown: the renderer's `onDestroy` hook covers renderer-driven
|
|
1612
|
+
* exits, and the forced-fallback reset in `hard-exit.ts` carries the
|
|
1613
|
+
* restore for a teardown that never got there. Idempotent.
|
|
1614
|
+
*/
|
|
1615
|
+
function restoreTerminalAutowrap(stdout = process.stdout) {
|
|
1616
|
+
if (!disabledBy) return;
|
|
1617
|
+
disabledBy = false;
|
|
1618
|
+
stdout.write(AUTOWRAP_ON);
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1550
1621
|
//#endregion
|
|
1551
1622
|
//#region src/crash/install.ts
|
|
1552
1623
|
let installed = false;
|
|
@@ -1566,6 +1637,7 @@ function installCrashHandler(opts = {}) {
|
|
|
1566
1637
|
if (installed) return;
|
|
1567
1638
|
installed = true;
|
|
1568
1639
|
process.on("uncaughtException", (error) => {
|
|
1640
|
+
restoreTerminalAutowrap();
|
|
1569
1641
|
const memory = safeMemory();
|
|
1570
1642
|
printFatalNotice(writeCrashReport(buildCrashReport({
|
|
1571
1643
|
reason: "uncaughtException",
|
|
@@ -1594,4 +1666,4 @@ function printFatalNotice(file, memory) {
|
|
|
1594
1666
|
}
|
|
1595
1667
|
|
|
1596
1668
|
//#endregion
|
|
1597
|
-
export {
|
|
1669
|
+
export { aliasActivationHint as A, getActiveWorkspaceId as B, themeMode as C, themesForMode as D, themes as E, SUPPORTED_SHELLS as F, listWorkspaces as H, defaultInstallEnv as I, detectShell as L, installAgentAlias as M, slugifyAliasName as N, buildImportSeedPrompt as O, takenAliasNames as P, openBestEffort as R, themeForMode as S, themeVersion as T, resolveWorkspaceId as U, getSessionIdentity as V, setActiveWorkspace as W, applyTheme as _, writeCrashReport as a, noColorRequested as b, brandHelpArt as c, maybeStartProfiling as d, profileDir as f, DEFAULT_THEME_ID as g, writeArtifact as h, buildCrashReport as i, dedupeAliasName as j, machineOsFromPlatform as k, splashFitsWidth as l, record as m, disableTerminalAutowrap as n, MARK_CELLS as o, profilingEnabled as p, restoreTerminalAutowrap as r, WORDMARK as s, installCrashHandler as t, getAutomaticLogsDir as u, findTheme as v, themeModeFromColorFgBg as w, theme as x, monoTheme as y, ensureActiveOrganization as z };
|
|
@@ -47,7 +47,7 @@ function localhostCertSource(log) {
|
|
|
47
47
|
return async () => {
|
|
48
48
|
if (await hasDeclined()) return null;
|
|
49
49
|
try {
|
|
50
|
-
const { certificateFor } = await import("./dist-
|
|
50
|
+
const { certificateFor } = await import("./dist-C-qLSjvr.mjs").then((m) => /* @__PURE__ */ __toESM(m.default, 1));
|
|
51
51
|
const { key, cert } = await certificateFor("localhost", { skipHostsFile: true });
|
|
52
52
|
return {
|
|
53
53
|
key: key.toString(),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as errorMessage, i as sendErrorMessage, o as isRecord, t as createRestClient } from "./rest-
|
|
2
|
+
import { a as errorMessage, i as sendErrorMessage, o as isRecord, t as createRestClient } from "./rest-DLAvazAr.mjs";
|
|
3
3
|
import { t as HttpError } from "./http-error-BF2NZZE3.mjs";
|
|
4
4
|
import { i as billingBlockedOutcomeFromSendResponse, n as BillingBlockedError } from "./billing-blocked-D3l5kJlX.mjs";
|
|
5
5
|
import path from "node:path";
|
|
@@ -512,18 +512,18 @@ function readQuestionEntries(raw) {
|
|
|
512
512
|
}];
|
|
513
513
|
});
|
|
514
514
|
}
|
|
515
|
-
/**
|
|
516
|
-
* The `platform ask` form. The web renders it as an interactive picker; here
|
|
517
|
-
* it maps onto an answer_question button so ctrl+r (and letter shortcuts)
|
|
518
|
-
* open an in-TUI picker. The composed answer is sent as a normal user
|
|
519
|
-
* message — same non-blocking model as the web card. Without this the spec
|
|
520
|
-
* is not a Card, parses to null, and the question is silently dropped.
|
|
521
|
-
*/
|
|
522
515
|
/** Compose the user-message body the web card also sends. */
|
|
523
516
|
function composeQuestionAnswer(questions, answers) {
|
|
524
517
|
if (questions.length === 1) return (answers[0] ?? []).join(", ");
|
|
525
518
|
return questions.map((q, i) => `${q.question} → ${(answers[i] ?? []).join(", ")}`).join("\n");
|
|
526
519
|
}
|
|
520
|
+
/** Build the typed durable payload that settles this exact question card. */
|
|
521
|
+
function composeQuestionAnswerEntries(questions, answers) {
|
|
522
|
+
return questions.map((question, index) => ({
|
|
523
|
+
question: question.question,
|
|
524
|
+
answer: (answers[index] ?? []).join(", ")
|
|
525
|
+
}));
|
|
526
|
+
}
|
|
527
527
|
function parseQuestionCard(rootEl) {
|
|
528
528
|
const props = isRecord(rootEl.props) ? rootEl.props : {};
|
|
529
529
|
const cardId = optionalString(props.cardId);
|
|
@@ -708,8 +708,8 @@ function isBlockingCardAction(action) {
|
|
|
708
708
|
* should target, prioritizing blocking cards over `platform ask` forms. When
|
|
709
709
|
* both a blocking card and a stale `answer_question` are waiting, ctrl+r must
|
|
710
710
|
* approve the thing the agent is stalled on, not re-answer an earlier ask that
|
|
711
|
-
*
|
|
712
|
-
*
|
|
711
|
+
* can remain actionable when ignored or answered as plain composer text.
|
|
712
|
+
* Falls through to the
|
|
713
713
|
* ask forms only when they are the only actionable cards left. Preserves input
|
|
714
714
|
* order within a tier, so a single card and genuine multi-card cases behave
|
|
715
715
|
* exactly as before.
|
|
@@ -823,7 +823,7 @@ function summarizeConnectCard(card, appUrl) {
|
|
|
823
823
|
}
|
|
824
824
|
/**
|
|
825
825
|
* Parse a `data-anyone-render-spec` stream chunk into a connect-card summary,
|
|
826
|
-
* or null if the chunk isn't a connect card (other render specs —
|
|
826
|
+
* or null if the chunk isn't a connect card (other render specs — e.g.
|
|
827
827
|
* deep-learn — parse to null, same as the TUI). A card that arrives already
|
|
828
828
|
* settled (a compute request's decided re-emission) is an FYI, not an action,
|
|
829
829
|
* so it stays out of the "[action needed]" stream too.
|
|
@@ -900,6 +900,7 @@ async function runPrint({ appUrl, sessionToken, workspaceId, prompt, agentSelect
|
|
|
900
900
|
conversationId,
|
|
901
901
|
content: prompt,
|
|
902
902
|
attachmentIds: [],
|
|
903
|
+
questionAnswer: null,
|
|
903
904
|
clientSurface: "cli"
|
|
904
905
|
});
|
|
905
906
|
} catch (err) {
|
|
@@ -1077,4 +1078,4 @@ async function readStdin() {
|
|
|
1077
1078
|
}
|
|
1078
1079
|
|
|
1079
1080
|
//#endregion
|
|
1080
|
-
export {
|
|
1081
|
+
export { getPreference as A, resolveChatAuth as B, DEFAULT_API_URL as C, getConfigPath as D, deleteConfig as E, getStoredApiKeyId as F, saveConfig as G, resolveManagementAuth as H, getStoredApiKeyWorkspaceName as I, setLastSeenVersion as J, saveSession as K, getUpdateCheckDisabled as L, getReviewStateDir as M, getSavedTheme as N, getDefaultAgent as O, getShareMachineDefault as P, recordDefaultAgent as R, API_KEY_PREFIX as S, PREFERENCES as T, resolveSession as U, resolveConfig as V, resolveWebUrl as W, computeSettledLabel as _, runPrint as a, API_KEYS_URL as b, actionableCardVerb as c, parseOauthConnectParams as d, prioritizeActionableCards as f, composeQuestionAnswerEntries as g, composeQuestionAnswer as h, resolveAgent as i, getPromptHistoryPath as j, getLastSeenVersion as k, cardActionErrorMessage as l, resolveConnectUrl as m, messageGet as n, toPrintError as o, reconcileMaskedInput as p, saveTheme as q, readStdin as r, MASK_CHAR as s, collectRunText as t, parseExternalOauthConnectParams as u, parseConnectCard as v, DEFAULT_APP_URL as w, API_KEY_FAMILY_PREFIX as x, specKeyFor as y, resolveAppUrl as z };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./rest-
|
|
3
|
-
import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-
|
|
2
|
+
import "./rest-DLAvazAr.mjs";
|
|
3
|
+
import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-DKvuaK5F.mjs";
|
|
4
4
|
import "./billing-blocked-D3l5kJlX.mjs";
|
|
5
5
|
|
|
6
6
|
export { messageGet, readStdin, resolveAgent, runPrint };
|
|
@@ -14,8 +14,8 @@ import { n as printError } from "./output-C9mb3sUB.mjs";
|
|
|
14
14
|
* the reply (and to --json).
|
|
15
15
|
*/
|
|
16
16
|
async function connectMachineShare({ appUrl, sessionToken, timeoutHint }) {
|
|
17
|
-
const { PortalClient } = await import("./client-
|
|
18
|
-
const { defaultTlsCertSource } = await import("./tls-cert-
|
|
17
|
+
const { PortalClient } = await import("./client-CORM9fve.mjs");
|
|
18
|
+
const { defaultTlsCertSource } = await import("./tls-cert-BQDesyjU.mjs");
|
|
19
19
|
let signalConnected;
|
|
20
20
|
const connected = new Promise((resolve) => {
|
|
21
21
|
signalConnected = resolve;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { i as sendErrorMessage, n as errorDetail, r as filenameFromContentDisposition, t as createRestClient } from "./rest-
|
|
2
|
+
import { i as sendErrorMessage, n as errorDetail, r as filenameFromContentDisposition, t as createRestClient } from "./rest-DLAvazAr.mjs";
|
|
3
3
|
import { t as HttpError } from "./http-error-BF2NZZE3.mjs";
|
|
4
4
|
import "./billing-blocked-D3l5kJlX.mjs";
|
|
5
5
|
|
|
@@ -6,7 +6,7 @@ import { createParser } from "eventsource-parser";
|
|
|
6
6
|
|
|
7
7
|
//#region package.json
|
|
8
8
|
var name = "skydive-cli";
|
|
9
|
-
var version = "0.6.0-beta.
|
|
9
|
+
var version = "0.6.0-beta.251";
|
|
10
10
|
|
|
11
11
|
//#endregion
|
|
12
12
|
//#region src/chat/util.ts
|
|
@@ -122,6 +122,49 @@ function sendErrorMessage(err) {
|
|
|
122
122
|
return fromServer ?? `Send failed (HTTP ${err.status}).`;
|
|
123
123
|
}
|
|
124
124
|
const MAX_STREAM_RECONNECTS = 5;
|
|
125
|
+
const GATEWAY_RETRY_STATUSES = new Set([502, 503]);
|
|
126
|
+
const MAX_GATEWAY_RETRIES = 3;
|
|
127
|
+
const MAX_GATEWAY_BACKOFF_MS = 4e3;
|
|
128
|
+
function retryAfterMs(res) {
|
|
129
|
+
const header = res.headers.get("retry-after");
|
|
130
|
+
if (!header) return null;
|
|
131
|
+
const seconds = Number(header);
|
|
132
|
+
if (Number.isFinite(seconds) && seconds >= 0) return Math.ceil(seconds) * 1e3;
|
|
133
|
+
const dateMs = Date.parse(header);
|
|
134
|
+
if (Number.isNaN(dateMs)) return null;
|
|
135
|
+
return Math.max(0, dateMs - Date.now());
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* First path segment after /api/v1/ — a route resource name ('conversations'),
|
|
139
|
+
* never a user id or user content.
|
|
140
|
+
*/
|
|
141
|
+
function gatewayPathGroup(url) {
|
|
142
|
+
return url.match(/\/api\/v1\/([^/?]+)/)?.[1] ?? "other";
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Fetch wrapper for the JSON endpoints: idempotent requests (GET/DELETE)
|
|
146
|
+
* retry on 502/503 with short backoff, honoring Retry-After. Non-idempotent
|
|
147
|
+
* methods hard-fail on the first gateway error — a retried POST can
|
|
148
|
+
* double-apply a side effect the upstream already committed before the
|
|
149
|
+
* proxy saw its response.
|
|
150
|
+
*/
|
|
151
|
+
async function fetchWithGatewayRetry(url, init) {
|
|
152
|
+
const idempotent = init.method === void 0 || init.method === "GET" || init.method === "DELETE" || init.method === "HEAD";
|
|
153
|
+
for (let attempt = 0;; attempt++) {
|
|
154
|
+
const res = await fetch(url, init);
|
|
155
|
+
if (res.ok || !idempotent || attempt >= MAX_GATEWAY_RETRIES || !GATEWAY_RETRY_STATUSES.has(res.status)) return res;
|
|
156
|
+
const backoff = Math.min(250 * 2 ** attempt, MAX_GATEWAY_BACKOFF_MS);
|
|
157
|
+
const waitMs = retryAfterMs(res) ?? backoff;
|
|
158
|
+
init.onGatewayRetry?.({
|
|
159
|
+
status: res.status,
|
|
160
|
+
attempt: attempt + 1,
|
|
161
|
+
retryAfterMs: waitMs,
|
|
162
|
+
pathGroup: gatewayPathGroup(url)
|
|
163
|
+
});
|
|
164
|
+
await res.text().catch(() => "");
|
|
165
|
+
await sleep(waitMs);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
125
168
|
function createRestClient({ appUrl, sessionToken, workspaceId }) {
|
|
126
169
|
const baseHeaders = {
|
|
127
170
|
authorization: `Bearer ${sessionToken}`,
|
|
@@ -130,7 +173,13 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
|
|
|
130
173
|
};
|
|
131
174
|
if (workspaceId) baseHeaders["x-workspace-id"] = workspaceId;
|
|
132
175
|
async function get(path, schema) {
|
|
133
|
-
const res = await
|
|
176
|
+
const res = await fetchWithGatewayRetry(`${appUrl}${path}`, {
|
|
177
|
+
headers: baseHeaders,
|
|
178
|
+
onGatewayRetry: (info) => trackGatewayRetry({
|
|
179
|
+
...info,
|
|
180
|
+
method: "GET"
|
|
181
|
+
})
|
|
182
|
+
});
|
|
134
183
|
if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
|
|
135
184
|
return schema.parse(await res.json());
|
|
136
185
|
}
|
|
@@ -146,6 +195,12 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
|
|
|
146
195
|
if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
|
|
147
196
|
return schema.parse(await res.json());
|
|
148
197
|
}
|
|
198
|
+
const trackGatewayRetry = (properties) => {
|
|
199
|
+
post("/api/v1/cli-analytics/track", {
|
|
200
|
+
event: "CLI Rest Gateway Retry",
|
|
201
|
+
properties
|
|
202
|
+
}, z.object({ ok: z.boolean() })).catch(() => {});
|
|
203
|
+
};
|
|
149
204
|
/**
|
|
150
205
|
* The agent PATCH is a merge: only the keys present are written, so each
|
|
151
206
|
* caller sends exactly the settings it means to change.
|
|
@@ -154,9 +209,13 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
|
|
|
154
209
|
return post(`/api/v1/agents/${encodeURIComponent(agentId)}`, body, updateAgentResponseSchema, "PATCH");
|
|
155
210
|
}
|
|
156
211
|
async function del(path) {
|
|
157
|
-
const res = await
|
|
212
|
+
const res = await fetchWithGatewayRetry(`${appUrl}${path}`, {
|
|
158
213
|
method: "DELETE",
|
|
159
|
-
headers: baseHeaders
|
|
214
|
+
headers: baseHeaders,
|
|
215
|
+
onGatewayRetry: (info) => trackGatewayRetry({
|
|
216
|
+
...info,
|
|
217
|
+
method: "DELETE"
|
|
218
|
+
})
|
|
160
219
|
});
|
|
161
220
|
if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
|
|
162
221
|
}
|
|
@@ -270,8 +329,11 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
|
|
|
270
329
|
return conversation;
|
|
271
330
|
},
|
|
272
331
|
listModels: async () => {
|
|
273
|
-
const { models } = await get("/api/v1/models", listModelsResponseSchema);
|
|
274
|
-
return
|
|
332
|
+
const { models, trainingOptedOut } = await get("/api/v1/models", listModelsResponseSchema);
|
|
333
|
+
return {
|
|
334
|
+
models,
|
|
335
|
+
trainingOptedOut: trainingOptedOut ?? false
|
|
336
|
+
};
|
|
275
337
|
},
|
|
276
338
|
listModelEfforts: async () => {
|
|
277
339
|
const { modelConfigOverrides } = await get("/api/v1/models/preferred", modelEffortPreferencesResponseSchema);
|
|
@@ -303,11 +365,17 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
|
|
|
303
365
|
thinkingLevel: agent.thinkingLevel ?? null
|
|
304
366
|
};
|
|
305
367
|
},
|
|
306
|
-
setConversationModel: async ({ conversationId, agentId, model, thinkingLevel }) =>
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
368
|
+
setConversationModel: async ({ conversationId, agentId, model, thinkingLevel }) => {
|
|
369
|
+
const settled = await post(`/api/v1/conversations/${conversationId}/model`, {
|
|
370
|
+
agentId,
|
|
371
|
+
model,
|
|
372
|
+
thinkingLevel
|
|
373
|
+
}, conversationModelResponseSchema);
|
|
374
|
+
return {
|
|
375
|
+
...settled,
|
|
376
|
+
scope: settled.scope ?? null
|
|
377
|
+
};
|
|
378
|
+
},
|
|
311
379
|
updateAgentEffort: async ({ agentId, thinkingLevel }) => {
|
|
312
380
|
const { agent } = await patchAgent(agentId, { thinkingLevel });
|
|
313
381
|
return { thinkingLevel: agent.thinkingLevel ?? null };
|
|
@@ -368,6 +436,10 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
|
|
|
368
436
|
const { recap } = await get(`/api/v1/conversations/${encodeURIComponent(conversationId)}/recap`, recapResponseSchema);
|
|
369
437
|
return recap?.text ?? null;
|
|
370
438
|
},
|
|
439
|
+
getConversationUsage: async ({ conversationId }) => {
|
|
440
|
+
const { usage } = await get(`/api/v1/conversations/${encodeURIComponent(conversationId)}/usage`, conversationUsageResponseSchema);
|
|
441
|
+
return usage;
|
|
442
|
+
},
|
|
371
443
|
listSubagentTasks: async ({ conversationId }) => {
|
|
372
444
|
const { result } = await get(`/api/v1/trpc/v2.subagentTasks.list?input=${encodeURIComponent(JSON.stringify({ conversationId }))}`, subagentTasksResponseSchema);
|
|
373
445
|
return new Map(result.data.subagentTasks.map((task) => [task.id, task]));
|
|
@@ -430,9 +502,13 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
|
|
|
430
502
|
setConversationArchived: async ({ conversationId, archived }) => {
|
|
431
503
|
await post(`/api/v1/conversations/${encodeURIComponent(conversationId)}/archive`, { archived }, z.object({ archived: z.boolean() }));
|
|
432
504
|
},
|
|
505
|
+
setConversationPinned: async ({ conversationId, pinned }) => {
|
|
506
|
+
await post(`/api/v1/conversations/${encodeURIComponent(conversationId)}/pin`, { pinned }, z.object({ pinned: z.boolean() }));
|
|
507
|
+
},
|
|
433
508
|
markConversationRead: async ({ conversationId }) => {
|
|
434
509
|
await post(`/api/v1/conversations/${encodeURIComponent(conversationId)}/read`, {}, z.object({ read: z.boolean() }));
|
|
435
510
|
},
|
|
511
|
+
trackAnalyticsEvent: async (input) => post("/api/v1/cli-analytics/track", input, z.object({ ok: z.boolean() })),
|
|
436
512
|
renameConversation: async ({ conversationId, title }) => {
|
|
437
513
|
await post(`/api/v1/conversations/${encodeURIComponent(conversationId)}`, { title }, z.object({ conversation: z.object({ id: z.string() }) }), "PATCH");
|
|
438
514
|
},
|
|
@@ -549,10 +625,19 @@ const platformModelSchema = z.object({
|
|
|
549
625
|
providerDisplay: z.string().optional(),
|
|
550
626
|
reasoning: z.boolean().optional(),
|
|
551
627
|
compliant: z.boolean().optional(),
|
|
628
|
+
noTraining: z.boolean().optional(),
|
|
552
629
|
thinkingLevels: z.array(z.string()).optional(),
|
|
553
|
-
thinkingLevelLabels: z.record(z.string(), z.string()).optional()
|
|
630
|
+
thinkingLevelLabels: z.record(z.string(), z.string()).optional(),
|
|
631
|
+
signals: z.object({
|
|
632
|
+
intelligence: z.number().int().min(1).max(6).nullable(),
|
|
633
|
+
speed: z.number().int().min(1).max(6).nullable(),
|
|
634
|
+
cost: z.number().int().min(1).max(6).nullable()
|
|
635
|
+
}).optional()
|
|
554
636
|
}).passthrough();
|
|
555
|
-
const listModelsResponseSchema = z.object({
|
|
637
|
+
const listModelsResponseSchema = z.object({
|
|
638
|
+
models: z.array(platformModelSchema),
|
|
639
|
+
trainingOptedOut: z.boolean().optional()
|
|
640
|
+
});
|
|
556
641
|
const modelEffortPreferencesResponseSchema = z.object({ modelConfigOverrides: z.record(z.string(), z.object({ thinkingLevel: z.string().optional() })).optional() });
|
|
557
642
|
const billingSummaryResponseSchema = z.object({ result: z.object({ data: z.object({
|
|
558
643
|
planKey: z.string().nullable(),
|
|
@@ -566,7 +651,8 @@ const freeFloorStateResponseSchema = z.object({ result: z.object({ data: z.objec
|
|
|
566
651
|
}).nullable() }) });
|
|
567
652
|
const conversationModelResponseSchema = z.object({
|
|
568
653
|
model: z.string().nullable(),
|
|
569
|
-
thinkingLevel: z.string().nullable()
|
|
654
|
+
thinkingLevel: z.string().nullable(),
|
|
655
|
+
scope: z.enum(["agent", "conversation"]).nullish()
|
|
570
656
|
});
|
|
571
657
|
const updateAgentResponseSchema = z.object({ agent: z.object({
|
|
572
658
|
model: z.string().nullable().optional(),
|
|
@@ -600,8 +686,10 @@ const conversationSummarySchema = z.object({
|
|
|
600
686
|
channel: z.string().nullable(),
|
|
601
687
|
channelLabel: z.string().nullable(),
|
|
602
688
|
viewerArchivedAt: z.string().nullable().optional(),
|
|
689
|
+
viewerPinnedAt: z.string().nullable().optional(),
|
|
603
690
|
viewerParticipates: z.boolean().optional(),
|
|
604
691
|
unread: z.boolean().optional(),
|
|
692
|
+
activeRun: z.boolean().optional(),
|
|
605
693
|
agent: conversationAgentSchema,
|
|
606
694
|
agents: z.array(conversationAgentSchema).optional()
|
|
607
695
|
});
|
|
@@ -655,6 +743,31 @@ const uiMessageSchema = z.object({
|
|
|
655
743
|
agentName: z.string().nullish()
|
|
656
744
|
}).passthrough().optional() }).passthrough().optional()
|
|
657
745
|
});
|
|
746
|
+
const conversationUsageModelSchema = z.object({
|
|
747
|
+
model: z.string().nullable(),
|
|
748
|
+
promptTokens: z.number(),
|
|
749
|
+
completionTokens: z.number(),
|
|
750
|
+
cacheCreationTokens: z.number(),
|
|
751
|
+
cacheReadTokens: z.number(),
|
|
752
|
+
cents: z.number()
|
|
753
|
+
});
|
|
754
|
+
const conversationUsageSchema = z.object({
|
|
755
|
+
own: z.object({
|
|
756
|
+
inferenceCents: z.number(),
|
|
757
|
+
byModel: z.array(conversationUsageModelSchema)
|
|
758
|
+
}),
|
|
759
|
+
children: z.object({
|
|
760
|
+
inferenceCents: z.number(),
|
|
761
|
+
conversationCount: z.number()
|
|
762
|
+
}),
|
|
763
|
+
compute: z.object({
|
|
764
|
+
cents: z.number(),
|
|
765
|
+
vcpuSeconds: z.number()
|
|
766
|
+
}),
|
|
767
|
+
unpriced: z.array(z.string()),
|
|
768
|
+
totalCents: z.number()
|
|
769
|
+
});
|
|
770
|
+
const conversationUsageResponseSchema = z.object({ usage: conversationUsageSchema.nullable() });
|
|
658
771
|
const recapResponseSchema = z.object({ recap: z.object({ text: z.string() }).nullable() });
|
|
659
772
|
const subagentTaskSnapshotSchema = z.object({
|
|
660
773
|
id: z.string(),
|
|
@@ -737,7 +850,8 @@ const conversationStreamEventSchema = z.discriminatedUnion("kind", [
|
|
|
737
850
|
id: z.string(),
|
|
738
851
|
model: z.string().nullable(),
|
|
739
852
|
modelLocked: z.boolean(),
|
|
740
|
-
thinkingLevel: z.string().nullish()
|
|
853
|
+
thinkingLevel: z.string().nullish(),
|
|
854
|
+
agentModel: z.string().nullish()
|
|
741
855
|
}),
|
|
742
856
|
z.object({
|
|
743
857
|
kind: z.literal("run"),
|
|
@@ -43,7 +43,7 @@ function defaultTlsCertSource(env, log) {
|
|
|
43
43
|
const fromEnv = envTlsCertSource(env, log);
|
|
44
44
|
return async () => {
|
|
45
45
|
if (env.SKYDIVE_PORTAL_TLS_KEY || env.SKYDIVE_PORTAL_TLS_CERT || env.SKYDIVE_PORTAL_TLS_HOST) return fromEnv();
|
|
46
|
-
const { localhostCertSource } = await import("./localhost-cert-
|
|
46
|
+
const { localhostCertSource } = await import("./localhost-cert-Dq2MRdXk.mjs");
|
|
47
47
|
return localhostCertSource(log)();
|
|
48
48
|
};
|
|
49
49
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skydive-cli",
|
|
3
|
-
"version": "0.6.0-beta.
|
|
3
|
+
"version": "0.6.0-beta.251",
|
|
4
4
|
"description": "Skydive CLI — cloud agents from the command line",
|
|
5
5
|
"homepage": "https://skydive.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"build": "node ./scripts/embed-changelog.mjs && tsdown --no-dts --out-dir dist/js",
|
|
24
24
|
"build:binary": "node ./scripts/embed-changelog.mjs && bun scripts/build-binary.ts",
|
|
25
25
|
"test:unit": "vitest run --passWithNoTests && yarn test:tui",
|
|
26
|
-
"test:ci": "vitest run --coverage --coverage.reporter=lcovonly --reporter=default --reporter=github-actions --
|
|
26
|
+
"test:ci": "vitest run --coverage --coverage.reporter=lcovonly --reporter=default --reporter=github-actions --maxWorkers=2 --passWithNoTests && yarn test:tui",
|
|
27
27
|
"test:tui": "node scripts/run-tui-tests.mjs",
|
|
28
28
|
"test:blackbox": "bun test test/blackbox --timeout 120000",
|
|
29
29
|
"render:frames": "bun scripts/render-frames.tsx",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"typecheck": "tsgo --noEmit"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@opentui/react": "0.
|
|
34
|
+
"@opentui/react": "0.6.6",
|
|
35
35
|
"conf": "^13.0.1",
|
|
36
36
|
"diff": "9.0.0",
|
|
37
37
|
"eventsource-parser": "^3.0.8",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"zustand": "^5.0.2"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
+
"@createinc/anyone-agents": "^0.0.0",
|
|
54
55
|
"@createinc/anyone-portal-daemon": "0.0.0",
|
|
55
56
|
"@createinc/anyone-sandbox-stream-protocol": "0.0.0",
|
|
56
57
|
"@createinc/tsconfig": "0.0.0",
|
|
@@ -60,10 +61,11 @@
|
|
|
60
61
|
"@types/semver": "^7.7.1",
|
|
61
62
|
"@types/yargs": "^17.0.33",
|
|
62
63
|
"@typescript/native-preview": "^7.0.0-dev.20260113.1",
|
|
63
|
-
"@vitest/coverage-v8": "^
|
|
64
|
+
"@vitest/coverage-v8": "^5.0.5",
|
|
64
65
|
"tsdown": "^0.20.3",
|
|
65
66
|
"typescript": "^5.9.3",
|
|
66
|
-
"
|
|
67
|
+
"vite": "^7.3.2",
|
|
68
|
+
"vitest": "^5.0.5"
|
|
67
69
|
},
|
|
68
70
|
"engines": {
|
|
69
71
|
"bun": ">=1.3.14",
|
|
@@ -71,9 +73,9 @@
|
|
|
71
73
|
},
|
|
72
74
|
"//optionalDependencies": "The per-platform binary packages (skydive-cli-<os>-<arch>) are INTENTIONALLY not committed here. They are published by release-skydive-cli-binaries.yml and injected — pinned to the exact version being published — into the published launcher's optionalDependencies at release time (scripts/prepare-platform-packages.mjs --pin-version, run in release-skydive-cli.yml). Committing them would make `yarn install` try to resolve versions that only exist post-publish, breaking local dev and CI. npm installs of a published skydive-cli still get them (os/cpu-gated); the launcher (src/launcher.ts) resolves and execs the matching one. Local dev never needs them: it runs the JS bundle or `build:binary` directly.",
|
|
73
75
|
"optionalDependencies": {
|
|
74
|
-
"skydive-cli-darwin-arm64": "0.6.0-beta.
|
|
75
|
-
"skydive-cli-darwin-x64": "0.6.0-beta.
|
|
76
|
-
"skydive-cli-linux-x64": "0.6.0-beta.
|
|
77
|
-
"skydive-cli-linux-arm64": "0.6.0-beta.
|
|
76
|
+
"skydive-cli-darwin-arm64": "0.6.0-beta.251",
|
|
77
|
+
"skydive-cli-darwin-x64": "0.6.0-beta.251",
|
|
78
|
+
"skydive-cli-linux-x64": "0.6.0-beta.251",
|
|
79
|
+
"skydive-cli-linux-arm64": "0.6.0-beta.251"
|
|
78
80
|
}
|
|
79
81
|
}
|