@noir-ai/cli 1.11.0-beta.1 → 1.11.1-beta.1

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 (41) hide show
  1. package/README.md +4 -3
  2. package/dist/bin.js +19 -16
  3. package/dist/bin.js.map +1 -1
  4. package/dist/{chunk-CN3KUSQT.js → chunk-CT6TYLHN.js} +4 -3
  5. package/dist/chunk-CT6TYLHN.js.map +1 -0
  6. package/dist/{chunk-S2PDLS4C.js → chunk-I2R55ACD.js} +4 -2
  7. package/dist/{chunk-S2PDLS4C.js.map → chunk-I2R55ACD.js.map} +1 -1
  8. package/dist/{chunk-Y4M2YC6U.js → chunk-QIVHIKYD.js} +3 -3
  9. package/dist/{chunk-US567S5T.js → chunk-RON2XESF.js} +2 -2
  10. package/dist/{create-DJ6B5K36.js → create-VK2QMXYS.js} +3 -3
  11. package/dist/index.js +2 -2
  12. package/dist/{install-X7QSYLLS.js → install-4U4UDDNE.js} +3 -3
  13. package/dist/{sync-FKWXQOWT.js → sync-VXZKODQA.js} +3 -3
  14. package/dist/tui/{chunk-2KJ27HGA.js → chunk-F6XRS72N.js} +4 -3
  15. package/dist/tui/chunk-F6XRS72N.js.map +1 -0
  16. package/dist/tui/{chunk-4OBUMHF6.js → chunk-NLK6UNIA.js} +3 -3
  17. package/dist/tui/{chunk-R6246ISQ.js → chunk-QF6777NJ.js} +2 -2
  18. package/dist/tui/{chunk-MD6WXZBF.js → chunk-R5YX44UL.js} +4 -2
  19. package/dist/tui/{chunk-MD6WXZBF.js.map → chunk-R5YX44UL.js.map} +1 -1
  20. package/dist/tui/{create-CFAXIIYW.js → create-WKFVM237.js} +3 -3
  21. package/dist/tui/index.js +185 -153
  22. package/dist/tui/index.js.map +1 -1
  23. package/dist/tui/{install-HZKZXBQD.js → install-JR6ZXNYA.js} +3 -3
  24. package/dist/tui/{sync-Y7MTQ4JC.js → sync-DUWJAF7Z.js} +3 -3
  25. package/dist/tui/{update-OM2EP2F5.js → update-PGAM6OVL.js} +4 -4
  26. package/dist/{update-S3IZXXKO.js → update-ZDAVLOXB.js} +4 -4
  27. package/package.json +12 -12
  28. package/dist/chunk-CN3KUSQT.js.map +0 -1
  29. package/dist/tui/chunk-2KJ27HGA.js.map +0 -1
  30. /package/dist/{chunk-Y4M2YC6U.js.map → chunk-QIVHIKYD.js.map} +0 -0
  31. /package/dist/{chunk-US567S5T.js.map → chunk-RON2XESF.js.map} +0 -0
  32. /package/dist/{create-DJ6B5K36.js.map → create-VK2QMXYS.js.map} +0 -0
  33. /package/dist/{install-X7QSYLLS.js.map → install-4U4UDDNE.js.map} +0 -0
  34. /package/dist/{sync-FKWXQOWT.js.map → sync-VXZKODQA.js.map} +0 -0
  35. /package/dist/tui/{chunk-4OBUMHF6.js.map → chunk-NLK6UNIA.js.map} +0 -0
  36. /package/dist/tui/{chunk-R6246ISQ.js.map → chunk-QF6777NJ.js.map} +0 -0
  37. /package/dist/tui/{create-CFAXIIYW.js.map → create-WKFVM237.js.map} +0 -0
  38. /package/dist/tui/{install-HZKZXBQD.js.map → install-JR6ZXNYA.js.map} +0 -0
  39. /package/dist/tui/{sync-Y7MTQ4JC.js.map → sync-DUWJAF7Z.js.map} +0 -0
  40. /package/dist/tui/{update-OM2EP2F5.js.map → update-PGAM6OVL.js.map} +0 -0
  41. /package/dist/{update-S3IZXXKO.js.map → update-ZDAVLOXB.js.map} +0 -0
package/dist/tui/index.js CHANGED
@@ -2,12 +2,12 @@ import {
2
2
  DEFAULT_UPDATE_CONFIG,
3
3
  buildUpdateNotice,
4
4
  runAsyncUpdateCheck
5
- } from "./chunk-4OBUMHF6.js";
6
- import "./chunk-R6246ISQ.js";
5
+ } from "./chunk-NLK6UNIA.js";
6
+ import "./chunk-QF6777NJ.js";
7
7
  import {
8
8
  doctor,
9
9
  init
10
- } from "./chunk-2KJ27HGA.js";
10
+ } from "./chunk-F6XRS72N.js";
11
11
  import {
12
12
  EXIT,
13
13
  NoirCliError,
@@ -30,7 +30,7 @@ import {
30
30
  tip,
31
31
  useColor,
32
32
  warn
33
- } from "./chunk-MD6WXZBF.js";
33
+ } from "./chunk-R5YX44UL.js";
34
34
 
