skydive-cli 0.1.0-beta.239 → 0.1.0-beta.276

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/README.md CHANGED
@@ -61,6 +61,11 @@ skydive secrets rm <KEY> --agent-id <id>
61
61
 
62
62
  skydive chat # interactive chat TUI (Node; fetches Bun on first run)
63
63
  skydive chat -p "<prompt>" # one-shot, non-interactive (runs under Node)
64
+
65
+ skydive portal open # open the portal to this machine, headless
66
+ skydive portal grant --agent <id|name>
67
+ skydive portal revoke --agent <id|name>
68
+ skydive portal status
64
69
  ```
65
70
 
66
71
  Global flags: `--json`, `--quiet`, `--api-url <url>`.
@@ -218,12 +223,44 @@ What to know before enabling it:
218
223
  outside the launch directory. Launch from a scoped directory, but treat the
219
224
  grant as "this agent can act as me on this machine," not "this agent can only
220
225
  see this folder."
221
- - **Teardown is automatic.** Access is revoked when the TUI unmounts, so
222
- quitting the chat drops the grant and reaps any processes the agent spawned.
223
- - **Interactive only.** Machine sharing is **not** available in `-p`
224
- (non-interactive) mode \u2014 `--share-machine` has no effect there and the
225
- print path carries no portal code. You can have scripted (`-p`) or local
226
- access, not both, in a single invocation.
226
+ - **Teardown drops reachability, not the grant.** Quitting the chat closes the
227
+ portal connection (the machine becomes unreachable) and reaps any processes
228
+ the agent spawned, but the per-agent grant itself **persists** until you
229
+ revoke it (`skydive portal revoke` or the web app). The next time this
230
+ machine shares, previously granted agents can run commands immediately, with
231
+ no new prompt.
232
+ - **The TUI is not required.** With `-p`, `--share-machine` shares this machine
233
+ for the duration of that one run: the portal connects before the prompt is
234
+ sent, the target agent is granted access (the flag is the consent — there is
235
+ no prompt to approve), and the connection is torn down when the run ends.
236
+ For access that outlives a single run, use `skydive portal open` (below).
237
+
238
+ ### Headless machine access (`skydive portal`)
239
+
240
+ `skydive portal` manages machine sharing without the TUI, for scripts, CI-ish
241
+ environments, and driving agents from other tools:
242
+
243
+ ```sh
244
+ skydive portal open # connect and stay in the foreground (ctrl+c to close)
245
+ skydive portal open --agent grace # \u2026and grant that agent on first connect
246
+ skydive portal open --cwd ~/some/repo # working directory for the agent's commands
247
+ skydive portal grant --agent grace # allow an agent (machine must have opened once)
248
+ skydive portal revoke --agent grace # remove an agent's access
249
+ skydive portal status # machines, connection state, granted agents
250
+ ```
251
+
252
+ Semantics match the TUI exactly \u2014 same default-deny grants, same audit log,
253
+ same server-side kill switch. `open` keeps the machine reachable only while
254
+ the process runs (park it in tmux for long-lived access); `grant`/`revoke`
255
+ edit the persistent per-agent access.
256
+
257
+ Two unattended patterns:
258
+
259
+ - **One-shot:** `skydive chat -p "<prompt>" --agent <name> --share-machine` \u2014
260
+ shares, grants the target agent, runs the prompt, tears down.
261
+ - **Daemon:** one `skydive portal open` process, plus plain
262
+ `skydive chat -p "<prompt>" --agent <name>` one-shots from another shell \u2014
263
+ cheaper when firing many prompts, since each one skips the connect/grant.
227
264
 
228
265
  ## Environment variables
229
266
 
package/dist/js/bin.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { t as __exportAll } from "./rolldown-runtime-Cz4Tg37Z.mjs";
2
3
  import { hideBin } from "yargs/helpers";
3
4
  import yargs from "yargs";
4
5
  import { createInterface } from "node:readline";
@@ -12,26 +13,10 @@ import { createHash } from "node:crypto";
12
13
  import fs from "node:fs";
13
14
  import zlib from "node:zlib";
14
15
  import { createParser } from "eventsource-parser";
16
+ import os from "node:os";
15
17
 
16
- //#region \0rolldown/runtime.js
17
- var __defProp = Object.defineProperty;
18
- var __exportAll = (all, no_symbols) => {
19
- let target = {};
20
- for (var name in all) {
21
- __defProp(target, name, {
22
- get: all[name],
23
- enumerable: true
24
- });
25
- }
26
- if (!no_symbols) {
27
- __defProp(target, Symbol.toStringTag, { value: "Module" });
28
- }
29
- return target;
30
- };
31
-
32
- //#endregion
33
18
  //#region package.json
34
- var version$1 = "0.1.0-beta.239";
19
+ var version$1 = "0.1.0-beta.276";
35
20
 
36
21
  //#endregion
37
22
  //#region src/types.ts
@@ -743,7 +728,7 @@ const logoutCommand = {
743
728
  else if (!argv.quiet) console.log("Logged out.");
744
729
  }
745
730
  };
746
- const statusCommand = {
731
+ const statusCommand$1 = {
747
732
  command: "status",
748
733
  describe: "Show current authentication state",
749
734
  handler: async (argv) => {
@@ -798,7 +783,7 @@ const statusCommand = {
798
783
  const authCommand = {
799
784
  command: "auth",
800
785
  describe: "Manage authentication",
801
- builder: (y) => y.command(loginCommand).command(logoutCommand).command(statusCommand).demandCommand(1, "Specify a subcommand: login, logout, status"),
786
+ builder: (y) => y.command(loginCommand).command(logoutCommand).command(statusCommand$1).demandCommand(1, "Specify a subcommand: login, logout, status"),
802
787
  handler: () => {}
803
788
  };
804
789
 
@@ -1031,7 +1016,7 @@ const createCommand = {
1031
1016
  console.log(" Save this key — it will not be shown again.");
1032
1017
  }
1033
1018
  };
1034
- const revokeCommand = {
1019
+ const revokeCommand$1 = {
1035
1020
  command: "revoke <id>",
1036
1021
  describe: "Revoke an API key",
1037
1022
  builder: (y) => y.positional("id", {
@@ -1059,7 +1044,7 @@ const keysCommand = {
1059
1044
  type: "string",
1060
1045
  demandOption: true,
1061
1046
  describe: "Agent ID"
1062
- }).command(listCommand$3).command(createCommand).command(revokeCommand).demandCommand(1, "Specify a subcommand: list, create, revoke"),
1047
+ }).command(listCommand$3).command(createCommand).command(revokeCommand$1).demandCommand(1, "Specify a subcommand: list, create, revoke"),
1063
1048
  handler: () => {}
1064
1049
  };
1065
1050
 
@@ -2108,7 +2093,7 @@ const chatCommand = {
2108
2093
  }).option("share-machine", {
2109
2094
  type: "boolean",
2110
2095
  default: false,
2111
- describe: "Share this machine with the agent over the portal so it can run commands here (default-deny; you approve per agent)"
2096
+ describe: "Share this machine with the agent over the portal so it can run commands here (default-deny; in the TUI you approve per agent, with -p the flag grants the target agent for the run)"
2112
2097
  }).option("theme", {
2113
2098
  type: "string",
2114
2099
  describe: `Pin a colorscheme (disables automatic light/dark switching). One of: ${themes.map((t) => t.id).join(", ")}. Defaults to following the terminal background; also settable via SKYDIVE_THEME.`
@@ -2152,7 +2137,7 @@ const chatCommand = {
2152
2137
  printError(`Unknown theme "${themeId}". Valid themes: ${themes.map((t) => t.id).join(", ")}`);
2153
2138
  process.exit(1);
2154
2139
  }
2155
- const { runChat } = await import("./boot-CIdJ5Als.mjs");
2140
+ const { runChat } = await import("./boot-FPpnUcyM.mjs");
2156
2141
  await runChat({
2157
2142
  appUrl,
2158
2143
  sessionToken: session.value.sessionToken,
@@ -2183,6 +2168,29 @@ async function runPrintMode({ argv, appUrl }) {
2183
2168
  process.exit(1);
2184
2169
  }
2185
2170
  }
2171
+ let machineShare = null;
2172
+ if (argv["share-machine"]) {
2173
+ const { PortalClient } = await import("./client-_OL8-XGH.mjs").then((n) => n.n);
2174
+ let signalConnected;
2175
+ const connected = new Promise((resolve) => {
2176
+ signalConnected = resolve;
2177
+ });
2178
+ machineShare = new PortalClient({
2179
+ appUrl,
2180
+ sessionToken: session.value.sessionToken,
2181
+ cwd: process.cwd(),
2182
+ onState: (state) => {
2183
+ if (state.status === "connected") signalConnected();
2184
+ if (state.status === "error") console.error(`portal: connection error: ${state.error ?? "unknown"} — retrying`);
2185
+ }
2186
+ });
2187
+ machineShare.enable();
2188
+ if (await Promise.race([connected.then(() => false), new Promise((resolve) => setTimeout(() => resolve(true), 3e4).unref())])) {
2189
+ machineShare.dispose();
2190
+ printError("Could not connect the portal within 30s — machine sharing is unavailable (network, or the portal kill switch is off). Re-run without --share-machine, or check `skydive portal status`.");
2191
+ process.exit(1);
2192
+ }
2193
+ }
2186
2194
  try {
2187
2195
  const result = await runPrint({
2188
2196
  appUrl,
@@ -2190,12 +2198,15 @@ async function runPrintMode({ argv, appUrl }) {
2190
2198
  prompt,
2191
2199
  agentSelector: argv.agent ?? null,
2192
2200
  conversationId: argv.conversation ?? null,
2193
- json: argv.json
2201
+ json: argv.json,
2202
+ machineShare
2194
2203
  });
2195
2204
  if (argv.json) output(argv, result);
2196
2205
  } catch (error) {
2197
2206
  printError(error instanceof Error ? error.message : String(error));
2198
2207
  process.exit(1);
2208
+ } finally {
2209
+ machineShare?.dispose();
2199
2210
  }
2200
2211
  }
2201
2212
 
@@ -2963,7 +2974,7 @@ var print_exports = /* @__PURE__ */ __exportAll({
2963
2974
  * disambiguate: an `--agent` selector must match exactly one agent, and
2964
2975
  * when it's omitted we only auto-pick if the account has exactly one.
2965
2976
  */
2966
- async function runPrint({ appUrl, sessionToken, prompt, agentSelector, conversationId, json }) {
2977
+ async function runPrint({ appUrl, sessionToken, prompt, agentSelector, conversationId, json, machineShare }) {
2967
2978
  const client = createRestClient({
2968
2979
  appUrl,
2969
2980
  sessionToken
@@ -2972,6 +2983,10 @@ async function runPrint({ appUrl, sessionToken, prompt, agentSelector, conversat
2972
2983
  scope: "org",
2973
2984
  onPage: null
2974
2985
  }), agentSelector);
2986
+ if (machineShare) {
2987
+ if (!machineShare.isGranted(agent.id)) await machineShare.grantAgent(agent.id);
2988
+ console.error(`portal: shared this machine with ${agent.name} for this run (grant persists until revoked)`);
2989
+ }
2975
2990
  let send;
2976
2991
  try {
2977
2992
  send = await client.sendMessage({
@@ -3330,7 +3345,7 @@ const switchCommand = {
3330
3345
  printError("The workspace picker needs the Bun runtime and it could not be set up automatically. Pass a workspace slug instead, or install Bun and retry.");
3331
3346
  process.exit(1);
3332
3347
  }
3333
- const { runWorkspacePicker } = await import("./boot-CIdJ5Als.mjs");
3348
+ const { runWorkspacePicker } = await import("./boot-FPpnUcyM.mjs");
3334
3349
  await runWorkspacePicker(session);
3335
3350
  return;
3336
3351
  }
@@ -3367,10 +3382,301 @@ const workspaceCommand = {
3367
3382
  handler: (argv) => runList(argv, { hint: true })
3368
3383
  };
3369
3384
 
3385
+ //#endregion
3386
+ //#region src/chat/portal/machine.ts
3387
+ /**
3388
+ * Identity this machine registers under when the CLI shares it via the portal.
3389
+ *
3390
+ * The `-cli` suffix / `(CLI)` label keep a CLI-shared machine a DISTINCT portal
3391
+ * device from the same host's Skydive Desktop app. `portal_device` is unique on
3392
+ * (org, user, machineName), and directives route to whichever socket holds the
3393
+ * device — if the CLI and desktop registered the same name they'd share a
3394
+ * device row and both execute every directive. Distinct names also make the
3395
+ * grant UI unambiguous about which surface is being authorized.
3396
+ */
3397
+ function machineIdentity() {
3398
+ const host = (os.hostname() || "machine").trim().replace(/\.local$/i, "") || "machine";
3399
+ return {
3400
+ machineName: `${host}-cli`,
3401
+ friendlyName: `${host} (CLI)`
3402
+ };
3403
+ }
3404
+ const INHERITED_ENV = [
3405
+ "HOME",
3406
+ "USER",
3407
+ "LOGNAME",
3408
+ "SHELL",
3409
+ "LANG",
3410
+ "LC_ALL",
3411
+ "TMPDIR",
3412
+ "TERM",
3413
+ "PATH"
3414
+ ];
3415
+ function buildEnv(extra) {
3416
+ const env = {};
3417
+ for (const key of INHERITED_ENV) {
3418
+ const value = process.env[key];
3419
+ if (value !== void 0) env[key] = value;
3420
+ }
3421
+ if (extra) for (const [key, value] of Object.entries(extra)) env[key] = value;
3422
+ return env;
3423
+ }
3424
+ /**
3425
+ * Build the desktop-portal WebSocket URL from the chat origin. Mirrors the Rust
3426
+ * desktop client: http→ws, https→wss, scheme-less defaults to wss, and the
3427
+ * machine/label ride as query pairs (percent-encoded by URL).
3428
+ */
3429
+ function portalWsUrl(appUrl, machine, label) {
3430
+ const base = appUrl.replace(/\/+$/, "");
3431
+ let wsBase;
3432
+ if (base.startsWith("https://")) wsBase = `wss://${base.slice(8)}`;
3433
+ else if (base.startsWith("http://")) wsBase = `ws://${base.slice(7)}`;
3434
+ else wsBase = `wss://${base}`;
3435
+ const url = new URL(`${wsBase}/api/v1/portal/desktop`);
3436
+ url.searchParams.set("machine", machine);
3437
+ url.searchParams.set("label", label);
3438
+ return url.toString();
3439
+ }
3440
+
3441
+ //#endregion
3442
+ //#region src/chat/portal/api.ts
3443
+ /**
3444
+ * The portal's session-authed REST surface, shared by `PortalClient` (the
3445
+ * TUI/`portal open` connection) and the `skydive portal` management
3446
+ * commands, so the endpoint contracts and response schemas live in exactly
3447
+ * one place.
3448
+ */
3449
+ const deviceSchema = z.object({
3450
+ id: z.string(),
3451
+ machineName: z.string(),
3452
+ friendlyName: z.string(),
3453
+ connected: z.boolean(),
3454
+ lastSeen: z.string().nullable(),
3455
+ grantedAgentIds: z.array(z.string())
3456
+ });
3457
+ const devicesResponseSchema = z.object({
3458
+ devices: z.array(deviceSchema),
3459
+ agents: z.array(z.object({
3460
+ id: z.string(),
3461
+ name: z.string()
3462
+ }))
3463
+ });
3464
+ const deviceTokenSchema = z.object({ token: z.string().min(1) });
3465
+ async function portalFetch(auth, path, init) {
3466
+ const res = await fetch(`${auth.appUrl}${path}`, {
3467
+ method: init.method,
3468
+ headers: {
3469
+ authorization: `Bearer ${auth.sessionToken}`,
3470
+ accept: "application/json",
3471
+ ...init.body ? { "content-type": "application/json" } : {}
3472
+ },
3473
+ ...init.body ? { body: init.body } : {}
3474
+ });
3475
+ if (!res.ok) {
3476
+ const body = await res.text().then((text) => text.slice(0, 120)).catch(() => "");
3477
+ throw new Error(`${init.method} ${path} failed (${res.status}): ${body}`);
3478
+ }
3479
+ return res.json();
3480
+ }
3481
+ async function fetchPortalDevices(auth) {
3482
+ const json = await portalFetch(auth, "/api/v1/portal/devices", { method: "GET" });
3483
+ return devicesResponseSchema.parse(json);
3484
+ }
3485
+ /** Short-lived token the machine presents when dialing the portal WebSocket. */
3486
+ async function mintPortalDeviceToken(auth) {
3487
+ const json = await portalFetch(auth, "/api/v1/portal/device-token", { method: "POST" });
3488
+ return deviceTokenSchema.parse(json).token;
3489
+ }
3490
+ async function grantPortalAccess(auth, { deviceId, agentId }) {
3491
+ await portalFetch(auth, `/api/v1/portal/devices/${encodeURIComponent(deviceId)}/grants`, {
3492
+ method: "POST",
3493
+ body: JSON.stringify({ agentId })
3494
+ });
3495
+ }
3496
+ async function revokePortalAccess(auth, { deviceId, agentId }) {
3497
+ await portalFetch(auth, `/api/v1/portal/devices/${encodeURIComponent(deviceId)}/grants/${encodeURIComponent(agentId)}`, { method: "DELETE" });
3498
+ }
3499
+ /**
3500
+ * The device row for a given machine identity. Matching is by `machineName`
3501
+ * equality — the stable per-surface handle (`<host>-cli` vs the desktop's
3502
+ * `<host>`), not the display label.
3503
+ */
3504
+ function findThisDevice(devices, machineName) {
3505
+ return devices.find((device) => device.machineName === machineName) ?? null;
3506
+ }
3507
+
3508
+ //#endregion
3509
+ //#region src/commands/portal.ts
3510
+ /**
3511
+ * Resolve everything a grant/revoke needs in one round trip: the devices
3512
+ * response carries both the user's machines and the org's agent roster, so
3513
+ * the `--agent` selector (id or name) resolves without a separate paginated
3514
+ * agent listing.
3515
+ */
3516
+ async function resolveGrantTarget(argv) {
3517
+ const session = requireSession(argv);
3518
+ const { devices, agents } = await fetchPortalDevices(session);
3519
+ const agent = resolveAgent(agents, argv.agent);
3520
+ const { machineName } = machineIdentity();
3521
+ const device = findThisDevice(devices, machineName);
3522
+ if (!device) throw new Error(`this machine (${machineName}) isn't registered with the portal yet — run \`skydive portal open\` once to register it.`);
3523
+ return {
3524
+ session,
3525
+ agent,
3526
+ device
3527
+ };
3528
+ }
3529
+ function agentOption(y) {
3530
+ return y.option("agent", {
3531
+ type: "string",
3532
+ demandOption: true,
3533
+ describe: "Agent, by id or name"
3534
+ });
3535
+ }
3536
+ const openCommand = {
3537
+ command: "open",
3538
+ describe: "Open the portal to this machine so granted agents can run commands here (stays in the foreground; ctrl+c to close)",
3539
+ builder: (y) => y.option("agent", {
3540
+ type: "string",
3541
+ describe: "Also grant this agent (by id or name) access to the machine once connected"
3542
+ }).option("cwd", {
3543
+ type: "string",
3544
+ describe: "Working directory for the agent's commands (default: the current directory)"
3545
+ }),
3546
+ handler: async (argv) => {
3547
+ const session = requireSession(argv);
3548
+ const agent = argv.agent ? resolveAgent((await fetchPortalDevices(session)).agents, argv.agent) : null;
3549
+ const cwd = argv.cwd ? path.resolve(argv.cwd) : process.cwd();
3550
+ const { machineName } = machineIdentity();
3551
+ const { PortalClient } = await import("./client-_OL8-XGH.mjs").then((n) => n.n);
3552
+ let lastLine = "";
3553
+ let signalConnected;
3554
+ const connected = new Promise((resolve) => {
3555
+ signalConnected = resolve;
3556
+ });
3557
+ const client = new PortalClient({
3558
+ appUrl: session.appUrl,
3559
+ sessionToken: session.sessionToken,
3560
+ cwd,
3561
+ onState: (state) => {
3562
+ if (state.status === "connected") signalConnected();
3563
+ const line = state.status === "error" ? `portal: connection error: ${state.error ?? "unknown"} — retrying` : `portal: ${state.status}`;
3564
+ if (line === lastLine) return;
3565
+ lastLine = line;
3566
+ if (state.status === "error") console.error(line);
3567
+ else if (!argv.quiet) console.log(line);
3568
+ }
3569
+ });
3570
+ const stop = () => {
3571
+ client.dispose();
3572
+ process.exit(0);
3573
+ };
3574
+ process.on("SIGINT", stop);
3575
+ process.on("SIGTERM", stop);
3576
+ client.enable();
3577
+ await connected;
3578
+ if (agent) {
3579
+ await client.grantAgent(agent.id);
3580
+ console.log(`portal: granted ${agent.name} access to this machine (persists until revoked)`);
3581
+ }
3582
+ console.log(`portal: open — granted agents can run commands on ${machineName} as your user, cwd ${cwd}. ctrl+c to close.`);
3583
+ await new Promise(() => {});
3584
+ }
3585
+ };
3586
+ const grantCommand = {
3587
+ command: "grant",
3588
+ describe: "Allow an agent to run commands on this machine (persists until revoked)",
3589
+ builder: agentOption,
3590
+ handler: async (argv) => {
3591
+ const { session, agent, device } = await resolveGrantTarget(argv);
3592
+ await grantPortalAccess(session, {
3593
+ deviceId: device.id,
3594
+ agentId: agent.id
3595
+ });
3596
+ if (argv.json) {
3597
+ output(argv, {
3598
+ deviceId: device.id,
3599
+ agentId: agent.id
3600
+ });
3601
+ return;
3602
+ }
3603
+ console.log(`Granted ${agent.name} access to ${device.friendlyName}. The agent can run commands here whenever its portal is open (\`skydive portal open\` or the chat TUI). Revoke with \`skydive portal revoke --agent ${agent.name}\`.`);
3604
+ }
3605
+ };
3606
+ const revokeCommand = {
3607
+ command: "revoke",
3608
+ describe: "Remove an agent's access to this machine",
3609
+ builder: agentOption,
3610
+ handler: async (argv) => {
3611
+ const { session, agent, device } = await resolveGrantTarget(argv);
3612
+ await revokePortalAccess(session, {
3613
+ deviceId: device.id,
3614
+ agentId: agent.id
3615
+ });
3616
+ if (argv.json) {
3617
+ output(argv, {
3618
+ deviceId: device.id,
3619
+ agentId: agent.id
3620
+ });
3621
+ return;
3622
+ }
3623
+ console.log(`Revoked ${agent.name}'s access to ${device.friendlyName}.`);
3624
+ }
3625
+ };
3626
+ const statusCommand = {
3627
+ command: "status",
3628
+ describe: "Show your portal machines, connection state, and granted agents",
3629
+ handler: async (argv) => {
3630
+ const { devices, agents } = await fetchPortalDevices(requireSession(argv));
3631
+ if (argv.json) {
3632
+ output(argv, {
3633
+ devices,
3634
+ agents
3635
+ });
3636
+ return;
3637
+ }
3638
+ if (devices.length === 0) {
3639
+ console.log("No machines registered. Run `skydive portal open` to register this one.");
3640
+ return;
3641
+ }
3642
+ const { headers, rows } = buildDeviceTable(devices, agents, machineIdentity().machineName);
3643
+ printTable(headers, rows);
3644
+ }
3645
+ };
3646
+ /**
3647
+ * Build the `portal status` table. Grant ids are shown as agent names when the
3648
+ * org roster resolves them (deleted agents fall back to the raw id), and the
3649
+ * device this CLI would register as is marked so "which row is me" doesn't
3650
+ * depend on knowing the `-cli` naming convention.
3651
+ */
3652
+ function buildDeviceTable(devices, agents, thisMachineName) {
3653
+ const agentNames = new Map(agents.map((a) => [a.id, a.name]));
3654
+ return {
3655
+ headers: [
3656
+ "Machine",
3657
+ "Connected",
3658
+ "Last seen",
3659
+ "Granted agents"
3660
+ ],
3661
+ rows: devices.map((device) => [
3662
+ device.machineName === thisMachineName ? `${device.friendlyName} *` : device.friendlyName,
3663
+ device.connected ? "yes" : "no",
3664
+ device.lastSeen ?? "-",
3665
+ device.grantedAgentIds.length === 0 ? "-" : device.grantedAgentIds.map((id) => agentNames.get(id) ?? id).join(", ")
3666
+ ])
3667
+ };
3668
+ }
3669
+ const portalCommand = {
3670
+ command: "portal",
3671
+ describe: "Open this machine's portal to agents and manage their access",
3672
+ builder: (y) => y.command(openCommand).command(grantCommand).command(revokeCommand).command(statusCommand).demandCommand(1, "Specify a subcommand: open, grant, revoke, status"),
3673
+ handler: () => {}
3674
+ };
3675
+
3370
3676
  //#endregion
