skydive-cli 0.3.0 → 0.4.0

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.
Files changed (53) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +28 -2
  3. package/dist/js/billing-blocked-CwfG1BTR.mjs +71 -0
  4. package/dist/js/billing-blocked-euid6MN9.mjs +4 -0
  5. package/dist/js/bin.mjs +247 -89
  6. package/dist/js/{boot-HAgDg_b4.mjs → boot-on5ucI_Z.mjs} +228 -50
  7. package/dist/js/chat/tui/chunks/grammars/assets/bash.highlights.scm +56 -0
  8. package/dist/js/chat/tui/chunks/grammars/assets/bash.wasm +0 -0
  9. package/dist/js/chat/tui/chunks/grammars/assets/c.highlights.scm +81 -0
  10. package/dist/js/chat/tui/chunks/grammars/assets/c.wasm +0 -0
  11. package/dist/js/chat/tui/chunks/grammars/assets/cpp.highlights.scm +70 -0
  12. package/dist/js/chat/tui/chunks/grammars/assets/cpp.wasm +0 -0
  13. package/dist/js/chat/tui/chunks/grammars/assets/css.highlights.scm +76 -0
  14. package/dist/js/chat/tui/chunks/grammars/assets/css.wasm +0 -0
  15. package/dist/js/chat/tui/chunks/grammars/assets/go.highlights.scm +123 -0
  16. package/dist/js/chat/tui/chunks/grammars/assets/go.wasm +0 -0
  17. package/dist/js/chat/tui/chunks/grammars/assets/html.highlights.scm +13 -0
  18. package/dist/js/chat/tui/chunks/grammars/assets/html.wasm +0 -0
  19. package/dist/js/chat/tui/chunks/grammars/assets/java.highlights.scm +149 -0
  20. package/dist/js/chat/tui/chunks/grammars/assets/java.wasm +0 -0
  21. package/dist/js/chat/tui/chunks/grammars/assets/json.highlights.scm +16 -0
  22. package/dist/js/chat/tui/chunks/grammars/assets/json.wasm +0 -0
  23. package/dist/js/chat/tui/chunks/grammars/assets/lua.highlights.scm +201 -0
  24. package/dist/js/chat/tui/chunks/grammars/assets/lua.wasm +0 -0
  25. package/dist/js/chat/tui/chunks/grammars/assets/python.highlights.scm +137 -0
  26. package/dist/js/chat/tui/chunks/grammars/assets/python.wasm +0 -0
  27. package/dist/js/chat/tui/chunks/grammars/assets/ruby.highlights.scm +154 -0
  28. package/dist/js/chat/tui/chunks/grammars/assets/ruby.wasm +0 -0
  29. package/dist/js/chat/tui/chunks/grammars/assets/rust.highlights.scm +161 -0
  30. package/dist/js/chat/tui/chunks/grammars/assets/rust.wasm +0 -0
  31. package/dist/js/chat/tui/chunks/grammars/assets/toml.highlights.scm +53 -0
  32. package/dist/js/chat/tui/chunks/grammars/assets/toml.wasm +0 -0
  33. package/dist/js/chat/tui/chunks/grammars/assets/yaml.highlights.scm +79 -0
  34. package/dist/js/chat/tui/chunks/grammars/assets/yaml.wasm +0 -0
  35. package/dist/js/chat/tui/chunks/grammars/grammars.ts +146 -0
  36. package/dist/js/{client-Dc7GZ3PG.mjs → client-CKzK-12y.mjs} +1 -1
  37. package/dist/js/client-D6apKmV0.mjs +6 -0
  38. package/dist/js/daemon-BADmpJAs.mjs +7 -0
  39. package/dist/js/{daemon-Co4CtpXZ.mjs → daemon-CaFz7Wtw.mjs} +2 -2
  40. package/dist/js/{install-CMYBFvR2.mjs → install-Ba-UTi-l.mjs} +2 -2
  41. package/dist/js/{print-CPCFci60.mjs → print-BXNq9UxE.mjs} +3 -2
  42. package/dist/js/{print-Cd-bVSEJ.mjs → print-X2N5wJ5l.mjs} +84 -2
  43. package/dist/js/{print-share-7qfpQtWK.mjs → print-share-D72CfWJk.mjs} +1 -1
  44. package/dist/js/{raw-pty-BcjbTjHJ.mjs → raw-pty-DAmb8uqt.mjs} +1 -1
  45. package/dist/js/raw-pty-t5DC-e1T.mjs +5 -0
  46. package/dist/js/rest-Br2eFTlj.mjs +5 -0
  47. package/dist/js/{rest-BlN_uWmL.mjs → rest-imDZZGQA.mjs} +10 -2
  48. package/package.json +1 -1
  49. package/dist/js/client-DuwxEDG4.mjs +0 -5
  50. package/dist/js/daemon-Ch_MSDmQ.mjs +0 -6
  51. package/dist/js/raw-pty-ChUHav4d.mjs +0 -5
  52. package/dist/js/rest-CDTXCmUb.mjs +0 -4
  53. /package/dist/js/{client-DfcJFEbh.mjs → client-D8s9vY4p.mjs} +0 -0
package/dist/js/bin.mjs CHANGED
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { C as setActiveWorkspace, S as listWorkspaces, T as version, _ as themes, b as getActiveWorkspaceId, o as brandHelpArt, t as installCrashHandler, w as name, x as getSessionIdentity, y as ensureActiveOrganization } from "./install-CMYBFvR2.mjs";
3
- import { A as resolveAppUrl, C as getPromptHistoryPath, D as getStoredApiKeyId, E as getShareMachineDefault, F as saveConfig, I as saveSession, M as resolveManagementAuth, N as resolveSession, O as getStoredApiKeyWorkspaceName, R as setLastSeenVersion, S as getLastSeenVersion, _ as API_KEY_PREFIX, b as deleteConfig, g as API_KEY_FAMILY_PREFIX, h as API_KEYS_URL, i as resolveAgent, j as resolveConfig, k as getUpdateCheckDisabled, v as DEFAULT_API_URL, x as getConfigPath } from "./print-Cd-bVSEJ.mjs";
2
+ import { C as setActiveWorkspace, S as listWorkspaces, T as version, _ as themes, b as getActiveWorkspaceId, o as brandHelpArt, t as installCrashHandler, w as name, x as getSessionIdentity, y as ensureActiveOrganization } from "./install-Ba-UTi-l.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-X2N5wJ5l.mjs";
4
4
  import { n as printError, r as printTable, t as output } from "./output-DYzzdXYV.mjs";