35
35
  // src/tui/index.tsx
36
36
  import { loadProjectInfo as loadProjectInfo9 } from "@noir-ai/core";
@@ -2965,7 +2965,7 @@ function createProgram() {
2965
2965
  const force = opts.force === true;
2966
2966
  const host = parseHost(opts.host);
2967
2967
  const dryRun = opts.dryRun === true || opts.preview === true;
2968
- const { create } = await import("./create-CFAXIIYW.js");
2968
+ const { create } = await import("./create-WKFVM237.js");
2969
2969
  const result = await create(dir, {
2970
2970
  transport,
2971
2971
  url: opts.url,
@@ -3004,7 +3004,7 @@ function createProgram() {
3004
3004
  ).choices(SUPPORTED_HOSTS2)
3005
3005
  ).action(
3006
3006
  async (opts, cmd) => {
3007
- const { sync } = await import("./sync-Y7MTQ4JC.js");
3007
+ const { sync } = await import("./sync-DUWJAF7Z.js");
3008
3008
  const host = parseHost(opts.host);
3009
3009
  const force = opts.force === true;
3010
3010
  const merge = opts.merge === true;
@@ -3066,7 +3066,7 @@ function createProgram() {
3066
3066
  program2.command("status").description("project + daemon + workflow + store snapshot").action(async (...args) => {
3067
3067
  await status(toStatusOptions(actionGlobals(args)));
3068
3068
  });
3069
- const contextGrp = program2.command("context").description("context engine (S6)");
3069
+ const contextGrp = program2.command("context").description("context engine");
3070
3070
  contextGrp.command("search").description("hybrid search over the indexed context").argument("<query>", "search query").option("--limit <n>", "max results", "10").action(async (...args) => {
3071
3071
  const cmd = trailingCmd(args);
3072
3072
  const g = cmd.optsWithGlobals();
@@ -3155,17 +3155,17 @@ function createProgram() {
3155
3155
  "Usage: noir memory recall|save|sessions|forget|consolidate"
3156
3156
  );
3157
3157
  });
3158
- const skillsGrp = program2.command("skills").description("builtin skills (S5)");
3158
+ const skillsGrp = program2.command("skills").description("builtin skills");
3159
3159
  skillsGrp.command("list").description("list installed Noir skills").action(async (...args) => {
3160
3160
  await skillsList(toCliOptions(actionGlobals(args)));
3161
3161
  });
3162
3162
  skillsGrp.command("sync").description("re-emit skills to the host skills dir").action(async (...args) => {
3163
3163
  await skillsSync(toCliOptions(actionGlobals(args)));
3164
3164
  });
3165
- skillsGrp.command("lint").description("structural quality gate over the shipped pack (C3)").action(async (...args) => {
3165
+ skillsGrp.command("lint").description("structural quality gate over the shipped pack").action(async (...args) => {
3166
3166
  await skillsLint(toCliOptions(actionGlobals(args)));
3167
3167
  });
3168
- skillsGrp.command("registry").description("emit the runtime-derived skill registry (C3)").action(async (...args) => {
3168
+ skillsGrp.command("registry").description("emit the runtime-derived skill registry").action(async (...args) => {
3169
3169
  await skillsRegistry(toCliOptions(actionGlobals(args)));
3170
3170
  });
3171
3171
  skillsGrp.action(() => {
@@ -3215,7 +3215,7 @@ function createProgram() {
3215
3215
  });
3216
3216
  taskGrp.command("decompose").description(
3217
3217
  "decompose a capability into buildable slices (template-only offline; see the spec)"
3218
- ).argument("<capability>", "capability id, e.g. C4").option("--out <path>", "output path for the SlicePlan JSON").action(async (...args) => {
3218
+ ).argument("<capability>", "capability id").option("--out <path>", "output path for the slice-plan JSON").action(async (...args) => {
3219
3219
  const cmd = trailingCmd(args);
3220
3220
  const g = cmd.optsWithGlobals();
3221
3221
  const capability = typeof args[0] === "string" ? args[0] : "";
@@ -3307,7 +3307,7 @@ function createProgram() {
3307
3307
  "dismiss the migration banner for the current version (persists in install.json)"
3308
3308
  ).argument("[spec]", "channel ('latest'|'beta') or exact version (default: latest)").action(
3309
3309
  async (spec, opts, cmd) => {
3310
- const { install } = await import("./install-HZKZXBQD.js");
3310
+ const { install } = await import("./install-JR6ZXNYA.js");
3311
3311
  const cli = cmd.optsWithGlobals();
3312
3312
  await install({
3313
3313
  ...cli,
@@ -3320,7 +3320,7 @@ function createProgram() {
3320
3320
  );
3321
3321
  installCmd.alias("migrate");
3322
3322
  program2.command("update").description("update Noir to the latest version via the active install method").option("--check", "check for a new version without changing anything").argument("[spec]", "channel ('latest'|'beta') or exact version (default: latest)").action(async (spec, opts, cmd) => {
3323
- const { update } = await import("./update-OM2EP2F5.js");
3323
+ const { update } = await import("./update-PGAM6OVL.js");
3324
3324
  const cli = cmd.optsWithGlobals();
3325
3325
  await update({ ...cli, spec, check: opts.check === true });
3326
3326
  });
@@ -3348,6 +3348,7 @@ function createProgram() {
3348
3348
  const opts = toCliOptions(g);
3349
3349
  requireInteractive(opts, "`noir tui`");
3350
3350
  const tuiUrl = new URL("./tui/index.js", import.meta.url).href;
3351
+ process.env.NODE_ENV ||= "production";
3351
3352
  const { runTui: runTui2 } = await import(
3352
3353
  /* @vite-ignore */
3353
3354
  tuiUrl
@@ -3367,6 +3368,7 @@ function createProgram() {
3367
3368
  const opts = toCliOptions(g);
3368
3369
  requireInteractive(opts, "`noir palette`");
3369
3370
  const tuiUrl = new URL("./tui/index.js", import.meta.url).href;
3371
+ process.env.NODE_ENV ||= "production";
3370
3372
  const { runPalette: runPalette2 } = await import(
3371
3373
  /* @vite-ignore */
3372
3374
  tuiUrl
@@ -3381,9 +3383,10 @@ function createProgram() {
3381
3383
  };
3382
3384
  await runPalette2(opts, dispatch);
3383
3385
  });
3384
- program2.command("run").description("drive the host CLI headless and render its stream-json (v2)").argument("[prompt...]", "prompt to send to the host").addOption(new Option("--host <id>", "host to drive (default claude)").choices(SUPPORTED_HOSTS2)).option("--command <binary>", "custom host binary (e.g. claude-work)").action(async (...args) => {
3385
- const g = trailingCmd(args).optsWithGlobals();
3386
- const prompt = args.filter((a) => typeof a === "string").join(" ").trim();
3386
+ program2.command("run").description("ask the host agent a question and print the answer").argument("[prompt...]", "prompt to send to the host").addOption(new Option("--host <id>", "host to drive (default claude)").choices(SUPPORTED_HOSTS2)).option("--command <binary>", "custom host binary (e.g. claude-work)").action(async (...args) => {
3387
+ const cmd = trailingCmd(args);
3388
+ const g = cmd.optsWithGlobals();
3389
+ const prompt = cmd.args.join(" ").trim();
3387
3390
  const opts = {
3388
3391
  ...toCliOptions(g),
3389
3392
  ...typeof g.host === "string" ? { host: g.host } : {},
@@ -3443,7 +3446,7 @@ if (isMainModule) {
3443
3446
 
3444
3447
  // src/tui/App.tsx
3445
3448
  import { Box as Box5, Text as Text8, useApp, useInput } from "ink";
3446
- import { useEffect, useState as useState2 } from "react";
3449
+ import { useEffect, useMemo, useState as useState2 } from "react";
3447
3450
 
3448
3451
  // src/tui/CommandInput.tsx
3449
3452
  import { Box, Text } from "ink";
@@ -3554,17 +3557,18 @@ function Header({ tagline }) {
3554
3557
  }
3555
3558
 
3556
3559
  // src/tui/hooks/useInputBuffer.ts
3557
- import { useRef, useState } from "react";
3560
+ import { useCallback, useRef, useState } from "react";
3561
+ var MAX_HISTORY = 50;
3558
3562
  function useInputBuffer() {
3559
3563
  const [buffer, setBuffer] = useState("");
3560
3564
  const historyRef = useRef([]);
3561
3565
  const cursorRef = useRef(-1);
3562
- function pushHistory(text) {
3566
+ const pushHistory = useCallback((text) => {
3563
3567
  if (text.length === 0) return;
3564
3568
  const history = historyRef.current.filter((e) => e !== text);
3565
- historyRef.current = [text, ...history];
3566
- }
3567
- function recall(dir) {
3569
+ historyRef.current = [text, ...history].slice(0, MAX_HISTORY);
3570
+ }, []);
3571
+ const recall = useCallback((dir) => {
3568
3572
  const history = historyRef.current;
3569
3573
  if (history.length === 0) return null;
3570
3574
  let cursor = cursorRef.current;
@@ -3575,51 +3579,37 @@ function useInputBuffer() {
3575
3579
  }
3576
3580
  cursorRef.current = cursor;
3577
3581
  return cursor === -1 ? null : history[cursor] ?? null;
3578
- }
3579
- function clear() {
3582
+ }, []);
3583
+ const clear = useCallback(() => {
3580
3584
  setBuffer(() => "");
3581
3585
  cursorRef.current = -1;
3582
- }
3583
- function seed(entries) {
3586
+ }, []);
3587
+ const seed = useCallback((entries) => {
3584
3588
  historyRef.current = [...entries];
3585
3589
  cursorRef.current = -1;
3586
- }
3590
+ }, []);
3587
3591
  return { buffer, setBuffer, pushHistory, recall, clear, seed };
3588
3592
  }
3589
3593
 
3590
3594
  // src/tui/OutputPane.tsx
3591
3595
  import { Text as Text4 } from "ink";
3592
3596
  import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
3593
- function toRows(visible, baseIndex) {
3597
+ function toRows(visible) {
3594
3598
  const rows = [];
3595
3599
  const seen = /* @__PURE__ */ new Map();
3596
3600
  for (let i = 0; i < visible.length; i++) {
3597
3601
  const text = visible[i] ?? "";
3598
3602
  const n = (seen.get(text) ?? 0) + 1;
3599
3603
  seen.set(text, n);
3600
- rows.push({
3601
- key: n === 1 ? text || "_blank" : `${text || "_blank"}#${n}`,
3602
- text,
3603
- index: baseIndex + i
3604
- });
3604
+ rows.push({ key: n === 1 ? text || "_blank" : `${text || "_blank"}#${n}`, text });
3605
3605
  }
3606
3606
  return rows;
3607
3607
  }
3608
- function highlightFirst(text, query) {
3609
- if (query.length === 0) return text;
3610
- const idx = text.toLowerCase().indexOf(query.toLowerCase());
3611
- if (idx < 0) return text;
3612
- return `${text.slice(0, idx)}${c.bold(text.slice(idx, idx + query.length))}${text.slice(
3613
- idx + query.length
3614
- )}`;
3615
- }
3616
3608
  function OutputPane({
3617
3609
  lines,
3618
3610
  scrollOffset,
3619
3611
  height = 12,
3620
- title,
3621
- highlightQuery,
3622
- activeLine
3612
+ title
3623
3613
  }) {
3624
3614
  const width = contentWidth();
3625
3615
  if (lines.length === 0) {
@@ -3627,18 +3617,12 @@ function OutputPane({
3627
3617
  }
3628
3618
  const maxOffset = Math.max(0, lines.length - height);
3629
3619
  const offset = Math.min(Math.max(0, scrollOffset), maxOffset);
3630
- const rows = toRows(lines.slice(offset, offset + height), offset);
3620
+ const rows = toRows(lines.slice(offset, offset + height));
3631
3621
  return /* @__PURE__ */ jsxs3(Fragment, { children: [
3632
3622
  title !== void 0 ? /* @__PURE__ */ jsx4(Text4, { wrap: "truncate-end", children: c.dim(`\u2500\u2500 ${title} \u2500\u2500`) }) : null,
3633
3623
  rows.map((row) => {
3634
3624
  const truncated = row.text.length > width ? `${row.text.slice(0, Math.max(1, width - 1))}\u2026` : row.text;
3635
- let body = truncated;
3636
- if (activeLine !== void 0 && row.index === activeLine) {
3637
- body = c.accent(truncated);
3638
- } else if (highlightQuery !== void 0 && highlightQuery.length > 0) {
3639
- body = highlightFirst(truncated, highlightQuery);
3640
- }
3641
- return /* @__PURE__ */ jsx4(Text4, { wrap: "truncate-end", children: body }, row.key);
3625
+ return /* @__PURE__ */ jsx4(Text4, { wrap: "truncate-end", children: truncated }, row.key);
3642
3626
  })
3643
3627
  ] });
3644
3628
  }
@@ -3758,66 +3742,6 @@ var handRolledMatcher = {
3758
3742
 
3759
3743
  // src/tui/palette/Palette.tsx
3760
3744
  import { Box as Box4, Text as Text6 } from "ink";
3761
- import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
3762
- var PALETTE_WIDTH = 64;
3763
- var VISIBLE_ROWS = 10;
3764
- function highlight(label, matchedIndices) {
3765
- if (matchedIndices.length === 0) return /* @__PURE__ */ jsx7(Fragment2, { children: label });
3766
- const set = new Set(matchedIndices);
3767
- const chars = [];
3768
- for (let i = 0; i < label.length; i++) {
3769
- const ch = label[i] ?? "";
3770
- chars.push(set.has(i) ? /* @__PURE__ */ jsx7(Text6, { children: c.accent(ch) }, i) : /* @__PURE__ */ jsx7(Text6, { children: ch }, i));
3771
- }
3772
- return /* @__PURE__ */ jsx7(Fragment2, { children: chars });
3773
- }
3774
- function truncate2(label) {
3775
- const max = Math.max(20, PALETTE_WIDTH - 8);
3776
- return label.length > max ? `${label.slice(0, max - 1)}\u2026` : label;
3777
- }
3778
- function placeholder(corpus) {
3779
- if (corpus === "commands") return "type to search commands";
3780
- if (corpus === "output") return "type to filter output";
3781
- return "keybindings";
3782
- }
3783
- function Palette({ corpus, query, active, rows }) {
3784
- const visible = rows.slice(0, VISIBLE_ROWS);
3785
- const activeIndex = Math.min(Math.max(active, 0), Math.max(0, visible.length - 1));
3786
- const elements = [
3787
- /* @__PURE__ */ jsx7(Box4, { paddingX: 1, children: /* @__PURE__ */ jsxs5(Text6, { children: [
3788
- c.bold("\u25B8 palette "),
3789
- /* @__PURE__ */ jsx7(Text6, { children: c.dim(`(${corpus} \xB7 ${visible.length} shown)`) })
3790
- ] }) }, "header"),
3791
- /* @__PURE__ */ jsx7(Box4, { paddingX: 1, children: /* @__PURE__ */ jsxs5(Text6, { children: [
3792
- c.dim("> "),
3793
- query.length > 0 ? query : c.dim(placeholder(corpus)),
3794
- /* @__PURE__ */ jsx7(Text6, { children: c.dim("\u258C") })
3795
- ] }) }, "query")
3796
- ];
3797
- let lastGroup = null;
3798
- for (let i = 0; i < visible.length; i++) {
3799
- const row = visible[i];
3800
- if (!row) continue;
3801
- if (row.group !== null && row.group !== lastGroup) {
3802
- elements.push(
3803
- /* @__PURE__ */ jsx7(Box4, { paddingX: 1, children: /* @__PURE__ */ jsx7(Text6, { children: c.dim(`\u2500\u2500 ${row.group} \u2500\u2500`) }) }, `cat:${row.group}`)
3804
- );
3805
- lastGroup = row.group;
3806
- }
3807
- const isActive = i === activeIndex;
3808
- elements.push(
3809
- /* @__PURE__ */ jsx7(Box4, { paddingX: 1, children: /* @__PURE__ */ jsxs5(Text6, { children: [
3810
- isActive ? "\u25B8 " : " ",
3811
- isActive ? c.accent(truncate2(row.primary)) : highlight(truncate2(row.primary), row.indices),
3812
- /* @__PURE__ */ jsx7(Text6, { children: c.dim(` ${row.secondary}`) })
3813
- ] }) }, row.key)
3814
- );
3815
- }
3816
- return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", children: [
3817
- /* @__PURE__ */ jsx7(Panel, { maxWidth: PALETTE_WIDTH, children: elements }),
3818
- /* @__PURE__ */ jsx7(Text6, { children: c.dim(LIST_NAV_HINT) })
3819
- ] });
3820
- }
3821
3745
 
3822
3746
  // src/tui/palette/output.ts
3823
3747
  function computeMatches(lines, query) {
@@ -3833,6 +3757,7 @@ function computeMatches(lines, query) {
3833
3757
  // src/tui/palette/rows.ts
3834
3758
  var CORPORA = ["commands", "output", "help"];
3835
3759
  var MATCH_LIMIT = 20;
3760
+ var VISIBLE_ROWS = 10;
3836
3761
  function flattenHome(sections) {
3837
3762
  const out = [];
3838
3763
  for (const section of sections) {
@@ -3853,12 +3778,23 @@ function buildPaletteRows(input) {
3853
3778
  secondary: e.desc,
3854
3779
  argv: null,
3855
3780
  destructive: false,
3856
- indices: [],
3857
3781
  group: "keybindings"
3858
3782
  }));
3859
3783
  }
3860
3784
  if (corpus === "output") {
3861
3785
  const matches = computeMatches(outputLines, query);
3786
+ if (query.length > 0 && matches.length === 0) {
3787
+ return [
3788
+ {
3789
+ key: "empty",
3790
+ primary: "(no matches)",
3791
+ secondary: `nothing matches "${query}"`,
3792
+ argv: null,
3793
+ destructive: false,
3794
+ group: null
3795
+ }
3796
+ ];
3797
+ }
3862
3798
  if (matches.length === 0) {
3863
3799
  if (outputLines.length === 0) {
3864
3800
  return [
@@ -3868,7 +3804,6 @@ function buildPaletteRows(input) {
3868
3804
  secondary: "run a /command first",
3869
3805
  argv: null,
3870
3806
  destructive: false,
3871
- indices: [],
3872
3807
  group: null
3873
3808
  }
3874
3809
  ];
@@ -3879,8 +3814,7 @@ function buildPaletteRows(input) {
3879
3814
  secondary: String(i + 1),
3880
3815
  argv: null,
3881
3816
  destructive: false,
3882
- indices: [],
3883
- group: query.length === 0 ? null : "matches"
3817
+ group: null
3884
3818
  }));
3885
3819
  }
3886
3820
  return matches.map((i) => ({
@@ -3889,7 +3823,6 @@ function buildPaletteRows(input) {
3889
3823
  secondary: String(i + 1),
3890
3824
  argv: null,
3891
3825
  destructive: false,
3892
- indices: [],
3893
3826
  group: "matches"
3894
3827
  }));
3895
3828
  }
@@ -3904,12 +3837,12 @@ function buildPaletteRows(input) {
3904
3837
  secondary: cmd.description,
3905
3838
  argv: cmd.argv,
3906
3839
  destructive: cmd.destructive,
3907
- indices: [],
3908
3840
  group: "recent"
3909
3841
  });
3910
3842
  }
3911
3843
  const homeIds = /* @__PURE__ */ new Set();
3912
3844
  for (const { group, action } of flattenHome(homeSections)) {
3845
+ if (recentIds.has(action.id)) continue;
3913
3846
  const argv = action.dispatch ?? [action.id];
3914
3847
  homeIds.add(action.id);
3915
3848
  rows.push({
@@ -3918,7 +3851,6 @@ function buildPaletteRows(input) {
3918
3851
  secondary: action.hint,
3919
3852
  argv,
3920
3853
  destructive: action.destructive ?? false,
3921
- indices: [],
3922
3854
  group
3923
3855
  });
3924
3856
  }
@@ -3930,7 +3862,6 @@ function buildPaletteRows(input) {
3930
3862
  secondary: cmd.description,
3931
3863
  argv: cmd.argv,
3932
3864
  destructive: cmd.destructive,
3933
- indices: [],
3934
3865
  group: cmd.category
3935
3866
  });
3936
3867
  }
@@ -3942,11 +3873,105 @@ function buildPaletteRows(input) {
3942
3873
  secondary: m.item.description,
3943
3874
  argv: m.item.argv,
3944
3875
  destructive: m.item.destructive,
3945
- indices: m.matchedIndices,
3946
3876
  group: null
3947
3877
  }));
3948
3878
  }
3949
3879
 
3880
+ // src/tui/palette/Palette.tsx
3881
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
3882
+ var PALETTE_WIDTH = 64;
3883
+ var LABEL_WIDTH = 26;
3884
+ var HINT_WIDTH = 34;
3885
+ function wrap(text, width) {
3886
+ const words = text.split(/\s+/).filter((w) => w.length > 0);
3887
+ const lines = [];
3888
+ let cur = "";
3889
+ for (const w of words) {
3890
+ if (cur.length === 0) cur = w;
3891
+ else if ((cur + " " + w).length <= width) cur = `${cur} ${w}`;
3892
+ else {
3893
+ lines.push(cur);
3894
+ cur = w;
3895
+ }
3896
+ }
3897
+ if (cur) lines.push(cur);
3898
+ return lines;
3899
+ }
3900
+ function truncateLabel(label) {
3901
+ const max = LABEL_WIDTH - 2;
3902
+ return label.length > max ? `${label.slice(0, max - 1)}\u2026` : label;
3903
+ }
3904
+ function placeholder(corpus) {
3905
+ if (corpus === "commands") return "type to search commands";
3906
+ if (corpus === "output") return "type to filter output";
3907
+ return "keybindings";
3908
+ }
3909
+ function Palette({ corpus, query, active, rows }) {
3910
+ const total = rows.length;
3911
+ const scroll = Math.max(0, Math.min(active - (VISIBLE_ROWS - 1), total - VISIBLE_ROWS));
3912
+ const visible = rows.slice(scroll, scroll + VISIBLE_ROWS);
3913
+ const activeIndex = Math.min(Math.max(active - scroll, 0), Math.max(0, visible.length - 1));
3914
+ const elements = [
3915
+ /* @__PURE__ */ jsx7(Box4, { paddingX: 1, children: /* @__PURE__ */ jsxs5(Text6, { children: [
3916
+ c.bold("\u25B8 palette "),
3917
+ /* @__PURE__ */ jsx7(Text6, { children: c.dim(`\xB7 ${corpus}`) })
3918
+ ] }) }, "header"),
3919
+ /* @__PURE__ */ jsx7(Box4, { paddingX: 1, children: /* @__PURE__ */ jsxs5(Text6, { children: [
3920
+ c.dim("> "),
3921
+ query.length > 0 ? query : c.dim(placeholder(corpus)),
3922
+ /* @__PURE__ */ jsx7(Text6, { children: c.dim("\u258C") })
3923
+ ] }) }, "query")
3924
+ ];
3925
+ let lastGroup = null;
3926
+ for (let i = 0; i < visible.length; i++) {
3927
+ const row = visible[i];
3928
+ if (!row) continue;
3929
+ if (row.group !== null && row.group !== lastGroup) {
3930
+ if (lastGroup !== null) {
3931
+ elements.push(
3932
+ /* @__PURE__ */ jsx7(Box4, { children: /* @__PURE__ */ jsx7(Text6, { children: " " }) }, `gap:${row.group}`)
3933
+ );
3934
+ }
3935
+ elements.push(
3936
+ /* @__PURE__ */ jsx7(Box4, { paddingX: 1, children: /* @__PURE__ */ jsx7(Text6, { children: c.bold(`\u2500\u2500 ${row.group} \u2500\u2500`) }) }, `cat:${row.group}`)
3937
+ );
3938
+ lastGroup = row.group;
3939
+ }
3940
+ const isActive = i === activeIndex;
3941
+ const prefix = isActive ? "\u25B8 " : " ";
3942
+ const label = truncateLabel(row.primary);
3943
+ const labelCol = (prefix + label).padEnd(LABEL_WIDTH);
3944
+ const hintRaw = row.argv ? `/${row.argv.join(" ")}` : row.secondary;
3945
+ const hint = hintRaw.length > HINT_WIDTH ? `${hintRaw.slice(0, HINT_WIDTH - 1)}\u2026` : hintRaw;
3946
+ if (isActive) {
3947
+ elements.push(
3948
+ /* @__PURE__ */ jsx7(Box4, { paddingX: 1, children: /* @__PURE__ */ jsx7(Text6, { children: c.inverse(`${labelCol}${hint}`) }) }, row.key)
3949
+ );
3950
+ } else {
3951
+ elements.push(
3952
+ /* @__PURE__ */ jsx7(Box4, { paddingX: 1, children: /* @__PURE__ */ jsxs5(Text6, { children: [
3953
+ c.bold(labelCol),
3954
+ hint.length > 0 ? c.dim(hint) : null
3955
+ ] }) }, row.key)
3956
+ );
3957
+ }
3958
+ if (isActive && row.argv && row.secondary) {
3959
+ const detailLines = wrap(row.secondary, PALETTE_WIDTH - 12);
3960
+ for (let di = 0; di < detailLines.length; di++) {
3961
+ const line = detailLines[di] ?? "";
3962
+ const prefix2 = di === 0 ? " \u21B3 " : " ";
3963
+ elements.push(
3964
+ /* @__PURE__ */ jsx7(Box4, { paddingX: 1, children: /* @__PURE__ */ jsx7(Text6, { children: c.dim(`${prefix2}${line}`) }) }, `${row.key}:d:${di}`)
3965
+ );
3966
+ }
3967
+ }
3968
+ }
3969
+ return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", children: [
3970
+ /* @__PURE__ */ jsx7(Panel, { maxWidth: PALETTE_WIDTH, children: elements }),
3971
+ /* @__PURE__ */ jsx7(Text6, { children: c.dim(LIST_NAV_HINT) })
3972
+ ] });
3973
+ }
3974
+
3950
3975
  // src/tui/StatusBar.tsx
3951
3976
  import { Text as Text7 } from "ink";
3952
3977
  import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
@@ -4030,31 +4055,30 @@ function App({
4030
4055
  };
4031
4056
  }, [deps.commands]);
4032
4057
  useEffect(() => {
4033
- if (running) return;
4058
+ if (running || mode.kind !== "dashboard") return;
4034
4059
  let cancelled = false;
4060
+ let inFlight = false;
4061
+ const fetched = (p) => {
4062
+ inFlight = false;
4063
+ if (cancelled) return;
4064
+ setPayload(p);
4065
+ setLoading(false);
4066
+ };
4035
4067
  setLoading(true);
4036
- void deps.fetchStatus().then((p) => {
4037
- if (!cancelled) {
4038
- setPayload(p);
4039
- setLoading(false);
4040
- }
4041
- }).catch(() => {
4042
- if (!cancelled) {
4043
- setPayload(null);
4044
- setLoading(false);
4045
- }
4046
- });
4068
+ inFlight = true;
4069
+ void deps.fetchStatus().then(fetched).catch(() => fetched(null));
4047
4070
  const id = setInterval(() => {
4048
- void deps.fetchStatus().then((p) => {
4049
- if (!cancelled) setPayload(p);
4050
- }).catch(() => {
4071
+ if (inFlight) return;
4072
+ inFlight = true;
4073
+ void deps.fetchStatus().then(fetched).catch(() => {
4074
+ inFlight = false;
4051
4075
  });
4052
4076
  }, refreshMs);
4053
4077
  return () => {
4054
4078
  cancelled = true;
4055
4079
  clearInterval(id);
4056
4080
  };
4057
- }, [running, refreshMs, deps]);
4081
+ }, [running, mode.kind, refreshMs, deps.fetchStatus]);
4058
4082
  useEffect(() => {
4059
4083
  if (pending === null) return;
4060
4084
  const argv = pending;
@@ -4076,7 +4100,7 @@ function App({
4076
4100
  return () => {
4077
4101
  active = false;
4078
4102
  };
4079
- }, [pending, deps]);
4103
+ }, [pending, deps.dispatch]);
4080
4104
  function dispatchCmd(argv) {
4081
4105
  setOutput(null);
4082
4106
  setRunning(true);
@@ -4188,9 +4212,15 @@ function App({
4188
4212
  setBuffer((b) => b + input);
4189
4213
  }
4190
4214
  }
4215
+ function moveActive(delta) {
4216
+ const max = Math.max(0, paletteRows.length - 1);
4217
+ setPaletteActive((a) => Math.min(max, Math.max(0, a + delta)));
4218
+ }
4191
4219
  function handlePaletteInput(input, key) {
4192
4220
  if (mode.kind !== "palette") return;
4193
4221
  if (key.escape) {
4222
+ setPaletteQuery("");
4223
+ setPaletteActive(0);
4194
4224
  setMode({ kind: "dashboard" });
4195
4225
  return;
4196
4226
  }
@@ -4201,8 +4231,8 @@ function App({
4201
4231
  return;
4202
4232
  }
4203
4233
  if (mode.corpus === "help") {
4204
- if (key.upArrow) setPaletteActive((a) => Math.max(0, a - 1));
4205
- else if (key.downArrow) setPaletteActive((a) => a + 1);
4234
+ if (key.upArrow) moveActive(-1);
4235
+ else if (key.downArrow) moveActive(1);
4206
4236
  return;
4207
4237
  }
4208
4238
  if (key.return) {
@@ -4210,16 +4240,16 @@ function App({
4210
4240
  const row = paletteRows[paletteActive];
4211
4241
  if (row?.argv) handleRun(row.argv, row.destructive);
4212
4242
  } else {
4213
- setPaletteActive((a) => a + 1);
4243
+ moveActive(1);
4214
4244
  }
4215
4245
  return;
4216
4246
  }
4217
4247
  if (key.upArrow) {
4218
- setPaletteActive((a) => Math.max(0, a - 1));
4248
+ moveActive(-1);
4219
4249
  return;
4220
4250
  }
4221
4251
  if (key.downArrow) {
4222
- setPaletteActive((a) => a + 1);
4252
+ moveActive(1);
4223
4253
  return;
4224
4254
  }
4225
4255
  if (key.backspace || key.delete) {
@@ -4282,16 +4312,18 @@ function App({
4282
4312
  dispatchCmd(argv);
4283
4313
  void recordRuns(argv);
4284
4314
  }
4285
- const paletteCorpus = mode.kind === "palette" ? mode.corpus : "commands";
4286
- const paletteRows = buildPaletteRows({
4287
- corpus: paletteCorpus,
4288
- query: paletteQuery,
4289
- commands: deps.commands ?? [],
4290
- matcher: deps.matcher ?? handRolledMatcher,
4291
- recent,
4292
- homeSections,
4293
- outputLines: searchLines
4294
- });
4315
+ const paletteRows = useMemo(
4316
+ () => buildPaletteRows({
4317
+ corpus: mode.kind === "palette" ? mode.corpus : "commands",
4318
+ query: paletteQuery,
4319
+ commands: deps.commands ?? [],
4320
+ matcher: deps.matcher ?? handRolledMatcher,
4321
+ recent,
4322
+ homeSections,
4323
+ outputLines: searchLines
4324
+ }),
4325
+ [mode, paletteQuery, deps.commands, deps.matcher, recent, homeSections, searchLines]
4326
+ );
4295
4327
  if (mode.kind === "palette") {
4296
4328
  return /* @__PURE__ */ jsxs7(Box5, { flexDirection: "column", children: [
4297
4329
  /* @__PURE__ */ jsx9(Header, { tagline: `palette \xB7 ${mode.corpus}` }),