zidane 5.1.13 → 5.1.14

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 (45) hide show
  1. package/dist/{agent-skiQGYs2.d.ts → agent-BuGxYfqh.d.ts} +11 -4
  2. package/dist/agent-BuGxYfqh.d.ts.map +1 -0
  3. package/dist/chat.d.ts +172 -6
  4. package/dist/chat.d.ts.map +1 -1
  5. package/dist/chat.js +2 -2
  6. package/dist/{index-CjPh6CRE.d.ts → index-Aaa1tP6E.d.ts} +2 -2
  7. package/dist/{index-CjPh6CRE.d.ts.map → index-Aaa1tP6E.d.ts.map} +1 -1
  8. package/dist/{index-YM7SipFz.d.ts → index-Cv5wED8j.d.ts} +2 -2
  9. package/dist/{index-YM7SipFz.d.ts.map → index-Cv5wED8j.d.ts.map} +1 -1
  10. package/dist/index.d.ts +3 -3
  11. package/dist/index.js +5 -5
  12. package/dist/{login-Cc6Q-Fpu.js → login-DJscx_sS.js} +4 -4
  13. package/dist/{login-Cc6Q-Fpu.js.map → login-DJscx_sS.js.map} +1 -1
  14. package/dist/{mcp-CUt-N8zn.js → mcp-Bq_rD6e9.js} +2 -2
  15. package/dist/{mcp-CUt-N8zn.js.map → mcp-Bq_rD6e9.js.map} +1 -1
  16. package/dist/mcp.d.ts +1 -1
  17. package/dist/mcp.js +1 -1
  18. package/dist/{presets-Ce79MK4J.js → presets-BEruW0Ji.js} +2 -2
  19. package/dist/{presets-Ce79MK4J.js.map → presets-BEruW0Ji.js.map} +1 -1
  20. package/dist/presets.d.ts +2 -2
  21. package/dist/presets.js +1 -1
  22. package/dist/providers.d.ts +1 -1
  23. package/dist/session/sqlite.d.ts +1 -1
  24. package/dist/session.d.ts +1 -1
  25. package/dist/skills.d.ts +2 -2
  26. package/dist/{tools-BG2wMa3X.js → tools-BBFu1UsV.js} +3 -3
  27. package/dist/{tools-BG2wMa3X.js.map → tools-BBFu1UsV.js.map} +1 -1
  28. package/dist/tools.d.ts +2 -2
  29. package/dist/tools.js +1 -1
  30. package/dist/{tool-formatters-0aOMYbH-.d.ts → transcript-anchors-FJMZyLS4.d.ts} +242 -97
  31. package/dist/transcript-anchors-FJMZyLS4.d.ts.map +1 -0
  32. package/dist/tui.d.ts +55 -39
  33. package/dist/tui.d.ts.map +1 -1
  34. package/dist/tui.js +335 -314
  35. package/dist/tui.js.map +1 -1
  36. package/dist/{turn-operations-CDmQ2h-T.js → turn-operations-CeLlc7jt.js} +572 -91
  37. package/dist/turn-operations-CeLlc7jt.js.map +1 -0
  38. package/dist/{types-Bx_F8jet.js → types-IcokUOyC.js} +11 -4
  39. package/dist/{types-Bx_F8jet.js.map → types-IcokUOyC.js.map} +1 -1
  40. package/dist/types.d.ts +2 -2
  41. package/dist/types.js +1 -1
  42. package/package.json +1 -1
  43. package/dist/agent-skiQGYs2.d.ts.map +0 -1
  44. package/dist/tool-formatters-0aOMYbH-.d.ts.map +0 -1
  45. package/dist/turn-operations-CDmQ2h-T.js.map +0 -1
package/dist/tui.js CHANGED
@@ -1,18 +1,18 @@
1
- import { S as resolvePersistDir, b as cleanupPersistedSession, d as createAgent } from "./tools-BG2wMa3X.js";
2
- import { s as McpOAuthProvider, t as connectMcpServers } from "./mcp-CUt-N8zn.js";
3
- import { C as summaryToTurn, a as selectFilesFromSession, r as buildPostCompactAttachments, s as compactConversation, t as loginMcpServer } from "./login-Cc6Q-Fpu.js";
1
+ import { S as resolvePersistDir, b as cleanupPersistedSession, d as createAgent } from "./tools-BBFu1UsV.js";
2
+ import { s as McpOAuthProvider, t as connectMcpServers } from "./mcp-Bq_rD6e9.js";
3
+ import { C as summaryToTurn, a as selectFilesFromSession, r as buildPostCompactAttachments, s as compactConversation, t as loginMcpServer } from "./login-DJscx_sS.js";
4
4
  import { n as formatTokenUsage } from "./stats-DgOvY7wd.js";
5
5
  import { n as loadSession, t as createSession } from "./session-DtLD1Sl1.js";
6
6
  import { createTuiStore } from "./session/sqlite.js";