5
- import { n as createRestClient, t as HttpError } from "./rest-BlN_uWmL.mjs";
6
- import { a as registerPortalDevice, i as grantPortalAccess, n as fetchPortalDevices, o as revokePortalAccess, r as findThisDevice, s as machineIdentity } from "./client-Dc7GZ3PG.mjs";
7
- import { c as PORTAL_DAEMON_FLAG, i as queryDaemonStatus, l as daemonPaths, n as ensureDaemonRunning, o as stopDaemon } from "./daemon-Co4CtpXZ.mjs";
8
- import { t as SandboxStream } from "./client-DfcJFEbh.mjs";
5
+ import { n as createRestClient, t as HttpError } from "./rest-imDZZGQA.mjs";
6
+ import "./billing-blocked-CwfG1BTR.mjs";
7
+ import { a as registerPortalDevice, i as grantPortalAccess, n as fetchPortalDevices, o as revokePortalAccess, r as findThisDevice, s as machineIdentity } from "./client-CKzK-12y.mjs";
8
+ import { c as PORTAL_DAEMON_FLAG, i as queryDaemonStatus, l as daemonPaths, n as ensureDaemonRunning, o as stopDaemon } from "./daemon-CaFz7Wtw.mjs";
9
+ import { t as SandboxStream } from "./client-D8s9vY4p.mjs";
9
10
  import { hideBin } from "yargs/helpers";
10
11
  import yargs from "yargs";
11
12
  import os, { hostname } from "node:os";
@@ -72,31 +73,24 @@ var SkydiveApiClient = class {
72
73
  this.authKind = config.kind;
73
74
  }
74
75
  /**
75
- * List agents in the caller's workspace, following the server's cursor
76
- * until `limit` is satisfied. The endpoint caps one page at
77
- * {@link MAX_AGENT_PAGE}, so a larger limit takes several requests
78
- * callers just ask for what they want. `hasMore` reports whether the
79
- * roster continues past what was returned.
76
+ * List one page of agents in the caller's workspace. The server caps a
77
+ * page at {@link MAX_AGENT_PAGE}, so `limit` is clamped to that. Callers
78
+ * page forward by passing the returned `nextCursor` back in as `cursor`;
79
+ * `nextCursor` is null once the roster is exhausted.
80
80
  */
81
81
  async listAgents(params) {
82
- const agents = [];
83
- let cursor = null;
84
- do {
85
- const query = new URLSearchParams({ limit: String(Math.min(MAX_AGENT_PAGE, params.limit - agents.length)) });
86
- if (params.scope) query.set("scope", params.scope);
87
- if (cursor) query.set("cursor", cursor);
88
- const page = await this.request({
89
- method: "GET",
90
- path: `/agents?${query.toString()}`,
91
- schema: ListAgentsResponseSchema
92
- });
93
- if (page.isErr()) return err(page.error);
94
- agents.push(...page.value.agents);
95
- cursor = page.value.nextCursor;
96
- } while (cursor && agents.length < params.limit);
82
+ const query = new URLSearchParams({ limit: String(Math.min(MAX_AGENT_PAGE, params.limit)) });
83
+ if (params.scope) query.set("scope", params.scope);
84
+ if (params.cursor) query.set("cursor", params.cursor);
85
+ const page = await this.request({
86
+ method: "GET",
87
+ path: `/agents?${query.toString()}`,
88
+ schema: ListAgentsResponseSchema
89
+ });
90
+ if (page.isErr()) return err(page.error);
97
91
  return ok({
98
- agents,
99
- hasMore: cursor !== null
92
+ agents: page.value.agents,
93
+ nextCursor: page.value.nextCursor
100
94
  });
101
95
  }
