skydive-cli 0.5.0-beta.14 → 0.5.0-beta.16

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-COGO4LN8.mjs";
2
+ import { A as version, D as listWorkspaces, E as getSessionIdentity, O as setActiveWorkspace, S as themes, T as getActiveWorkspaceId, a as installCrashHandler, k as name, t as maybeStartProfiling, u as brandHelpArt, w as ensureActiveOrganization } from "./profiler-DXusZ2OB.mjs";
3
3
  import { A as getStoredApiKeyId, B as saveConfig, C as getDefaultAgent, E as getPromptHistoryPath, F as resolveChatAuth, I as resolveConfig, L as resolveManagementAuth, M as getUpdateCheckDisabled, P as resolveAppUrl, R as resolveSession, S as getConfigPath, T as getPreference, U as setLastSeenVersion, V as saveSession, _ as API_KEY_PREFIX, b as PREFERENCES, g as API_KEY_FAMILY_PREFIX, h as API_KEYS_URL, i as resolveAgent, j as getStoredApiKeyWorkspaceName, k as getShareMachineDefault, v as DEFAULT_API_URL, w as getLastSeenVersion, x as deleteConfig } from "./print-riiY5Gly.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
6
  import { t as createRestClient } from "./rest-CLrHVkxh.mjs";
7
7
  import "./billing-blocked-2wju4gC_.mjs";
8
8
  import { r as resolveMachineIdentity } from "./client-DbqRBquD.mjs";
9
- import { i as queryDaemonStatus, l as PORTAL_DAEMON_FLAG, n as ensureDaemonRunning, s as stopDaemon, u as daemonPaths } from "./daemon-CL25CwjW.mjs";
9
+ import { i as queryDaemonStatus, l as PORTAL_DAEMON_FLAG, n as ensureDaemonRunning, s as stopDaemon, u as daemonPaths } from "./daemon-OlciVTBj.mjs";
10
10
  import { i as grantPortalAccess, n as fetchPortalDevices, o as registerPortalDevice, r as findThisDevice, s as revokePortalAccess } from "./api-DG5W6iwx.mjs";
11
11
  import { t as SandboxStream } from "./client-c4c5MmgN.mjs";
12
12
  import { hideBin } from "yargs/helpers";
@@ -1517,7 +1517,7 @@ const importCommand = {
1517
1517
  process.exit(1);
1518
1518
  }
1519
1519
  }
