skydive-cli 0.3.0 → 0.4.0-beta.1029

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-BSHn51iw.mjs} +279 -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-BpAnESuq.mjs +6 -0
  37. package/dist/js/{client-Dc7GZ3PG.mjs → client-DiaJZQG6.mjs} +1 -1
  38. package/dist/js/{daemon-Co4CtpXZ.mjs → daemon-BSSZpzto.mjs} +2 -2
  39. package/dist/js/daemon-ybnlDUgi.mjs +7 -0
  40. package/dist/js/{install-CMYBFvR2.mjs → install-C_N51msl.mjs} +2 -2
  41. package/dist/js/{print-Cd-bVSEJ.mjs → print-CiaU2ygh.mjs} +84 -2
  42. package/dist/js/{print-share-7qfpQtWK.mjs → print-share-DlO5RSBt.mjs} +1 -1
  43. package/dist/js/{print-CPCFci60.mjs → print-vUf9stcK.mjs} +3 -2
  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-BlN_uWmL.mjs → rest-CG7VpQ56.mjs} +13 -2
  47. package/dist/js/rest-DfpoRJ_Q.mjs +5 -0
  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
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { C as setActiveWorkspace, S as listWorkspaces, a as WORDMARK, b as getActiveWorkspaceId, c as applyTheme, d as noColorRequested, f as theme, g as themeVersion, h as themeModeFromColorFgBg, i as MARK_CELLS, l as findTheme, m as themeMode, n as buildCrashReport, p as themeForMode, r as writeCrashReport, s as DEFAULT_THEME_ID, t as installCrashHandler, u as monoTheme, v as themesForMode } from "./install-CMYBFvR2.mjs";
3
- import { L as saveTheme, P as resolveWebUrl, T as getSavedTheme, 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, w as getReviewStateDir, x as getConfigPath, y as DEFAULT_APP_URL } from "./print-Cd-bVSEJ.mjs";
4
- import { a as errorMessage, i as sendErrorMessage, n as createRestClient, o as isRecord, r as errorDetail, t as HttpError } from "./rest-BlN_uWmL.mjs";
5
- import { t as PortalClient } from "./client-Dc7GZ3PG.mjs";
6
- import { d as makeLineParser, f as parseDaemonMessage, l as daemonPaths, n as ensureDaemonRunning, s as LOCAL_PROTOCOL_VERSION, u as encodeLine } from "./daemon-Co4CtpXZ.mjs";
7
- import { t as SandboxStream } from "./client-DfcJFEbh.mjs";
8
- import { t as runRawPtyPassthrough } from "./raw-pty-BcjbTjHJ.mjs";
2
+ import { C as setActiveWorkspace, S as listWorkspaces, a as WORDMARK, b as getActiveWorkspaceId, c as applyTheme, d as noColorRequested, f as theme, g as themeVersion, h as themeModeFromColorFgBg, i as MARK_CELLS, l as findTheme, m as themeMode, n as buildCrashReport, p as themeForMode, r as writeCrashReport, s as DEFAULT_THEME_ID, t as installCrashHandler, u as monoTheme, v as themesForMode } from "./install-C_N51msl.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-CiaU2ygh.mjs";
4
+ import { a as errorMessage, i as sendErrorMessage, n as createRestClient, o as isRecord, r as errorDetail, t as HttpError } from "./rest-CG7VpQ56.mjs";
5
+ import { i as billingBlockedOutcomeFromSendResponse } from "./billing-blocked-CwfG1BTR.mjs";
6
+ import { t as PortalClient } from "./client-DiaJZQG6.mjs";
7
+ import { d as makeLineParser, f as parseDaemonMessage, l as daemonPaths, n as ensureDaemonRunning, s as LOCAL_PROTOCOL_VERSION, u as encodeLine } from "./daemon-BSSZpzto.mjs";
8
+ import { t as SandboxStream } from "./client-D8s9vY4p.mjs";
9
+ import { t as runRawPtyPassthrough } from "./raw-pty-DAmb8uqt.mjs";
9
10
  import * as os$1 from "node:os";
10
11
  import { homedir, platform, release, tmpdir } from "node:os";
11
12
  import path, { basename, extname, isAbsolute, join, win32 } from "node:path";
@@ -16,7 +17,7 @@ import { execFile, spawn } from "node:child_process";
16
17
  import { createHash, randomUUID } from "node:crypto";
17
18
  import { connect, createConnection } from "node:net";
