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
@@ -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-Ba-UTi-l.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-X2N5wJ5l.mjs";
4
+ import { a as errorMessage, i as sendErrorMessage, n as createRestClient, o as isRecord, r as errorDetail, t as HttpError } from "./rest-imDZZGQA.mjs";
5
+ import { i as billingBlockedOutcomeFromSendResponse } from "./billing-blocked-CwfG1BTR.mjs";
6
+ import { t as PortalClient } from "./client-CKzK-12y.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-CaFz7Wtw.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";
@@ -5711,6 +5839,10 @@ const keybindGroups = [
5711
5839
  keys: "↑ / ↓",
5712
5840
  action: "move selection"
5713
5841
  },
5842
+ {
5843
+ keys: "pgup / pgdn",
5844
+ action: "move selection a page at a time"
5845
+ },
5714
5846
  {
5715
5847
  keys: "↵",
5716
5848
  action: "open agent"
@@ -5736,6 +5868,10 @@ const keybindGroups = [
5736
5868
  keys: "↑ / ↓",
5737
5869
  action: "move selection"
5738
5870
  },
5871
+ {
5872
+ keys: "pgup / pgdn",
5873
+ action: "move selection a page at a time"
5874
+ },
5739
5875
  {
5740
5876
  keys: "↵",
5741
5877
  action: "open conversation"
@@ -9651,8 +9787,14 @@ function ChatScreen({ agent, conversation }) {
9651
9787
  setItems((prev) => applyChunk(prev, event.chunk, streamAgentName));
9652
9788
  return;
9653
9789
  }
9790
+ const billingOutcome = event.outcome;
9654
9791
  const errorText = event.error;
9655
- if (event.status !== "ok" && errorText) setItems((prev) => [...prev, {
9792
+ if (billingOutcome) setItems((prev) => [...prev, {
9793
+ kind: "system-notice",
9794
+ id: crypto.randomUUID(),
9795
+ text: billingOutcome.message
9796
+ }]);
9797
+ else if (event.status !== "ok" && errorText) setItems((prev) => [...prev, {
9656
9798
  kind: "error",
9657
9799
  id: crypto.randomUUID(),
9658
9800
  text: errorText
@@ -9787,7 +9929,15 @@ function ChatScreen({ agent, conversation }) {
9787
9929
  }
9788
9930
  attachToRun(result.runId, result.agentName ?? null);
9789
9931
  } catch (err) {
9790
- setItems((prev) => [...prev.filter((m) => m.id !== optimisticId), {
9932
+ const billing = err instanceof HttpError ? billingBlockedOutcomeFromSendResponse({
9933
+ status: err.status,
9934
+ body: err.body
9935
+ }) : null;
9936
+ setItems((prev) => [...prev.filter((m) => m.id !== optimisticId), billing ? {
9937
+ kind: "system-notice",
9938
+ id: crypto.randomUUID(),
9939
+ text: billing.message
9940
+ } : {
9791
9941
  kind: "error",
9792
9942
  id: crypto.randomUUID(),
9793
9943
  text: sendErrorMessage(err)
@@ -10434,7 +10584,10 @@ function ChatScreen({ agent, conversation }) {
10434
10584
  leaveForScreen
10435
10585
  ]);
10436
10586
  const toggleShare = useCallback(() => {
10437
- if (!portalClient) return;
10587
+ if (!portalClient) {
10588
+ 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" });
10589
+ return;
10590
+ }
10438
10591
  if (portal.status === "off") {
10439
10592
  declinedRef.current.delete(agent.id);
10440
10593
  portalClient.enable();
@@ -10475,7 +10628,7 @@ function ChatScreen({ agent, conversation }) {
10475
10628
  }, [agent.id, setPortalPrompt]);
10476
10629
  const openPortalAccess = useCallback(() => {
10477
10630
  if (!portalClient) {
10478
- useStore.getState().showToast({ message: "local access is unavailable in this session" });
10631
+ 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
10632
  return;
10480
10633
  }
10481
10634
  switch (portal.status) {
@@ -11486,6 +11639,7 @@ function ToastView() {
11486
11639
  maxWidth: Math.min(60, Math.max(20, width - 6)),
11487
11640
  border: true,
11488
11641
  borderColor: toastColor(toast.variant),
11642
+ backgroundColor: theme.surface ?? "#1a1a1a",
11489
11643
  paddingLeft: 1,
11490
11644
  paddingRight: 1
11491
11645
  },
@@ -11499,7 +11653,7 @@ function ToastView() {
11499
11653
 
11500
11654
  //#endregion
11501
11655
  //#region src/chat/tui/app.tsx
11502
- function App({ appUrl, sessionToken, shareMachine, promptHistoryPath, notifications, agentSelector, conversationId, newConversation }) {
11656
+ function App({ appUrl, sessionToken, authKind, shareMachine, promptHistoryPath, notifications, agentSelector, conversationId, newConversation }) {
11503
11657
  const renderer = useRenderer();
11504
11658
  const screen = useStore((s) => s.screen);
11505
11659
  const chatTitle = useStore((s) => s.chatTitle);
@@ -11515,7 +11669,8 @@ function App({ appUrl, sessionToken, shareMachine, promptHistoryPath, notificati
11515
11669
  usePortal({
11516
11670
  appUrl,
11517
11671
  sessionToken,
11518
- shareMachine
11672
+ shareMachine,
11673
+ enabled: authKind === "session"
11519
11674
  });
11520
11675
  useEffect(() => {
11521
11676
  agentHost.transition({ state: "idle" });
@@ -11543,6 +11698,7 @@ function App({ appUrl, sessionToken, shareMachine, promptHistoryPath, notificati
11543
11698
  const fail = useStore((s) => s.fail);
11544
11699
  const setWorkspaceName = useStore((s) => s.setWorkspaceName);
11545
11700
  useEffect(() => {
11701
+ if (authKind !== "session") return;
11546
11702
  let cancelled = false;
11547
11703
  (async () => {
11548
11704
  const [activeId, workspaces] = await Promise.all([getActiveWorkspaceId({
@@ -11561,6 +11717,7 @@ function App({ appUrl, sessionToken, shareMachine, promptHistoryPath, notificati
11561
11717
  }, [
11562
11718
  appUrl,
11563
11719
  sessionToken,
11720
+ authKind,
11564
11721
  setWorkspaceName
11565
11722
  ]);
11566
11723
  useEffect(() => {
@@ -11899,6 +12056,24 @@ var LinkifiedMarkdownRenderable = class extends MarkdownRenderable {
11899
12056
  }
11900
12057
  };
11901
12058
 
12059
+ //#endregion
12060
+ //#region src/chat/tui/chunks/grammars/index.ts
12061
+ /**
12062
+ * Register the extra tree-sitter grammars (python, go, rust, bash, …) before
12063
+ * the renderer boots its tree-sitter client, so fenced code blocks and diffs
12064
+ * in those languages highlight instead of rendering as plain text.
12065
+ *
12066
+ * `addDefaultParsers` merges by filetype into the set the client registers on
12067
+ * init, so this must run BEFORE `createCliRenderer`. The grammar definitions
12068
+ * live in ./grammars.ts, which is externalized from the tsdown bundle (it
12069
+ * imports `.wasm`/`.scm` assets rolldown can't parse) — so we reach it through
12070
+ * a dynamic import that both Bun-runtime (npm) and the compiled binary resolve.
12071
+ */
12072
+ async function registerGrammars() {
12073
+ const { EXTRA_GRAMMARS } = await import("./chat/tui/chunks/grammars/grammars.js");
12074
+ addDefaultParsers(EXTRA_GRAMMARS);
12075
+ }
12076
+
11902
12077
  //#endregion
11903
12078
  //#region src/chat/memory-watchdog.ts
11904
12079
  const MB = 1024 * 1024;
@@ -12066,12 +12241,14 @@ async function settleBootThemeMode({ source, applyThemeMode, graceMs = THEME_QUE
12066
12241
  * module — and the heavy OpenTUI dependency graph it pulls in — is
12067
12242
  * dynamically imported and never loaded on the Node management path.
12068
12243
  */
12069
- async function runChat({ appUrl, sessionToken, shareMachine, promptHistoryPath, theme: themeOverride, notifications, agentSelector, conversationId, newConversation, seedPrompt }) {
12244
+ async function runChat({ appUrl, sessionToken, authKind = "session", shareMachine, promptHistoryPath, theme: themeOverride, notifications, agentSelector, conversationId, newConversation, seedPrompt }) {
12245
+ await registerGrammars();
12070
12246
  const watchdog = startMemoryWatchdog();
12071
12247
  installCrashHandler({ memoryProvider: () => watchdog.snapshot() });
12072
12248
  const renderer = await createCliRenderer({ exitOnCtrlC: false });
12073
12249
  extend({ markdown: LinkifiedMarkdownRenderable });
12074
12250
  useStore.setState({
12251
+ authKind,
12075
12252
  seedPrompt: seedPrompt ?? null,
12076
12253
  autoGrantMachine: Boolean(seedPrompt && shareMachine),
12077
12254
  newConversationIntent: newConversation
@@ -12091,6 +12268,7 @@ async function runChat({ appUrl, sessionToken, shareMachine, promptHistoryPath,
12091
12268
  createRoot(renderer).render(/* @__PURE__ */ jsx(App, {
12092
12269
  appUrl,
12093
12270
  sessionToken,
12271
+ authKind,
12094
12272
  shareMachine,
12095
12273
  promptHistoryPath,
12096
12274
  notifications,
@@ -0,0 +1,56 @@
1
+ [
2
+ (string)
3
+ (raw_string)
4
+ (heredoc_body)
5
+ (heredoc_start)
6
+ ] @string
7
+
8
+ (command_name) @function
9
+
10
+ (variable_name) @property
11
+
12
+ [
13
+ "case"
14
+ "do"
15
+ "done"
16
+ "elif"
17
+ "else"
18
+ "esac"
19
+ "export"
20
+ "fi"
21
+ "for"
22
+ "function"
23
+ "if"
24
+ "in"
25
+ "select"
26
+ "then"
27
+ "unset"
28
+ "until"
29
+ "while"
30
+ ] @keyword
31
+
32
+ (comment) @comment
33
+
34
+ (function_definition name: (word) @function)
35
+
36
+ (file_descriptor) @number
37
+
38
+ [
39
+ (command_substitution)
40
+ (process_substitution)
41
+ (expansion)
42
+ ]@embedded
43
+
44
+ [
45
+ "$"
46
+ "&&"
47
+ ">"
48
+ ">>"
49
+ "<"
50
+ "|"
51
+ ] @operator
52
+
53
+ (
54
+ (command (_) @constant)
55
+ (#match? @constant "^-")
56
+ )