1520
- const { runChat } = await import("./boot-CJKq8rvL.mjs");
1520
+ const { runChat } = await import("./boot-MnSkqFnR.mjs");
1521
1521
  await runChat({
1522
1522
  appUrl,
1523
1523
  sessionToken: session.value.sessionToken,
@@ -1930,7 +1930,7 @@ const chatCommand = {
1930
1930
  sessionToken: auth.value.token,
1931
1931
  agentSelector: argv.agent ?? null
1932
1932
  });
1933
- const { runChat } = await import("./boot-CJKq8rvL.mjs");
1933
+ const { runChat } = await import("./boot-MnSkqFnR.mjs");
1934
1934
  await runChat({
1935
1935
  appUrl: auth.value.appUrl,
1936
1936
  sessionToken: auth.value.token,
@@ -2290,7 +2290,7 @@ const switchCommand = {
2290
2290
  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.");
2291
2291
  process.exit(1);
2292
2292
  }
2293
- const { runWorkspacePicker } = await import("./boot-CJKq8rvL.mjs");
2293
+ const { runWorkspacePicker } = await import("./boot-MnSkqFnR.mjs");
2294
2294
  await runWorkspacePicker(session);
2295
2295
  return;
2296
2296
  }
@@ -2369,7 +2369,7 @@ const openCommand = {
2369
2369
  const agent = argv.agent ? resolveAgent((await fetchPortalDevices(session)).agents, argv.agent) : null;
2370
2370
  const cwd = argv.cwd ? path.resolve(argv.cwd) : process.cwd();
2371
2371
  const { machineName } = await resolveMachineIdentity(null);
2372
- const { PortalDaemonClient } = await import("./daemon-client-DEyOdfdA.mjs");
2372
+ const { PortalDaemonClient } = await import("./daemon-client-CtLwJOvc.mjs");
2373
2373
  let lastLine = "";
2374
2374
  let signalConnected;
2375
2375
  const connected = new Promise((resolve) => {
@@ -4215,7 +4215,7 @@ if (process.argv.includes(UPDATE_WORKER_FLAG)) {
4215
4215
  process.exit(0);
4216
4216
  }
4217
4217
  if (process.argv.includes(PORTAL_DAEMON_FLAG)) {
4218
- const { runPortalDaemon } = await import("./daemon-DjsUMczp.mjs");
4218
+ const { runPortalDaemon } = await import("./daemon-DPWYOa1E.mjs");
4219
4219
  runPortalDaemon(process.argv);
4220
4220
  } else runCli();
4221
4221
  function runCli() {
@@ -1,14 +1,14 @@
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-COGO4LN8.mjs";
2
+ import { C as themesForMode, D as listWorkspaces, O as setActiveWorkspace, T as getActiveWorkspaceId, _ as theme, a as installCrashHandler, b as themeModeFromColorFgBg, c as MARK_CELLS, d as splashFitsWidth, f as DEFAULT_THEME_ID, g as noColorRequested, h as monoTheme, i as writeArtifact, l as WORDMARK, m as findTheme, n as profilingEnabled, o as buildCrashReport, p as applyTheme, r as record, s as writeCrashReport, v as themeForMode, x as themeVersion, y as themeMode } from "./profiler-DXusZ2OB.mjs";
3
3
  import { D as getReviewStateDir, H as saveTheme, N as recordDefaultAgent, O as getSavedTheme, 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, z as resolveWebUrl } from "./print-riiY5Gly.mjs";
4
4
  import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
5
5
  import { a as isRecord, i as errorMessage, n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-CLrHVkxh.mjs";
6
6
  import { i as billingBlockedOutcomeFromSendResponse } from "./billing-blocked-2wju4gC_.mjs";
7
7
  import { t as PortalClient } from "./client-DbqRBquD.mjs";
8
- import "./daemon-CL25CwjW.mjs";
8
+ import "./daemon-OlciVTBj.mjs";
9
9
  import "./api-DG5W6iwx.mjs";
10
10
  import { t as SandboxStream } from "./client-c4c5MmgN.mjs";
11
- import { t as PortalDaemonClient } from "./daemon-client-k8ICifN2.mjs";
11
+ import { t as PortalDaemonClient } from "./daemon-client-Bue4JMhq.mjs";
12
12
  import { t as runRawPtyPassthrough } from "./raw-pty-DY4KelZW.mjs";
13
13
  import * as os$1 from "node:os";
14
14
  import { homedir, platform, release, tmpdir } from "node:os";
@@ -1080,42 +1080,133 @@ function createAgentHost({ renderer, notifications }) {
1080
1080
  * take the one host we can identify unambiguously (github.com, incl. `www.`),
1081
1081
  * which covers the common product case; GHES stays the native badge's job.
1082
1082
  *
1083
- * Only the `/pull/<n>` form (not `/issues/`, not a bare `#123` — ambiguous with
1084
- * issues without local repo context we don't have). A trailing path/query/
1085
- * fragment (e.g. `/files`, `#discussion`) is allowed but not captured into the
1086
- * canonical URL. Also matches inside markdown links, backticks, and trailing
1087
- * punctuation.
1088
- */
1089
- const PR_URL_RE = /https?:\/\/(?:www\.)?github\.com\/([\w.-]+)\/([\w.-]+)\/pull\/(\d+)/gi;
1090
- /**
1091
- * Extracts every distinct GitHub PR referenced in a block of text, in first-seen
1092
- * order, de-duplicated by **repo-qualified identity** (`owner/repo#number`)
1093
- * NOT by number, since `#42` can exist in many repos. Each ref carries a
1094
- * canonicalized URL (scheme + host + `/owner/repo/pull/number`, no trailing
1095
- * path/query/fragment) so the same PR linked twice with different fragments
1096
- * collapses to one pill.
1097
- */
1098
- function extractPullRequests(text, options) {
1099
- const requireCompleteNumber = options?.requireCompleteNumber ?? true;
1100
- const byKey = /* @__PURE__ */ new Map();
1101
- for (const match of text.matchAll(PR_URL_RE)) {
1083
+ * Only the `/pull/<n>` form (not `/issues/`). A trailing path/query/fragment
1084
+ * (e.g. `/files`, `#discussion`) is allowed but not captured into the canonical
1085
+ * URL. Also matches inside markdown links, backticks, and trailing punctuation.
1086
+ *
1087
+ * The `https://` (and `www.`) prefix is optional: agents and people routinely
1088
+ * write a bare `github.com/o/r/pull/7`, and that should link a PR the same way
1089
+ * the full URL does. To keep a bare host from matching inside a longer hostname
1090
+ * (`mygithub.com/...`, `notgithub.com/...`), a protocol-less match must be
1091
+ * preceded by a start-of-string or a non-host character (anything but a word
1092
+ * char or `.`) a leading boundary the `(?:^|[^\w.])` group asserts and
1093
+ * consumes. The consumed boundary char is outside the captured groups, so it
1094
+ * never leaks into owner/repo. `matchAll` won't overlap, but the boundary is a
1095
+ * single delimiter (space, `(`, `[`, etc.) between PRs in practice, so distinct
1096
+ * back-to-back URLs still each match.
1097
+ */
1098
+ const PR_URL_RE = /(?:^|[^\w.])(?:https?:\/\/)?(?:www\.)?github\.com\/([\w.-]+)\/([\w.-]+)\/pull\/(\d+)/gi;
1099
+ /**
1100
+ * Matches a bare, human-style PR reference `#<n>` — the way people (and LLMs)
1101
+ * name a PR in prose ("opened #20858", "landed #42"). Requires a non-word
1102
+ * boundary before the `#` so a fragment tail like `/pull/7#discussion_r5` or a
1103
+ * hex color `#20858` embedded in a word doesn't count, and captures the digits
1104
+ * up to a word boundary. We only harvest these from **assistant prose**, never
1105
+ * from tool output, because `#N` is exactly the relevance signal we're after:
1106
+ * the agent chose to talk about this PR.
1107
+ */
1108
+ const PR_HASH_RE = /(?:^|[^\w#/])#(\d+)\b/g;
1109
+ /**
1110
+ * Every full `/pull/<n>` github.com URL in `urlText`, keyed by number to the
1111
+ * candidate refs for that number. A number can map to more than one repo, so we
1112
+ * keep all candidates; a bare `#N` label can't say which repo, but the pill's
1113
+ * own URL does. Deduped by repo-qualified identity (`owner/repo#number`) so the
1114
+ * same PR linked twice — even with a different trailing path/fragment —
1115
+ * collapses to one ref.
1116
+ *
1117
+ * `deferTrailingNumber` guards the streaming truncation case: when true, a URL
1118
+ * whose number runs to the very end of `urlText` is skipped because the next
1119
+ * delta may extend those digits (`.../pull/19` now, `.../pull/19739` later).
1120
+ * Only the still-growing PROSE buffer sets this; settled tool output is always
1121
+ * complete, so its URLs are harvested with the guard off.
1122
+ */
1123
+ function urlsByNumber(urlText, deferTrailingNumber, byNumber) {
1124
+ for (const match of urlText.matchAll(PR_URL_RE)) {
1102
1125
  const owner = match[1];
1103
1126
  const repo = match[2];
1104
1127
  const numberText = match[3];
1105
1128
  if (!owner || !repo || !numberText) continue;
1106
- if (requireCompleteNumber) {
1107
- if (match.index + match[0].length >= text.length) continue;
1129
+ if (deferTrailingNumber) {
1130
+ if (match.index + match[0].length >= urlText.length) continue;
1108
1131
  }
1109
1132
  const number = Number.parseInt(numberText, 10);
1110
1133
  if (!Number.isFinite(number) || number <= 0) continue;
1111
- const key = `${owner.toLowerCase()}/${repo.toLowerCase()}#${number}`;
1112
- if (byKey.has(key)) continue;
1113
- byKey.set(key, {
1134
+ const ref = {
1114
1135
  number,
1115
- key,
1136
+ key: `${owner.toLowerCase()}/${repo.toLowerCase()}#${number}`,
1116
1137
  url: `https://github.com/${owner}/${repo}/pull/${number}`
1117
- });
1138
+ };
1139
+ const existing = byNumber.get(number);
1140
+ if (existing) {
1141
+ if (!existing.some((r) => r.key === ref.key)) existing.push(ref);
1142
+ } else byNumber.set(number, [ref]);
1143
+ }
1144
+ }
1145
+ /** Every human-style `#N` named in `proseText` — the relevance signal. */
1146
+ function hashNumbers(proseText) {
1147
+ const numbers = /* @__PURE__ */ new Set();
1148
+ for (const match of proseText.matchAll(PR_HASH_RE)) {
1149
+ const numberText = match[1];
1150
+ if (!numberText) continue;
1151
+ const number = Number.parseInt(numberText, 10);
1152
+ if (Number.isFinite(number) && number > 0) numbers.add(number);
1153
+ }
1154
+ return numbers;
1155
+ }
1156
+ /**
1157
+ * The relevance contract, in one place.
1158
+ *
1159
+ * A GitHub PR URL is cheap to emit and shows up in bulk any time a tool dumps
1160
+ * PRs the conversation isn't actually about — `gh pr list`, `gh pr status`, a
1161
+ * `gh search` — printing dozens of `/pull/N` URLs the agent never discusses.
1162
+ * Pinning a sidebar pill for every one of those is noise.
1163
+ *
1164
+ * So a PR is surfaced only when BOTH hold:
1165
+ * 1. a fully-qualified `github.com/<o>/<r>/pull/<n>` URL exists — this may
1166
+ * come from the assistant's prose OR from tool output (a `gh pr create`
1167
+ * that printed the URL the agent then referenced by number), and
1168
+ * 2. the **assistant's prose** names the number as a human-style `#<n>`.
1169
+ *
1170
+ * The `#N` in prose is the intent signal — "the agent is talking about this
1171
+ * PR", as opposed to "a tool happened to list it". The full URL (from wherever)
1172
+ * proves the PR is real and pins down its repo + canonical link. A URL with no
1173
+ * `#N` in prose (the whole of a `gh pr list` dump) is dropped; a `#N` with no
1174
+ * matching URL anywhere (a bare issue-style ref) is dropped.
1175
+ *
1176
+ * `proseText` is the assistant's prose alone (the only source of the `#N`
1177
+ * relevance signal); `urlOnlyText` is settled tool output that may carry full
1178
+ * PR URLs but no `#N` intent. URLs are harvested from both; `#N` from prose
1179
+ * only.
1180
+ */
1181
+ function collectRelevant(proseText, urlOnlyText, options, byKey) {
1182
+ const byNumber = /* @__PURE__ */ new Map();
1183
+ urlsByNumber(proseText, options.requireCompleteNumber, byNumber);
1184
+ if (urlOnlyText) urlsByNumber(urlOnlyText, false, byNumber);
1185
+ if (byNumber.size === 0) return;
1186
+ const named = hashNumbers(proseText);
1187
+ if (named.size === 0) return;
1188
+ for (const [number, refs] of byNumber) {
1189
+ if (!named.has(number)) continue;
1190
+ for (const ref of refs) if (!byKey.has(ref.key)) byKey.set(ref.key, ref);
1118
1191
  }
1192
+ }
1193
+ /**
1194
+ * Extracts every distinct GitHub PR that is both linked (a full `/pull/<n>`
1195
+ * URL) AND named (`#<n>`), de-duplicated by **repo-qualified identity**
1196
+ * (`owner/repo#number`) — NOT by number, since `#42` can exist in many repos.
1197
+ *
1198
+ * The two-argument form takes the URL pool and the prose separately, so a URL
1199
+ * from tool output can satisfy the link requirement while the `#N` stays a
1200
+ * prose-only signal. The one-argument form (prose only) is the common case: it
1201
+ * requires the URL to be in the same prose that names it.
1202
+ *
1203
+ * See {@link collectRelevant} for the contract.
1204
+ */
1205
+ function extractPullRequests(proseText, options) {
1206
+ const requireCompleteNumber = options?.requireCompleteNumber ?? true;
1207
+ const urlOnly = options?.urlOnlyText ?? "";
1208
+ const byKey = /* @__PURE__ */ new Map();
1209
+ collectRelevant(proseText, urlOnly, { requireCompleteNumber }, byKey);
1119
1210
  return [...byKey.values()];
1120
1211
  }
1121
1212
  /**
@@ -1154,28 +1245,34 @@ function stringifyToolOutput(value) {
1154
1245
  }
1155
1246
  /**
1156
1247
  * A streaming PR scanner: fed run chunks as they stream, it reports each
1157
- * distinct PR the moment its URL is seen, exactly once, without ever rescanning
1158
- * the transcript. Two sources feed it:
1248
+ * distinct PR the moment BOTH conditions hold a full `/pull/<n>` URL has been
1249
+ * seen (in prose OR tool output) AND the assistant's prose names it `#<n>` —
1250
+ * exactly once, without ever rescanning the transcript.
1159
1251
  *
1160
- * - **Assistant text** (`text-delta`): accumulated per part id (a URL can split
1161
- * across two deltas) and only the current part's buffer is scanned, so cost
1162
- * is bounded by one text block, not the whole scrollback.
1163
- * - **Tool output** (`tool-output-available`): the settled output of a tool
1164
- * call (e.g. `gh pr create` stdout) arrives whole, not delta-streamed, so we
1165
- * flatten and scan it in one pass. This catches PRs a tool printed that the
1166
- * agent never restated in prose the common case for an opened PR.
1252
+ * Two sources feed it, with different roles:
1253
+ * - **Assistant text** (`text-delta`): the ONLY source of the `#N` relevance
1254
+ * signal, and also a source of URLs. Accumulated per part id (a URL or `#N`
1255
+ * can split across two deltas); only the current part's buffer is scanned.
1256
+ * - **Tool output** (`tool-output-available`): a URL-only source. A
1257
+ * `gh pr create` prints the URL the agent then references by number, so the
1258
+ * URL for a #N mention often lives here, not in the prose. Tool output can
1259
+ * NEVER originate a pill on its own — a `gh pr list` dump (URLs, no `#N`)
1260
+ * stays silent — but it satisfies the URL half for a PR the prose names.
1167
1261
  *
1168
1262
  * `onPullRequest` fires once per never-before-seen PR (keyed by
1169
- * `owner/repo#number`) for the life of the scanner, regardless of which source
1170
- * it came from. Construct one per run (or per conversation) and feed it the
1171
- * same chunks the transcript reducer sees.
1263
+ * `owner/repo#number`). Construct one per run (or per conversation) and feed it
1264
+ * the same chunks the transcript reducer sees. See {@link collectRelevant}.
1172
1265
  */
1173
1266
  function createPullRequestScanner(onPullRequest) {
1174
1267
  let text = "";
1175
1268
  let lastPartId;
1269
+ let toolUrlText = "";
1176
1270
  const seen = /* @__PURE__ */ new Set();
1177
- const report = (buffer, final) => {
1178
- for (const pr of extractPullRequests(buffer, { requireCompleteNumber: !final })) {
1271
+ const report = (final) => {
1272
+ for (const pr of extractPullRequests(text, {
1273
+ requireCompleteNumber: !final,
1274
+ urlOnlyText: toolUrlText
1275
+ })) {
1179
1276
  if (seen.has(pr.key)) continue;
1180
1277
  seen.add(pr.key);
1181
1278
  onPullRequest(pr);
@@ -1185,7 +1282,8 @@ function createPullRequestScanner(onPullRequest) {
1185
1282
  if (chunk["type"] === "tool-output-available") {
1186
1283
  const outputText = stringifyToolOutput(chunk["output"]);
1187
1284
  if (!outputText.includes("/pull/")) return;
1188
- report(outputText, true);
1285
+ toolUrlText += `\n${outputText}`;
1286
+ report(true);
1189
1287
  return;
1190
1288
  }
1191
1289
  if (chunk["type"] !== "text-delta") return;
@@ -1193,13 +1291,12 @@ function createPullRequestScanner(onPullRequest) {
1193
1291
  if (!delta) return;
1194
1292
  const partId = typeof chunk["id"] === "string" ? chunk["id"] : void 0;
1195
1293
  if (partId && lastPartId && partId !== lastPartId) {
1196
- if (text.includes("/pull/")) report(text, true);
1294
+ report(true);
1197
1295
  text = "";
1198
1296
  }
1199
1297
  text += delta;
1200
1298
  lastPartId = partId ?? lastPartId;
1201
- if (!text.includes("/pull/")) return;
1202
- report(text, false);
1299
+ report(false);
1203
1300
  } };
1204
1301
  }
1205
1302
 
@@ -1713,13 +1810,15 @@ function WordmarkRows({ mono }) {
1713
1810
  });
1714
1811
  }
1715
1812
  /**
1716
- * Height-aware brand header for the pickers. Always shown (including while the
1717
- * list is loading / empty / erroring) so the splash is never gated on data.
1718
- * Full mark + wordmark when the terminal is tall enough; compact one-liner on
1719
- * short terminals so the list still has room.
1813
+ * Height- and width-aware brand header for the pickers. Always shown
1814
+ * (including while the list is loading / empty / erroring) so the splash is
1815
+ * never gated on data. Full mark + wordmark only when the terminal is tall
1816
+ * enough AND wide enough that the art sits on six lines; otherwise a compact
1817
+ * one-liner. Wrapping the splash would blow the 7-row budget the list
1818
+ * subtracts, and the overflow collapses the top list rows onto each other.
1720
1819
  */
1721
- function BrandHeader({ height }) {
1722
- if (height >= 24) return /* @__PURE__ */ jsxs("box", {
1820
+ function BrandHeader({ height, width }) {
1821
+ if (showFullSplash(height, width)) return /* @__PURE__ */ jsxs("box", {
1723
1822
  style: { flexDirection: "column" },
1724
1823
  children: [/* @__PURE__ */ jsx(SkydiveMark, { variant: "full" }), /* @__PURE__ */ jsx("box", { style: { height: 1 } })]
1725
1824
  });
@@ -1728,9 +1827,12 @@ function BrandHeader({ height }) {
1728
1827
  children: "✦ Skydive"
1729
1828
  });
1730
1829
  }
1830
+ function showFullSplash(height, width) {
1831
+ return height >= 24 && splashFitsWidth(width);
1832
+ }
1731
1833
  /** Row budget the BrandHeader consumes; callers subtract this from visibleRows. */
1732
- function brandHeaderRows(height) {
1733
- return height >= 24 ? 7 : 1;
1834
+ function brandHeaderRows(height, width) {
1835
+ return showFullSplash(height, width) ? 7 : 1;
1734
1836
  }
1735
1837
  function SkydiveMark({ variant = "full" }) {
1736
1838
  const mono = noColor();
@@ -1817,13 +1919,16 @@ function AgentPickerScreen() {
1817
1919
  scope,
1818
1920
  cache
1819
1921
  ]);
1820
- const { height } = useTerminalDimensions();
1922
+ const { width, height } = useTerminalDimensions();
1821
1923
  if (state.kind === "loading") return /* @__PURE__ */ jsxs("box", {
1822
1924
  style: {
1823
1925
  flexDirection: "column",
1824
1926
  flexGrow: 1
1825
1927
  },
1826
- children: [/* @__PURE__ */ jsx(BrandHeader, { height }), /* @__PURE__ */ jsx("text", {
1928
+ children: [/* @__PURE__ */ jsx(BrandHeader, {
1929
+ height,
1930
+ width
1931
+ }), /* @__PURE__ */ jsx("text", {
1827
1932
  fg: theme.muted,
1828
1933
  children: "loading agents…"
1829
1934
  })]
@@ -1833,7 +1938,10 @@ function AgentPickerScreen() {
1833
1938
  flexDirection: "column",
1834
1939
  flexGrow: 1
1835
1940
  },
1836
- children: [/* @__PURE__ */ jsx(BrandHeader, { height }), /* @__PURE__ */ jsxs("text", {
1941
+ children: [/* @__PURE__ */ jsx(BrandHeader, {
1942
+ height,
1943
+ width
1944
+ }), /* @__PURE__ */ jsxs("text", {
1837
1945
  fg: theme.error,
1838
1946
  children: ["error: ", state.message]
1839
1947
  })]
@@ -1843,7 +1951,10 @@ function AgentPickerScreen() {
1843
1951
  flexDirection: "column",
1844
1952
  flexGrow: 1
1845
1953
  },
1846
- children: [/* @__PURE__ */ jsx(BrandHeader, { height }), /* @__PURE__ */ jsx("text", {
1954
+ children: [/* @__PURE__ */ jsx(BrandHeader, {
1955
+ height,
1956
+ width
1957
+ }), /* @__PURE__ */ jsx("text", {
1847
1958
  fg: theme.muted,
1848
1959
  children: scope === "mine" ? "no agents of yours yet. press tab to see all agents, ctrl+w to switch workspace, or ctrl+n to create one." : "no agents in this workspace. press ctrl+w to switch workspace, or ctrl+n to create one."
1849
1960
  })]
@@ -1864,7 +1975,7 @@ function FilterableAgentList({ agents, scope, onPick }) {
1864
1975
  const hits = fuzzyFilter(query, agents, agentKeys);
1865
1976
  const filtered = hits.map((h) => h.item);
1866
1977
  const clamped = Math.min(highlight, Math.max(0, filtered.length - 1));
1867
- const visibleRows = Math.max(3, height - 6 - brandHeaderRows(height));
1978
+ const visibleRows = Math.max(3, height - 6 - brandHeaderRows(height, width));
1868
1979
  const start = windowStart(clamped, filtered.length, visibleRows);
1869
1980
  const windowed = hits.slice(start, start + visibleRows);
1870
1981
  useKeyboard((key) => {
@@ -1896,7 +2007,10 @@ function FilterableAgentList({ agents, scope, onPick }) {
1896
2007
  flexGrow: 1
1897
2008
  },
1898
2009
  children: [
1899
- /* @__PURE__ */ jsx(BrandHeader, { height }),
2010
+ /* @__PURE__ */ jsx(BrandHeader, {
2011
+ height,
2012
+ width
2013
+ }),
1900
2014
  /* @__PURE__ */ jsxs("box", {
1901
2015
  style: {
1902
2016
  flexDirection: "row",
@@ -2556,13 +2670,16 @@ function ConversationPickerScreen({ agent }) {
2556
2670
  cacheKey,
2557
2671
  cache
2558
2672
  ]);
2559
- const { height } = useTerminalDimensions();
2673
+ const { width, height } = useTerminalDimensions();
2560
2674
  if (state.kind === "loading") return /* @__PURE__ */ jsxs("box", {
2561
2675
  style: {
2562
2676
  flexDirection: "column",
2563
2677
  flexGrow: 1
2564
2678
  },
2565
- children: [/* @__PURE__ */ jsx(BrandHeader, { height }), /* @__PURE__ */ jsx("text", {
2679
+ children: [/* @__PURE__ */ jsx(BrandHeader, {
2680
+ height,
2681
+ width
2682
+ }), /* @__PURE__ */ jsx("text", {
2566
2683
  fg: theme.muted,
2567
2684
  children: "loading conversations…"
2568
2685
  })]
@@ -2572,7 +2689,10 @@ function ConversationPickerScreen({ agent }) {
2572
2689
  flexDirection: "column",
2573
2690
  flexGrow: 1
2574
2691
  },
2575
- children: [/* @__PURE__ */ jsx(BrandHeader, { height }), /* @__PURE__ */ jsxs("text", {
2692
+ children: [/* @__PURE__ */ jsx(BrandHeader, {
2693
+ height,
2694
+ width
2695
+ }), /* @__PURE__ */ jsxs("text", {
2576
2696
  fg: theme.error,
2577
2697
  children: ["error: ", state.message]
2578
2698
  })]
@@ -2701,7 +2821,7 @@ function ConversationList({ agent, conversations, complete, showAutomated, onTog
2701
2821
  "↵ open"
2702
2822
  ], innerWidth)
2703
2823
  };
2704
- const visibleRows = Math.max(3, height - 6 - brandHeaderRows(height));
2824
+ const visibleRows = Math.max(3, height - 6 - brandHeaderRows(height, width));
2705
2825
  const start = windowStart(clamped, rows.length, visibleRows);
2706
2826
  const windowed = rows.slice(start, start + visibleRows);
2707
2827
  const open = (row) => {
@@ -2802,7 +2922,10 @@ function ConversationList({ agent, conversations, complete, showAutomated, onTog
2802
2922
  flexGrow: 1
2803
2923
  },
2804
2924
  children: [
2805
- /* @__PURE__ */ jsx(BrandHeader, { height }),
2925
+ /* @__PURE__ */ jsx(BrandHeader, {
2926
+ height,
2927
+ width
2928
+ }),
2806
2929
  /* @__PURE__ */ jsxs("box", {
2807
2930
  style: {
2808
2931
  flexDirection: "row",
@@ -10886,10 +11009,13 @@ function ChatScreen({ agent, conversation }) {
10886
11009
  text: recapText
10887
11010
  });
10888
11011
  setItems(loaded);
11012
+ const toolUrlText = loaded.map((item) => item.kind === "tool" && item.output?.kind === "ok" ? stringifyToolOutput(item.output.value) : "").join("\n");
10889
11013
  for (const item of loaded) {
10890
- const text = item.kind === "assistant-text" ? item.text : item.kind === "tool" && item.output?.kind === "ok" ? stringifyToolOutput(item.output.value) : null;
10891
- if (text === null) continue;
10892
- for (const pr of extractPullRequests(text, { requireCompleteNumber: false })) {
11014
+ if (item.kind !== "assistant-text") continue;
11015
+ for (const pr of extractPullRequests(item.text, {
11016
+ requireCompleteNumber: false,
11017
+ urlOnlyText: toolUrlText
11018
+ })) {
10893
11019
  if (reportedPrsRef.current.has(pr.key)) continue;
10894
11020
  reportedPrsRef.current.set(pr.key, pr);
10895
11021
  setPrLinks((prev) => [...prev, pr]);
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import "./client-DbqRBquD.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-CL25CwjW.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-OlciVTBj.mjs";
4
4
  import "./api-DG5W6iwx.mjs";
5
5
 
6
6
  export { runPortalDaemon };
@@ -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 "1786604432";
23
+ return "1786633773";
24
24
  }
25
25
  /**
26
26
  * Whether a client carrying `mine` should replace a running daemon carrying
@@ -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-CL25CwjW.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-OlciVTBj.mjs";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { connect } from "node:net";
5
5
 
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import "./client-DbqRBquD.mjs";
3
+ import "./daemon-OlciVTBj.mjs";
4
+ import "./api-DG5W6iwx.mjs";
5
+ import { t as PortalDaemonClient } from "./daemon-client-Bue4JMhq.mjs";
6
+
7
+ export { PortalDaemonClient };
@@ -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.14";
11
+ var version$1 = "0.5.0-beta.16";
12
12
 
13
13
  //#endregion
14
14
  //#region src/auth/organization.ts
@@ -907,6 +907,17 @@ const WORDMARK = [
907
907
  "███████║██║ ██╗ ██║ ██████╔╝██║ ╚████╔╝ ███████╗",
908
908
  "╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚══════╝"
909
909
  ];
910
+ /** Columns between the pinwheel and the wordmark. */
911
+ const SPLASH_GAP = 3;
912
+ /** Columns the full mark + wordmark occupies (no chrome). */
913
+ const SPLASH_WIDTH = MARK_WIDTH + SPLASH_GAP + WORDMARK.reduce((max, line) => Math.max(max, line.length), 0);
914
+ /** Extra columns around the splash: `skydive --help` indents by 2, the TUI
915
+ * app pads 1 on each side. Same number, so one threshold covers both. */
916
+ const SPLASH_CHROME = 2;
917
+ /** Whether the full splash fits on one line of `columns` without wrapping. */
918
+ function splashFitsWidth(columns) {
919
+ return columns >= SPLASH_WIDTH + SPLASH_CHROME;
920
+ }
910
921
  const RESET = "\x1B[0m";
911
922
  function hexToRgb(hex) {
912
923
  const n = Number.parseInt(hex.slice(1), 16);
@@ -933,7 +944,7 @@ function markLinesAnsi() {
933
944
  function brandHelpArt(stream = process.stdout) {
934
945
  if (!stream.isTTY) return "";
935
946
  const truecolor = (typeof stream.getColorDepth === "function" ? stream.getColorDepth() : 1) >= 24;
936
- if ((stream.columns ?? 80) < 66) return truecolor ? `\n ${sgr("✦", BRAND_ACCENT)} Skydive\n` : "\n ✦ Skydive\n";
947
+ if (!splashFitsWidth(stream.columns ?? 80)) return truecolor ? `\n ${sgr("✦", BRAND_ACCENT)} Skydive\n` : "\n ✦ Skydive\n";
937
948
  if (!truecolor) return `\n${WORDMARK.map((l) => ` ${l}`).join("\n")}\n`;
938
949
  const mark = markLinesAnsi();
939
950
  const word = [...WORDMARK];
@@ -1264,4 +1275,4 @@ function maybeStartProfiling(argv, cliVersion) {
1264
1275
  }
1265
1276
 
1266
1277
  //#endregion
1267
- export { ensureActiveOrganization as C, setActiveWorkspace as D, listWorkspaces as E, name as O, themesForMode as S, getSessionIdentity as T, themeForMode as _, installCrashHandler as a, themeVersion as b, MARK_CELLS as c, DEFAULT_THEME_ID as d, applyTheme as f, theme as g, noColorRequested as h, writeArtifact as i, version$1 as k, WORDMARK as l, monoTheme as m, profilingEnabled as n, buildCrashReport as o, findTheme as p, record as r, writeCrashReport as s, maybeStartProfiling as t, brandHelpArt as u, themeMode as v, getActiveWorkspaceId as w, themes as x, themeModeFromColorFgBg as y };
1278
+ export { version$1 as A, themesForMode as C, listWorkspaces as D, getSessionIdentity as E, setActiveWorkspace as O, themes as S, getActiveWorkspaceId as T, theme as _, installCrashHandler as a, themeModeFromColorFgBg as b, MARK_CELLS as c, splashFitsWidth as d, DEFAULT_THEME_ID as f, noColorRequested as g, monoTheme as h, writeArtifact as i, name as k, WORDMARK as l, findTheme as m, profilingEnabled as n, buildCrashReport as o, applyTheme as p, record as r, writeCrashReport as s, maybeStartProfiling as t, brandHelpArt as u, themeForMode as v, ensureActiveOrganization as w, themeVersion as x, themeMode as y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skydive-cli",
3
- "version": "0.5.0-beta.14",
3
+ "version": "0.5.0-beta.16",
4
4
  "description": "Skydive CLI — cloud agents from the command line",
5
5
  "homepage": "https://skydive.com",
6
6
  "license": "MIT",
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env node
2
- import "./client-DbqRBquD.mjs";
3
- import "./daemon-CL25CwjW.mjs";
4
- import "./api-DG5W6iwx.mjs";
5
- import { t as PortalDaemonClient } from "./daemon-client-k8ICifN2.mjs";
6
-
7
- export { PortalDaemonClient };