102
96
  async getAgent(id) {
@@ -1047,22 +1041,29 @@ function requireManagementClient(argv) {
1047
1041
  }
1048
1042
  /** Resolve the session (see {@link requireSession}) and build a REST client. */
1049
1043
  function requireRestClient(argv) {
1050
- const session = requireSession(argv);
1044
+ const auth = resolveChatAuth({ appUrl: resolveAppUrl({ appUrl: argv["api-url"] }) });
1045
+ if (auth.isErr()) {
1046
+ printError(`${auth.error.message} Run \`skydive auth login\` first, or set SKYDIVE_API_KEY / SKYDIVE_SESSION_TOKEN.`);
1047
+ process.exit(1);
1048
+ }
1051
1049
  return createRestClient({
1052
- appUrl: session.appUrl,
1053
- sessionToken: session.sessionToken
1050
+ appUrl: auth.value.appUrl,
1051
+ sessionToken: auth.value.token
1054
1052
  });
1055
1053
  }
1056
1054
 
1057
1055
  //#endregion
1058
1056
  //#region src/commands/agents.ts
1059
- const listCommand$4 = {
1057
+ const listCommand$5 = {
1060
1058
  command: "list",
1061
1059
  describe: "List agents in the active workspace",
1062
1060
  builder: (y) => y.option("limit", {
1063
1061
  type: "number",
1064
1062
  default: 20,
1065
- describe: "Max results"
1063
+ describe: "Results per page"
1064
+ }).option("cursor", {
1065
+ type: "string",
1066
+ describe: "Page token from a previous list to fetch the next page"
1066
1067
  }).option("scope", {
1067
1068
  type: "string",
1068
1069
  choices: ["mine", "org"],
@@ -1071,15 +1072,19 @@ const listCommand$4 = {
1071
1072
  handler: async (argv) => {
1072
1073
  const result = await requireManagementClient(argv).listAgents({
1073
1074
  limit: argv.limit,
1074
- scope: argv.scope ?? null
1075
+ scope: argv.scope ?? null,
1076
+ cursor: argv.cursor
1075
1077
  });
1076
1078
  if (result.isErr()) {
1077
1079
  printError(result.error.message);
1078
1080
  process.exit(1);
1079
1081
  }
1080
- const { agents, hasMore } = result.value;
1082
+ const { agents, nextCursor } = result.value;
1081
1083
  if (argv.json) {
1082
- output(argv, agents);
1084
+ output(argv, {
1085
+ agents,
1086
+ nextCursor
1087
+ });
1083
1088
  return;
1084
1089
  }
1085
1090
  if (agents.length === 0) {
@@ -1088,7 +1093,7 @@ const listCommand$4 = {
1088
1093
  }
1089
1094
  const { headers, rows } = buildAgentTable(agents);
1090
1095
  printTable(headers, rows);
1091
- if (hasMore && !argv.quiet) console.log(`\nShowing the ${agents.length} newest. Raise --limit to see more.`);
1096
+ if (nextCursor && !argv.quiet) console.log(`\nMore results. Next page: --cursor ${nextCursor}`);
1092
1097
  }
1093
1098
  };
1094
1099
  const DESCRIPTION_MAX = 48;
@@ -1131,7 +1136,7 @@ function truncate(value, max) {
1131
1136
  if (trimmed.length <= max) return trimmed || "-";
1132
1137
  return `${trimmed.slice(0, max - 1)}\u2026`;
1133
1138
  }
1134
- const getCommand$1 = {
1139
+ const getCommand$2 = {
1135
1140
  command: "get <id>",
1136
1141
  describe: "Get agent details",
1137
1142
  builder: (y) => y.positional("id", {
@@ -1201,7 +1206,7 @@ const createCommand$1 = {
1201
1206
  const agentsCommand = {
1202
1207
  command: "agents",
1203
1208
  describe: "Manage agents",
1204
- builder: (y) => y.command(listCommand$4).command(getCommand$1).command(createCommand$1).demandCommand(1, "Specify a subcommand: list, get, create"),
1209
+ builder: (y) => y.command(listCommand$5).command(getCommand$2).command(createCommand$1).demandCommand(1, "Specify a subcommand: list, get, create"),
1205
1210
  handler: () => {}
1206
1211
  };
1207
1212
 
@@ -1500,7 +1505,7 @@ const importCommand = {
1500
1505
  process.exit(1);
1501
1506
  }
1502
1507
  }
1503
- const { runChat } = await import("./boot-HAgDg_b4.mjs");
1508
+ const { runChat } = await import("./boot-on5ucI_Z.mjs");
1504
1509
  await runChat({
1505
1510
  appUrl,
1506
1511
  sessionToken: session.value.sessionToken,
@@ -1527,7 +1532,7 @@ async function runImportPrintMode({ argv, appUrl }) {
1527
1532
  printError(`${session.error.message} For non-interactive use, run \`skydive auth login --web\` first, or set SKYDIVE_SESSION_TOKEN.`);
1528
1533
  process.exit(1);
1529
1534
  }
1530
- const { connectMachineShare } = await import("./print-share-7qfpQtWK.mjs");
1535
+ const { connectMachineShare } = await import("./print-share-D72CfWJk.mjs");
1531
1536
  const machineShare = await connectMachineShare({
1532
1537
  appUrl,
1533
1538
  sessionToken: session.value.sessionToken,
@@ -1535,7 +1540,7 @@ async function runImportPrintMode({ argv, appUrl }) {
1535
1540
  });
1536
1541
  const extra = (argv.print ?? "").trim();
1537
1542
  const prompt = buildImportSeedPrompt(process.cwd()) + (extra ? `\n\nAdditional instructions: ${extra}` : "");
1538
- const { runPrint } = await import("./print-CPCFci60.mjs");
1543
+ const { runPrint } = await import("./print-BXNq9UxE.mjs");
1539
1544
  try {
1540
1545
  const result = await runPrint({
1541
1546
  appUrl,
@@ -1559,7 +1564,7 @@ async function runImportPrintMode({ argv, appUrl }) {
1559
1564
 
1560
1565
  //#endregion
1561
1566
  //#region src/commands/keys.ts
1562
- const listCommand$3 = {
1567
+ const listCommand$4 = {
1563
1568
  command: "list",
1564
1569
  describe: "List API keys for an agent",
1565
1570
  handler: async (argv) => {
@@ -1648,7 +1653,7 @@ const keysCommand = {
1648
1653
  type: "string",
1649
1654
  demandOption: true,
1650
1655
  describe: "Agent ID"
1651
- }).command(listCommand$3).command(createCommand).command(revokeCommand$1).demandCommand(1, "Specify a subcommand: list, create, revoke"),
1656
+ }).command(listCommand$4).command(createCommand).command(revokeCommand$1).demandCommand(1, "Specify a subcommand: list, create, revoke"),
1652
1657
  handler: () => {}
1653
1658
  };
1654
1659
 
@@ -1660,7 +1665,7 @@ async function readStdin() {
1660
1665
  for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1661
1666
  return Buffer.concat(chunks).toString("utf8").replace(/\n$/, "");
1662
1667
  }
1663
- const listCommand$2 = {
1668
+ const listCommand$3 = {
1664
1669
  command: "list",
1665
1670
  describe: "List secret names for an agent (values are never shown)",
1666
1671
  handler: async (argv) => {
@@ -1681,7 +1686,7 @@ const listCommand$2 = {
1681
1686
  printTable(["Name"], keys.map((k) => [k]));
1682
1687
  }
1683
1688
  };
1684
- const setCommand = {
1689
+ const setCommand$1 = {
1685
1690
  command: "set <key> [value]",
1686
1691
  describe: "Set (create or overwrite) a secret. Reads value from stdin if omitted.",
1687
1692
  builder: (y) => y.positional("key", {
@@ -1746,7 +1751,7 @@ const secretsCommand = {
1746
1751
  type: "string",
1747
1752
  demandOption: true,
1748
1753
  describe: "Agent ID"
1749
- }).command(listCommand$2).command(setCommand).command(rmCommand).demandCommand(1, "Specify a subcommand: list, set, rm"),
1754
+ }).command(listCommand$3).command(setCommand$1).command(rmCommand).demandCommand(1, "Specify a subcommand: list, set, rm"),
1750
1755
  handler: () => {}
1751
1756
  };
1752
1757
 
@@ -1885,10 +1890,10 @@ const chatCommand = {
1885
1890
  printError(`chat needs the Bun runtime and it couldn't be set up automatically (no network, an unsupported platform, or a failed download). Install Bun (https://bun.sh) and run chat under it, e.g. \`bun ${process.argv[1] ?? "skydive"} chat\`, or point SKYDIVE_BUN_PATH at an existing bun binary. For a non-interactive one-shot that runs under Node, use \`chat -p "<prompt>"\`.`);
1886
1891
  process.exit(1);
1887
1892
  }
1888
- let session = resolveSession({ appUrl });
1889
- if (session.isErr()) {
1893
+ let auth = resolveChatAuth({ appUrl });
1894
+ if (auth.isErr()) {
1890
1895
  if (isNonInteractive()) {
1891
- printError("Not signed in for chat and no interactive terminal. Run `skydive auth login`, or set SKYDIVE_SESSION_TOKEN.");
1896
+ printError("Not signed in and no interactive terminal. Run `skydive auth login`, or set SKYDIVE_API_KEY / SKYDIVE_SESSION_TOKEN.");
1892
1897
  process.exit(1);
1893
1898
  }
1894
1899
  const login = await loginWithDevice({ appUrl });
@@ -1896,8 +1901,8 @@ const chatCommand = {
1896
1901
  printError(login.error.message);
1897
1902
  process.exit(1);
1898
1903
  }
1899
- session = resolveSession({ appUrl });
1900
- if (session.isErr()) {
1904
+ auth = resolveChatAuth({ appUrl });
1905
+ if (auth.isErr()) {
1901
1906
  printError("Signed in, but no session was stored.");
1902
1907
  process.exit(1);
1903
1908
  }
@@ -1909,13 +1914,14 @@ const chatCommand = {
1909
1914
  }
1910
1915
  await ensureAgentWorkspace({
1911
1916
  appUrl,
1912
- sessionToken: session.value.sessionToken,
1917
+ sessionToken: auth.value.token,
1913
1918
  agentSelector: argv.agent ?? null
1914
1919
  });
1915
- const { runChat } = await import("./boot-HAgDg_b4.mjs");
1920
+ const { runChat } = await import("./boot-on5ucI_Z.mjs");
1916
1921
  await runChat({
1917
- appUrl,
1918
- sessionToken: session.value.sessionToken,
1922
+ appUrl: auth.value.appUrl,
1923
+ sessionToken: auth.value.token,
1924
+ authKind: auth.value.kind,
1919
1925
  shareMachine: resolveShareMachine(argv),
1920
1926
  promptHistoryPath: getPromptHistoryPath(),
1921
1927
  theme: themeId,
@@ -1973,15 +1979,15 @@ async function ensureAgentWorkspace({ appUrl, sessionToken, agentSelector }) {
1973
1979
  process.exit(1);
1974
1980
  }
1975
1981
  async function runPrintMode({ argv, appUrl }) {
1976
- const session = resolveSession({ appUrl });
1977
- if (session.isErr()) {
1978
- printError(`${session.error.message} For non-interactive use, run \`skydive auth login\` first, or set SKYDIVE_SESSION_TOKEN.`);
1982
+ const auth = resolveChatAuth({ appUrl });
1983
+ if (auth.isErr()) {
1984
+ printError(`${auth.error.message} For non-interactive use, run \`skydive auth login\` first, or set SKYDIVE_API_KEY / SKYDIVE_SESSION_TOKEN.`);
1979
1985
  process.exit(1);
1980
1986
  }
1981
- const { runPrint, readStdin } = await import("./print-CPCFci60.mjs");
1987
+ const { runPrint, readStdin } = await import("./print-BXNq9UxE.mjs");
1982
1988
  await ensureAgentWorkspace({
1983
1989
  appUrl,
1984
- sessionToken: session.value.sessionToken,
1990
+ sessionToken: auth.value.token,
1985
1991
  agentSelector: argv.agent ?? null
1986
1992
  });
1987
1993
  let prompt = (argv.print ?? "").trim();
@@ -1997,18 +2003,23 @@ async function runPrintMode({ argv, appUrl }) {
1997
2003
  }
1998
2004
  }
1999
2005
  let machineShare = null;
2000
- if (resolveShareMachine(argv)) {
2001
- const { connectMachineShare } = await import("./print-share-7qfpQtWK.mjs");
2006
+ if (resolveShareMachine(argv)) if (auth.value.kind === "api-key") {
2007
+ if (argv["share-machine"] === true) {
2008
+ printError("Machine sharing needs a browser session (API keys cannot grant local access). Run `skydive auth login --web` first, or re-run with --no-share-machine.");
2009
+ process.exit(1);
2010
+ }
2011
+ } else {
2012
+ const { connectMachineShare } = await import("./print-share-D72CfWJk.mjs");
2002
2013
  machineShare = await connectMachineShare({
2003
- appUrl,
2004
- sessionToken: session.value.sessionToken,
2014
+ appUrl: auth.value.appUrl,
2015
+ sessionToken: auth.value.token,
2005
2016
  timeoutHint: "Re-run with --no-share-machine (or unset `shareMachineDefault` in the CLI config), or check `skydive portal status`."
2006
2017
  });
2007
2018
  }
2008
2019
  try {
2009
2020
  const result = await runPrint({
2010
- appUrl,
2011
- sessionToken: session.value.sessionToken,
2021
+ appUrl: auth.value.appUrl,
2022
+ sessionToken: auth.value.token,
2012
2023
  prompt,
2013
2024
  agentSelector: argv.agent ?? null,
2014
2025
  conversationId: argv.conversation ?? null,
@@ -2018,6 +2029,12 @@ async function runPrintMode({ argv, appUrl }) {
2018
2029
  });
2019
2030
  if (argv.json) output(argv, result);
2020
2031
  } catch (error) {
2032
+ const { BillingBlockedError, BILLING_BLOCKED_EXIT_CODE, billingBlockedJsonResult } = await import("./billing-blocked-euid6MN9.mjs");
2033
+ if (error instanceof BillingBlockedError) {
2034
+ if (argv.json) output(argv, billingBlockedJsonResult(error, null));
2035
+ printError(error.message);
2036
+ process.exit(BILLING_BLOCKED_EXIT_CODE);
2037
+ }
2021
2038
  printError(error instanceof Error ? error.message : String(error));
2022
2039
  process.exit(1);
2023
2040
  } finally {
@@ -2042,7 +2059,7 @@ async function runPrintMode({ argv, appUrl }) {
2042
2059
  * Message ids are the currency of this API: `chat -p --json` reports the
2043
2060
  * `messageId` and this command consumes it. Run ids stay server-side.
2044
2061
  */
2045
- const getCommand = {
2062
+ const getCommand$1 = {
2046
2063
  command: "get <message-id>",
2047
2064
  describe: "Fetch a message by id and print the reply (recovers a timed-out -p)",
2048
2065
  builder: (y) => y.positional("message-id", {
@@ -2051,22 +2068,27 @@ const getCommand = {
2051
2068
  describe: "Message id (the `messageId` from a prior `chat -p --json`)"
2052
2069
  }),
2053
2070
  handler: async (argv) => {
2054
- const appUrl = resolveAppUrl({ appUrl: argv["api-url"] });
2055
- const session = resolveSession({ appUrl });
2056
- if (session.isErr()) {
2057
- printError(`${session.error.message} Run \`skydive auth login\` first, or set SKYDIVE_SESSION_TOKEN.`);
2071
+ const auth = resolveChatAuth({ appUrl: resolveAppUrl({ appUrl: argv["api-url"] }) });
2072
+ if (auth.isErr()) {
2073
+ printError(`${auth.error.message} Run \`skydive auth login\` first, or set SKYDIVE_API_KEY / SKYDIVE_SESSION_TOKEN.`);
2058
2074
  process.exit(1);
2059
2075
  }
2060
- const { messageGet } = await import("./print-CPCFci60.mjs");
2076
+ const { messageGet } = await import("./print-BXNq9UxE.mjs");
2061
2077
  try {
2062
2078
  const result = await messageGet({
2063
- appUrl,
2064
- sessionToken: session.value.sessionToken,
2079
+ appUrl: auth.value.appUrl,
2080
+ sessionToken: auth.value.token,
2065
2081
  messageId: argv["message-id"],
2066
2082
  json: argv.json
2067
2083
  });
2068
2084
  if (argv.json) output(argv, result);
2069
2085
  } catch (error) {
2086
+ const { BillingBlockedError, BILLING_BLOCKED_EXIT_CODE, billingBlockedJsonResult } = await import("./billing-blocked-euid6MN9.mjs");
2087
+ if (error instanceof BillingBlockedError) {
2088
+ if (argv.json) output(argv, billingBlockedJsonResult(error, argv["message-id"]));
2089
+ printError(error.message);
2090
+ process.exit(BILLING_BLOCKED_EXIT_CODE);
2091
+ }
2070
2092
  printError(error instanceof Error ? error.message : String(error));
2071
2093
  process.exit(1);
2072
2094
  }
@@ -2075,7 +2097,7 @@ const getCommand = {
2075
2097
  const messagesCommand = {
2076
2098
  command: "messages",
2077
2099
  describe: "Inspect chat messages and re-fetch agent replies",
2078
- builder: (y) => y.command(getCommand).demandCommand(1, "Specify a subcommand: get"),
2100
+ builder: (y) => y.command(getCommand$1).demandCommand(1, "Specify a subcommand: get"),
2079
2101
  handler: () => {}
2080
2102
  };
2081
2103
 
@@ -2098,7 +2120,7 @@ function messageToText(message) {
2098
2120
  }
2099
2121
  return chunks.join("\n").trim();
2100
2122
  }
2101
- const listCommand$1 = {
2123
+ const listCommand$2 = {
2102
2124
  command: "list",
2103
2125
  describe: "List an agent's conversations",
2104
2126
  builder: (y) => y.option("agent", {
@@ -2188,7 +2210,7 @@ const conversationsCommand = {
2188
2210
  command: "conversations",
2189
2211
  aliases: ["conv"],
2190
2212
  describe: "List and read agent conversations",
2191
- builder: (y) => y.command(listCommand$1).command(showCommand).demandCommand(1, "Specify a subcommand: list, show"),
2213
+ builder: (y) => y.command(listCommand$2).command(showCommand).demandCommand(1, "Specify a subcommand: list, show"),
2192
2214
  handler: () => {}
2193
2215
  };
2194
2216
 
@@ -2230,7 +2252,7 @@ async function runList(argv, { hint }) {
2230
2252
  ]));
2231
2253
  if (hint && !argv.quiet) console.log("\nRun `skydive workspace switch` to pick a workspace, or pass its slug directly.");
2232
2254
  }
2233
- const listCommand = {
2255
+ const listCommand$1 = {
2234
2256
  command: "list",
2235
2257
  describe: "List workspaces on your account",
2236
2258
  handler: (argv) => runList(argv, { hint: false })
@@ -2254,7 +2276,7 @@ const switchCommand = {
2254
2276
  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.");
2255
2277
  process.exit(1);
2256
2278
  }
2257
- const { runWorkspacePicker } = await import("./boot-HAgDg_b4.mjs");
2279
+ const { runWorkspacePicker } = await import("./boot-on5ucI_Z.mjs");
2258
2280
  await runWorkspacePicker(session);
2259
2281
  return;
2260
2282
  }
@@ -2287,7 +2309,7 @@ const switchCommand = {
2287
2309
  const workspaceCommand = {
2288
2310
  command: "workspace",
2289
2311
  describe: "List or switch the workspace all `skydive` commands act on",
2290
- builder: (y) => y.command(listCommand).command(switchCommand),
2312
+ builder: (y) => y.command(listCommand$1).command(switchCommand),
2291
2313
  handler: (argv) => runList(argv, { hint: true })
2292
2314
  };
2293
2315
 
@@ -2332,7 +2354,7 @@ const openCommand = {
2332
2354
  const agent = argv.agent ? resolveAgent((await fetchPortalDevices(session)).agents, argv.agent) : null;
2333
2355
  const cwd = argv.cwd ? path.resolve(argv.cwd) : process.cwd();
2334
2356
  const { machineName } = machineIdentity();
2335
- const { PortalClient } = await import("./client-DuwxEDG4.mjs");
2357
+ const { PortalClient } = await import("./client-D6apKmV0.mjs");
2336
2358
  let lastLine = "";
2337
2359
  let signalConnected;
2338
2360
  const connected = new Promise((resolve) => {
@@ -2605,8 +2627,8 @@ const sandboxCommand = {
2605
2627
  }).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`"),
2606
2628
  handler: async (argv) => {
2607
2629
  const session = requireSession(argv);
2608
- const { createRestClient } = await import("./rest-CDTXCmUb.mjs");
2609
- const { resolveAgent } = await import("./print-CPCFci60.mjs");
2630
+ const { createRestClient } = await import("./rest-Br2eFTlj.mjs");
2631
+ const { resolveAgent } = await import("./print-BXNq9UxE.mjs");
2610
2632
  const client = createRestClient({
2611
2633
  appUrl: session.appUrl,
2612
2634
  sessionToken: session.sessionToken
@@ -2675,7 +2697,7 @@ async function runPty({ session, agentId, agentName }) {
2675
2697
  return 1;
2676
2698
  }
2677
2699
  console.error(`Connecting to ${agentName}'s sandbox… (Ctrl-] detaches)`);
2678
- const { runRawPtyPassthrough } = await import("./raw-pty-ChUHav4d.mjs");
2700
+ const { runRawPtyPassthrough } = await import("./raw-pty-t5DC-e1T.mjs");
2679
2701
  const result = await runRawPtyPassthrough({
2680
2702
  stdin: process.stdin,
2681
2703
  stdout: process.stdout,
@@ -3356,10 +3378,146 @@ const updateCommand = {
3356
3378
  }
3357
3379
  };
3358
3380
 
3381
+ //#endregion
3382
+ //#region src/commands/config.ts
3383
+ /**
3384
+ * Resolve a preference key or exit 1 with a clear message. `choices` on each
3385
+ * positional already rejects unknown keys through yargs' own validation
3386
+ * (routing to the top-level `.fail()` handler), so this is a defensive guard
3387
+ * that also narrows the type. It follows the CLI's convention of
3388
+ * `printError` + `process.exit(1)` rather than throwing, because a throw from
3389
+ * an async yargs handler rejects `parseAsync` instead of being caught by
3390
+ * `.fail()`. `process.exit` returns `never`, so callers can treat the result
3391
+ * as always defined.
3392
+ */
3393
+ function resolvePreference(key) {
3394
+ const pref = getPreference(key ?? "");
3395
+ if (!pref) {
3396
+ printError(`Unknown preference "${key}". Run \`skydive config list\` to see editable keys.`);
3397
+ process.exit(1);
3398
+ }
3399
+ return pref;
3400
+ }
3401
+ /** One row's worth of state for a preference, used by `list` and `get`. */
3402
+ function snapshot(pref) {
3403
+ return {
3404
+ key: pref.key,
3405
+ value: pref.read(),
3406
+ source: pref.isSet() ? "set" : "default",
3407
+ describe: pref.describe
3408
+ };
3409
+ }
3410
+ const listCommand = {
3411
+ command: "list",
3412
+ aliases: ["ls"],
3413
+ describe: "Show every editable preference, its value, and its source",
3414
+ handler: (argv) => {
3415
+ const rows = PREFERENCES.map(snapshot);
3416
+ if (argv.json) {
3417
+ output(argv, rows);
3418
+ return;
3419
+ }
3420
+ printTable([
3421
+ "Key",
3422
+ "Value",
3423
+ "Source",
3424
+ "Description"
3425
+ ], rows.map((r) => [
3426
+ r.key,
3427
+ String(r.value),
3428
+ r.source,
3429
+ r.describe
3430
+ ]));
3431
+ }
3432
+ };
3433
+ const getCommand = {
3434
+ command: "get <key>",
3435
+ describe: "Print the effective value of one preference",
3436
+ builder: (y) => y.positional("key", {
3437
+ type: "string",
3438
+ demandOption: true,
3439
+ choices: PREFERENCES.map((p) => p.key),
3440
+ describe: "Preference key"
3441
+ }),
3442
+ handler: (argv) => {
3443
+ const pref = resolvePreference(argv.key);
3444
+ if (argv.json) {
3445
+ output(argv, snapshot(pref));
3446
+ return;
3447
+ }
3448
+ console.log(String(pref.read()));
3449
+ }
3450
+ };
3451
+ const setCommand = {
3452
+ command: "set <key> <value>",
3453
+ describe: "Set a preference (e.g. `config set updateCheck false`)",
3454
+ builder: (y) => y.positional("key", {
3455
+ type: "string",
3456
+ demandOption: true,
3457
+ choices: PREFERENCES.map((p) => p.key),
3458
+ describe: "Preference key"
3459
+ }).positional("value", {
3460
+ type: "string",
3461
+ demandOption: true,
3462
+ describe: "New value"
3463
+ }),
3464
+ handler: (argv) => {
3465
+ const pref = resolvePreference(argv.key);
3466
+ const parsed = pref.parse(argv.value ?? "");
3467
+ if (parsed.isErr()) {
3468
+ printError(`${pref.key}: ${parsed.error}`);
3469
+ process.exit(1);
3470
+ }
3471
+ pref.write(parsed.value);
3472
+ if (argv.json) {
3473
+ output(argv, snapshot(pref));
3474
+ return;
3475
+ }
3476
+ if (!argv.quiet) console.log(`Set ${pref.key} = ${String(parsed.value)}`);
3477
+ }
3478
+ };
3479
+ const unsetCommand = {
3480
+ command: "unset <key>",
3481
+ aliases: ["reset"],
3482
+ describe: "Remove a preference, reverting it to the built-in default",
3483
+ builder: (y) => y.positional("key", {
3484
+ type: "string",
3485
+ demandOption: true,
3486
+ choices: PREFERENCES.map((p) => p.key),
3487
+ describe: "Preference key"
3488
+ }),
3489
+ handler: (argv) => {
3490
+ const pref = resolvePreference(argv.key);
3491
+ pref.clear();
3492
+ if (argv.json) {
3493
+ output(argv, snapshot(pref));
3494
+ return;
3495
+ }
3496
+ if (!argv.quiet) console.log(`Unset ${pref.key} (now ${String(pref.read())} by default)`);
3497
+ }
3498
+ };
3499
+ const pathCommand = {
3500
+ command: "path",
3501
+ describe: "Print the on-disk config file path",
3502
+ handler: (argv) => {
3503
+ if (argv.json) {
3504
+ output(argv, { path: getConfigPath() });
3505
+ return;
3506
+ }
3507
+ console.log(getConfigPath());
3508
+ }
3509
+ };
3510
+ const configCommand = {
3511
+ command: "config",
3512
+ describe: "View and set CLI preferences (safe, hand-editable keys only)",
3513
+ builder: (y) => y.command(listCommand).command(getCommand).command(setCommand).command(unsetCommand).command(pathCommand),
3514
+ handler: (argv) => listCommand.handler(argv)
3515
+ };
3516
+
3359
3517
  //#endregion
3360
3518
  //#region src/cli.ts
3361
3519
  function createCli(argv) {
3362
- return yargs(argv).scriptName("skydive").usage("$0 <command> [options]").parserConfiguration({ "populate--": true }).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 completion install", "Set up TAB completion for your shell").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 the workspace all `skydive` commands act on").example("skydive portal open --agent grace", "Open the portal to this machine for an agent, headless (no TUI)").option("json", {
3520
+ return yargs(argv).scriptName("skydive").usage("$0 <command> [options]").parserConfiguration({ "populate--": true }).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 completion install", "Set up TAB completion for your shell").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 config set updateCheck false", "Disable the daily update check").example("skydive workspace switch acme-team", "Switch the workspace all `skydive` commands act on").example("skydive portal open --agent grace", "Open the portal to this machine for an agent, headless (no TUI)").option("json", {
3363
3521
  type: "boolean",
3364
3522
  default: false,
3365
3523
  global: true,
@@ -3373,7 +3531,7 @@ function createCli(argv) {
3373
3531
  type: "string",
3374
3532
  global: true,
3375
3533
  describe: "Override API base URL"
3376
- }).command(authCommand).command(chatCommand).command(messagesCommand).command(conversationsCommand).command(agentsCommand).command(importCommand).command(keysCommand).command(secretsCommand).command(workspaceCommand).command(portalCommand).command(sandboxCommand).command(updateCommand).command(completionCommand).demandCommand(1, "Specify a command. Run --help for usage.").strict().wrap(null).version(version).alias("v", "version").alias("h", "help").help().fail((msg, err) => {
3534
+ }).command(authCommand).command(chatCommand).command(messagesCommand).command(conversationsCommand).command(agentsCommand).command(importCommand).command(keysCommand).command(secretsCommand).command(workspaceCommand).command(portalCommand).command(sandboxCommand).command(updateCommand).command(configCommand).command(completionCommand).demandCommand(1, "Specify a command. Run --help for usage.").strict().wrap(null).version(version).alias("v", "version").alias("h", "help").help().fail((msg, err) => {
3377
3535
  printError(err ? err instanceof Error ? err.message : String(err) : msg ?? "Unknown error");
3378
3536
  process.exit(1);
3379
3537
  });
@@ -3586,7 +3744,7 @@ function setupUpdateCheck(argv) {
3586
3744
 
3587
3745
  //#endregion
3588
3746
  //#region src/changelog.generated.ts
3589
- const CHANGELOG_MD = "# Changelog\n\nAll notable changes to the Skydive CLI are documented here.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n\n## [0.3.0] - 2026-08-04\n\n### Added\n\n**Chat TUI**\n\n- `/fork` slash command to fork a conversation at any point and continue in a new thread.\n- `/conversation` switches threads from inside a live session.\n- `/compact` triggers manual conversation compaction.\n- `/portal` and `/copy` slash commands.\n- Archive the current conversation from the chat TUI.\n- `--new` flag on `skydive chat` to start a fresh conversation.\n- Debounced server-side conversation search in the picker, with title matches ranked first.\n- File review pane orientation toggle, and review comments in the file pane.\n- Pinned plan card controls and a jump-to-latest hint.\n- Terminal title (OSC) follows the live conversation title on every screen; inside cmux the workspace is renamed too, and linked PRs are pushed to the cmux sidebar.\n- Every participating agent shows in the conversation list; connect cards each have their own identity, and ctrl+r asks which connect card to act on.\n- Colored Skydive splash art in `skydive --help` and the TUI pickers.\n- New-agent name prefilled from the server's suggestion.\n\n**CLI & platform**\n\n- `skydive update` self-updater, with an update notice when a new version is available.\n- Shell completions for bash, zsh, and fish via `completion install`.\n- Portal daemon: one connection per host with per-conversation working directories.\n- Agent-led coding-agent import (`skydive import`).\n- Per-workspace scoping on the REST client, and cross-org `--agent <uuid>` auto-switches workspace.\n- `skydive-beta` wrapper for the canary channel.\n\n### Fixed\n\n- `skydive update` no longer claims an update is available on every npm install.\n- Chat memory is bounded (payload clamps + windowed rows) with a memory watchdog and OOM diagnostic report; transcript rows are memoized so streaming stops re-rendering the whole chat.\n- Chat-send failures map to real error messages.\n- Notification and sidebar previews use the last text block of the reply and strip inline markdown.\n- Picker rows no longer overlap when a conversation title contains a newline, tab, or control character.\n- Sending to non-web channel conversations is blocked in the TUI.\n- GitHub bot connect opens on the web front door, not api.skydive.com.\n- TUI activity counter times from the run, not the screen.\n\n## [0.2.0] - 2026-07-29\n\n### Added\n\n**Sandbox & machine access**\n\n- Standalone `skydive sandbox` command for direct access to an agent's sandbox.\n- `/sandbox` in the chat TUI — live PTY session and one-shot command execution.\n- Headless machine sharing via `skydive portal`, with self-registering portal grants.\n- `shareMachineDefault` config key for always-on portal sharing.\n\n**Chat TUI**\n\n- File pane (`ctrl+g`) with Changes and Files tabs: a shared file tree plus source viewer and a workspace browser, mouse-resizable and responsive to terminal size.\n- Agent todo list rendered in the chat TUI, above the composer, mirroring the web chat's todo card.\n- Fuzzy finder in the conversation and agent pickers.\n- Slash-command autocomplete menu.\n- Paste or drag-and-drop any file into the composer; paste clipboard images with Cmd+V.\n- Run local shell commands with `!` in the composer.\n- Conversation recaps, streamed live title updates, and per-agent attribution on assistant turns.\n- Working timer rolls up into minutes and hours.\n- Conversation picker paginates past 50 conversations and shows only your own conversations.\n- Esc leaves a live run; typing `exit` quits the chat.\n- Cursor Dark theme.\n- Picker rows no longer overlap when a conversation title contains a newline, tab, or control character.\n\n**Headless & scripting**\n\n- Resume a conversation by id.\n- `conversations list` and `conversations show` for transcript reads.\n- `messages get`, with run recovery keyed on message id.\n- Connect cards surface in headless `-p` mode so a driving agent never gets stuck.\n\n**Authentication**\n\n- `skydive auth login` via the browser now auto-mints an API key, so one login yields both a chat session and a usable management credential. Management commands announce the key's pinned workspace when it drives them, so a workspace mismatch is visible at use time.\n- Workspace picker on the device authorization page.\n- Account and workspace identity shown in `auth status`.\n\n**Platform**\n\n- Standalone binary builds compiling the CLI into a per-target executable.\n- Interactive workspace switcher; management commands follow the active workspace.\n- Terminal host integrations and agent notifications.\n\n### Fixed\n\n- Transcript errors collapse to one line, click to expand (REST and portal errors keep their full body).\n- Dragged/pasted image file paths attach the file instead of inserting path text, including macOS paths with literal parentheses.\n- Bare URLs in chat markdown are hyperlinked so they survive text wrap.\n- Composer draft is preserved across TUI overlays.\n- Relative connect links resolve before opening the browser.\n- Numbered markdown headings render colored in the TUI.\n- Chat transcript pages by 75% of a screen; picker rows stay on one line.\n- Run starts push to the TUI over the conversation stream.\n- Security: remediated high-severity dependency findings and cleared tar/shell-quote CVEs.\n\n## [0.1.0] - 2026-07-21\n\nInitial public release: `skydive chat` TUI, agent and conversation management,\ndevice authorization, and headless `-p` mode.\n";
3747
+ const CHANGELOG_MD = "# Changelog\n\nAll notable changes to the Skydive CLI are documented here.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n\n## [0.4.0] - 2026-08-07\n\n### Added\n\n- `skydive chat` works with API key auth: an API key alone now opens the chat TUI (and headless `chat -p`) without a browser login, using the key's pinned workspace.\n- `skydive config` command to view and set the safe, hand-editable CLI preferences (`shareMachineDefault`, `updateCheck`) without opening `config.json`. `config`/`config list` shows each key, its effective value, and whether it comes from the file or the built-in default; `config get`/`set`/`unset` read and change one key; `config path` prints the config file location. It only ever touches those preference keys, never credentials or flow-managed state.\n- Billing-blocked sends and runs now surface the server-authored recovery\n guidance from the typed billing outcome. The chat TUI renders it as a\n warning-toned system-notice row — live when a send is refused or a run is\n stopped at the billing boundary, and again when reopening a conversation\n that holds a persisted billing notice — instead of a generic error.\n Headless `chat -p` and `messages get` print the guidance (state, recovery\n step, and manage-billing URL) on stderr, keep any partial output already\n streamed on stdout, and exit with code `5` (distinct from the generic `1`)\n so scripted callers can tell a billing pause from a failure.\n- Syntax highlighting for 14 more languages in the chat transcript and file/review panes (python, bash, go, rust, c, cpp, java, ruby, css, html, json, yaml, and more) — previously only JavaScript/TypeScript rendered colored.\n\n### Changed\n\n- `skydive agent list` lists the full agent roster by default instead of a truncated page.\n\n### Fixed\n\n- Long multi-line bash tool-call commands (e.g. heredocs) are clamped with a `+N more · click to expand` tail instead of dumping their whole body into the scrollback.\n- Tool error output is clipped the same way as ok output, so a failing command no longer floods the transcript.\n- The TUI toast overlay has an opaque background instead of letting the transcript bleed through.\n\n## [0.3.0] - 2026-08-04\n\n### Added\n\n**Chat TUI**\n\n- `/fork` slash command to fork a conversation at any point and continue in a new thread.\n- `/conversation` switches threads from inside a live session.\n- `/compact` triggers manual conversation compaction.\n- `/portal` and `/copy` slash commands.\n- Archive the current conversation from the chat TUI.\n- `--new` flag on `skydive chat` to start a fresh conversation.\n- Debounced server-side conversation search in the picker, with title matches ranked first.\n- File review pane orientation toggle, and review comments in the file pane.\n- Pinned plan card controls and a jump-to-latest hint.\n- Terminal title (OSC) follows the live conversation title on every screen; inside cmux the workspace is renamed too, and linked PRs are pushed to the cmux sidebar.\n- Every participating agent shows in the conversation list; connect cards each have their own identity, and ctrl+r asks which connect card to act on.\n- Colored Skydive splash art in `skydive --help` and the TUI pickers.\n- New-agent name prefilled from the server's suggestion.\n\n**CLI & platform**\n\n- `skydive update` self-updater, with an update notice when a new version is available.\n- Shell completions for bash, zsh, and fish via `completion install`.\n- Portal daemon: one connection per host with per-conversation working directories.\n- Agent-led coding-agent import (`skydive import`).\n- Per-workspace scoping on the REST client, and cross-org `--agent <uuid>` auto-switches workspace.\n- `skydive-beta` wrapper for the canary channel.\n\n### Fixed\n\n- `skydive update` no longer claims an update is available on every npm install.\n- Chat memory is bounded (payload clamps + windowed rows) with a memory watchdog and OOM diagnostic report; transcript rows are memoized so streaming stops re-rendering the whole chat.\n- Chat-send failures map to real error messages.\n- Notification and sidebar previews use the last text block of the reply and strip inline markdown.\n- Picker rows no longer overlap when a conversation title contains a newline, tab, or control character.\n- Sending to non-web channel conversations is blocked in the TUI.\n- GitHub bot connect opens on the web front door, not api.skydive.com.\n- TUI activity counter times from the run, not the screen.\n\n## [0.2.0] - 2026-07-29\n\n### Added\n\n**Sandbox & machine access**\n\n- Standalone `skydive sandbox` command for direct access to an agent's sandbox.\n- `/sandbox` in the chat TUI — live PTY session and one-shot command execution.\n- Headless machine sharing via `skydive portal`, with self-registering portal grants.\n- `shareMachineDefault` config key for always-on portal sharing.\n\n**Chat TUI**\n\n- File pane (`ctrl+g`) with Changes and Files tabs: a shared file tree plus source viewer and a workspace browser, mouse-resizable and responsive to terminal size.\n- Agent todo list rendered in the chat TUI, above the composer, mirroring the web chat's todo card.\n- Fuzzy finder in the conversation and agent pickers.\n- Slash-command autocomplete menu.\n- Paste or drag-and-drop any file into the composer; paste clipboard images with Cmd+V.\n- Run local shell commands with `!` in the composer.\n- Conversation recaps, streamed live title updates, and per-agent attribution on assistant turns.\n- Working timer rolls up into minutes and hours.\n- Conversation picker paginates past 50 conversations and shows only your own conversations.\n- Esc leaves a live run; typing `exit` quits the chat.\n- Cursor Dark theme.\n- Picker rows no longer overlap when a conversation title contains a newline, tab, or control character.\n\n**Headless & scripting**\n\n- Resume a conversation by id.\n- `conversations list` and `conversations show` for transcript reads.\n- `messages get`, with run recovery keyed on message id.\n- Connect cards surface in headless `-p` mode so a driving agent never gets stuck.\n\n**Authentication**\n\n- `skydive auth login` via the browser now auto-mints an API key, so one login yields both a chat session and a usable management credential. Management commands announce the key's pinned workspace when it drives them, so a workspace mismatch is visible at use time.\n- Workspace picker on the device authorization page.\n- Account and workspace identity shown in `auth status`.\n\n**Platform**\n\n- Standalone binary builds compiling the CLI into a per-target executable.\n- Interactive workspace switcher; management commands follow the active workspace.\n- Terminal host integrations and agent notifications.\n\n### Fixed\n\n- Transcript errors collapse to one line, click to expand (REST and portal errors keep their full body).\n- Dragged/pasted image file paths attach the file instead of inserting path text, including macOS paths with literal parentheses.\n- Bare URLs in chat markdown are hyperlinked so they survive text wrap.\n- Composer draft is preserved across TUI overlays.\n- Relative connect links resolve before opening the browser.\n- Numbered markdown headings render colored in the TUI.\n- Chat transcript pages by 75% of a screen; picker rows stay on one line.\n- Run starts push to the TUI over the conversation stream.\n- Security: remediated high-severity dependency findings and cleared tar/shell-quote CVEs.\n\n## [0.1.0] - 2026-07-21\n\nInitial public release: `skydive chat` TUI, agent and conversation management,\ndevice authorization, and headless `-p` mode.\n";
3590
3748
 
3591
3749
  //#endregion
3592
3750
  //#region src/whats-new.ts
@@ -3728,7 +3886,7 @@ if (process.argv.includes(UPDATE_WORKER_FLAG)) {
3728
3886
  process.exit(0);
3729
3887
  }
3730
3888
  if (process.argv.includes(PORTAL_DAEMON_FLAG)) {
3731
- const { runPortalDaemon } = await import("./daemon-Ch_MSDmQ.mjs");
3889
+ const { runPortalDaemon } = await import("./daemon-BADmpJAs.mjs");
3732
3890
  runPortalDaemon(process.argv);
3733
3891
  } else runCli();
3734
3892
  function runCli() {