18
19
  import { access, appendFile, mkdir, mkdtemp, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
19
- import { MarkdownRenderable, RenderableEvents, StyledText, SyntaxStyle, createCliRenderer, decodePasteBytes, detectLinks, fg, link } from "@opentui/core";
20
+ import { MarkdownRenderable, RenderableEvents, StyledText, SyntaxStyle, addDefaultParsers, createCliRenderer, decodePasteBytes, detectLinks, fg, link } from "@opentui/core";
20
21
  import { createRoot, extend, useKeyboard, usePaste, useRenderer, useTerminalDimensions } from "@opentui/react";
21
22
  import { createContext, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
22
23
  import { create } from "zustand";
@@ -69,6 +70,7 @@ const useStore = create((set, get) => ({
69
70
  themeLocked: false,
70
71
  appUrl: null,
71
72
  sessionToken: null,
73
+ authKind: "session",
72
74
  workspaceName: null,
73
75
  rest: null,
74
76
  promptHistoryPath: null,
@@ -437,10 +439,11 @@ function createPortalClient(opts) {
437
439
  * user turns sharing on from the chat screen. Detaching on unmount leaves the
438
440
  * daemon running for any other attached CLIs.
439
441
  */
440
- function usePortal({ appUrl, sessionToken, shareMachine }) {
442
+ function usePortal({ appUrl, sessionToken, shareMachine, enabled = true }) {
441
443
  const syncPortal = useStore((s) => s.syncPortal);
442
444
  const setPortalClient = useStore((s) => s.setPortalClient);
443
445
  useEffect(() => {
446
+ if (!enabled) return;
444
447
  const client = createPortalClient({
445
448
  appUrl,
446
449
  sessionToken,
@@ -463,6 +466,7 @@ function usePortal({ appUrl, sessionToken, shareMachine }) {
463
466
  appUrl,
464
467
  sessionToken,
465
468
  shareMachine,
469
+ enabled,
466
470
  syncPortal,
467
471
  setPortalClient
468
472
  ]);
@@ -1687,8 +1691,13 @@ function AgentPickerScreen() {
1687
1691
  const [cache, setCache] = useState({});
1688
1692
  const [state, setState] = useState({ kind: "loading" });
1689
1693
  useKeyboard((key) => {
1690
- if (key.name === "w" && key.ctrl) goTo({ kind: "workspace-picker" });
1691
- else if (key.name === "n" && key.ctrl) goTo({ kind: "agent-create" });
1694
+ if (key.name === "w" && key.ctrl) {
1695
+ if (useStore.getState().authKind === "api-key") {
1696
+ useStore.getState().showToast({ message: "API keys are pinned to one workspace. Run `skydive auth login --web` to switch workspaces." });
1697
+ return;
1698
+ }
1699
+ goTo({ kind: "workspace-picker" });
1700
+ } else if (key.name === "n" && key.ctrl) goTo({ kind: "agent-create" });
1692
1701
  else if (key.name === "tab") setScope((s) => s === "mine" ? "org" : "mine");
1693
1702
  });
1694
1703
  useEffect(() => {
@@ -1797,6 +1806,8 @@ function FilterableAgentList({ agents, scope, onPick }) {
1797
1806
  useKeyboard((key) => {
1798
1807
  if (key.name === "up") setHighlight(Math.max(0, clamped - 1));
1799
1808
  else if (key.name === "down") setHighlight(Math.min(Math.max(0, filtered.length - 1), clamped + 1));
1809
+ else if (key.name === "pageup" || key.name === "pgup") setHighlight(Math.max(0, clamped - Math.max(1, visibleRows - 1)));
1810
+ else if (key.name === "pagedown" || key.name === "pgdn") setHighlight(Math.min(Math.max(0, filtered.length - 1), clamped + Math.max(1, visibleRows - 1)));
1800
1811
  else if (key.name === "return") {
1801
1812
  const agent = filtered[clamped];
1802
1813
  if (!agent) return;
@@ -2269,6 +2280,31 @@ function CollapseHint() {
2269
2280
  children: "· click to collapse"
2270
2281
  });
2271
2282
  }
2283
+ /** Collapsed line cap for tool error output. */
2284
+ const maxErrorLines = 8;
2285
+ /**
2286
+ * Tool error output, clamped like ok output: collapsed it clips to
2287
+ * `maxErrorLines` with the "+N more · click to expand" tail; expanded it
2288
+ * renders in full, word-wrapped. Same click-to-toggle box as the rest of
2289
+ * the block — a failing command's output shouldn't flood the scrollback
2290
+ * just because it arrived on the error channel.
2291
+ */
2292
+ function ErrorLines({ text, expanded }) {
2293
+ if (expanded) return /* @__PURE__ */ jsx(ExpandedLines, {
2294
+ text,
2295
+ fg: theme.error
2296
+ });
2297
+ return /* @__PURE__ */ jsx("box", {
2298
+ style: { flexDirection: "column" },
2299
+ children: clipLines(text, {
2300
+ maxLines: maxErrorLines,
2301
+ maxLineLen: 100
2302
+ }).map((line, idx) => /* @__PURE__ */ jsx("text", {
2303
+ fg: theme.error,
2304
+ children: line
2305
+ }, idx))
2306
+ });
2307
+ }
2272
2308
  /** Indents tool output two columns under its header — no rule, just space. */
2273
2309
  function Indented({ children }) {
2274
2310
  return /* @__PURE__ */ jsx("box", {
@@ -2669,6 +2705,8 @@ function ConversationList({ agent, conversations, complete, showAutomated, onTog
2669
2705
  if (key.name === "escape") onPick({ kind: "agent-picker" });
2670
2706
  else if (key.name === "up") setHighlight(Math.max(0, clamped - 1));
2671
2707
  else if (key.name === "down") setHighlight(Math.min(Math.max(0, rows.length - 1), clamped + 1));
2708
+ else if (key.name === "pageup" || key.name === "pgup") setHighlight(Math.max(0, clamped - Math.max(1, visibleRows - 1)));
2709
+ else if (key.name === "pagedown" || key.name === "pgdn") setHighlight(Math.min(Math.max(0, rows.length - 1), clamped + Math.max(1, visibleRows - 1)));
2672
2710
  else if (key.name === "d" && key.ctrl) {
2673
2711
  const row = rows[clamped];
2674
2712
  if (row?.kind === "conv") {
@@ -3056,8 +3094,13 @@ function syntaxStyleTable() {
3056
3094
  "keyword.function": { fg: theme.syntaxMagenta },
3057
3095
  "keyword.return": { fg: theme.syntaxMagenta },
3058
3096
  "keyword.operator": { fg: theme.syntaxMagenta },
3097
+ conditional: { fg: theme.syntaxMagenta },
3098
+ repeat: { fg: theme.syntaxMagenta },
3099
+ preproc: { fg: theme.syntaxMagenta },
3059
3100
  string: { fg: theme.syntaxGreen },
3060
3101
  "string.special": { fg: theme.syntaxTeal },
3102
+ escape: { fg: theme.syntaxTeal },
3103
+ "string.escape": { fg: theme.syntaxTeal },
3061
3104
  character: { fg: theme.syntaxGreen },
3062
3105
  comment: {
3063
3106
  fg: theme.dim,
@@ -3066,10 +3109,13 @@ function syntaxStyleTable() {
3066
3109
  function: { fg: theme.syntaxBlue },
3067
3110
  "function.method": { fg: theme.syntaxBlue },
3068
3111
  "function.builtin": { fg: theme.syntaxCyan },
3112
+ method: { fg: theme.syntaxBlue },
3069
3113
  variable: { fg: theme.fg },
3070
3114
  "variable.builtin": { fg: theme.syntaxRed },
3071
3115
  "variable.parameter": { fg: theme.syntaxYellow },
3072
3116
  "variable.member": { fg: theme.syntaxTeal },
3117
+ parameter: { fg: theme.syntaxYellow },
3118
+ field: { fg: theme.syntaxTeal },
3073
3119
  type: { fg: theme.syntaxCyan },
3074
3120
  "type.builtin": { fg: theme.syntaxCyan },
3075
3121
  constant: { fg: theme.syntaxOrange },
@@ -3084,6 +3130,7 @@ function syntaxStyleTable() {
3084
3130
  "punctuation.bracket": { fg: theme.muted },
3085
3131
  "punctuation.delimiter": { fg: theme.muted },
3086
3132
  "punctuation.special": { fg: theme.syntaxCyan },
3133
+ delimiter: { fg: theme.muted },
3087
3134
  constructor: { fg: theme.syntaxBlue },
3088
3135
  module: { fg: theme.syntaxBlue },
3089
3136
  label: { fg: theme.syntaxCyan },
@@ -3199,6 +3246,22 @@ function StatusLine({ item }) {
3199
3246
  }
3200
3247
  }
3201
3248
 
3249
+ //#endregion
3250
+ //#region src/chat/tui/chunks/system-notice-item.tsx
3251
+ /**
3252
+ * Platform system-notice row: the server stopped or refused work for a
3253
+ * platform reason (today: billing-blocked), so it reads as a warning-toned
3254
+ * notice — visually distinct from the ✗ transport/agent error row. The text
3255
+ * already carries the full guidance, produced server-side or derived from a
3256
+ * typed outcome by the producer that pushed the item.
3257
+ */
3258
+ function SystemNoticeItem({ item }) {
3259
+ return /* @__PURE__ */ jsxs("text", {
3260
+ fg: theme.warning,
3261
+ children: ["◆ ", item.text.trim()]
3262
+ });
3263
+ }
3264
+
3202
3265
  //#endregion
3203
3266
  //#region src/chat/tui/chunks/error-item.tsx
3204
3267
  /**
@@ -3331,9 +3394,9 @@ function ToolGeneric({ item }) {
3331
3394
  fg: theme.dim,
3332
3395
  children: " · click to expand"
3333
3396
  })]
3334
- }) : /* @__PURE__ */ jsx("text", {
3335
- fg: theme.error,
3336
- children: item.output.errorText
3397
+ }) : /* @__PURE__ */ jsx(ErrorLines, {
3398
+ text: item.output.errorText,
3399
+ expanded
3337
3400
  }) }) : null,
3338
3401
  expanded && /* @__PURE__ */ jsx(Indented, { children: /* @__PURE__ */ jsx(CollapseHint, {}) })
3339
3402
  ]
@@ -3480,10 +3543,16 @@ function trimCommonIndent(hunks) {
3480
3543
  for (const hunk of hunks) hunk.lines = hunk.lines.map((line) => isContentLine(line) ? line[0] + line.slice(1 + min) : line);
3481
3544
  }
3482
3545
  /**
3483
- * OpenTUI 0.2.15 bundles tree-sitter parsers for javascript(+react),
3484
- * typescript(+react), markdown, and zig only anything else renders
3485
- * unhighlighted (but still red/green). Returning undefined skips the
3486
- * highlight pass entirely instead of asking for a parser that isn't there.
3546
+ * Maps a file extension to the tree-sitter filetype used to highlight it in
3547
+ * diffs and the file/review pane. OpenTUI bundles parsers for
3548
+ * javascript(+react), typescript(+react), markdown, and zig; the rest are
3549
+ * registered by ./grammars. An extension absent here returns
3550
+ * undefined, which skips the highlight pass (the diff still renders red/green)
3551
+ * rather than asking the client for a parser it doesn't have.
3552
+ *
3553
+ * Keep the filetype values in sync with the canonical `filetype`/`aliases`
3554
+ * registered in ./grammars/grammars.ts — an extension mapping to a filetype no
3555
+ * parser is registered for just renders plain.
3487
3556
  */
3488
3557
  const FILETYPE_BY_EXT = {
3489
3558
  ts: "typescript",
@@ -3496,7 +3565,34 @@ const FILETYPE_BY_EXT = {
3496
3565
  jsx: "javascriptreact",
3497
3566
  md: "markdown",
3498
3567
  markdown: "markdown",
3499
- zig: "zig"
3568
+ zig: "zig",
3569
+ py: "python",
3570
+ pyi: "python",
3571
+ pyw: "python",
3572
+ sh: "bash",
3573
+ bash: "bash",
3574
+ zsh: "bash",
3575
+ go: "go",
3576
+ rs: "rust",
3577
+ c: "c",
3578
+ h: "c",
3579
+ cpp: "cpp",
3580
+ cxx: "cpp",
3581
+ cc: "cpp",
3582
+ hpp: "cpp",
3583
+ hxx: "cpp",
3584
+ hh: "cpp",
3585
+ java: "java",
3586
+ rb: "ruby",
3587
+ css: "css",
3588
+ html: "html",
3589
+ htm: "html",
3590
+ json: "json",
3591
+ jsonc: "json",
3592
+ yaml: "yaml",
3593
+ yml: "yaml",
3594
+ toml: "toml",
3595
+ lua: "lua"
3500
3596
  };
3501
3597
  function filetypeForPath(filePath) {
3502
3598
  const base = filePath.split("/").pop() ?? filePath;
@@ -3711,10 +3807,10 @@ function ToolEdit({ item }) {
3711
3807
  fg: theme.muted,
3712
3808
  children: "(no changes)"
3713
3809
  }) }) : null,
3714
- item.output?.kind === "error" && /* @__PURE__ */ jsx(Indented, { children: /* @__PURE__ */ jsx("text", {
3715
- fg: theme.error,
3716
- children: item.output.errorText
3717
- }) }),
3810
+ item.output?.kind === "error" && /* @__PURE__ */ jsxs(Indented, { children: [/* @__PURE__ */ jsx(ErrorLines, {
3811
+ text: item.output.errorText,
3812
+ expanded
3813
+ }), expanded && isClipped(item.output.errorText, maxErrorLines) && /* @__PURE__ */ jsx(CollapseHint, {})] }),
3718
3814
  item.output?.kind === "ok" && pairs === null && inputReady && /* @__PURE__ */ jsx(Indented, { children: /* @__PURE__ */ jsx("text", {
3719
3815
  fg: theme.muted,
3720
3816
  children: summarize(item.output.value, 60)
@@ -3840,12 +3936,13 @@ function PlainDiff({ diff }) {
3840
3936
  //#endregion
3841
3937
  //#region src/chat/tui/chunks/tool-bash.tsx
3842
3938
  const maxLines$1 = 8;
3939
+ const maxCommandLines = 3;
3843
3940
  /**
3844
3941
  * Rich view for the `bash`/`shell` tool. Leads with the model-authored
3845
3942
  * summary when present (mirroring web chat's activity labels), with the
3846
3943
  * command on its own dim line beneath; then its output
3847
3944
  * (truncated to keep the scrollback usable — click the block to toggle the
3848
- * full output, ANY-3999).
3945
+ * full command and output).
3849
3946
  */
3850
3947
  function ToolBash({ item }) {
3851
3948
  const glyph = toolGlyph(item.state);
@@ -3854,6 +3951,10 @@ function ToolBash({ item }) {
3854
3951
  const output = item.output?.kind === "ok" ? typeof item.output.value === "string" ? item.output.value : extractStdout(item.output.value) : null;
3855
3952
  const summary = getToolCallSummary(item.input);
3856
3953
  const [expanded, setExpanded] = useState(false);
3954
+ const commandText = command || "(empty command)";
3955
+ const headerCommandLine = commandText.split("\n", 1)[0] ?? commandText;
3956
+ const commandBlockText = summary !== null ? `$ ${commandText}` : commandText.slice(headerCommandLine.length + 1);
3957
+ const showCommandBlock = commandBlockText.length > 0;
3857
3958
  return /* @__PURE__ */ jsxs("box", {
3858
3959
  style: {
3859
3960
  flexDirection: "column",
@@ -3866,14 +3967,19 @@ function ToolBash({ item }) {
3866
3967
  children: [glyph.ch, " "]
3867
3968
  }), /* @__PURE__ */ jsx("span", {
3868
3969
  fg: theme.tool,
3869
- children: summary !== null ? summary : `$ ${command || "(empty command)"}`
3970
+ children: summary !== null ? summary : `$ ${headerCommandLine}`
3870
3971
  })] }),
3871
- summary !== null && /* @__PURE__ */ jsx(Indented, { children: /* @__PURE__ */ jsxs("text", {
3972
+ showCommandBlock && /* @__PURE__ */ jsx(Indented, { children: expanded ? /* @__PURE__ */ jsx(ExpandedLines, {
3973
+ text: commandBlockText,
3974
+ fg: theme.muted
3975
+ }) : clipLines(commandBlockText, {
3976
+ maxLines: maxCommandLines,
3977
+ maxLineLen: 100
3978
+ }).map((line, idx) => /* @__PURE__ */ jsx("text", {
3872
3979
  fg: theme.muted,
3873
- style: { wrapMode: "word" },
3874
- children: ["$ ", command || "(empty command)"]
3875
- }) }),
3876
- output !== null && output.length > 0 && /* @__PURE__ */ jsxs(Indented, { children: [expanded ? /* @__PURE__ */ jsx(ExpandedLines, {
3980
+ children: line
3981
+ }, idx)) }),
3982
+ output !== null && output.length > 0 && /* @__PURE__ */ jsx(Indented, { children: expanded ? /* @__PURE__ */ jsx(ExpandedLines, {
3877
3983
  text: output,
3878
3984
  fg: theme.muted
3879
3985
  }) : clipLines(output, {
@@ -3882,11 +3988,12 @@ function ToolBash({ item }) {
3882
3988
  }).map((line, idx) => /* @__PURE__ */ jsx("text", {
3883
3989
  fg: theme.muted,
3884
3990
  children: line
3885
- }, idx)), expanded && isClipped(output, maxLines$1) && /* @__PURE__ */ jsx(CollapseHint, {})] }),
3886
- item.output?.kind === "error" && /* @__PURE__ */ jsx(Indented, { children: /* @__PURE__ */ jsx("text", {
3887
- fg: theme.error,
3888
- children: item.output.errorText
3889
- }) })
3991
+ }, idx)) }),
3992
+ item.output?.kind === "error" && /* @__PURE__ */ jsx(Indented, { children: /* @__PURE__ */ jsx(ErrorLines, {
3993
+ text: item.output.errorText,
3994
+ expanded
3995
+ }) }),
3996
+ expanded && (showCommandBlock && isClipped(commandBlockText, maxCommandLines) || output !== null && isClipped(output, maxLines$1) || item.output?.kind === "error" && isClipped(item.output.errorText, maxErrorLines)) && /* @__PURE__ */ jsx(Indented, { children: /* @__PURE__ */ jsx(CollapseHint, {}) })
3890
3997
  ]
3891
3998
  });
3892
3999
  }
@@ -3954,10 +4061,10 @@ function ToolRead({ item }) {
3954
4061
  fg: theme.muted,
3955
4062
  children: line
3956
4063
  }, idx)), expanded && isClipped(preview, maxLines) && /* @__PURE__ */ jsx(CollapseHint, {})] }),
3957
- item.output?.kind === "error" && /* @__PURE__ */ jsx(Indented, { children: /* @__PURE__ */ jsx("text", {
3958
- fg: theme.error,
3959
- children: item.output.errorText
3960
- }) })
4064
+ item.output?.kind === "error" && /* @__PURE__ */ jsxs(Indented, { children: [/* @__PURE__ */ jsx(ErrorLines, {
4065
+ text: item.output.errorText,
4066
+ expanded
4067
+ }), expanded && isClipped(item.output.errorText, maxErrorLines) && /* @__PURE__ */ jsx(CollapseHint, {})] })
3961
4068
  ]
3962
4069
  });
3963
4070
  }
@@ -4038,10 +4145,10 @@ function ToolGrep({ item }) {
4038
4145
  }),
4039
4146
  expanded && matches.length > maxMatches && /* @__PURE__ */ jsx(CollapseHint, {})
4040
4147
  ] }),
4041
- item.output?.kind === "error" && /* @__PURE__ */ jsx(Indented, { children: /* @__PURE__ */ jsx("text", {
4042
- fg: theme.error,
4043
- children: item.output.errorText
4044
- }) })
4148
+ item.output?.kind === "error" && /* @__PURE__ */ jsxs(Indented, { children: [/* @__PURE__ */ jsx(ErrorLines, {
4149
+ text: item.output.errorText,
4150
+ expanded
4151
+ }), expanded && isClipped(item.output.errorText, maxErrorLines) && /* @__PURE__ */ jsx(CollapseHint, {})] })
4045
4152
  ]
4046
4153
  });
4047
4154
  }
@@ -4165,6 +4272,10 @@ function RenderItem({ item }) {
4165
4272
  barColor: null,
4166
4273
  children: /* @__PURE__ */ jsx(ErrorItem, { item })
4167
4274
  });
4275
+ case "system-notice": return /* @__PURE__ */ jsx(Row, {
4276
+ barColor: null,
4277
+ children: /* @__PURE__ */ jsx(SystemNoticeItem, { item })
4278
+ });
4168
4279
  case "card": return /* @__PURE__ */ jsx(Row, {
4169
4280
  barColor: null,
4170
4281
  children: /* @__PURE__ */ jsx(CardItem, { item })
@@ -4804,6 +4915,15 @@ function uiMessagesToItems(messages) {
4804
4915
  const out = [];
4805
4916
  for (const m of messages) {
4806
4917
  if (m.role === "user") {
4918
+ const billingNotice = billingBlockedNoticeText(m.parts);
4919
+ if (billingNotice) {
4920
+ out.push({
4921
+ kind: "system-notice",
4922
+ id: m.id,
4923
+ text: billingNotice
4924
+ });
4925
+ continue;
4926
+ }
4807
4927
  const text = m.parts.map((p) => p.type === "text" ? p.text : "").join("").trim();
4808
4928
  const attachments = m.parts.filter((p) => p.type === "data-anyone-attachment").map((p) => {
4809
4929
  const data = isRecord(p) && "data" in p && isRecord(p.data) ? p.data : null;
@@ -4898,6 +5018,14 @@ function uiMessagesToItems(messages) {
4898
5018
  }
4899
5019
  return out.map(clampItem);
4900
5020
  }
5021
+ function billingBlockedNoticeText(parts) {
5022
+ for (const part of parts) {
5023
+ if (part.type !== "data-anyone-system-notification") continue;
5024
+ const data = isRecord(part) && isRecord(part.data) ? part.data : null;
5025
+ if (data && data.kind === "billing-blocked" && typeof data.text === "string" && data.text.trim()) return data.text;
5026
+ }
5027
+ return null;
5028
+ }
4901
5029
  function mapToolState(raw) {
4902
5030
  switch (raw) {
4903
5031
  case "output-error": return "error";
@@ -5600,6 +5728,10 @@ const keybindGroups = [
5600
5728
  keys: "/plan [show|hide]",
5601
5729
  action: "show / hide the pinned plan card"
5602
5730
  },
5731
+ {
5732
+ keys: "/rename <title>",
5733
+ action: "rename this conversation (for everyone)"
5734
+ },
5603
5735
  {
5604
5736
  keys: "/",
5605
5737
  action: "open the command menu (start of message)"
@@ -5711,6 +5843,10 @@ const keybindGroups = [
5711
5843
  keys: "↑ / ↓",
5712
5844
  action: "move selection"
5713
5845
  },
5846
+ {
5847
+ keys: "pgup / pgdn",
5848
+ action: "move selection a page at a time"
5849
+ },
5714
5850
  {
5715
5851
  keys: "↵",
5716
5852
  action: "open agent"
@@ -5736,6 +5872,10 @@ const keybindGroups = [
5736
5872
  keys: "↑ / ↓",
5737
5873
  action: "move selection"
5738
5874
  },
5875
+ {
5876
+ keys: "pgup / pgdn",
5877
+ action: "move selection a page at a time"
5878
+ },
5739
5879
  {
5740
5880
  keys: "↵",
5741
5881
  action: "open conversation"
@@ -6052,6 +6192,13 @@ const slashCommands = [
6052
6192
  description: "archive this conversation and hide it from your list",
6053
6193
  action: { kind: "archive-conversation" }
6054
6194
  },
6195
+ {
6196
+ name: "rename",
6197
+ aliases: ["title"],
6198
+ argsHint: "<new title>",
6199
+ description: "rename this conversation (for everyone)",
6200
+ action: { kind: "rename-conversation" }
6201
+ },
6055
6202
  {
6056
6203
  name: "conversation",
6057
6204
  aliases: ["conversations", "conv"],
@@ -9185,6 +9332,10 @@ function routeInput(raw) {
9185
9332
  kind: "open-conversation",
9186
9333
  ref: match.rest
9187
9334
  } : { kind: "conversation-picker" };
9335
+ case "rename-conversation": return {
9336
+ kind: "rename-conversation",
9337
+ title: match.rest
9338
+ };
9188
9339
  case "plan": {
9189
9340
  const arg = match.rest.toLowerCase();
9190
9341
  return {
@@ -9651,8 +9802,14 @@ function ChatScreen({ agent, conversation }) {
9651
9802
  setItems((prev) => applyChunk(prev, event.chunk, streamAgentName));
9652
9803
  return;
9653
9804
  }
9805
+ const billingOutcome = event.outcome;
9654
9806
  const errorText = event.error;
9655
- if (event.status !== "ok" && errorText) setItems((prev) => [...prev, {
9807
+ if (billingOutcome) setItems((prev) => [...prev, {
9808
+ kind: "system-notice",
9809
+ id: crypto.randomUUID(),
9810
+ text: billingOutcome.message
9811
+ }]);
9812
+ else if (event.status !== "ok" && errorText) setItems((prev) => [...prev, {
9656
9813
  kind: "error",
9657
9814
  id: crypto.randomUUID(),
9658
9815
  text: errorText
@@ -9787,7 +9944,15 @@ function ChatScreen({ agent, conversation }) {
9787
9944
  }
9788
9945
  attachToRun(result.runId, result.agentName ?? null);
9789
9946
  } catch (err) {
9790
- setItems((prev) => [...prev.filter((m) => m.id !== optimisticId), {
9947
+ const billing = err instanceof HttpError ? billingBlockedOutcomeFromSendResponse({
9948
+ status: err.status,
9949
+ body: err.body
9950
+ }) : null;
9951
+ setItems((prev) => [...prev.filter((m) => m.id !== optimisticId), billing ? {
9952
+ kind: "system-notice",
9953
+ id: crypto.randomUUID(),
9954
+ text: billing.message
9955
+ } : {
9791
9956
  kind: "error",
9792
9957
  id: crypto.randomUUID(),
9793
9958
  text: sendErrorMessage(err)
@@ -10375,6 +10540,38 @@ function ChatScreen({ agent, conversation }) {
10375
10540
  goTo,
10376
10541
  agent
10377
10542
  ]);
10543
+ const renameConversation = useCallback((rawTitle) => {
10544
+ const title = rawTitle.trim();
10545
+ if (!title) {
10546
+ useStore.getState().showToast({ message: "usage: /rename <new title>" });
10547
+ return;
10548
+ }
10549
+ if (title.length > 100) {
10550
+ useStore.getState().showToast({
10551
+ variant: "error",
10552
+ message: `title is too long (${String(title.length)}/100 characters)`
10553
+ });
10554
+ return;
10555
+ }
10556
+ if (!conversationId) {
10557
+ useStore.getState().showToast({ message: "nothing to rename yet — send a message to start this one" });
10558
+ return;
10559
+ }
10560
+ if (!rest) return;
10561
+ rest.renameConversation({
10562
+ conversationId,
10563
+ title
10564
+ }).then(() => {
10565
+ useStore.getState().setChatTitle(title);
10566
+ useStore.getState().showToast({
10567
+ variant: "success",
10568
+ message: `renamed to “${title}”`
10569
+ });
10570
+ }).catch((err) => useStore.getState().showToast({
10571
+ variant: "error",
10572
+ message: `couldn't rename: ${errorMessage(err)}`
10573
+ }));
10574
+ }, [conversationId, rest]);
10378
10575
  /**
10379
10576
  * Leave this conversation for another screen without ending its run: drop
10380
10577
  * our own SSE subscription and nothing else — never `rest.cancelRun` — so
@@ -10434,7 +10631,10 @@ function ChatScreen({ agent, conversation }) {
10434
10631
  leaveForScreen
10435
10632
  ]);
10436
10633
  const toggleShare = useCallback(() => {
10437
- if (!portalClient) return;
10634
+ if (!portalClient) {
10635
+ useStore.getState().showToast({ message: useStore.getState().authKind === "api-key" ? "local access needs a browser session — run `skydive auth login --web`" : "local access is unavailable in this session" });
10636
+ return;
10637
+ }
10438
10638
  if (portal.status === "off") {
10439
10639
  declinedRef.current.delete(agent.id);
10440
10640
  portalClient.enable();
@@ -10475,7 +10675,7 @@ function ChatScreen({ agent, conversation }) {
10475
10675
  }, [agent.id, setPortalPrompt]);
10476
10676
  const openPortalAccess = useCallback(() => {
10477
10677
  if (!portalClient) {
10478
- useStore.getState().showToast({ message: "local access is unavailable in this session" });
10678
+ useStore.getState().showToast({ message: useStore.getState().authKind === "api-key" ? "local access needs a browser session — run `skydive auth login --web`" : "local access is unavailable in this session" });
10479
10679
  return;
10480
10680
  }
10481
10681
  switch (portal.status) {
@@ -10540,6 +10740,9 @@ function ChatScreen({ agent, conversation }) {
10540
10740
  case "archive-conversation":
10541
10741
  archiveConversation();
10542
10742
  break;
10743
+ case "rename-conversation":
10744
+ renameConversation(routed.title);
10745
+ break;
10543
10746
  case "new-conversation":
10544
10747
  if (runRef.current.kind !== "idle") {
10545
10748
  useStore.getState().showToast({ message: "finish or cancel the run first (ctrl+c)" });
@@ -10653,6 +10856,7 @@ function ChatScreen({ agent, conversation }) {
10653
10856
  runSandboxExec,
10654
10857
  runSandboxPty,
10655
10858
  archiveConversation,
10859
+ renameConversation,
10656
10860
  forkAndContinue,
10657
10861
  leaveForScreen,
10658
10862
  openConversationRef,
@@ -11486,6 +11690,7 @@ function ToastView() {
11486
11690
  maxWidth: Math.min(60, Math.max(20, width - 6)),
11487
11691
  border: true,
11488
11692
  borderColor: toastColor(toast.variant),
11693
+ backgroundColor: theme.surface ?? "#1a1a1a",
11489
11694
  paddingLeft: 1,
11490
11695
  paddingRight: 1
11491
11696
  },
@@ -11499,7 +11704,7 @@ function ToastView() {
11499
11704
 
11500
11705
  //#endregion
11501
11706
  //#region src/chat/tui/app.tsx
11502
- function App({ appUrl, sessionToken, shareMachine, promptHistoryPath, notifications, agentSelector, conversationId, newConversation }) {
11707
+ function App({ appUrl, sessionToken, authKind, shareMachine, promptHistoryPath, notifications, agentSelector, conversationId, newConversation }) {
11503
11708
  const renderer = useRenderer();
11504
11709
  const screen = useStore((s) => s.screen);
11505
11710
  const chatTitle = useStore((s) => s.chatTitle);
@@ -11515,7 +11720,8 @@ function App({ appUrl, sessionToken, shareMachine, promptHistoryPath, notificati
11515
11720
  usePortal({
11516
11721
  appUrl,
11517
11722
  sessionToken,
11518
- shareMachine
11723
+ shareMachine,
11724
+ enabled: authKind === "session"
11519
11725
  });
11520
11726
  useEffect(() => {
11521
11727
  agentHost.transition({ state: "idle" });
@@ -11543,6 +11749,7 @@ function App({ appUrl, sessionToken, shareMachine, promptHistoryPath, notificati
11543
11749
  const fail = useStore((s) => s.fail);
11544
11750
  const setWorkspaceName = useStore((s) => s.setWorkspaceName);
11545
11751
  useEffect(() => {
11752
+ if (authKind !== "session") return;
11546
11753
  let cancelled = false;
11547
11754
  (async () => {
11548
11755
  const [activeId, workspaces] = await Promise.all([getActiveWorkspaceId({
@@ -11561,6 +11768,7 @@ function App({ appUrl, sessionToken, shareMachine, promptHistoryPath, notificati
11561
11768
  }, [
11562
11769
  appUrl,
11563
11770
  sessionToken,
11771
+ authKind,
11564
11772
  setWorkspaceName
11565
11773
  ]);
11566
11774
  useEffect(() => {
@@ -11899,6 +12107,24 @@ var LinkifiedMarkdownRenderable = class extends MarkdownRenderable {
11899
12107
  }
11900
12108
  };
11901
12109
 
12110
+ //#endregion
12111
+ //#region src/chat/tui/chunks/grammars/index.ts
12112
+ /**
12113
+ * Register the extra tree-sitter grammars (python, go, rust, bash, …) before
12114
+ * the renderer boots its tree-sitter client, so fenced code blocks and diffs
12115
+ * in those languages highlight instead of rendering as plain text.
12116
+ *
12117
+ * `addDefaultParsers` merges by filetype into the set the client registers on
12118
+ * init, so this must run BEFORE `createCliRenderer`. The grammar definitions
12119
+ * live in ./grammars.ts, which is externalized from the tsdown bundle (it
12120
+ * imports `.wasm`/`.scm` assets rolldown can't parse) — so we reach it through
12121
+ * a dynamic import that both Bun-runtime (npm) and the compiled binary resolve.
12122
+ */
12123
+ async function registerGrammars() {
12124
+ const { EXTRA_GRAMMARS } = await import("./chat/tui/chunks/grammars/grammars.js");
12125
+ addDefaultParsers(EXTRA_GRAMMARS);
12126
+ }
12127
+
11902
12128
  //#endregion
11903
12129
  //#region src/chat/memory-watchdog.ts
11904
12130
  const MB = 1024 * 1024;
@@ -12066,12 +12292,14 @@ async function settleBootThemeMode({ source, applyThemeMode, graceMs = THEME_QUE
12066
12292
  * module — and the heavy OpenTUI dependency graph it pulls in — is
12067
12293
  * dynamically imported and never loaded on the Node management path.
12068
12294
  */
12069
- async function runChat({ appUrl, sessionToken, shareMachine, promptHistoryPath, theme: themeOverride, notifications, agentSelector, conversationId, newConversation, seedPrompt }) {
12295
+ async function runChat({ appUrl, sessionToken, authKind = "session", shareMachine, promptHistoryPath, theme: themeOverride, notifications, agentSelector, conversationId, newConversation, seedPrompt }) {
12296
+ await registerGrammars();
12070
12297
  const watchdog = startMemoryWatchdog();
12071
12298
  installCrashHandler({ memoryProvider: () => watchdog.snapshot() });
12072
12299
  const renderer = await createCliRenderer({ exitOnCtrlC: false });
12073
12300
  extend({ markdown: LinkifiedMarkdownRenderable });
12074
12301
  useStore.setState({
12302
+ authKind,
12075
12303
  seedPrompt: seedPrompt ?? null,
12076
12304
  autoGrantMachine: Boolean(seedPrompt && shareMachine),
12077
12305
  newConversationIntent: newConversation
@@ -12091,6 +12319,7 @@ async function runChat({ appUrl, sessionToken, shareMachine, promptHistoryPath,
12091
12319
  createRoot(renderer).render(/* @__PURE__ */ jsx(App, {
12092
12320
  appUrl,
12093
12321
  sessionToken,
12322
+ authKind,
12094
12323
  shareMachine,
12095
12324
  promptHistoryPath,
12096
12325
  notifications,