skydive-cli 0.5.0-beta.5 → 0.5.0-beta.52
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 +12 -0
- package/README.md +60 -12
- package/dist/js/api-TwLD7ibI.mjs +315 -0
- package/dist/js/{billing-blocked-Dgu5-oDy.mjs → billing-blocked-SE6tySLd.mjs} +1 -1
- package/dist/js/bin.mjs +658 -279
- package/dist/js/{boot-Brwh0it_.mjs → boot-D_VFIRJj.mjs} +3556 -995
- package/dist/js/chunk-BbwQpWto.mjs +33 -0
- package/dist/js/{client-Cn2af31H.mjs → client-Btq6bMzX.mjs} +108 -2
- package/dist/js/client-CkPQG8M1.mjs +5 -0
- package/dist/js/client-aFwHzCPG.mjs +963 -0
- package/dist/js/daemon-CORAITjj.mjs +7 -0
- package/dist/js/{daemon-client-DcuD4v12.mjs → daemon-client-Cfi66Xy9.mjs} +12 -8
- package/dist/js/daemon-client-DJPW9cRp.mjs +8 -0
- package/dist/js/{daemon-BhArnzeW.mjs → daemon-uzpOdRPL.mjs} +135 -10
- package/dist/js/dist-CRtjM7ba.mjs +1750 -0
- package/dist/js/forward-Ct8Vd2WW.mjs +208 -0
- package/dist/js/{profiler-LLaIFZgn.mjs → install-BBplm-Zp.mjs} +529 -215
- package/dist/js/launcher.mjs +49 -0
- package/dist/js/localhost-cert-Bn-UBUmj.mjs +67 -0
- package/dist/js/{print-ba_0hiV9.mjs → print-Cuao6LN6.mjs} +3 -3
- package/dist/js/{print-CbayCa87.mjs → print-DR6Gas-M.mjs} +291 -39
- package/dist/js/{print-share-uwUG16Ov.mjs → print-share-BypBUrNh.mjs} +9 -3
- package/dist/js/raw-pty-Ci2qFR9F.mjs +5 -0
- package/dist/js/{raw-pty-B6mAroiI.mjs → raw-pty-D5PhKZSl.mjs} +1 -1
- package/dist/js/rest-CJkBP2Jz.mjs +6 -0
- package/dist/js/{rest-BY2nADw5.mjs → rest-DkuT5_oX.mjs} +117 -28
- package/dist/js/tls-cert-CLgSQALB.mjs +4 -0
- package/dist/js/tls-cert-CV-pwxVN.mjs +67 -0
- package/package.json +11 -4
- package/dist/js/client-DZstLQ_1.mjs +0 -4
- package/dist/js/client-Dd5sMXPv.mjs +0 -620
- package/dist/js/daemon-CjzrUvXx.mjs +0 -5
- package/dist/js/daemon-client-D8bNXTxu.mjs +0 -6
- package/dist/js/raw-pty-3EkG-jjH.mjs +0 -5
- package/dist/js/rest-DADJh0bi.mjs +0 -6
- /package/dist/js/{billing-blocked-2wju4gC_.mjs → billing-blocked-D3l5kJlX.mjs} +0 -0
- /package/dist/js/{http-error-DzyrsLAZ.mjs → http-error-BF2NZZE3.mjs} +0 -0
- /package/dist/js/{output-DYzzdXYV.mjs → output-C9mb3sUB.mjs} +0 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
//#region src/launcher.ts
|
|
8
|
+
const PLATFORM_PACKAGES = {
|
|
9
|
+
"darwin-arm64": "skydive-cli-darwin-arm64",
|
|
10
|
+
"darwin-x64": "skydive-cli-darwin-x64",
|
|
11
|
+
"linux-x64": "skydive-cli-linux-x64",
|
|
12
|
+
"linux-arm64": "skydive-cli-linux-arm64"
|
|
13
|
+
};
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
/** The compiled per-platform binary, or null if none is installed/available. */
|
|
16
|
+
function resolveBinaryPath() {
|
|
17
|
+
const pkg = PLATFORM_PACKAGES[`${process.platform}-${process.arch}`];
|
|
18
|
+
if (!pkg) return null;
|
|
19
|
+
try {
|
|
20
|
+
return require.resolve(`${pkg}/bin`);
|
|
21
|
+
} catch (_error) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** The JS bundle sibling of this launcher (`dist/js/bin.mjs`). */
|
|
26
|
+
function jsBundlePath() {
|
|
27
|
+
return path.join(path.dirname(fileURLToPath(import.meta.url)), "bin.mjs");
|
|
28
|
+
}
|
|
29
|
+
const args = process.argv.slice(2);
|
|
30
|
+
const binaryPath = resolveBinaryPath();
|
|
31
|
+
let child;
|
|
32
|
+
if (binaryPath) {
|
|
33
|
+
const env = { ...process.env };
|
|
34
|
+
if (env["SKYDIVE_CLI_INSTALL_SOURCE"] === void 0) env["SKYDIVE_CLI_INSTALL_SOURCE"] = "package-manager";
|
|
35
|
+
child = spawnSync(binaryPath, args, {
|
|
36
|
+
stdio: "inherit",
|
|
37
|
+
env
|
|
38
|
+
});
|
|
39
|
+
} else child = spawnSync(process.execPath, [jsBundlePath(), ...args], { stdio: "inherit" });
|
|
40
|
+
if (child.error) {
|
|
41
|
+
const what = binaryPath ? `the platform binary (${binaryPath})` : `the CLI under Node (${process.execPath})`;
|
|
42
|
+
process.stderr.write(`skydive: failed to launch ${what}: ${child.error.message}\n`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
if (child.signal) process.kill(process.pid, child.signal);
|
|
46
|
+
else process.exit(child.status ?? 0);
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
export { };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { r as __toESM } from "./chunk-BbwQpWto.mjs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
6
|
+
|
|
7
|
+
//#region ../portal-daemon/src/localhost-cert.ts
|
|
8
|
+
/**
|
|
9
|
+
* Durable home for the one piece of state devcert doesn't keep for us: the
|
|
10
|
+
* user's decision to skip automatic cert setup. The daemon's own state dir is
|
|
11
|
+
* tmpdir-based (wiped on reboot), and re-raising a system trust prompt on
|
|
12
|
+
* every `portal forward` after a decline is exactly the nagging that trains
|
|
13
|
+
* people to fear the feature.
|
|
14
|
+
*/
|
|
15
|
+
function declineMarkerPath() {
|
|
16
|
+
return path.join(os.homedir(), ".config", "skydive-portal", "tls-declined");
|
|
17
|
+
}
|
|
18
|
+
async function hasDeclined() {
|
|
19
|
+
try {
|
|
20
|
+
await readFile(declineMarkerPath());
|
|
21
|
+
return true;
|
|
22
|
+
} catch (_error) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async function recordDecline(reason) {
|
|
27
|
+
const marker = declineMarkerPath();
|
|
28
|
+
try {
|
|
29
|
+
await mkdir(path.dirname(marker), { recursive: true });
|
|
30
|
+
await writeFile(marker, `Automatic localhost TLS setup failed or was declined:\n${reason}\n\nDelete this file to let it try again.\n`);
|
|
31
|
+
} catch (_error) {}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Fully automatic localhost cert: devcert generates a per-machine local CA on
|
|
35
|
+
* first use, installs it into the system/browser trust stores (one OS-native
|
|
36
|
+
* consent prompt, the mkcert pattern), signs a `localhost` leaf, and reuses
|
|
37
|
+
* both silently on every later run. The private key never leaves the machine
|
|
38
|
+
* and the CA is unique to it, so trusting it vouches only for this user's own
|
|
39
|
+
* loopback.
|
|
40
|
+
*
|
|
41
|
+
* Every failure — the user declining the trust prompt included — degrades to
|
|
42
|
+
* the plain-http origin (the product default) with one log line, and writes a
|
|
43
|
+
* marker so subsequent runs don't nag. `resetLocalhostCertDecline` (or
|
|
44
|
+
* deleting the marker file) re-arms it.
|
|
45
|
+
*/
|
|
46
|
+
function localhostCertSource(log) {
|
|
47
|
+
return async () => {
|
|
48
|
+
if (await hasDeclined()) return null;
|
|
49
|
+
try {
|
|
50
|
+
const { certificateFor } = await import("./dist-CRtjM7ba.mjs").then((m) => /* @__PURE__ */ __toESM(m.default, 1));
|
|
51
|
+
const { key, cert } = await certificateFor("localhost", { skipHostsFile: true });
|
|
52
|
+
return {
|
|
53
|
+
key: key.toString(),
|
|
54
|
+
cert: cert.toString(),
|
|
55
|
+
hostname: "localhost"
|
|
56
|
+
};
|
|
57
|
+
} catch (error) {
|
|
58
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
59
|
+
await recordDecline(reason);
|
|
60
|
+
log(`portal: automatic localhost TLS setup didn't complete (${reason}). Continuing with http only; delete ~/.config/skydive-portal/tls-declined to try again.`);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
export { localhostCertSource };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import "./
|
|
4
|
-
import "./billing-blocked-
|
|
2
|
+
import "./rest-DkuT5_oX.mjs";
|
|
3
|
+
import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-DR6Gas-M.mjs";
|
|
4
|
+
import "./billing-blocked-D3l5kJlX.mjs";
|
|
5
5
|
|
|
6
6
|
export { messageGet, readStdin, resolveAgent, runPrint };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as
|
|
3
|
-
import {
|
|
4
|
-
import { i as billingBlockedOutcomeFromSendResponse, n as BillingBlockedError } from "./billing-blocked-
|
|
2
|
+
import { a as isRecord, i as errorMessage, r as sendErrorMessage, t as createRestClient } from "./rest-DkuT5_oX.mjs";
|
|
3
|
+
import { t as HttpError } from "./http-error-BF2NZZE3.mjs";
|
|
4
|
+
import { i as billingBlockedOutcomeFromSendResponse, n as BillingBlockedError } from "./billing-blocked-D3l5kJlX.mjs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import Conf from "conf";
|
|
7
7
|
import { err, ok } from "neverthrow";
|
|
@@ -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 =
|
|
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;
|
|
@@ -40,7 +40,7 @@ const API_KEY_PREFIX = "sky_live_";
|
|
|
40
40
|
*/
|
|
41
41
|
const API_KEY_FAMILY_PREFIX = "sky_";
|
|
42
42
|
/** Where users mint and copy API keys. Shown in the login prompt. */
|
|
43
|
-
const API_KEYS_URL = "skydive.com/settings/
|
|
43
|
+
const API_KEYS_URL = "skydive.com/settings/workspace";
|
|
44
44
|
const store = new Conf({
|
|
45
45
|
projectName: process.env["SKYDIVE_CONFIG_NAME"] ?? "skydive",
|
|
46
46
|
projectSuffix: "",
|
|
@@ -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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
},
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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 (
|
|
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:
|
|
333
|
+
deviceId: optionalString(params.deviceId),
|
|
334
|
+
conversationId: optionalString(params.conversationId)
|
|
293
335
|
},
|
|
294
336
|
primary
|
|
295
337
|
};
|
|
@@ -323,12 +365,196 @@ function parseButton(element) {
|
|
|
323
365
|
function specKeyFor(spec) {
|
|
324
366
|
return stableStringify(spec) ?? crypto.randomUUID();
|
|
325
367
|
}
|
|
368
|
+
const COMPUTE_TIER_NAMES = {
|
|
369
|
+
small: "Lite",
|
|
370
|
+
large: "Standard",
|
|
371
|
+
xlarge: "Pro",
|
|
372
|
+
xxlarge: "Max",
|
|
373
|
+
xxxlarge: "Ultra"
|
|
374
|
+
};
|
|
375
|
+
function computeTierLabel(tier, memoryGb) {
|
|
376
|
+
const name = typeof tier === "string" ? COMPUTE_TIER_NAMES[tier] ?? null : null;
|
|
377
|
+
const memory = Number(memoryGb);
|
|
378
|
+
const memoryLabel = Number.isFinite(memory) && memory > 0 ? `${Math.round(memory)} GB` : null;
|
|
379
|
+
if (name && memoryLabel) return `${name} (${memoryLabel})`;
|
|
380
|
+
if (name) return name;
|
|
381
|
+
if (memoryLabel) return memoryLabel;
|
|
382
|
+
return typeof tier === "string" && tier ? tier : "unknown tier";
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* The card line for a settled decision, shared by the spec parser (a settled
|
|
386
|
+
* re-emission) and the chat screen (the decision POST's response, which on
|
|
387
|
+
* `alreadyDecided` reports whatever decision actually stuck). Wording follows
|
|
388
|
+
* the web card's chips (Upgraded / Kept current / Withdrawn).
|
|
389
|
+
*/
|
|
390
|
+
/**
|
|
391
|
+
* The full status domain the api emits on a compute request. Anything else —
|
|
392
|
+
* missing, or a value this CLI predates — returns null so the caller can
|
|
393
|
+
* refuse it instead of guessing at unknown semantics.
|
|
394
|
+
*/
|
|
395
|
+
function parseComputeRequestStatus(value) {
|
|
396
|
+
switch (value) {
|
|
397
|
+
case "pending":
|
|
398
|
+
case "approved":
|
|
399
|
+
case "denied":
|
|
400
|
+
case "cancelled": return value;
|
|
401
|
+
default: return null;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function computeSettledLabel(status) {
|
|
405
|
+
switch (status) {
|
|
406
|
+
case "approved": return "upgraded";
|
|
407
|
+
case "denied": return "kept the current tier";
|
|
408
|
+
case "cancelled": return "withdrawn";
|
|
409
|
+
default: return null;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* The `platform portal request` consent card. The server emits one
|
|
414
|
+
* DesktopHandoffCard spec for every surface (api routes/portal.ts): the web
|
|
415
|
+
* renders it as the "continue in the Skydive desktop app" handoff, but this
|
|
416
|
+
* terminal's own portal daemon can provide the machine, so here it maps onto
|
|
417
|
+
* the existing grant_portal approval instead of a pointer to the app. Without
|
|
418
|
+
* this the spec is not a Card, parses to null, and the request is silently
|
|
419
|
+
* dropped — `platform portal request` looks desktop-only from the CLI
|
|
420
|
+
* (ANY-6521).
|
|
421
|
+
*/
|
|
422
|
+
function parseDesktopHandoffCard(rootEl) {
|
|
423
|
+
const props = isRecord(rootEl.props) ? rootEl.props : {};
|
|
424
|
+
const agentId = optionalString(props.agentId);
|
|
425
|
+
if (!agentId) return null;
|
|
426
|
+
const agentName = optionalString(props.agentName) ?? "This agent";
|
|
427
|
+
return {
|
|
428
|
+
title: `Let ${agentName} run commands on your machine?`,
|
|
429
|
+
subtitle: null,
|
|
430
|
+
description: `Approving opens the portal to ${agentName} while you're signed in. Revoke anytime: skydive portal revoke --agent "${agentName}"`,
|
|
431
|
+
fields: [],
|
|
432
|
+
button: {
|
|
433
|
+
label: "Approve",
|
|
434
|
+
action: {
|
|
435
|
+
kind: "grant_portal",
|
|
436
|
+
agentId,
|
|
437
|
+
deviceId: optionalString(props.deviceId),
|
|
438
|
+
conversationId: optionalString(props.conversationId)
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
state: {},
|
|
442
|
+
computeRequestId: null,
|
|
443
|
+
settled: null,
|
|
444
|
+
questions: []
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* The `platform compute request` approval card. The web renders it as the
|
|
449
|
+
* Approve/Deny "More compute requested" card; here it maps onto a
|
|
450
|
+
* decide_compute button (ctrl+r opens the y/n decision prompt). Without this
|
|
451
|
+
* the spec is not a Card, parses to null, and the request is silently
|
|
452
|
+
* dropped — the user never sees it and the agent waits on a decision the
|
|
453
|
+
* terminal can't give (ANY-6653). A settled spec (the decision route re-emits
|
|
454
|
+
* the card with its final status) parses to a button-less resolved card. A
|
|
455
|
+
* status this CLI doesn't know also parses to null — rendering it as pending
|
|
456
|
+
* would offer a decision on a request in a state we can't represent.
|
|
457
|
+
*/
|
|
458
|
+
function parseComputeRequestCard(rootEl) {
|
|
459
|
+
const props = isRecord(rootEl.props) ? rootEl.props : {};
|
|
460
|
+
const requestId = optionalString(props.requestId);
|
|
461
|
+
if (!requestId) return null;
|
|
462
|
+
const status = parseComputeRequestStatus(props.status);
|
|
463
|
+
if (status === null) return null;
|
|
464
|
+
const agentName = optionalString(props.agentName) ?? "This agent";
|
|
465
|
+
const from = computeTierLabel(props.fromTier, props.fromMemoryGb);
|
|
466
|
+
const to = computeTierLabel(props.requestedTier, props.requestedMemoryGb);
|
|
467
|
+
const settled = computeSettledLabel(status);
|
|
468
|
+
return {
|
|
469
|
+
title: "More compute requested",
|
|
470
|
+
subtitle: `${agentName}: ${from} → ${to}`,
|
|
471
|
+
description: optionalString(props.reason),
|
|
472
|
+
fields: [],
|
|
473
|
+
button: settled ? null : {
|
|
474
|
+
label: "Decide",
|
|
475
|
+
action: {
|
|
476
|
+
kind: "decide_compute",
|
|
477
|
+
requestId
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
state: {},
|
|
481
|
+
computeRequestId: requestId,
|
|
482
|
+
settled,
|
|
483
|
+
questions: []
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
function readQuestionOptions(raw) {
|
|
487
|
+
if (!Array.isArray(raw)) return [];
|
|
488
|
+
return raw.flatMap((o) => {
|
|
489
|
+
if (!isRecord(o)) return [];
|
|
490
|
+
const label = optionalString(o.label);
|
|
491
|
+
if (!label) return [];
|
|
492
|
+
return [{ label }];
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
function readQuestionEntries(raw) {
|
|
496
|
+
if (!Array.isArray(raw)) return [];
|
|
497
|
+
return raw.flatMap((q) => {
|
|
498
|
+
if (!isRecord(q)) return [];
|
|
499
|
+
const question = optionalString(q.question);
|
|
500
|
+
if (!question) return [];
|
|
501
|
+
const options = readQuestionOptions(q.options);
|
|
502
|
+
if (options.length < 2) return [];
|
|
503
|
+
return [{
|
|
504
|
+
question,
|
|
505
|
+
options,
|
|
506
|
+
multiSelect: Boolean(q.multiSelect)
|
|
507
|
+
}];
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* The `platform ask` form. The web renders it as an interactive picker; here
|
|
512
|
+
* it maps onto an answer_question button so ctrl+r (and letter shortcuts)
|
|
513
|
+
* open an in-TUI picker. The composed answer is sent as a normal user
|
|
514
|
+
* message — same non-blocking model as the web card. Without this the spec
|
|
515
|
+
* is not a Card, parses to null, and the question is silently dropped.
|
|
516
|
+
*/
|
|
517
|
+
/** Compose the user-message body the web card also sends. */
|
|
518
|
+
function composeQuestionAnswer(questions, answers) {
|
|
519
|
+
if (questions.length === 1) return (answers[0] ?? []).join(", ");
|
|
520
|
+
return questions.map((q, i) => `${q.question} → ${(answers[i] ?? []).join(", ")}`).join("\n");
|
|
521
|
+
}
|
|
522
|
+
function parseQuestionCard(rootEl) {
|
|
523
|
+
const props = isRecord(rootEl.props) ? rootEl.props : {};
|
|
524
|
+
const cardId = optionalString(props.cardId);
|
|
525
|
+
if (!cardId) return null;
|
|
526
|
+
const questions = readQuestionEntries(props.questions);
|
|
527
|
+
if (questions.length === 0) return null;
|
|
528
|
+
const settled = (props.status === "answered" ? "answered" : "pending") === "answered" ? optionalString(props.answerSummary) ?? "answered" : null;
|
|
529
|
+
const only = questions.length === 1 ? questions[0] : void 0;
|
|
530
|
+
return {
|
|
531
|
+
title: only ? only.question : "Questions",
|
|
532
|
+
subtitle: null,
|
|
533
|
+
description: null,
|
|
534
|
+
fields: [],
|
|
535
|
+
button: settled ? null : {
|
|
536
|
+
label: "Answer",
|
|
537
|
+
action: {
|
|
538
|
+
kind: "answer_question",
|
|
539
|
+
cardId
|
|
540
|
+
}
|
|
541
|
+
},
|
|
542
|
+
state: {},
|
|
543
|
+
computeRequestId: null,
|
|
544
|
+
settled,
|
|
545
|
+
questions
|
|
546
|
+
};
|
|
547
|
+
}
|
|
326
548
|
function parseConnectCard(spec) {
|
|
327
549
|
if (!isRecord(spec)) return null;
|
|
328
550
|
const { root, elements } = spec;
|
|
329
551
|
if (typeof root !== "string" || !isRecord(elements)) return null;
|
|
330
552
|
const rootEl = elements[root];
|
|
331
|
-
if (!isRecord(rootEl)
|
|
553
|
+
if (!isRecord(rootEl)) return null;
|
|
554
|
+
if (rootEl.type === "DesktopHandoffCard") return parseDesktopHandoffCard(rootEl);
|
|
555
|
+
if (rootEl.type === "ComputeRequestCard") return parseComputeRequestCard(rootEl);
|
|
556
|
+
if (rootEl.type === "QuestionCard") return parseQuestionCard(rootEl);
|
|
557
|
+
if (rootEl.type !== "Card") return null;
|
|
332
558
|
const rootProps = isRecord(rootEl.props) ? rootEl.props : {};
|
|
333
559
|
const title = optionalString(rootProps.title);
|
|
334
560
|
if (!title) return null;
|
|
@@ -366,7 +592,10 @@ function parseConnectCard(spec) {
|
|
|
366
592
|
label: preferred.label,
|
|
367
593
|
action: preferred.action
|
|
368
594
|
} : null,
|
|
369
|
-
state: isRecord(spec.state) ? spec.state : {}
|
|
595
|
+
state: isRecord(spec.state) ? spec.state : {},
|
|
596
|
+
computeRequestId: null,
|
|
597
|
+
settled: null,
|
|
598
|
+
questions: []
|
|
370
599
|
};
|
|
371
600
|
}
|
|
372
601
|
|
|
@@ -507,6 +736,20 @@ function summarizeConnectCard(card, appUrl) {
|
|
|
507
736
|
agentId: act.agentId
|
|
508
737
|
}
|
|
509
738
|
};
|
|
739
|
+
case "decide_compute": return {
|
|
740
|
+
...base,
|
|
741
|
+
action: {
|
|
742
|
+
kind: "decide_compute",
|
|
743
|
+
requestId: act.requestId
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
case "answer_question": return {
|
|
747
|
+
...base,
|
|
748
|
+
action: {
|
|
749
|
+
kind: "answer_question",
|
|
750
|
+
questions: card.questions.map((q) => q.question)
|
|
751
|
+
}
|
|
752
|
+
};
|
|
510
753
|
default: return {
|
|
511
754
|
...base,
|
|
512
755
|
action: { kind: "unsupported" }
|
|
@@ -516,14 +759,16 @@ function summarizeConnectCard(card, appUrl) {
|
|
|
516
759
|
/**
|
|
517
760
|
* Parse a `data-anyone-render-spec` stream chunk into a connect-card summary,
|
|
518
761
|
* or null if the chunk isn't a connect card (other render specs — training,
|
|
519
|
-
* deep-learn
|
|
762
|
+
* deep-learn — parse to null, same as the TUI). A card that arrives already
|
|
763
|
+
* settled (a compute request's decided re-emission) is an FYI, not an action,
|
|
764
|
+
* so it stays out of the "[action needed]" stream too.
|
|
520
765
|
*/
|
|
521
766
|
function connectCardFromChunk(chunk, appUrl) {
|
|
522
767
|
if (chunk["type"] !== "data-anyone-render-spec") return null;
|
|
523
768
|
const data = chunk["data"];
|
|
524
769
|
if (!isRecord(data)) return null;
|
|
525
770
|
const card = parseConnectCard(data["spec"]);
|
|
526
|
-
if (!card) return null;
|
|
771
|
+
if (!card || card.settled !== null) return null;
|
|
527
772
|
return summarizeConnectCard(card, appUrl);
|
|
528
773
|
}
|
|
529
774
|
/** Render a connect-card summary as a human-readable action block. */
|
|
@@ -540,7 +785,13 @@ function formatConnectCard(card) {
|
|
|
540
785
|
lines.push(`Provide credential (${card.action.fields.join(", ") || "value"}) at: ${card.action.url}`);
|
|
541
786
|
break;
|
|
542
787
|
case "approve_portal":
|
|
543
|
-
lines.push(`Approve
|
|
788
|
+
lines.push(`Approve portal access to this machine for agent ${card.action.agentId} in the TUI or web app.`);
|
|
789
|
+
break;
|
|
790
|
+
case "decide_compute":
|
|
791
|
+
lines.push("Approve or deny this compute increase in the interactive TUI or web app.");
|
|
792
|
+
break;
|
|
793
|
+
case "answer_question":
|
|
794
|
+
lines.push(`Answer in the interactive TUI or web app: ${card.action.questions.join("; ")}`);
|
|
544
795
|
break;
|
|
545
796
|
case "unsupported":
|
|
546
797
|
lines.push("Open this conversation in the web app to continue.");
|
|
@@ -562,17 +813,18 @@ function formatConnectCard(card) {
|
|
|
562
813
|
* disambiguate: an `--agent` selector must match exactly one agent, and
|
|
563
814
|
* when it's omitted we only auto-pick if the account has exactly one.
|
|
564
815
|
*/
|
|
565
|
-
async function runPrint({ appUrl, sessionToken, prompt, agentSelector, conversationId, json, machineShare, grantTargetAgent }) {
|
|
816
|
+
async function runPrint({ appUrl, sessionToken, workspaceId, prompt, agentSelector, conversationId, json, machineShare, grantTargetAgent }) {
|
|
566
817
|
const client = createRestClient({
|
|
567
818
|
appUrl,
|
|
568
|
-
sessionToken
|
|
819
|
+
sessionToken,
|
|
820
|
+
workspaceId
|
|
569
821
|
});
|
|
570
822
|
const agent = resolveAgent(await client.listAgents({
|
|
571
823
|
scope: "org",
|
|
572
824
|
onPage: null
|
|
573
825
|
}), agentSelector);
|
|
574
826
|
if (machineShare) {
|
|
575
|
-
if (!machineShare.isGranted(agent.id) && grantTargetAgent) await machineShare.grantAgent(agent.id);
|
|
827
|
+
if (!machineShare.isGranted(agent.id) && grantTargetAgent) await machineShare.grantAgent(agent.id, null);
|
|
576
828
|
if (machineShare.isGranted(agent.id)) console.error(`portal: shared this machine with ${agent.name} for this run (grant persists until revoked)`);
|
|
577
829
|
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
830
|
}
|
|
@@ -657,7 +909,7 @@ async function collectRunText({ client, appUrl, target, onText, messageIdForHint
|
|
|
657
909
|
const onEvent = (event) => {
|
|
658
910
|
if (event.kind === "finished") {
|
|
659
911
|
if (event.outcome) billingBlocked = event.outcome;
|
|
660
|
-
if (event.error) streamError = event.error;
|
|
912
|
+
if (event.error && streamError === null) streamError = event.error;
|
|
661
913
|
return;
|
|
662
914
|
}
|
|
663
915
|
const chunk = event.chunk;
|
|
@@ -760,4 +1012,4 @@ async function readStdin() {
|
|
|
760
1012
|
}
|
|
761
1013
|
|
|
762
1014
|
//#endregion
|
|
763
|
-
export {
|
|
1015
|
+
export { getSavedTheme as A, resolveSession as B, deleteConfig as C, getPreference as D, getLastSeenVersion as E, recordDefaultAgent as F, setLastSeenVersion as G, saveConfig as H, resolveAppUrl as I, resolveChatAuth as L, getStoredApiKeyId as M, getStoredApiKeyWorkspaceName as N, getPromptHistoryPath as O, getUpdateCheckDisabled as P, resolveConfig as R, PREFERENCES as S, getDefaultAgent as T, saveSession as U, resolveWebUrl as V, saveTheme as W, API_KEYS_URL as _, runPrint as a, DEFAULT_API_URL as b, cardActionErrorMessage as c, reconcileMaskedInput as d, resolveConnectUrl as f, specKeyFor as g, parseConnectCard as h, resolveAgent as i, getShareMachineDefault as j, getReviewStateDir as k, parseExternalOauthConnectParams as l, computeSettledLabel as m, messageGet as n, toPrintError as o, composeQuestionAnswer as p, readStdin as r, MASK_CHAR as s, collectRunText as t, parseOauthConnectParams as u, API_KEY_FAMILY_PREFIX as v, getConfigPath as w, DEFAULT_APP_URL as x, API_KEY_PREFIX as y, resolveManagementAuth as z };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as printError } from "./output-
|
|
2
|
+
import { n as printError } from "./output-C9mb3sUB.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/chat/print-share.ts
|
|
5
5
|
/**
|
|
@@ -14,7 +14,8 @@ 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-
|
|
17
|
+
const { PortalClient } = await import("./client-CkPQG8M1.mjs");
|
|
18
|
+
const { defaultTlsCertSource } = await import("./tls-cert-CLgSQALB.mjs");
|
|
18
19
|
let signalConnected;
|
|
19
20
|
const connected = new Promise((resolve) => {
|
|
20
21
|
signalConnected = resolve;
|
|
@@ -26,6 +27,11 @@ async function connectMachineShare({ appUrl, sessionToken, timeoutHint }) {
|
|
|
26
27
|
deviceToken: null
|
|
27
28
|
}),
|
|
28
29
|
resolveCwd: () => process.cwd(),
|
|
30
|
+
tlsCertSource: defaultTlsCertSource(process.env, (msg) => {
|
|
31
|
+
console.error(msg);
|
|
32
|
+
}),
|
|
33
|
+
persistedMachineName: null,
|
|
34
|
+
onMachineName: () => {},
|
|
29
35
|
onState: (state) => {
|
|
30
36
|
if (state.status === "connected") signalConnected();
|
|
31
37
|
if (state.status === "error") console.error(`portal: connection error: ${state.error ?? "unknown"}; retrying`);
|
|
@@ -34,7 +40,7 @@ async function connectMachineShare({ appUrl, sessionToken, timeoutHint }) {
|
|
|
34
40
|
machineShare.enable();
|
|
35
41
|
if (await Promise.race([connected.then(() => false), new Promise((resolve) => setTimeout(() => resolve(true), 3e4).unref())])) {
|
|
36
42
|
machineShare.dispose();
|
|
37
|
-
printError(`Could not connect the portal within 30s.
|
|
43
|
+
printError(`Could not connect the portal within 30s. The portal is unavailable (network, or the portal kill switch is off). ${timeoutHint ?? "Check `skydive portal status`."}`);
|
|
38
44
|
process.exit(1);
|
|
39
45
|
}
|
|
40
46
|
return machineShare;
|