3371
3677
  //#region src/cli.ts
3372
3678
  function createCli(argv) {
3373
- return yargs(argv).scriptName("skydive").usage("$0 <command> [options]").example("skydive auth login", "Store your API key").example("skydive chat", "Open the interactive chat TUI (prod)").example("skydive chat --api-url http://localhost:4500", "Chat against a local dev API").example("skydive agents list", "List your agents").example("skydive keys create \"my-key\"", "Create an API key").example("skydive secrets set OPENAI_API_KEY --agent-id <id>", "Set a secret (value from stdin)").example("skydive workspace list", "List workspaces on your account").example("skydive workspace switch acme-team", "Switch which workspace `skydive chat` uses").option("json", {
3679
+ return yargs(argv).scriptName("skydive").usage("$0 <command> [options]").example("skydive auth login", "Store your API key").example("skydive chat", "Open the interactive chat TUI (prod)").example("skydive chat --api-url http://localhost:4500", "Chat against a local dev API").example("skydive agents list", "List your agents").example("skydive keys create \"my-key\"", "Create an API key").example("skydive secrets set OPENAI_API_KEY --agent-id <id>", "Set a secret (value from stdin)").example("skydive workspace list", "List workspaces on your account").example("skydive workspace switch acme-team", "Switch which workspace `skydive chat` uses").example("skydive portal open --agent grace", "Open the portal to this machine for an agent, headless (no TUI)").option("json", {
3374
3680
  type: "boolean",
3375
3681
  default: false,
3376
3682
  global: true,
@@ -3384,7 +3690,7 @@ function createCli(argv) {
3384
3690
  type: "string",
3385
3691
  global: true,
3386
3692
  describe: "Override API base URL"
3387
- }).command(authCommand).command(chatCommand).command(messagesCommand).command(conversationsCommand).command(agentsCommand).command(keysCommand).command(secretsCommand).command(workspaceCommand).demandCommand(1, "Specify a command. Run --help for usage.").strict().wrap(null).version(version$1).alias("v", "version").alias("h", "help").help().fail((msg, err) => {
3693
+ }).command(authCommand).command(chatCommand).command(messagesCommand).command(conversationsCommand).command(agentsCommand).command(keysCommand).command(secretsCommand).command(workspaceCommand).command(portalCommand).demandCommand(1, "Specify a command. Run --help for usage.").strict().wrap(null).version(version$1).alias("v", "version").alias("h", "help").help().fail((msg, err) => {
3388
3694
  printError(err ? err instanceof Error ? err.message : String(err) : msg ?? "Unknown error");
3389
3695
  process.exit(1);
3390
3696
  });
@@ -3457,4 +3763,4 @@ function resolveArgv(args, tty = {
3457
3763
  createCli(resolveArgv(hideBin(process.argv))).parse();
3458
3764
 
3459
3765
  //#endregion
3460
- export { getSavedTheme as A, themesForMode as C, DEFAULT_API_URL as D, setActiveWorkspace as E, saveTheme as M, DEFAULT_APP_URL as O, themeVersion as S, listWorkspaces as T, noColorRequested as _, parseOauthConnectParams as a, themeMode as b, parseConnectCard as c, HttpError as d, createRestClient as f, monoTheme as g, findTheme as h, parseExternalOauthConnectParams as i, resolveWebUrl as j, getConfigPath as k, errorMessage as l, applyTheme as m, MASK_CHAR as n, reconcileMaskedInput as o, DEFAULT_THEME_ID as p, cardActionErrorMessage as r, resolveConnectUrl as s, resolveAgent as t, isRecord as u, theme as v, getActiveWorkspaceId as w, themeModeFromColorFgBg as x, themeForMode as y };
3766
+ export { themesForMode as A, monoTheme as C, themeMode as D, themeForMode as E, DEFAULT_APP_URL as F, getConfigPath as I, getSavedTheme as L, listWorkspaces as M, setActiveWorkspace as N, themeModeFromColorFgBg as O, DEFAULT_API_URL as P, resolveWebUrl as R, findTheme as S, theme as T, isRecord as _, buildEnv as a, DEFAULT_THEME_ID as b, resolveAgent as c, parseExternalOauthConnectParams as d, parseOauthConnectParams as f, errorMessage as g, parseConnectCard as h, mintPortalDeviceToken as i, getActiveWorkspaceId as j, themeVersion as k, MASK_CHAR as l, resolveConnectUrl as m, findThisDevice as n, machineIdentity as o, reconcileMaskedInput as p, grantPortalAccess as r, portalWsUrl as s, fetchPortalDevices as t, cardActionErrorMessage as u, HttpError as v, noColorRequested as w, applyTheme as x, createRestClient as y, saveTheme as z };