skydive-cli 0.5.0-beta.5 → 0.5.0-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/js/bin.mjs CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { C as ensureActiveOrganization, D as setActiveWorkspace, E as listWorkspaces, O as name, T as getSessionIdentity, a as installCrashHandler, k as version, t as maybeStartProfiling, u as brandHelpArt, w as getActiveWorkspaceId, x as themes } from "./profiler-LLaIFZgn.mjs";
3
- import { A as getStoredApiKeyWorkspaceName, C as getLastSeenVersion, F as resolveManagementAuth, I as resolveSession, M as resolveAppUrl, N as resolveChatAuth, O as getShareMachineDefault, P as resolveConfig, R as saveConfig, S as getConfigPath, T as getPromptHistoryPath, V as setLastSeenVersion, _ as API_KEY_PREFIX, b as PREFERENCES, g as API_KEY_FAMILY_PREFIX, h as API_KEYS_URL, i as resolveAgent, j as getUpdateCheckDisabled, k as getStoredApiKeyId, v as DEFAULT_API_URL, w as getPreference, x as deleteConfig, z as saveSession } from "./print-CbayCa87.mjs";
2
+ import { C as ensureActiveOrganization, D as setActiveWorkspace, E as listWorkspaces, O as name, T as getSessionIdentity, a as installCrashHandler, k as version, t as maybeStartProfiling, u as brandHelpArt, w as getActiveWorkspaceId, x as themes } from "./profiler-wvJ7kT6W.mjs";
3
+ import { A as getStoredApiKeyWorkspaceName, C as getLastSeenVersion, F as resolveManagementAuth, I as resolveSession, M as resolveAppUrl, N as resolveChatAuth, O as getShareMachineDefault, P as resolveConfig, R as saveConfig, S as getConfigPath, T as getPromptHistoryPath, V as setLastSeenVersion, _ as API_KEY_PREFIX, b as PREFERENCES, g as API_KEY_FAMILY_PREFIX, h as API_KEYS_URL, i as resolveAgent, j as getUpdateCheckDisabled, k as getStoredApiKeyId, v as DEFAULT_API_URL, w as getPreference, x as deleteConfig, z as saveSession } from "./print-BpuyEfWX.mjs";
4
4
  import { n as printError, r as printTable, t as output } from "./output-DYzzdXYV.mjs";
5
5
  import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
6
- import { t as createRestClient } from "./rest-BY2nADw5.mjs";
6
+ import { t as createRestClient } from "./rest-I3imNduB.mjs";
7
7
  import "./billing-blocked-2wju4gC_.mjs";
8
- import { a as registerPortalDevice, c as resolveMachineIdentity, i as grantPortalAccess, n as fetchPortalDevices, o as revokePortalAccess, r as findThisDevice } from "./client-Dd5sMXPv.mjs";
9
- import { i as queryDaemonStatus, l as PORTAL_DAEMON_FLAG, n as ensureDaemonRunning, s as stopDaemon, u as daemonPaths } from "./daemon-BhArnzeW.mjs";
8
+ import { a as registerPortalDevice, c as resolveMachineIdentity, i as grantPortalAccess, n as fetchPortalDevices, o as revokePortalAccess, r as findThisDevice } from "./client-CmEF9zrz.mjs";
9
+ import { i as queryDaemonStatus, l as PORTAL_DAEMON_FLAG, n as ensureDaemonRunning, s as stopDaemon, u as daemonPaths } from "./daemon-DXuENdsx.mjs";
10
10
  import { t as SandboxStream } from "./client-Cn2af31H.mjs";
11
11
  import { hideBin } from "yargs/helpers";
12
12
  import yargs from "yargs";
@@ -1516,7 +1516,7 @@ const importCommand = {
1516
1516
  process.exit(1);
1517
1517
  }
1518
1518
  }