7
- import { $ as getMcpAuthStatus, $t as toolCallPreview, A as isOnSafelist, B as filterModelCatalog, Bn as setProviderCredential, Bt as eventsFromTurns, C as writeSessionExport, Cn as uniqueSkillNamesFromReferences, Ct as SettingsProvider, E as useSafeModeQueue, Ft as ConfigProvider, Gt as listSessionMeta, H as buildMcpServers, Ht as isTurnHighlighted, I as splitPromptSegments, It as useConfig, Jn as getContextWindow, L as runOAuthLogin, Lt as resolveConfig, Mn as tryOpenBrowser, Nn as shouldAutoCompact, O as addToSafelist, Ot as resolveChipColor, P as suggestSafelistEntry, Pn as detectAuth, Q as useMcpAuthState, R as supportsOAuth, Sn as createSkillsCompletionProvider, St as SETTINGS_TOGGLES, T as useSafeModeActions, Tn as createFilesCompletionProvider, Tt as useSettings, Ut as isVisible, V as indexOfEntry, Vt as isEditErrorResult, W as discoverProjectMcps, Wt as lastContextSizeFromTurns, X as McpAuthProvider, Xn as modelSupportsReasoning, Xt as stripSpawnTokensLine, Yt as selectableTurnIds, Z as useMcpAuthDispatch, Zt as sumRunCosts, _ as useStreamBuffer, _t as shortId, a as TOOL_DISPLAY, at as createInteractionTools, b as discoverProjectSkills, br as buildPlanSystem, bt as DEFAULT_SETTINGS, c as ThemeProvider, ct as pendingInteractionsFromTurns, d as useSurfaces, dt as useInteractionsQueue, en as toolResultText, er as piIdOf, g as turnContextSize, gn as matchesBinding, gt as fmtTokens, h as finalizeStreamingMarkdownForOwner, ht as compactPath, i as turnAsText, it as buildResumedToolResultsTurn, jn as useCompletion, k as getSafelist, kt as resolveTheme, l as useColors, m as finalizeStreamingMarkdown, mn as ensureKeybindingsFile, mt as ageString, n as deleteTurnSafely, nn as buildContextualDiff, nt as InteractionsProvider, o as displayNameFor, on as extractEditPayload, or as accentColor, p as useTheme, pt as generateSessionTitle, q as createFileMcpCredentialStore, qt as marginTopFor, r as truncateTurnsAt, rn as buildUnifiedDiff, s as formatToolCall, sn as filetypeFromPath, st as makeRequestInteraction, tn as turnSelectionOwnership, u as useSelectStyle, un as findGitRoot, ut as useInteractionsActions, v as buildSkillsConfig, vt as listProjectFiles, w as SafeModeProvider, wt as clampFps, xt as SETTINGS_CHOICES, y as defaultSkillScanPaths, yr as buildBuildSystem, yt as useEnabledToggleSet, z as buildModelCatalog, zt as deriveSessionTitle } from "./turn-operations-CDmQ2h-T.js";
7
+ import { $ as useMcpAuthState, $n as setProviderCredential, $t as lastContextSizeFromTurns, A as getSafelist, At as useSettings, B as buildModelCatalog, Bn as useCompletion, Bt as createDiscoverySlot, Cn as ensureKeybindingsFile, Ct as listProjectFiles, D as useSafeModeQueue, Dt as SETTINGS_TOGGLES, E as useSafeModeActions, Et as SETTINGS_CHOICES, F as suggestSafelistEntry, G as discoverProjectMcps, Gn as bootTick, Gt as useConfig, H as indexOfEntry, Hn as buildLinearRamp, Ht as useDiscovery, J as createFileMcpCredentialStore, Jt as deriveSessionTitle, Kn as shouldAutoCompact, Kt as resolveConfig, L as splitPromptSegments, Mn as uniqueSkillNamesFromReferences, Mr as buildBuildSystem, Nr as buildPlanSystem, Nt as resolveChipColor, Ot as SettingsProvider, Pn as createFilesCompletionProvider, Pt as resolveTheme, Q as useMcpAuthDispatch, Qt as isVisible, R as runOAuthLogin, St as shortId, T as SafeModeProvider, Tn as matchesBinding, Tt as DEFAULT_SETTINGS, U as buildMcpServers, Un as tryOpenBrowser, Ut as useDiscoveryOptional, V as filterModelCatalog, Vn as blendHsl, Vt as DiscoveryProvider, Wt as ConfigProvider, Xt as isEditErrorResult, Yt as eventsFromTurns, Z as McpAuthProvider, Zt as isTurnHighlighted, _ as turnContextSize, a as computeTurnAnchors, an as stripSpawnTokensLine, b as defaultSkillScanPaths, bt as compactPath, c as formatToolCall, cn as toolCallPreview, d as useSelectStyle, dn as buildContextualDiff, en as listSessionMeta, et as getMcpAuthStatus, f as useSurfaces, fn as buildUnifiedDiff, ft as useInteractionsActions, g as finalizeStreamingMarkdownForOwner, gn as filetypeFromPath, gt as truncateTrailing, h as finalizeStreamingMarkdown, hn as extractEditPayload, ht as hintsLength, i as turnAsText, in as selectableTurnIds, it as InteractionsProvider, j as isOnSafelist, jn as createSkillsCompletionProvider, k as addToSafelist, kt as clampFps, l as ThemeProvider, ln as toolResultText, lr as modelSupportsReasoning, lt as makeRequestInteraction, m as useTheme, mt as clipHintsToWidth, n as deleteTurnSafely, nn as marginTopFor, nt as splitMarkdownCodeBlocks, o as TOOL_DISPLAY, on as sumRunCosts, ot as buildResumedToolResultsTurn, pr as piIdOf, pt as useInteractionsQueue, qn as detectAuth, r as truncateTurnsAt, s as displayNameFor, sr as getContextWindow, st as createInteractionTools, u as useColors, un as turnSelectionOwnership, ut as pendingInteractionsFromTurns, v as useStreamBuffer, vt as generateSessionTitle, w as writeSessionExport, wt as useEnabledToggleSet, x as discoverProjectSkills, xt as fmtTokens, y as buildSkillsConfig, yn as findGitRoot, yr as accentColor, yt as ageString, z as supportsOAuth } from "./turn-operations-CeLlc7jt.js";
8
8
  import { spawn } from "node:child_process";
9
9
  import { Buffer } from "node:buffer";
10
10
  import * as fs from "node:fs";
11
11
  import { homedir } from "node:os";
12
12
  import { createContext, createElement, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
13
+ import { Fragment, jsx, jsxs } from "@opentui/react/jsx-runtime";
13
14
  import { BoxRenderable, CodeRenderable, RGBA, SyntaxStyle, TextRenderable, addDefaultParsers, createCliRenderer, decodePasteBytes, defaultTextareaKeyBindings, getTreeSitterClient, stripAnsiSequences } from "@opentui/core";
14
15
  import { createRoot, useKeyboard, useRenderer, useSelectionHandler, useTerminalDimensions } from "@opentui/react";
15
- import { Fragment, jsx, jsxs } from "@opentui/react/jsx-runtime";
16
16
  //#region src/tui/modal.tsx
17
17
  const ModalContext = createContext(null);
18
18
  function ModalRoot({ children }) {
@@ -318,95 +318,6 @@ function writeToClipboard(text) {
318
318
  return osc || helper;
319
319
  }
320
320
  //#endregion
321
- //#region src/tui/color-gradient.ts
322
- /** Parse `#rrggbb` (case-insensitive) into `[r, g, b]` 0–255 integers. */
323
- function parseHex(hex) {
324
- const h = hex.replace("#", "");
325
- return [
326
- Number.parseInt(h.slice(0, 2), 16),
327
- Number.parseInt(h.slice(2, 4), 16),
328
- Number.parseInt(h.slice(4, 6), 16)
329
- ];
330
- }
331
- /** Convert sRGB 0–255 → HSL 0–1. */
332
- function rgbToHsl(r, g, b) {
333
- r /= 255;
334
- g /= 255;
335
- b /= 255;
336
- const max = Math.max(r, g, b);
337
- const min = Math.min(r, g, b);
338
- const l = (max + min) / 2;
339
- if (max === min) return [
340
- 0,
341
- 0,
342
- l
343
- ];
344
- const d = max - min;
345
- const s = l > .5 ? d / (2 - max - min) : d / (max + min);
346
- let h;
347
- if (max === r) h = (g - b) / d + (g < b ? 6 : 0);
348
- else if (max === g) h = (b - r) / d + 2;
349
- else h = (r - g) / d + 4;
350
- return [
351
- h / 6,
352
- s,
353
- l
354
- ];
355
- }
356
- /** Convert HSL 0–1 → sRGB 0–255. Standard piecewise formula. */
357
- function hslToRgb(h, s, l) {
358
- if (s === 0) return [
359
- l * 255,
360
- l * 255,
361
- l * 255
362
- ];
363
- const hue2rgb = (p, q, t) => {
364
- if (t < 0) t += 1;
365
- if (t > 1) t -= 1;
366
- if (t < 1 / 6) return p + (q - p) * 6 * t;
367
- if (t < 1 / 2) return q;
368
- if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
369
- return p;
370
- };
371
- const q = l < .5 ? l * (1 + s) : l + s - l * s;
372
- const p = 2 * l - q;
373
- return [
374
- hue2rgb(p, q, h + 1 / 3) * 255,
375
- hue2rgb(p, q, h) * 255,
376
- hue2rgb(p, q, h - 1 / 3) * 255
377
- ];
378
- }
379
- function toHex(rgb) {
380
- const pad = (v) => Math.round(Math.max(0, Math.min(255, v))).toString(16).padStart(2, "0");
381
- return `#${pad(rgb[0])}${pad(rgb[1])}${pad(rgb[2])}`;
382
- }
383
- /**
384
- * Blend two hex colors in HSL space with shortest-path hue interpolation.
385
- * `t` ∈ [0, 1]; `t=0` returns `from`, `t=1` returns `to`.
386
- */
387
- function blendHsl(from, to, t) {
388
- const [r1, g1, b1] = parseHex(from);
389
- const [r2, g2, b2] = parseHex(to);
390
- const [h1, s1, l1] = rgbToHsl(r1, g1, b1);
391
- const [h2, s2, l2] = rgbToHsl(r2, g2, b2);
392
- let dh = h2 - h1;
393
- if (dh > .5) dh -= 1;
394
- else if (dh < -.5) dh += 1;
395
- return toHex(hslToRgb((h1 + dh * t + 1) % 1, s1 + (s2 - s1) * t, l1 + (l2 - l1) * t));
396
- }
397
- /**
398
- * Static gradient ramp of length `n` going from `from` (index 0) to
399
- * `to` (index n-1) in HSL space. For the cycling A→B→A→B ramp the
400
- * throbber uses, see `buildCycleRamp` in `crush-throbber.tsx`.
401
- */
402
- function buildLinearRamp(from, to, n) {
403
- if (n <= 0) return [];
404
- if (n === 1) return [blendHsl(from, to, .5)];
405
- const ramp = [];
406
- for (let i = 0; i < n; i++) ramp.push(blendHsl(from, to, i / (n - 1)));
407
- return ramp;
408
- }
409
- //#endregion
410
321
  //#region src/tui/crush-throbber.tsx
411
322
  /** @jsxImportSource @opentui/react */
412
323
  const CRUSH_RUNES = "0123456789abcdefABCDEF~!@#$£€%^&*()+=_";
@@ -947,67 +858,6 @@ function metaSegmentsLength(meta) {
947
858
  if (typeof meta === "string") return meta.length;
948
859
  return meta.reduce((sum, seg) => sum + seg.text.length, 0);
949
860
  }
950
- /**
951
- * Truncate `text` to at most `max` characters, replacing the trailing
952
- * overflow with `…`. Edge cases:
953
- * - `max <= 0` → empty string (no room to render at all).
954
- * - `max === 1` → just the ellipsis glyph.
955
- * - `text.length <= max` → unchanged.
956
- *
957
- * Trailing-style truncation matches the natural read order of titles:
958
- * the prefix carries enough signal to identify the surface.
959
- *
960
- * Exported for unit-tests; consumers should normally lean on
961
- * {@link TitleOverlay} instead of calling this directly.
962
- */
963
- function truncateTrailing(text, max) {
964
- if (max <= 0) return "";
965
- if (text.length <= max) return text;
966
- if (max === 1) return "…";
967
- return `${text.slice(0, max - 1)}…`;
968
- }
969
- /**
970
- * Plain-text width estimate for a list of {@link Hint}s rendered via
971
- * `renderHintSpans` — `<key> <label> · <key> <label> · …`. Exported so
972
- * the prompt-box overlay (in `screens.tsx`) can run the same responsive
973
- * math as the bottom-bar footer when deciding whether trigger hints
974
- * fit. Pure / total.
975
- */
976
- function hintsLength(hints) {
977
- if (hints.length === 0) return 0;
978
- return hints.reduce((sum, h, i) => sum + hintLength(h) + (i > 0 ? 3 : 0), 0);
979
- }
980
- /** Plain-text width of a single hint as rendered by {@link renderHintSpans}. */
981
- function hintLength(h) {
982
- return h.key.length + 1 + h.label.length + (h.extra ? h.extra.key.length + 1 + h.extra.label.length : 0);
983
- }
984
- /** Stable empty list so callers can compare by reference. */
985
- const EMPTY_HINTS = Object.freeze([]);
986
- /**
987
- * Return the longest prefix of `hints` whose rendered width via
988
- * {@link renderHintSpans} fits within `budget`. Used to degrade hint
989
- * rows gracefully at narrow terminal widths instead of letting OpenTUI
990
- * wrap an absolutely-positioned `<text>` mid-segment (which paints the
991
- * overflow over the prompt box's border and looks like garbled glyphs).
992
- *
993
- * Prefix-only (no reordering, no last-hint priority) so the survivors
994
- * keep their authored order — the user's muscle memory for "leftmost
995
- * hint = primary action" stays intact as the terminal shrinks.
996
- */
997
- function clipHintsToWidth(hints, budget) {
998
- if (budget <= 0 || hints.length === 0) return EMPTY_HINTS;
999
- const out = [];
1000
- let used = 0;
1001
- for (let i = 0; i < hints.length; i++) {
1002
- const h = hints[i];
1003
- const cost = (i > 0 ? 3 : 0) + hintLength(h);
1004
- if (used + cost > budget) break;
1005
- out.push(h);
1006
- used += cost;
1007
- }
1008
- if (out.length === 0) return EMPTY_HINTS;
1009
- return out.length === hints.length ? hints : out;
1010
- }
1011
861
  function contextIndicatorLength(context) {
1012
862
  const ratio = context.max > 0 ? context.used / context.max : 0;
1013
863
  const pct = Math.round(ratio * 100);
@@ -1135,41 +985,6 @@ function Transcript({ events, settings, selectedTurnId = null, busy = false }) {
1135
985
  });
1136
986
  }
1137
987
  /**
1138
- * Per-item anchor ids for auto-scroll. Walks `items` in render order and,
1139
- * for each event, returns either:
1140
- * - `'turn-anchor-<turnId>'` — the first event of this turn (the
1141
- * scrollbox's target).
1142
- * - `undefined` — later event of an already-tagged turn (or a synthetic
1143
- * event with no `turnId`).
1144
- *
1145
- * `ids[i]` is a tuple per item: length 1 for plain events, length N for
1146
- * subagent runs (one entry per inner event). `idByTurn` is the inverse
1147
- * lookup used by the scroll effect. `lastTurnId` is the most-recently-
1148
- * rendered turn — the scroll effect special-cases it to snap to bottom.
1149
- *
1150
- * Exported so the anchor-tagging matrix can be unit-tested without rendering.
1151
- */
1152
- function computeTurnAnchors(items) {
1153
- const idByTurn = /* @__PURE__ */ new Map();
1154
- let lastTurnId;
1155
- const tag = (turnId) => {
1156
- if (!turnId) return void 0;
1157
- lastTurnId = turnId;
1158
- if (idByTurn.has(turnId)) return void 0;
1159
- const id = `turn-anchor-${turnId}`;
1160
- idByTurn.set(turnId, id);
1161
- return id;
1162
- };
1163
- const ids = [];
1164
- for (const item of items) if (item.kind === "event") ids.push([tag(item.event.turnId)]);
1165
- else ids.push(item.events.map((e) => tag(e.turnId)));
1166
- return {
1167
- idByTurn,
1168
- ids,
1169
- lastTurnId
1170
- };
1171
- }
1172
- /**
1173
988
  * Walk the visible-event list once and group consecutive child events
1174
989
  * (`depth > 0`) into runs so we can wrap each run in a single bordered
1175
990
  * subagent box.
@@ -1589,7 +1404,7 @@ var CodeBlockWrapper = class extends BoxRenderable {
1589
1404
  flexDirection: "column",
1590
1405
  flexShrink: 0,
1591
1406
  alignSelf: "stretch",
1592
- backgroundColor: surfaces.background
1407
+ backgroundColor: surfaces.modal
1593
1408
  });
1594
1409
  this.body = body;
1595
1410
  this.bag = options.bag;
@@ -1597,11 +1412,12 @@ var CodeBlockWrapper = class extends BoxRenderable {
1597
1412
  body.marginTop = 0;
1598
1413
  body.marginBottom = 0;
1599
1414
  body.drawUnstyledText = false;
1415
+ body.bg = surfaces.modal;
1600
1416
  this.header = new BoxRenderable(ctx, {
1601
1417
  flexDirection: "row",
1602
1418
  height: 1,
1603
1419
  alignSelf: "stretch",
1604
- backgroundColor: surfaces.background
1420
+ backgroundColor: surfaces.modal
1605
1421
  });
1606
1422
  this.langLabel = new TextRenderable(ctx, {
1607
1423
  content: options.lang && options.lang.length > 0 ? options.lang : "code",
@@ -1610,7 +1426,7 @@ var CodeBlockWrapper = class extends BoxRenderable {
1610
1426
  });
1611
1427
  this.spacer = new BoxRenderable(ctx, {
1612
1428
  flexGrow: 1,
1613
- backgroundColor: surfaces.background
1429
+ backgroundColor: surfaces.modal
1614
1430
  });
1615
1431
  this.button = new TextRenderable(ctx, {
1616
1432
  content: "[copy]",
@@ -1653,9 +1469,10 @@ var CodeBlockWrapper = class extends BoxRenderable {
1653
1469
  * `[copy]`/`[copied]` button colour (which depends on `this.copied`).
1654
1470
  */
1655
1471
  applyTheme(colors, surfaces) {
1656
- this.backgroundColor = surfaces.background;
1657
- this.header.backgroundColor = surfaces.background;
1658
- this.spacer.backgroundColor = surfaces.background;
1472
+ this.backgroundColor = surfaces.modal;
1473
+ this.header.backgroundColor = surfaces.modal;
1474
+ this.spacer.backgroundColor = surfaces.modal;
1475
+ this.body.bg = surfaces.modal;
1659
1476
  this.langLabel.fg = colors.mute;
1660
1477
  this.button.fg = this.copied ? colors.accent : colors.warn;
1661
1478
  }
@@ -1684,11 +1501,23 @@ var CodeBlockWrapper = class extends BoxRenderable {
1684
1501
  set fg(value) {
1685
1502
  this.body.fg = value;
1686
1503
  }
1504
+ /**
1505
+ * Always reports the live elevated-panel paint: see the setter doc.
1506
+ */
1687
1507
  get bg() {
1688
1508
  return this.body.bg;
1689
1509
  }
1690
- set bg(value) {
1691
- this.body.bg = value;
1510
+ /**
1511
+ * Pinned to {@link ThemeSurfaces.modal} the elevated code-panel
1512
+ * surface. The markdown's `applyCodeBlockRenderable` writes `bg` on
1513
+ * every delta from whatever surface the parent `<markdown>` carries
1514
+ * (the prose body paint). Forwarding that through would flip the
1515
+ * code panel back to the prose surface on every keystroke and erase
1516
+ * the visual lift. Theme switches are funnelled through
1517
+ * {@link applyTheme} instead, which rewrites `body.bg` directly.
1518
+ */
1519
+ set bg(_value) {
1520
+ this.body.bg = this.bag.surfaces.modal;
1692
1521
  }
1693
1522
  get conceal() {
1694
1523
  return this.body.conceal;
@@ -2007,6 +1836,156 @@ function ToolCallBlock({ event, display, dim }) {
2007
1836
  });
2008
1837
  }
2009
1838
  //#endregion
1839
+ //#region src/tui/discovery-shell.tsx
1840
+ /**
1841
+ * SWR throttles. `files` is short so a long-open `@` popover picks up
1842
+ * new files within seconds; `skills` is long because SKILL.md changes
1843
+ * are rare and the walk is cheap enough that we already eager-load it.
1844
+ */
1845
+ const FILES_REFRESH_THROTTLE_MS = 3e3;
1846
+ const SKILLS_REFRESH_THROTTLE_MS = 3e4;
1847
+ function debugLog$1(...args) {
1848
+ if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/tui] ${args.map((a) => a instanceof Error ? a.message : String(a)).join(" ")}\n`);
1849
+ }
1850
+ function DiscoveryShell({ children }) {
1851
+ const config = useConfig();
1852
+ const dispatchAuth = useMcpAuthDispatch();
1853
+ const dispatchAuthRef = useRef(dispatchAuth);
1854
+ dispatchAuthRef.current = dispatchAuth;
1855
+ const [projectDir] = useState(() => findGitRoot(process.cwd()) ?? process.cwd());
1856
+ const dataDir = config.paths.userDir;
1857
+ const mcpCredentialStore = useMemo(() => createFileMcpCredentialStore(dataDir), [dataDir]);
1858
+ const [skillsCatalog, setSkillsCatalog] = useState([]);
1859
+ const [mcpsCatalog, setMcpsCatalog] = useState([]);
1860
+ const [mcpsErrors, setMcpsErrors] = useState([]);
1861
+ const [filesCatalog, setFilesCatalog] = useState([]);
1862
+ const filesSlotRef = useRef(null);
1863
+ const skillsSlotRef = useRef(null);
1864
+ useEffect(() => {
1865
+ filesSlotRef.current?.abort();
1866
+ skillsSlotRef.current?.abort();
1867
+ setFilesCatalog([]);
1868
+ setSkillsCatalog([]);
1869
+ const filesSlot = createDiscoverySlot({
1870
+ throttleMs: FILES_REFRESH_THROTTLE_MS,
1871
+ walk: (signal) => listProjectFiles({
1872
+ cwd: projectDir,
1873
+ signal
1874
+ }),
1875
+ onLoad: (items) => {
1876
+ if (filesSlotRef.current === filesSlot) setFilesCatalog(items);
1877
+ },
1878
+ onError: (err, phase) => {
1879
+ debugLog$1(`listProjectFiles ${phase} failed`, err);
1880
+ }
1881
+ });
1882
+ const skillsSlot = createDiscoverySlot({
1883
+ throttleMs: SKILLS_REFRESH_THROTTLE_MS,
1884
+ walk: () => discoverProjectSkills({
1885
+ cwd: projectDir,
1886
+ prefix: config.prefix
1887
+ }),
1888
+ onLoad: (items) => {
1889
+ if (skillsSlotRef.current === skillsSlot) setSkillsCatalog(items);
1890
+ },
1891
+ onError: (err, phase) => {
1892
+ debugLog$1(`discoverProjectSkills ${phase} failed`, err);
1893
+ }
1894
+ });
1895
+ filesSlotRef.current = filesSlot;
1896
+ skillsSlotRef.current = skillsSlot;
1897
+ skillsSlot.ensure().then((skills) => bootTick(`discovery:skills (${skills.length})`));
1898
+ try {
1899
+ const { servers, errors } = discoverProjectMcps({
1900
+ cwd: projectDir,
1901
+ prefix: config.prefix
1902
+ });
1903
+ setMcpsCatalog(servers);
1904
+ setMcpsErrors(errors);
1905
+ bootTick(`discovery:mcps (${servers.length} servers, ${errors.length} parse errors)`);
1906
+ for (const entry of servers) {
1907
+ if (entry.config.auth !== "oauth") continue;
1908
+ const hasTokens = !!mcpCredentialStore.load(entry.config.name)?.tokens;
1909
+ dispatchAuthRef.current(hasTokens ? {
1910
+ type: "auth-success",
1911
+ name: entry.config.name
1912
+ } : {
1913
+ type: "auth-required",
1914
+ name: entry.config.name,
1915
+ reason: "no-tokens"
1916
+ });
1917
+ }
1918
+ } catch (err) {
1919
+ debugLog$1("discoverProjectMcps failed", err);
1920
+ }
1921
+ return () => {
1922
+ filesSlot.abort();
1923
+ skillsSlot.abort();
1924
+ };
1925
+ }, [
1926
+ projectDir,
1927
+ config.prefix,
1928
+ mcpCredentialStore
1929
+ ]);
1930
+ const ensureFiles = useCallback(() => filesSlotRef.current?.ensure() ?? Promise.resolve([]), []);
1931
+ const ensureSkills = useCallback(() => skillsSlotRef.current?.ensure() ?? Promise.resolve([]), []);
1932
+ const refreshFiles = useCallback(() => filesSlotRef.current?.refresh() ?? Promise.resolve(), []);
1933
+ const refreshSkills = useCallback(() => skillsSlotRef.current?.refresh() ?? Promise.resolve(), []);
1934
+ const refreshMcps = useCallback(() => {
1935
+ try {
1936
+ const { servers, errors } = discoverProjectMcps({
1937
+ cwd: projectDir,
1938
+ prefix: config.prefix
1939
+ });
1940
+ setMcpsCatalog(servers);
1941
+ setMcpsErrors(errors);
1942
+ for (const entry of servers) {
1943
+ if (entry.config.auth !== "oauth") continue;
1944
+ const hasTokens = !!mcpCredentialStore.load(entry.config.name)?.tokens;
1945
+ dispatchAuthRef.current(hasTokens ? {
1946
+ type: "auth-success",
1947
+ name: entry.config.name
1948
+ } : {
1949
+ type: "auth-required",
1950
+ name: entry.config.name,
1951
+ reason: "no-tokens"
1952
+ });
1953
+ }
1954
+ } catch (err) {
1955
+ debugLog$1("refreshMcps failed", err);
1956
+ }
1957
+ return Promise.resolve();
1958
+ }, [
1959
+ projectDir,
1960
+ config.prefix,
1961
+ mcpCredentialStore
1962
+ ]);
1963
+ return /* @__PURE__ */ jsx(DiscoveryProvider, {
1964
+ value: useMemo(() => ({
1965
+ skillsCatalog,
1966
+ mcpsCatalog,
1967
+ mcpsErrors,
1968
+ filesCatalog,
1969
+ refreshSkills,
1970
+ refreshMcps,
1971
+ refreshFiles,
1972
+ ensureSkills,
1973
+ ensureFiles
1974
+ }), [
1975
+ skillsCatalog,
1976
+ mcpsCatalog,
1977
+ mcpsErrors,
1978
+ filesCatalog,
1979
+ refreshSkills,
1980
+ refreshMcps,
1981
+ refreshFiles,
1982
+ ensureSkills,
1983
+ ensureFiles
1984
+ ]),
1985
+ children
1986
+ });
1987
+ }
1988
+ //#endregion
2010
1989
  //#region src/tui/effort-picker.tsx
2011
1990
  const BASE_LEVELS = [
2012
1991
  {
@@ -5829,13 +5808,17 @@ function anchorIdFor(index) {
5829
5808
  }
5830
5809
  const COL_TITLE = " ";
5831
5810
  const SPACER_CHECKBOX_WIDTH = " ";
5832
- function SettingsModal({ skillsCatalog = [], mcpsCatalog = [], mcpsErrors, actions } = {}) {
5811
+ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCatalogProp, mcpsErrors: mcpsErrorsProp, actions } = {}) {
5833
5812
  const COLOR = useColors();
5834
5813
  const SURFACE = useSurfaces();
5835
5814
  const { settings, toggle: toggleBoolean, setSetting } = useSettings();
5836
5815
  const authState = useMcpAuthState();
5837
5816
  const inputRef = useRef(null);
5838
5817
  const scrollboxRef = useRef(null);
5818
+ const discovery = useDiscoveryOptional();
5819
+ const skillsCatalog = discovery?.skillsCatalog ?? skillsCatalogProp ?? [];
5820
+ const mcpsCatalog = discovery?.mcpsCatalog ?? mcpsCatalogProp ?? [];
5821
+ const mcpsErrors = discovery?.mcpsErrors ?? mcpsErrorsProp;
5839
5822
  const skillsToggle = useEnabledToggleSet({
5840
5823
  catalog: skillsCatalog,
5841
5824
  keyOf: (s) => s.name,
@@ -5975,6 +5958,13 @@ function SettingsModal({ skillsCatalog = [], mcpsCatalog = [], mcpsErrors, actio
5975
5958
  }
5976
5959
  if (key.name === "escape" && focusedMcpStatus.kind === "authorizing") actions?.onCancelLoginMcp?.(focusedMcp.config.name);
5977
5960
  }
5961
+ if (key.ctrl && key.name === "r") {
5962
+ if (activeTab === "skills" && actions?.onRefreshSkills) {
5963
+ actions.onRefreshSkills();
5964
+ return;
5965
+ }
5966
+ if (activeTab === "mcps" && actions?.onRefreshMcps) actions.onRefreshMcps();
5967
+ }
5978
5968
  });
5979
5969
  return /* @__PURE__ */ jsxs(Modal, {
5980
5970
  title: "settings",
@@ -6067,7 +6057,9 @@ function SettingsModal({ skillsCatalog = [], mcpsCatalog = [], mcpsErrors, actio
6067
6057
  children: /* @__PURE__ */ jsx(Hints, {
6068
6058
  activeTab,
6069
6059
  focusedMcp,
6070
- focusedMcpStatus
6060
+ focusedMcpStatus,
6061
+ canRefreshSkills: !!actions?.onRefreshSkills,
6062
+ canRefreshMcps: !!actions?.onRefreshMcps
6071
6063
  })
6072
6064
  })
6073
6065
  ]
@@ -6522,11 +6514,12 @@ function renderMcpErrors(errors, home, warnColor) {
6522
6514
  }, err.path))
6523
6515
  });
6524
6516
  }
6525
- function Hints({ activeTab, focusedMcp, focusedMcpStatus }) {
6517
+ function Hints({ activeTab, focusedMcp, focusedMcpStatus, canRefreshSkills, canRefreshMcps }) {
6526
6518
  const COLOR = useColors();
6527
6519
  const showLogin = activeTab === "mcps" && !!focusedMcp && !!focusedMcpStatus && canLogin$1(focusedMcp, focusedMcpStatus);
6528
6520
  const showLogout = activeTab === "mcps" && !!focusedMcpStatus && canLogout$1(focusedMcpStatus);
6529
6521
  const showCancel = activeTab === "mcps" && focusedMcpStatus?.kind === "authorizing";
6522
+ const showRefresh = activeTab === "skills" && canRefreshSkills || activeTab === "mcps" && canRefreshMcps;
6530
6523
  return /* @__PURE__ */ jsxs("text", {
6531
6524
  fg: COLOR.mute,
6532
6525
  children: [
@@ -6561,6 +6554,14 @@ function Hints({ activeTab, focusedMcp, focusedMcpStatus }) {
6561
6554
  }),
6562
6555
  " logout"
6563
6556
  ] }),
6557
+ showRefresh && /* @__PURE__ */ jsxs("span", { children: [
6558
+ " · ",
6559
+ /* @__PURE__ */ jsx("span", {
6560
+ fg: COLOR.warn,
6561
+ children: "ctrl+R"
6562
+ }),
6563
+ " refresh"
6564
+ ] }),
6564
6565
  showCancel ? /* @__PURE__ */ jsxs("span", { children: [
6565
6566
  " · ",
6566
6567
  /* @__PURE__ */ jsx("span", {
@@ -6976,16 +6977,20 @@ function ThemedShell() {
6976
6977
  const { settings } = useSettings();
6977
6978
  return /* @__PURE__ */ jsx(ThemeProvider, {
6978
6979
  theme: useMemo(() => resolveTheme(settings.theme), [settings.theme]),
6979
- children: /* @__PURE__ */ jsx(MdStyleProvider, { children: /* @__PURE__ */ jsx(ChipStyleProvider, { children: /* @__PURE__ */ jsx(SafeModeProvider, { children: /* @__PURE__ */ jsx(InteractionsProvider, { children: /* @__PURE__ */ jsx(McpAuthProvider, { children: /* @__PURE__ */ jsx(ModalRoot, { children: /* @__PURE__ */ jsx(AppShell, {}) }) }) }) }) }) })
6980
+ children: /* @__PURE__ */ jsx(MdStyleProvider, { children: /* @__PURE__ */ jsx(ChipStyleProvider, { children: /* @__PURE__ */ jsx(SafeModeProvider, { children: /* @__PURE__ */ jsx(InteractionsProvider, { children: /* @__PURE__ */ jsx(McpAuthProvider, { children: /* @__PURE__ */ jsx(DiscoveryShell, { children: /* @__PURE__ */ jsx(ModalRoot, { children: /* @__PURE__ */ jsx(AppShell, {}) }) }) }) }) }) }) })
6980
6981
  });
6981
6982
  }
6982
6983
  function AppShell() {
6984
+ bootTick("AppShell:render-enter");
6983
6985
  const renderer = useRenderer();
6984
6986
  const modal = useModal();
6985
6987
  const config = useConfig();
6986
6988
  const { settings } = useSettings();
6987
6989
  const COLOR = useColors();
6988
6990
  const SURFACE = useSurfaces();
6991
+ useEffect(() => {
6992
+ bootTick("AppShell:first-effect (post-first-paint)");
6993
+ }, []);
6989
6994
  const queue = useSafeModeQueue();
6990
6995
  const { requestApproval, resolveHead, denyAll } = useSafeModeActions();
6991
6996
  const pendingApproval = queue[0] ?? null;
@@ -7045,70 +7050,11 @@ function AppShell() {
7045
7050
  safelistRef.current = null;
7046
7051
  }, [dataDir, projectDir]);
7047
7052
  const sessionSafelistRef = useRef(/* @__PURE__ */ new Set());
7048
- const [skillsCatalog, setSkillsCatalog] = useState([]);
7049
- const [mcpsCatalog, setMcpsCatalog] = useState([]);
7050
- const [mcpsErrors, setMcpsErrors] = useState([]);
7051
- const [filesCatalog, setFilesCatalog] = useState([]);
7053
+ const { skillsCatalog, mcpsCatalog, filesCatalog, ensureFiles: ensureFilesCatalog, ensureSkills: ensureSkillsCatalog, refreshSkills: onRefreshSkills, refreshMcps: onRefreshMcps } = useDiscovery();
7052
7054
  const mcpCredentialStore = useMemo(() => createFileMcpCredentialStore(dataDir), [dataDir]);
7053
7055
  const dispatchAuth = useMcpAuthDispatch();
7054
7056
  const dispatchAuthRef = useRef(dispatchAuth);
7055
7057
  dispatchAuthRef.current = dispatchAuth;
7056
- useEffect(() => {
7057
- const ac = new AbortController();
7058
- let cancelled = false;
7059
- (async () => {
7060
- try {
7061
- const skills = await discoverProjectSkills({
7062
- cwd: projectDir,
7063
- prefix: config.prefix
7064
- });
7065
- if (!cancelled) setSkillsCatalog(skills);
7066
- } catch (err) {
7067
- debugLog("discoverProjectSkills failed", err);
7068
- }
7069
- })();
7070
- (async () => {
7071
- try {
7072
- const files = await listProjectFiles({
7073
- cwd: projectDir,
7074
- signal: ac.signal
7075
- });
7076
- if (!cancelled) setFilesCatalog(files);
7077
- } catch (err) {
7078
- debugLog("listProjectFiles failed", err);
7079
- }
7080
- })();
7081
- try {
7082
- const { servers, errors } = discoverProjectMcps({
7083
- cwd: projectDir,
7084
- prefix: config.prefix
7085
- });
7086
- setMcpsCatalog(servers);
7087
- setMcpsErrors(errors);
7088
- for (const entry of servers) {
7089
- if (entry.config.auth !== "oauth") continue;
7090
- const hasTokens = !!mcpCredentialStore.load(entry.config.name)?.tokens;
7091
- dispatchAuthRef.current(hasTokens ? {
7092
- type: "auth-success",
7093
- name: entry.config.name
7094
- } : {
7095
- type: "auth-required",
7096
- name: entry.config.name,
7097
- reason: "no-tokens"
7098
- });
7099
- }
7100
- } catch (err) {
7101
- debugLog("discoverProjectMcps failed", err);
7102
- }
7103
- return () => {
7104
- cancelled = true;
7105
- ac.abort();
7106
- };
7107
- }, [
7108
- projectDir,
7109
- config.prefix,
7110
- mcpCredentialStore
7111
- ]);
7112
7058
  const skillsCatalogRef = useRef(skillsCatalog);
7113
7059
  skillsCatalogRef.current = skillsCatalog;
7114
7060
  const enabledSkillsRef = useRef(settings.enabledSkills);
@@ -7119,10 +7065,18 @@ function AppShell() {
7119
7065
  enabledMcpsRef.current = settings.enabledMcps;
7120
7066
  const filesCatalogRef = useRef(filesCatalog);
7121
7067
  filesCatalogRef.current = filesCatalog;
7068
+ const ensureFilesCatalogRef = useRef(ensureFilesCatalog);
7069
+ ensureFilesCatalogRef.current = ensureFilesCatalog;
7070
+ const ensureSkillsCatalogRef = useRef(ensureSkillsCatalog);
7071
+ ensureSkillsCatalogRef.current = ensureSkillsCatalog;
7122
7072
  const completionProviders = useMemo(() => [createSkillsCompletionProvider({
7123
7073
  getCatalog: () => skillsCatalogRef.current,
7124
- getEnabled: () => enabledSkillsRef.current
7125
- }), createFilesCompletionProvider({ getCatalog: () => filesCatalogRef.current })], []);
7074
+ getEnabled: () => enabledSkillsRef.current,
7075
+ ensureCatalog: () => ensureSkillsCatalogRef.current()
7076
+ }), createFilesCompletionProvider({
7077
+ getCatalog: () => filesCatalogRef.current,
7078
+ ensureCatalog: () => ensureFilesCatalogRef.current()
7079
+ })], [filesCatalog, skillsCatalog]);
7126
7080
  /**
7127
7081
  * Single source of truth for "should this call execute?". Returns true to
7128
7082
  * let the call through, false to refuse it. Handles three short-circuits:
@@ -7687,13 +7641,19 @@ function AppShell() {
7687
7641
  const sessionMatchesProject = settings.showAllProjects || data?.projectRoot != null && data.projectRoot === projectDir;
7688
7642
  if (data && sessionMatchesProject) {
7689
7643
  await activateSession(lastResumedSessionId, resumeProvider.key);
7644
+ bootTick(`resume:activate (sessionId=${lastResumedSessionId.slice(-8)})`);
7690
7645
  return;
7691
7646
  }
7692
7647
  }
7693
7648
  const list = await refreshSessions();
7694
7649
  if (cancelled) return;
7695
- if (list.length === 0) await activateSession(null, resumeProvider.key);
7696
- else setScreen("sessions");
7650
+ if (list.length === 0) {
7651
+ await activateSession(null, resumeProvider.key);
7652
+ bootTick("resume:fresh-session (empty list)");
7653
+ } else {
7654
+ setScreen("sessions");
7655
+ bootTick(`resume:sessions-list (${list.length})`);
7656
+ }
7697
7657
  })();
7698
7658
  return () => {
7699
7659
  cancelled = true;
@@ -8578,18 +8538,15 @@ function AppShell() {
8578
8538
  return;
8579
8539
  }
8580
8540
  if (matchesBinding(key, keybindings.openSettings) && screen !== "auth") {
8581
- modal.open(/* @__PURE__ */ jsx(SettingsModal, {
8582
- skillsCatalog,
8583
- mcpsCatalog,
8584
- mcpsErrors,
8585
- actions: {
8586
- onReauth,
8587
- onOpenKeybindings: onOpenKeybindingsFile,
8588
- onLoginMcp,
8589
- onLogoutMcp,
8590
- onCancelLoginMcp
8591
- }
8592
- }));
8541
+ modal.open(/* @__PURE__ */ jsx(SettingsModal, { actions: {
8542
+ onReauth,
8543
+ onOpenKeybindings: onOpenKeybindingsFile,
8544
+ onLoginMcp,
8545
+ onLogoutMcp,
8546
+ onCancelLoginMcp,
8547
+ onRefreshSkills,
8548
+ onRefreshMcps
8549
+ } }));
8593
8550
  return;
8594
8551
  }
8595
8552
  if (matchesBinding(key, keybindings.openSessionDetails)) {
@@ -8691,23 +8648,16 @@ function AppShell() {
8691
8648
  drop: keybindings.dropQueuedMessage
8692
8649
  }), [keybindings]);
8693
8650
  const promptTriggerHints = useMemo(() => {
8694
- const out = [];
8695
- if (filesCatalog.length > 0) out.push({
8651
+ return [{
8696
8652
  key: "@",
8697
8653
  label: "files",
8698
8654
  keyColor: resolveChipColor(SURFACE.chips, "files").bg
8699
- });
8700
- if (skillsCatalog.length > 0) out.push({
8655
+ }, {
8701
8656
  key: "/",
8702
8657
  label: "skills",
8703
8658
  keyColor: resolveChipColor(SURFACE.chips, "skills").bg
8704
- });
8705
- return out;
8706
- }, [
8707
- filesCatalog,
8708
- skillsCatalog,
8709
- SURFACE
8710
- ]);
8659
+ }];
8660
+ }, [SURFACE]);
8711
8661
  const contextUsage = useMemo(() => {
8712
8662
  if (screen !== "chat" || !picked) return null;
8713
8663
  const descriptor = providerRegistry[picked.provider.key];
@@ -9085,16 +9035,45 @@ function resolveLocalParsers() {
9085
9035
  return resolved;
9086
9036
  }
9087
9037
  let registered = false;
9038
+ let workerInitStarted = false;
9088
9039
  /**
9089
- * Register the extra Tree-sitter parsers + start the worker. Idempotent —
9090
- * subsequent calls are no-ops. Safe to invoke from `runTui()` and from
9091
- * composition hosts that mount `<App>` directly.
9040
+ * Synchronously append every URL-fetched + locally-vendored grammar to
9041
+ * OpenTUI's global parser registry. Cheap (just a couple of `Array.push`
9042
+ * calls into a module-level table) and idempotent — subsequent calls
9043
+ * are no-ops.
9044
+ *
9045
+ * Split out from {@link initTreeSitterWorker} so the boot path can land
9046
+ * the registry *synchronously* on its critical path (no awaiting
9047
+ * required) and defer the heavier worker spin-up below until after the
9048
+ * first frame is on screen. OpenTUI's `highlightOnce` self-initialises
9049
+ * the worker on first use anyway — registering parsers ahead of time is
9050
+ * the only piece that genuinely has to happen synchronously.
9092
9051
  */
9093
- async function setupTreeSitter() {
9052
+ function registerTreeSitterParsers() {
9094
9053
  if (registered) return;
9095
9054
  registered = true;
9096
9055
  addDefaultParsers([...EXTRA_PARSERS, ...resolveLocalParsers()]);
9097
- await getTreeSitterClient().initialize();
9056
+ }
9057
+ /**
9058
+ * Spin up OpenTUI's parser worker thread. Idempotent — concurrent
9059
+ * callers share a single in-flight promise; subsequent calls after
9060
+ * resolution are no-ops.
9061
+ *
9062
+ * Safe to fire-and-forget after the renderer mounts: the worker boot
9063
+ * is the heaviest step in tree-sitter setup (~40–100ms on most
9064
+ * machines) and nothing visible needs it until the first fenced code
9065
+ * block highlight, which is many frames away even on the fastest
9066
+ * sessions. If a `<markdown>` element happens to call
9067
+ * `highlightOnce()` before our explicit init completes, OpenTUI's
9068
+ * client guards that call with its own `if (!this.initialized) await
9069
+ * this.initialize()` — no race.
9070
+ */
9071
+ function initTreeSitterWorker() {
9072
+ if (!workerInitStarted) {
9073
+ workerInitStarted = true;
9074
+ registerTreeSitterParsers();
9075
+ }
9076
+ return getTreeSitterClient().initialize();
9098
9077
  }
9099
9078
  //#endregion
9100
9079
  //#region src/tui/mcps-settings.tsx
@@ -9125,7 +9104,7 @@ async function setupTreeSitter() {
9125
9104
  * Errors (`DiscoveryError[]`) surface in a warn-colored preamble so a
9126
9105
  * broken `mcps.json` is loud rather than invisible.
9127
9106
  */
9128
- function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin }) {
9107
+ function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin, onRefresh }) {
9129
9108
  const COLOR = useColors();
9130
9109
  const home = homedir();
9131
9110
  const authState = useMcpAuthState();
@@ -9166,7 +9145,11 @@ function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin }
9166
9145
  onLogin(name);
9167
9146
  return;
9168
9147
  }
9169
- if (key.name === "o" && canLogout(status)) onLogout(name);
9148
+ if (key.name === "o" && canLogout(status)) {
9149
+ onLogout(name);
9150
+ return;
9151
+ }
9152
+ if (key.name === "r" && onRefresh) onRefresh();
9170
9153
  });
9171
9154
  if (catalog.length === 0) return /* @__PURE__ */ jsxs(Modal, {
9172
9155
  title: "mcp servers",
@@ -9227,10 +9210,17 @@ function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin }
9227
9210
  }),
9228
9211
  /* @__PURE__ */ jsxs("text", {
9229
9212
  fg: COLOR.mute,
9230
- children: [/* @__PURE__ */ jsx("span", {
9231
- fg: COLOR.warn,
9232
- children: "esc"
9233
- }), " close"]
9213
+ children: [
9214
+ onRefresh && /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("span", {
9215
+ fg: COLOR.warn,
9216
+ children: "r"
9217
+ }), " refresh · "] }),
9218
+ /* @__PURE__ */ jsx("span", {
9219
+ fg: COLOR.warn,
9220
+ children: "esc"
9221
+ }),
9222
+ " close"
9223
+ ]
9234
9224
  })
9235
9225
  ]
9236
9226
  });
@@ -9272,7 +9262,7 @@ function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin }
9272
9262
  })
9273
9263
  }),
9274
9264
  focusedEntry && focusedStatus && renderDetailPanel(focusedEntry, focusedStatus, COLOR),
9275
- renderActionHints(focusedEntry, focusedStatus, COLOR)
9265
+ renderActionHints(focusedEntry, focusedStatus, !!onRefresh, COLOR)
9276
9266
  ]
9277
9267
  });
9278
9268
  }
@@ -9383,7 +9373,7 @@ function renderDetailPanel(entry, status, COLOR) {
9383
9373
  });
9384
9374
  return null;
9385
9375
  }
9386
- function renderActionHints(entry, status, COLOR) {
9376
+ function renderActionHints(entry, status, showRefresh, COLOR) {
9387
9377
  const effectiveStatus = status ?? { kind: "idle" };
9388
9378
  const canL = entry ? canLogin(entry, effectiveStatus) : false;
9389
9379
  const canO = canLogout(effectiveStatus);
@@ -9417,6 +9407,14 @@ function renderActionHints(entry, status, COLOR) {
9417
9407
  }),
9418
9408
  " logout"
9419
9409
  ] }),
9410
+ showRefresh && /* @__PURE__ */ jsxs("span", { children: [
9411
+ " · ",
9412
+ /* @__PURE__ */ jsx("span", {
9413
+ fg: COLOR.warn,
9414
+ children: "r"
9415
+ }),
9416
+ " refresh"
9417
+ ] }),
9420
9418
  canCancel && /* @__PURE__ */ jsxs("span", { children: [
9421
9419
  " · ",
9422
9420
  /* @__PURE__ */ jsx("span", {
@@ -9467,7 +9465,7 @@ function displayPath(path, home) {
9467
9465
  * (chat layer) — a GUI shell can build its own toggle list against the
9468
9466
  * same hook without pulling OpenTUI.
9469
9467
  */
9470
- function ToggleListModal({ catalog, keyOf, settingKey, title, renderDetail, emptyState, preamble }) {
9468
+ function ToggleListModal({ catalog, keyOf, settingKey, title, renderDetail, emptyState, preamble, onRefresh }) {
9471
9469
  const COLOR = useColors();
9472
9470
  const { enabledSet, toggle } = useEnabledToggleSet({
9473
9471
  catalog,
@@ -9486,7 +9484,7 @@ function ToggleListModal({ catalog, keyOf, settingKey, title, renderDetail, empt
9486
9484
  else if (key.name === "return" || key.name === "space") {
9487
9485
  const entry = catalog[safeCursor];
9488
9486
  if (entry) toggle(keyOf(entry));
9489
- }
9487
+ } else if (key.ctrl && key.name === "r" && onRefresh) onRefresh();
9490
9488
  });
9491
9489
  if (catalog.length === 0) return /* @__PURE__ */ jsxs(Modal, {
9492
9490
  title,
@@ -9495,10 +9493,17 @@ function ToggleListModal({ catalog, keyOf, settingKey, title, renderDetail, empt
9495
9493
  emptyState,
9496
9494
  /* @__PURE__ */ jsxs("text", {
9497
9495
  fg: COLOR.mute,
9498
- children: [/* @__PURE__ */ jsx("span", {
9499
- fg: COLOR.warn,
9500
- children: "esc"
9501
- }), " close"]
9496
+ children: [
9497
+ onRefresh && /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("span", {
9498
+ fg: COLOR.warn,
9499
+ children: "ctrl+R"
9500
+ }), " refresh · "] }),
9501
+ /* @__PURE__ */ jsx("span", {
9502
+ fg: COLOR.warn,
9503
+ children: "esc"
9504
+ }),
9505
+ " close"
9506
+ ]
9502
9507
  })
9503
9508
  ]
9504
9509
  });
@@ -9548,6 +9553,10 @@ function ToggleListModal({ catalog, keyOf, settingKey, title, renderDetail, empt
9548
9553
  children: "↵"
9549
9554
  }),
9550
9555
  " toggle · ",
9556
+ onRefresh && /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("span", {
9557
+ fg: COLOR.warn,
9558
+ children: "ctrl+R"
9559
+ }), " refresh · "] }),
9551
9560
  /* @__PURE__ */ jsx("span", {
9552
9561
  fg: COLOR.warn,
9553
9562
  children: "esc"
@@ -9564,8 +9573,13 @@ function ToggleListModal({ catalog, keyOf, settingKey, title, renderDetail, empt
9564
9573
  * List + toggle modal for discovered skills. State machine + keyboard +
9565
9574
  * row geometry live in `<ToggleListModal>`; this file just supplies the
9566
9575
  * skill-specific column (description) and the empty-state hint.
9576
+ *
9577
+ * Pass `onRefresh` to expose `ctrl+R` (force re-scan) on the list. The
9578
+ * standalone modal is for embedders — the in-app flow goes through
9579
+ * `<SettingsModal>` which exposes the same affordance via its own
9580
+ * `SettingsActions.onRefreshSkills` plumbing.
9567
9581
  */
9568
- function SkillsSettingsModal({ catalog }) {
9582
+ function SkillsSettingsModal({ catalog, onRefresh }) {
9569
9583
  const COLOR = useColors();
9570
9584
  return /* @__PURE__ */ jsx(ToggleListModal, {
9571
9585
  catalog,
@@ -9573,6 +9587,7 @@ function SkillsSettingsModal({ catalog }) {
9573
9587
  settingKey: "enabledSkills",
9574
9588
  title: "skills",
9575
9589
  renderDetail: (skill) => skill.description,
9590
+ onRefresh,
9576
9591
  emptyState: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("text", {
9577
9592
  fg: COLOR.dim,
9578
9593
  children: "No skills discovered."
@@ -9662,14 +9677,14 @@ let runTuiInvoked = false;
9662
9677
  async function runTui(options = {}) {
9663
9678
  if (runTuiInvoked) throw new Error("runTui() can only be invoked once per process. Compose `<App config={resolveConfig(...)} />` against your own renderer if you need to run multiple TUIs in the same lifetime.");
9664
9679
  runTuiInvoked = true;
9665
- await setupTreeSitter().catch((err) => {
9666
- const cause = err instanceof Error ? err.message : String(err);
9667
- process.stderr.write(`[zidane/tui] tree-sitter setup failed: ${cause}\n`);
9668
- });
9680
+ bootTick("runTui:enter");
9681
+ registerTreeSitterParsers();
9682
+ bootTick("runTui:after-registerTreeSitterParsers");
9669
9683
  const config = resolveConfig({
9670
9684
  ...options,
9671
9685
  store: options.store ?? ((paths) => createTuiStore(paths.db))
9672
9686
  });
9687
+ bootTick("runTui:after-resolveConfig");
9673
9688
  let done = () => {};
9674
9689
  const exited = new Promise((resolve) => {
9675
9690
  done = resolve;
@@ -9684,7 +9699,13 @@ async function runTui(options = {}) {
9684
9699
  maxFps: bootFps,
9685
9700
  gatherStats
9686
9701
  });
9702
+ bootTick("runTui:after-createCliRenderer");
9687
9703
  createRoot(renderer).render(/* @__PURE__ */ jsx(App, { config }));
9704
+ bootTick("runTui:after-mount");
9705
+ initTreeSitterWorker().then(() => bootTick("runTui:after-treeSitterWorker"), (err) => {
9706
+ const cause = err instanceof Error ? err.message : String(err);
9707
+ process.stderr.write(`[zidane/tui] tree-sitter setup failed: ${cause}\n`);
9708
+ });
9688
9709
  await exited;
9689
9710
  if (gatherStats) try {
9690
9711
  const s = renderer.getStats();
@@ -9695,6 +9716,6 @@ async function runTui(options = {}) {
9695
9716
  process.exit(0);
9696
9717
  }
9697
9718
  //#endregion
9698
- export { AgentPickerModal, App, AuthScreen, ChatScreen, CompletionPopup, EffortPickerModal, Footer, InteractionBlock, McpsSettingsModal, Modal, ModalRoot, ModelPickerModal, SessionDetailsModal, SessionsScreen, SettingsModal, SkillsSettingsModal, Spinner, StatusSpinner, TOOL_DISPLAY, TitleOverlay, ToggleListModal, Transcript, TurnDetailsModal, accentColor, buildMdStyle, displayNameFor, formatToolCall, hintsLength, isEditErrorResult, isTurnHighlighted, isVisible, marginTopFor, onInputSubmit, renderHintSpans, runTui, selectableTurnIds, splitPromptSegments, turnSelectionOwnership, useMdStyle, useModal, useModalAwareFocus };
9719
+ export { AgentPickerModal, App, AuthScreen, ChatScreen, CompletionPopup, EffortPickerModal, Footer, InteractionBlock, McpsSettingsModal, Modal, ModalRoot, ModelPickerModal, SessionDetailsModal, SessionsScreen, SettingsModal, SkillsSettingsModal, Spinner, StatusSpinner, TOOL_DISPLAY, TitleOverlay, ToggleListModal, Transcript, TurnDetailsModal, accentColor, buildMdStyle, clipHintsToWidth, computeTurnAnchors, displayNameFor, formatToolCall, hintsLength, isEditErrorResult, isTurnHighlighted, isVisible, marginTopFor, onInputSubmit, renderHintSpans, runTui, selectableTurnIds, splitMarkdownCodeBlocks, splitPromptSegments, truncateTrailing, turnSelectionOwnership, useMdStyle, useModal, useModalAwareFocus };
9699
9720
 
9700
9721
  //# sourceMappingURL=tui.js.map