1519
- const { runChat } = await import("./boot-Brwh0it_.mjs");
1519
+ const { runChat } = await import("./boot-odaKymWy.mjs");
1520
1520
  await runChat({
1521
1521
  appUrl,
1522
1522
  sessionToken: session.value.sessionToken,
@@ -1543,7 +1543,7 @@ async function runImportPrintMode({ argv, appUrl }) {
1543
1543
  printError(`${session.error.message} For non-interactive use, run \`skydive auth login --web\` first, or set SKYDIVE_SESSION_TOKEN.`);
1544
1544
  process.exit(1);
1545
1545
  }
1546
- const { connectMachineShare } = await import("./print-share-uwUG16Ov.mjs");
1546
+ const { connectMachineShare } = await import("./print-share-D9RczQx8.mjs");
1547
1547
  const machineShare = await connectMachineShare({
1548
1548
  appUrl,
1549
1549
  sessionToken: session.value.sessionToken,
@@ -1551,7 +1551,7 @@ async function runImportPrintMode({ argv, appUrl }) {
1551
1551
  });
1552
1552
  const extra = (argv.print ?? "").trim();
1553
1553
  const prompt = buildImportSeedPrompt(process.cwd()) + (extra ? `\n\nAdditional instructions: ${extra}` : "");
1554
- const { runPrint } = await import("./print-ba_0hiV9.mjs");
1554
+ const { runPrint } = await import("./print-Wakr3GJd.mjs");
1555
1555
  try {
1556
1556
  const result = await runPrint({
1557
1557
  appUrl,
@@ -1928,7 +1928,7 @@ const chatCommand = {
1928
1928
  sessionToken: auth.value.token,
1929
1929
  agentSelector: argv.agent ?? null
1930
1930
  });
1931
- const { runChat } = await import("./boot-Brwh0it_.mjs");
1931
+ const { runChat } = await import("./boot-odaKymWy.mjs");
1932
1932
  await runChat({
1933
1933
  appUrl: auth.value.appUrl,
1934
1934
  sessionToken: auth.value.token,
@@ -1995,7 +1995,7 @@ async function runPrintMode({ argv, appUrl }) {
1995
1995
  printError(`${auth.error.message} For non-interactive use, run \`skydive auth login\` first, or set SKYDIVE_API_KEY / SKYDIVE_SESSION_TOKEN.`);
1996
1996
  process.exit(1);
1997
1997
  }
1998
- const { runPrint, readStdin } = await import("./print-ba_0hiV9.mjs");
1998
+ const { runPrint, readStdin } = await import("./print-Wakr3GJd.mjs");
1999
1999
  await ensureAgentWorkspace({
2000
2000
  appUrl,
2001
2001
  sessionToken: auth.value.token,
@@ -2020,7 +2020,7 @@ async function runPrintMode({ argv, appUrl }) {
2020
2020
  process.exit(1);
2021
2021
  }
2022
2022
  } else {
2023
- const { connectMachineShare } = await import("./print-share-uwUG16Ov.mjs");
2023
+ const { connectMachineShare } = await import("./print-share-D9RczQx8.mjs");
2024
2024
  machineShare = await connectMachineShare({
2025
2025
  appUrl: auth.value.appUrl,
2026
2026
  sessionToken: auth.value.token,
@@ -2084,7 +2084,7 @@ const getCommand$1 = {
2084
2084
  printError(`${auth.error.message} Run \`skydive auth login\` first, or set SKYDIVE_API_KEY / SKYDIVE_SESSION_TOKEN.`);
2085
2085
  process.exit(1);
2086
2086
  }
2087
- const { messageGet } = await import("./print-ba_0hiV9.mjs");
2087
+ const { messageGet } = await import("./print-Wakr3GJd.mjs");
2088
2088
  try {
2089
2089
  const result = await messageGet({
2090
2090
  appUrl: auth.value.appUrl,
@@ -2287,7 +2287,7 @@ const switchCommand = {
2287
2287
  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.");
2288
2288
  process.exit(1);
2289
2289
  }
2290
- const { runWorkspacePicker } = await import("./boot-Brwh0it_.mjs");
2290
+ const { runWorkspacePicker } = await import("./boot-odaKymWy.mjs");
2291
2291
  await runWorkspacePicker(session);
2292
2292
  return;
2293
2293
  }
@@ -2366,7 +2366,7 @@ const openCommand = {
2366
2366
  const agent = argv.agent ? resolveAgent((await fetchPortalDevices(session)).agents, argv.agent) : null;
2367
2367
  const cwd = argv.cwd ? path.resolve(argv.cwd) : process.cwd();
2368
2368
  const { machineName } = await resolveMachineIdentity();
2369
- const { PortalDaemonClient } = await import("./daemon-client-D8bNXTxu.mjs");
2369
+ const { PortalDaemonClient } = await import("./daemon-client-B3Yzyd46.mjs");
2370
2370
  let lastLine = "";
2371
2371
  let signalConnected;
2372
2372
  const connected = new Promise((resolve) => {
@@ -2395,7 +2395,7 @@ const openCommand = {
2395
2395
  client.enable();
2396
2396
  await connected;
2397
2397
  if (agent) {
2398
- await client.grantAgent(agent.id);
2398
+ await client.grantAgent(agent.id, null);
2399
2399
  console.log(`portal: granted ${agent.name} access to this machine (persists until revoked)`);
2400
2400
  }
2401
2401
  console.log(`portal: open. granted agents can run commands on ${machineName} as your user, cwd ${cwd}. ctrl+c to close.`);
@@ -2415,7 +2415,8 @@ const grantCommand = {
2415
2415
  })).id;
2416
2416
  await grantPortalAccess(session, {
2417
2417
  deviceId,
2418
- agentId: agent.id
2418
+ agentId: agent.id,
2419
+ conversationId: null
2419
2420
  });
2420
2421
  if (argv.json) {
2421
2422
  output(argv, {
@@ -2424,7 +2425,7 @@ const grantCommand = {
2424
2425
  });
2425
2426
  return;
2426
2427
  }
2427
- console.log(`Granted ${agent.name} access to ${device?.friendlyName ?? friendlyName}. The agent can run commands here whenever its portal is open. Start with \`skydive portal open\`, or pass \`--share-machine\` on a \`chat -p\` run. Revoke with \`skydive portal revoke --agent ${agent.name}\`.`);
2428
+ console.log(`Granted ${agent.name} access to ${device?.friendlyName ?? friendlyName}. The agent can run commands here whenever its portal is open. Start with \`skydive portal open\`, or pass \`--share-machine\` on a \`chat -p\` run. Revoke with \`skydive portal revoke --agent "${agent.name}"\`.`);
2428
2429
  }
2429
2430
  };
2430
2431
  const revokeCommand = {
@@ -2640,8 +2641,8 @@ const sandboxCommand = {
2640
2641
  }).example("skydive sandbox --agent grace", "Live terminal (Ctrl-] detaches)").example("skydive sandbox --agent grace -- tail -n 50 /tmp/harness.log", "One-shot command (use `--` so its flags reach the sandbox)").example("skydive sandbox --agent grace -- sh -c 'ls /tmp | wc -l'", "Shell features go through an explicit `sh -c`"),
2641
2642
  handler: async (argv) => {
2642
2643
  const session = requireSession(argv);
2643
- const { createRestClient } = await import("./rest-DADJh0bi.mjs");
2644
- const { resolveAgent } = await import("./print-ba_0hiV9.mjs");
2644
+ const { createRestClient } = await import("./rest-D29qNkto.mjs");
2645
+ const { resolveAgent } = await import("./print-Wakr3GJd.mjs");
2645
2646
  const client = createRestClient({
2646
2647
  appUrl: session.appUrl,
2647
2648
  sessionToken: session.sessionToken
@@ -2798,8 +2799,8 @@ const fsEditCommand = {
2798
2799
  handler: async (argv) => {
2799
2800
  const session = requireSession(argv);
2800
2801
  const remotePath = argv.path;
2801
- const { createRestClient } = await import("./rest-DADJh0bi.mjs");
2802
- const { resolveAgent } = await import("./print-ba_0hiV9.mjs");
2802
+ const { createRestClient } = await import("./rest-D29qNkto.mjs");
2803
+ const { resolveAgent } = await import("./print-Wakr3GJd.mjs");
2803
2804
  const client = createRestClient({
2804
2805
  appUrl: session.appUrl,
2805
2806
  sessionToken: session.sessionToken
@@ -4169,7 +4170,7 @@ if (process.argv.includes(UPDATE_WORKER_FLAG)) {
4169
4170
  process.exit(0);
4170
4171
  }
4171
4172
  if (process.argv.includes(PORTAL_DAEMON_FLAG)) {
4172
- const { runPortalDaemon } = await import("./daemon-CjzrUvXx.mjs");
4173
+ const { runPortalDaemon } = await import("./daemon-DUkLMp3w.mjs");
4173
4174
  runPortalDaemon(process.argv);
4174
4175
  } else runCli();
4175
4176
  function runCli() {
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import { D as setActiveWorkspace, E as listWorkspaces, S as themesForMode, _ as themeForMode, a as installCrashHandler, b as themeVersion, c as MARK_CELLS, d as DEFAULT_THEME_ID, f as applyTheme, g as theme, h as noColorRequested, i as writeArtifact, l as WORDMARK, m as monoTheme, n as profilingEnabled, o as buildCrashReport, p as findTheme, r as record, s as writeCrashReport, v as themeMode, w as getActiveWorkspaceId, y as themeModeFromColorFgBg } from "./profiler-LLaIFZgn.mjs";
3
- import { B as saveTheme, D as getSavedTheme, E as getReviewStateDir, L as resolveWebUrl, S as getConfigPath, c as cardActionErrorMessage, d as reconcileMaskedInput, f as resolveConnectUrl, i as resolveAgent, l as parseExternalOauthConnectParams, m as specKeyFor, p as parseConnectCard, s as MASK_CHAR, u as parseOauthConnectParams, v as DEFAULT_API_URL, y as DEFAULT_APP_URL } from "./print-CbayCa87.mjs";
2
+ import { D as setActiveWorkspace, E as listWorkspaces, S as themesForMode, _ as themeForMode, a as installCrashHandler, b as themeVersion, c as MARK_CELLS, d as DEFAULT_THEME_ID, f as applyTheme, g as theme, h as noColorRequested, i as writeArtifact, l as WORDMARK, m as monoTheme, n as profilingEnabled, o as buildCrashReport, p as findTheme, r as record, s as writeCrashReport, v as themeMode, w as getActiveWorkspaceId, y as themeModeFromColorFgBg } from "./profiler-wvJ7kT6W.mjs";
3
+ import { B as saveTheme, D as getSavedTheme, E as getReviewStateDir, L as resolveWebUrl, S as getConfigPath, c as cardActionErrorMessage, d as reconcileMaskedInput, f as resolveConnectUrl, i as resolveAgent, l as parseExternalOauthConnectParams, m as specKeyFor, p as parseConnectCard, s as MASK_CHAR, u as parseOauthConnectParams, v as DEFAULT_API_URL, y as DEFAULT_APP_URL } from "./print-BpuyEfWX.mjs";
4
4
  import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
5
- import { a as isRecord, i as errorMessage, n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-BY2nADw5.mjs";
5
+ import { a as isRecord, i as errorMessage, n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-I3imNduB.mjs";
6
6
  import { i as billingBlockedOutcomeFromSendResponse } from "./billing-blocked-2wju4gC_.mjs";
7
- import { t as PortalClient } from "./client-Dd5sMXPv.mjs";
8
- import "./daemon-BhArnzeW.mjs";
7
+ import { t as PortalClient } from "./client-CmEF9zrz.mjs";
8
+ import "./daemon-DXuENdsx.mjs";
9
9
  import { t as SandboxStream } from "./client-Cn2af31H.mjs";
10
- import { t as PortalDaemonClient } from "./daemon-client-DcuD4v12.mjs";
10
+ import { t as PortalDaemonClient } from "./daemon-client-DM8RYJDz.mjs";
11
11
  import { t as runRawPtyPassthrough } from "./raw-pty-B6mAroiI.mjs";
12
12
  import * as os$1 from "node:os";
13
13
  import { homedir, platform, release, tmpdir } from "node:os";
@@ -228,20 +228,31 @@ const useStore = create((set, get) => ({
228
228
  * `--agent`. When both are given, the conversation wins (it pins the agent
229
229
  * anyway).
230
230
  *
231
+ * None of these paths load the whole org agent roster. The only decision that
232
+ * needs every candidate is disambiguating a fuzzy name/slug selector; an id
233
+ * (or the `--resume` conversation's agentId) is exact and resolves with a
234
+ * single `getAgent`, and `--new` with no selector only needs "exactly one
235
+ * agent, or not", which a two-row page answers. A large org must never sit on
236
+ * the `connecting…` splash while the roster streams in.
237
+ *
231
238
  * Kept out of the React component so it's unit-testable under Node (the
232
239
  * component itself can't render without Bun + OpenTUI).
233
240
  */
241
+ /** A selector that is a bare uuid is an exact agent id: it can be resolved
242
+ * with a single `getAgent` fetch, so no boot path that carries one needs the
243
+ * org roster. A name/slug selector is the only case that can be ambiguous and
244
+ * therefore still needs the full candidate list to disambiguate. */
245
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
234
246
  async function resolveInitialScreen({ rest, agentSelector, conversationId, newConversation }) {
235
247
  if (conversationId) {
236
- const [conversation, agents] = await Promise.all([rest.getConversation({ conversationId }).catch((err) => {
248
+ const conversation = await rest.getConversation({ conversationId }).catch((err) => {
237
249
  if (err instanceof HttpError && err.status === 404) throw new Error(`No conversation with id ${conversationId}. It may have been deleted, or belong to another workspace.`);
238
250
  throw err;
239
- }), rest.listAgents({
240
- scope: "org",
241
- onPage: null
242
- })]);
243
- const agent = agents.find((a) => a.id === conversation.agentId);
244
- if (!agent) throw new Error(`Conversation ${conversationId} belongs to an agent you can't access.`);
251
+ });
252
+ const agent = await rest.getAgent({ agentId: conversation.agentId }).catch((err) => {
253
+ if (err instanceof HttpError && (err.status === 404 || err.status === 403)) throw new Error(`Conversation ${conversationId} belongs to an agent you can't access.`);
254
+ throw err;
255
+ });
245
256
  return {
246
257
  kind: "chat",
247
258
  agent,
@@ -263,12 +274,32 @@ async function resolveInitialScreen({ rest, agentSelector, conversationId, newCo
263
274
  };
264
275
  }
265
276
  if (!agentSelector && !newConversation) return { kind: "agent-picker" };
266
- const agents = await rest.listAgents({
267
- scope: "org",
268
- onPage: null
269
- });
277
+ if (agentSelector && UUID_RE.test(agentSelector)) {
278
+ const agent = await rest.getAgent({ agentId: agentSelector }).catch((err) => {
279
+ if (err instanceof HttpError && (err.status === 404 || err.status === 403)) throw new Error(`No agent with id ${agentSelector}. It may not exist, or belong to another workspace.`);
280
+ throw err;
281
+ });
282
+ if (newConversation) return {
283
+ kind: "chat",
284
+ agent,
285
+ conversation: {
286
+ kind: "new",
287
+ agentId: agent.id,
288
+ nonce: crypto.randomUUID()
289
+ }
290
+ };
291
+ return {
292
+ kind: "conversation-picker",
293
+ agent
294
+ };
295
+ }
270
296
  if (!agentSelector && newConversation) {
271
- const only = agents.length === 1 ? agents[0] : null;
297
+ const firstTwo = await rest.listAgents({
298
+ scope: "org",
299
+ onPage: null,
300
+ limit: 2
301
+ });
302
+ const only = firstTwo.length === 1 ? firstTwo[0] : null;
272
303
  if (!only) return { kind: "agent-picker" };
273
304
  return {
274
305
  kind: "chat",
@@ -280,7 +311,10 @@ async function resolveInitialScreen({ rest, agentSelector, conversationId, newCo
280
311
  }
281
312
  };
282
313
  }
283
- const agent = resolveAgent(agents, agentSelector);
314
+ const agent = resolveAgent(await rest.listAgents({
315
+ scope: "org",
316
+ onPage: null
317
+ }), agentSelector);
284
318
  if (newConversation) return {
285
319
  kind: "chat",
286
320
  agent,
@@ -337,7 +371,7 @@ function createPortalClient(opts) {
337
371
  start: () => Promise.resolve(),
338
372
  enable: () => client.enable(),
339
373
  disable: () => client.disable(),
340
- grantAgent: (agentId) => client.grantAgent(agentId),
374
+ grantAgent: (agentId, conversationId) => client.grantAgent(agentId, conversationId),
341
375
  decline: (_agentId, declined) => {
342
376
  if (declined && client.grantedAgentIds().length === 0) client.disable();
343
377
  },
@@ -10163,6 +10197,7 @@ function ChatScreen({ agent, conversation }) {
10163
10197
  const portalClient = useStore((s) => s.portalClient);
10164
10198
  const setPortalPrompt = useStore((s) => s.setPortalPrompt);
10165
10199
  const declinedRef = useRef(/* @__PURE__ */ new Set());
10200
+ const grantInFlightRef = useRef(/* @__PURE__ */ new Set());
10166
10201
  const grantPrompt = portal.prompt && portal.prompt.agentId === agent.id ? portal.prompt : null;
10167
10202
  const isShared = portal.status !== "off";
10168
10203
  const initialConversationId = isNewConversation(conversation) ? null : conversation.id;
@@ -10615,12 +10650,14 @@ function ChatScreen({ agent, conversation }) {
10615
10650
  });
10616
10651
  (async () => {
10617
10652
  if (autoGrantMachine && portalClient && portalStatus === "connected" && !portalClient.isGranted(agent.id)) try {
10618
- await portalClient.grantAgent(agent.id);
10653
+ grantInFlightRef.current.add(agent.id);
10654
+ await portalClient.grantAgent(agent.id, null);
10619
10655
  useStore.getState().showToast({
10620
- message: `Shared this machine with ${agent.name} for the import. The grant persists. Revoke anytime: skydive portal revoke --agent ${agent.name}`,
10656
+ message: `Shared this machine with ${agent.name} for the import. The grant persists. Revoke anytime: skydive portal revoke --agent "${agent.name}"`,
10621
10657
  durationMs: 1e4
10622
10658
  });
10623
10659
  } catch (err) {
10660
+ grantInFlightRef.current.delete(agent.id);
10624
10661
  useStore.getState().showToast({
10625
10662
  message: `Couldn't grant machine access automatically (${errorMessage(err)}). The agent will ask in chat instead.`,
10626
10663
  variant: "warning"
@@ -10684,9 +10721,12 @@ function ChatScreen({ agent, conversation }) {
10684
10721
  if (!portalClient) throw new Error("portal is unavailable in this session");
10685
10722
  const justEnabled = portal.status === "off";
10686
10723
  if (justEnabled) portalClient.enable();
10724
+ grantInFlightRef.current.add(action.agentId);
10725
+ if (useStore.getState().portal.prompt?.agentId === action.agentId) setPortalPrompt(null);
10687
10726
  try {
10688
- await portalClient.grantAgent(action.agentId);
10727
+ await portalClient.grantAgent(action.agentId, action.conversationId);
10689
10728
  } catch (grantErr) {
10729
+ grantInFlightRef.current.delete(action.agentId);
10690
10730
  if (justEnabled) portalClient.disable();
10691
10731
  throw grantErr;
10692
10732
  }
@@ -10747,7 +10787,8 @@ function ChatScreen({ agent, conversation }) {
10747
10787
  appUrl,
10748
10788
  updateCard,
10749
10789
  portalClient,
10750
- portal.status
10790
+ portal.status,
10791
+ setPortalPrompt
10751
10792
  ]);
10752
10793
  const promptForCards = useCallback(() => {
10753
10794
  const targets = itemsRef.current.filter(isActionableCard);
@@ -11290,19 +11331,24 @@ function ChatScreen({ agent, conversation }) {
11290
11331
  if (!portalClient) return;
11291
11332
  const agentName = agent.name;
11292
11333
  setPortalPrompt(null);
11293
- portalClient.grantAgent(agent.id).then(() => useStore.getState().showToast({
11334
+ grantInFlightRef.current.add(agent.id);
11335
+ portalClient.grantAgent(agent.id, conversationId).then(() => useStore.getState().showToast({
11294
11336
  variant: "success",
11295
11337
  message: `shared this machine with ${agentName}`
11296
- })).catch((err) => setItems((prev) => [...prev, {
11297
- kind: "error",
11298
- id: crypto.randomUUID(),
11299
- text: `couldn't share machine: ${errorDetail(err)}`
11300
- }]));
11338
+ })).catch((err) => {
11339
+ grantInFlightRef.current.delete(agent.id);
11340
+ setItems((prev) => [...prev, {
11341
+ kind: "error",
11342
+ id: crypto.randomUUID(),
11343
+ text: `couldn't share machine: ${errorDetail(err)}`
11344
+ }]);
11345
+ });
11301
11346
  }, [
11302
11347
  portalClient,
11303
11348
  agent.id,
11304
11349
  agent.name,
11305
- setPortalPrompt
11350
+ setPortalPrompt,
11351
+ conversationId
11306
11352
  ]);
11307
11353
  const declineGrant = useCallback(() => {
11308
11354
  declinedRef.current.add(agent.id);
@@ -11542,6 +11588,7 @@ function ChatScreen({ agent, conversation }) {
11542
11588
  if (portal.grantedAgentIds.includes(agent.id)) return;
11543
11589
  if (portal.declinedAgentIds.includes(agent.id)) return;
11544
11590
  if (declinedRef.current.has(agent.id)) return;
11591
+ if (grantInFlightRef.current.has(agent.id)) return;
11545
11592
  if (portal.prompt?.agentId === agent.id) return;
11546
11593
  setPortalPrompt({
11547
11594
  agentId: agent.id,
@@ -11565,6 +11612,14 @@ function ChatScreen({ agent, conversation }) {
11565
11612
  setPortalPrompt,
11566
11613
  agentHost
11567
11614
  ]);
11615
+ useEffect(() => {
11616
+ for (const agentId of portal.grantedAgentIds) grantInFlightRef.current.delete(agentId);
11617
+ if (portal.prompt && portal.grantedAgentIds.includes(portal.prompt.agentId)) setPortalPrompt(null);
11618
+ }, [
11619
+ portal.grantedAgentIds,
11620
+ portal.prompt,
11621
+ setPortalPrompt
11622
+ ]);
11568
11623
  useKeyboard((key) => {
11569
11624
  if (reviewOpen) {
11570
11625
  if (key.name === "g" && key.ctrl) {
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { t as PortalClient } from "./client-CmEF9zrz.mjs";
3
+
4
+ export { PortalClient };
@@ -361,10 +361,13 @@ async function mintPortalDeviceToken(auth) {
361
361
  const json = await portalFetch(auth, "/api/v1/portal/device-token", { method: "POST" });
362
362
  return deviceTokenSchema.parse(json).token;
363
363
  }
364
- async function grantPortalAccess(auth, { deviceId, agentId }) {
364
+ async function grantPortalAccess(auth, { deviceId, agentId, conversationId }) {
365
365
  await portalFetch(auth, `/api/v1/portal/devices/${encodeURIComponent(deviceId)}/grants`, {
366
366
  method: "POST",
367
- body: JSON.stringify({ agentId })
367
+ body: JSON.stringify({
368
+ agentId,
369
+ conversationId
370
+ })
368
371
  });
369
372
  }
370
373
  async function revokePortalAccess(auth, { deviceId, agentId }) {
@@ -396,7 +399,8 @@ async function unifyLegacyCliGrants(auth, machineName) {
396
399
  const missing = legacy.grantedAgentIds.filter((id) => !have.has(id));
397
400
  for (const agentId of missing) await grantPortalAccess(auth, {
398
401
  deviceId: merged.id,
399
- agentId
402
+ agentId,
403
+ conversationId: null
400
404
  });
401
405
  return missing.length;
402
406
  }
@@ -469,13 +473,19 @@ var PortalClient = class {
469
473
  this.ws?.close();
470
474
  this.ws = null;
471
475
  }
472
- /** Grant one agent access to this machine (default-deny; user-initiated). */
473
- async grantAgent(agentId) {
476
+ /**
477
+ * Grant one agent access to this machine (default-deny; user-initiated).
478
+ * `conversationId` is the conversation whose run asked (null when the grant
479
+ * is not answering an in-chat request) — the server uses it to wake the
480
+ * waiting agent.
481
+ */
482
+ async grantAgent(agentId, conversationId) {
474
483
  const auth = this.sessionAuth();
475
484
  if (!auth) throw new Error("granting needs a signed-in CLI session on this machine (the desktop connection alone cannot manage grants)");
476
485
  await grantPortalAccess(auth, {
477
486
  deviceId: await this.ensureDeviceId(),
478
- agentId
487
+ agentId,
488
+ conversationId
479
489
  });
480
490
  this.granted.add(agentId);
481
491
  this.emit();
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import "./client-Dd5sMXPv.mjs";
3
- import { a as runPortalDaemon, i as queryDaemonStatus, n as ensureDaemonRunning, o as startPortalDaemon, r as isDaemonListening, s as stopDaemon, t as PortalDaemon } from "./daemon-BhArnzeW.mjs";
2
+ import "./client-CmEF9zrz.mjs";
3
+ import { a as runPortalDaemon, i as queryDaemonStatus, n as ensureDaemonRunning, o as startPortalDaemon, r as isDaemonListening, s as stopDaemon, t as PortalDaemon } from "./daemon-DXuENdsx.mjs";
4
4
 
5
5
  export { runPortalDaemon };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { s as isRecord, t as PortalClient } from "./client-Dd5sMXPv.mjs";
2
+ import { s as isRecord, t as PortalClient } from "./client-CmEF9zrz.mjs";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { z } from "zod";
@@ -20,7 +20,7 @@ import { access, appendFile, mkdir, readFile, unlink, writeFile } from "node:fs/
20
20
  * the commit time of the built tree is one monotonic clock they share.
21
21
  */
22
22
  function portalDaemonBuild() {
23
- return "1786586981";
23
+ return "1786590033";
24
24
  }
25
25
  /**
26
26
  * Whether a client carrying `mine` should replace a running daemon carrying
@@ -175,10 +175,18 @@ const clientCwdSchema = z.object({
175
175
  t: z.literal("cwd"),
176
176
  cwd: z.string().min(1)
177
177
  });
178
- /** `grant` — authorize one agent to reach this machine (user-initiated). */
178
+ /**
179
+ * `grant` — authorize one agent to reach this machine (user-initiated).
180
+ * `conversationId` names the conversation whose run asked for access (the
181
+ * in-chat approval card); the grant carries it upstream so the server wakes
182
+ * the waiting agent. Defaults to null on parse so hellos from CLI builds that
183
+ * predate the field keep granting against a newer daemon — they just skip the
184
+ * wake, same as before the field existed.
185
+ */
179
186
  const clientGrantSchema = z.object({
180
187
  t: z.literal("grant"),
181
- agentId: z.string().min(1)
188
+ agentId: z.string().min(1),
189
+ conversationId: z.string().nullish().default(null)
182
190
  });
183
191
  /**
184
192
  * `decline` — the user answered no to an agent's request for this machine
@@ -455,7 +463,7 @@ var PortalDaemon = class {
455
463
  case "grant":
456
464
  this.declined.delete(msg.agentId);
457
465
  this.ensureClient();
458
- this.client?.grantAgent(msg.agentId).catch((error) => {
466
+ this.client?.grantAgent(msg.agentId, msg.conversationId).catch((error) => {
459
467
  this.logError("grantAgent failed", error);
460
468
  });
461
469
  return;
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ import "./client-CmEF9zrz.mjs";
3
+ import "./daemon-DXuENdsx.mjs";
4
+ import { t as PortalDaemonClient } from "./daemon-client-DM8RYJDz.mjs";
5
+
6
+ export { PortalDaemonClient };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { c as LOCAL_PROTOCOL_VERSION, d as encodeLine, f as makeLineParser, n as ensureDaemonRunning, p as parseDaemonMessage, u as daemonPaths } from "./daemon-BhArnzeW.mjs";
2
+ import { c as LOCAL_PROTOCOL_VERSION, d as encodeLine, f as makeLineParser, n as ensureDaemonRunning, p as parseDaemonMessage, u as daemonPaths } from "./daemon-DXuENdsx.mjs";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { connect } from "node:net";
5
5
 
@@ -12,7 +12,7 @@ var PortalDaemonClient = class {
12
12
  lastBind = null;
13
13
  fallbackCwd = null;
14
14
  wantEnabled = false;
15
- pendingGrants = /* @__PURE__ */ new Set();
15
+ pendingGrants = /* @__PURE__ */ new Map();
16
16
  pendingDeclines = /* @__PURE__ */ new Map();
17
17
  grantedAgentIds = /* @__PURE__ */ new Set();
18
18
  constructor(opts) {
@@ -50,9 +50,10 @@ var PortalDaemonClient = class {
50
50
  conversationId: this.lastBind.conversationId,
51
51
  cwd: this.lastBind.cwd
52
52
  });
53
- for (const agentId of this.pendingGrants) this.send({
53
+ for (const [agentId, conversationId] of this.pendingGrants) this.send({
54
54
  t: "grant",
55
- agentId
55
+ agentId,
56
+ conversationId
56
57
  });
57
58
  for (const [agentId, declined] of this.pendingDeclines) this.send({
58
59
  t: "decline",
@@ -157,13 +158,16 @@ var PortalDaemonClient = class {
157
158
  * Authorize one agent to run commands on this machine. Resolves once the
158
159
  * request is sent to the daemon (the daemon performs the grant and pushes the
159
160
  * updated state); kept async so it's a drop-in for the old in-process client's
160
- * awaited `grantAgent`.
161
+ * awaited `grantAgent`. `conversationId` names the conversation whose run
162
+ * asked, so the grant can wake the waiting agent; null when the grant is not
163
+ * answering an in-chat request.
161
164
  */
162
- grantAgent(agentId) {
163
- this.pendingGrants.add(agentId);
165
+ grantAgent(agentId, conversationId) {
166
+ this.pendingGrants.set(agentId, conversationId);
164
167
  this.send({
165
168
  t: "grant",
166
- agentId
169
+ agentId,
170
+ conversationId
167
171
  });
168
172
  return Promise.resolve();
169
173
  }
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
3
- import { a as isRecord, i as errorMessage, r as sendErrorMessage, t as createRestClient } from "./rest-BY2nADw5.mjs";
3
+ import { a as isRecord, i as errorMessage, r as sendErrorMessage, t as createRestClient } from "./rest-I3imNduB.mjs";
4
4
  import { i as billingBlockedOutcomeFromSendResponse, n as BillingBlockedError } from "./billing-blocked-2wju4gC_.mjs";
5
5
  import path from "node:path";
6
6
  import Conf from "conf";
@@ -9,7 +9,7 @@ import stableStringify from "safe-stable-stringify";
9
9
 
10
10
  //#region src/config.ts
11
11
  /** Default host for the public management API (`/v1`, API-key auth). */
12
- const DEFAULT_API_URL = "https://api.skydive.com";
12
+ const DEFAULT_API_URL = typeof SKYDIVE_BUILD_API_URL === "string" ? SKYDIVE_BUILD_API_URL : "https://api.skydive.com";
13
13
  /**
14
14
  * Default origin for the interactive chat client (`skydive chat`).
15
15
  *
@@ -20,9 +20,9 @@ const DEFAULT_API_URL = "https://api.skydive.com";
20
20
  * `DEFAULT_API_URL`; override with `--api-url` / `SKYDIVE_APP_URL` for local
21
21
  * dev or while the DNS record is still being provisioned.
22
22
  */
23
- const DEFAULT_APP_URL = "https://api.skydive.com";
23
+ const DEFAULT_APP_URL = DEFAULT_API_URL;
24
24
  /** Web front door, for pages opened in the user's browser. */
25
- const DEFAULT_WEB_URL = "https://skydive.com";
25
+ const DEFAULT_WEB_URL = typeof SKYDIVE_BUILD_WEB_URL === "string" ? SKYDIVE_BUILD_WEB_URL : "https://skydive.com";
26
26
  function resolveWebUrl(appUrl) {
27
27
  if (appUrl == null) return appUrl;
28
28
  return appUrl === DEFAULT_APP_URL ? DEFAULT_WEB_URL : appUrl;
@@ -282,14 +282,15 @@ function parseButton(element) {
282
282
  const params = isRecord(press.params) ? press.params : {};
283
283
  const primary = props.variant === "primary";
284
284
  if (press.action === "approve_portal_access") {
285
- const agentId = params.agentId;
286
- if (typeof agentId !== "string" || !agentId) return null;
285
+ const agentId = optionalString(params.agentId);
286
+ if (!agentId) return null;
287
287
  return {
288
288
  label,
289
289
  action: {
290
290
  kind: "grant_portal",
291
291
  agentId,
292
- deviceId: typeof params.deviceId === "string" && params.deviceId ? params.deviceId : null
292
+ deviceId: optionalString(params.deviceId),
293
+ conversationId: optionalString(params.conversationId)
293
294
  },
294
295
  primary
295
296
  };
@@ -323,12 +324,46 @@ function parseButton(element) {
323
324
  function specKeyFor(spec) {
324
325
  return stableStringify(spec) ?? crypto.randomUUID();
325
326
  }
327
+ /**
328
+ * The `platform portal request` consent card. The server emits one
329
+ * DesktopHandoffCard spec for every surface (api routes/portal.ts): the web
330
+ * renders it as the "continue in the Skydive desktop app" handoff, but this
331
+ * terminal's own portal daemon can provide the machine, so here it maps onto
332
+ * the existing grant_portal approval instead of a pointer to the app. Without
333
+ * this the spec is not a Card, parses to null, and the request is silently
334
+ * dropped — `platform portal request` looks desktop-only from the CLI
335
+ * (ANY-6521).
336
+ */
337
+ function parseDesktopHandoffCard(rootEl) {
338
+ const props = isRecord(rootEl.props) ? rootEl.props : {};
339
+ const agentId = optionalString(props.agentId);
340
+ if (!agentId) return null;
341
+ const agentName = optionalString(props.agentName) ?? "This agent";
342
+ return {
343
+ title: `Let ${agentName} use your computer?`,
344
+ subtitle: null,
345
+ description: `Approving shares this machine with ${agentName} while you're signed in. Revoke anytime: skydive portal revoke --agent "${agentName}"`,
346
+ fields: [],
347
+ button: {
348
+ label: "Approve",
349
+ action: {
350
+ kind: "grant_portal",
351
+ agentId,
352
+ deviceId: optionalString(props.deviceId),
353
+ conversationId: optionalString(props.conversationId)
354
+ }
355
+ },
356
+ state: {}
357
+ };
358
+ }
326
359
  function parseConnectCard(spec) {
327
360
  if (!isRecord(spec)) return null;
328
361
  const { root, elements } = spec;
329
362
  if (typeof root !== "string" || !isRecord(elements)) return null;
330
363
  const rootEl = elements[root];
331
- if (!isRecord(rootEl) || rootEl.type !== "Card") return null;
364
+ if (!isRecord(rootEl)) return null;
365
+ if (rootEl.type === "DesktopHandoffCard") return parseDesktopHandoffCard(rootEl);
366
+ if (rootEl.type !== "Card") return null;
332
367
  const rootProps = isRecord(rootEl.props) ? rootEl.props : {};
333
368
  const title = optionalString(rootProps.title);
334
369
  if (!title) return null;
@@ -572,7 +607,7 @@ async function runPrint({ appUrl, sessionToken, prompt, agentSelector, conversat
572
607
  onPage: null
573
608
  }), agentSelector);
574
609
  if (machineShare) {
575
- if (!machineShare.isGranted(agent.id) && grantTargetAgent) await machineShare.grantAgent(agent.id);
610
+ if (!machineShare.isGranted(agent.id) && grantTargetAgent) await machineShare.grantAgent(agent.id, null);
576
611
  if (machineShare.isGranted(agent.id)) console.error(`portal: shared this machine with ${agent.name} for this run (grant persists until revoked)`);
577
612
  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
613
  }
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-CbayCa87.mjs";
3
- import "./rest-BY2nADw5.mjs";
2
+ import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-BpuyEfWX.mjs";
3
+ import "./rest-I3imNduB.mjs";
4
4
  import "./billing-blocked-2wju4gC_.mjs";
5
5
 
6
6
  export { messageGet, readStdin, resolveAgent, runPrint };
@@ -14,7 +14,7 @@ import { n as printError } from "./output-DYzzdXYV.mjs";
14
14
  * the reply (and to --json).
15
15
  */
16
16
  async function connectMachineShare({ appUrl, sessionToken, timeoutHint }) {
17
- const { PortalClient } = await import("./client-DZstLQ_1.mjs");
17
+ const { PortalClient } = await import("./client-CGwes_WT.mjs");
18
18
  let signalConnected;
19
19
  const connected = new Promise((resolve) => {
20
20
  signalConnected = resolve;
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { S as getConfigPath } from "./print-CbayCa87.mjs";
2
+ import { S as getConfigPath } from "./print-BpuyEfWX.mjs";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { err, ok } from "neverthrow";
@@ -8,7 +8,7 @@ import fs from "node:fs";
8
8
 
9
9
  //#region package.json
10
10
  var name = "skydive-cli";
11
- var version$1 = "0.5.0-beta.5";
11
+ var version$1 = "0.5.0-beta.7";
12
12
 
13
13
  //#endregion
14
14
  //#region src/auth/organization.ts
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
3
- import { n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-BY2nADw5.mjs";
3
+ import { n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-I3imNduB.mjs";
4
4
  import "./billing-blocked-2wju4gC_.mjs";
5
5
 
6
6
  export { createRestClient };
@@ -193,13 +193,14 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
193
193
  }
194
194
  };
195
195
  return {
196
- listAgents: async ({ scope, onPage }) => {
196
+ listAgents: async ({ scope, onPage, limit }) => {
197
197
  const all = [];
198
198
  let cursor;
199
- const maxAgents = 2e3;
199
+ const maxAgents = limit ?? 2e3;
200
+ const pageSize = Math.min(100, maxAgents);
200
201
  do {
201
202
  const params = new URLSearchParams({
202
- limit: "100",
203
+ limit: String(pageSize),
203
204
  scope,
204
205
  sort: "mine_first_usage",
205
206
  includeStats: "false"
@@ -218,10 +219,7 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
218
219
  },
219
220
  getAgent: async ({ agentId }) => {
220
221
  const { agent } = await get(`/api/v1/agents/${encodeURIComponent(agentId)}`, getAgentResponseSchema);
221
- return {
222
- id: agent.id,
223
- name: agent.name
224
- };
222
+ return agent;
225
223
  },
226
224
  suggestAgentIdentity: async () => {
227
225
  const { suggestion } = await get("/api/v1/agents/suggest", suggestAgentResponseSchema);
@@ -453,10 +451,7 @@ const listAgentsResponseSchema = z.object({
453
451
  totalCount: z.number().nullable().optional()
454
452
  });
455
453
  const createAgentResponseSchema = z.object({ agent: agentSummarySchema });
456
- const getAgentResponseSchema = z.object({ agent: z.object({
457
- id: z.string(),
458
- name: z.string()
459
- }).passthrough() });
454
+ const getAgentResponseSchema = z.object({ agent: agentSummarySchema });
460
455
  const agentSuggestionSchema = z.object({ name: z.string() });
461
456
  const suggestAgentResponseSchema = z.object({ suggestion: agentSuggestionSchema.nullable() });
462
457
  const conversationAgentSchema = z.object({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skydive-cli",
3
- "version": "0.5.0-beta.5",
3
+ "version": "0.5.0-beta.7",
4
4
  "description": "Skydive CLI — cloud agents from the command line",
5
5
  "homepage": "https://skydive.com",
6
6
  "license": "MIT",
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env node
2
- import { t as PortalClient } from "./client-Dd5sMXPv.mjs";
3
-
4
- export { PortalClient };
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env node
2
- import "./client-Dd5sMXPv.mjs";
3
- import "./daemon-BhArnzeW.mjs";
4
- import { t as PortalDaemonClient } from "./daemon-client-DcuD4v12.mjs";
5
-
6
- export { PortalDaemonClient };