kritya 0.8.39-beta → 0.8.40-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -195,7 +195,8 @@ Custom `/commands` you define (see [docs/CONFIGURATION.md](docs/CONFIGURATION.md
195
195
 
196
196
  `Esc` cancels a running request. `↑/↓` recalls input history. `Ctrl+O` toggles
197
197
  full tool output. `Ctrl+E` opens the file the agent touched most recently in
198
- `$EDITOR`. `Ctrl+K` is the kill switch (see below). `Ctrl+C` exits.
198
+ `$EDITOR`. `Ctrl+P` opens the command palette (fuzzy-find commands and
199
+ checkpoints). `Ctrl+K` is the kill switch (see below). `Ctrl+C` exits.
199
200
 
200
201
  ### More features
201
202
 
package/dist/ui/App.js CHANGED
@@ -17,6 +17,7 @@ import { ModelPicker } from "./ModelPicker.js";
17
17
  import { ElicitationPrompt } from "./ElicitationPrompt.js";
18
18
  import { PermissionPrompt } from "./PermissionPrompt.js";
19
19
  import { SelectList } from "./SelectList.js";
20
+ import { buildPaletteItems } from "./palette.js";
20
21
  import { Spinner } from "./Spinner.js";
21
22
  import { StatusLine } from "./StatusLine.js";
22
23
  import { StreamViewport } from "./StreamViewport.js";
@@ -86,6 +87,7 @@ export function App({ agent, workspace, modelRef, providerRef, config, resumedCo
86
87
  const [inputKey, setInputKey] = useState(0);
87
88
  const [steerInput, setSteerInput] = useState("");
88
89
  const [resumeFilter, setResumeFilter] = useState("");
90
+ const [paletteFilter, setPaletteFilter] = useState("");
89
91
  const [cmdIndex, setCmdIndex] = useState(0);
90
92
  const [fileIndex, setFileIndex] = useState(0);
91
93
  const [fileList, setFileList] = useState([]);
@@ -243,6 +245,19 @@ export function App({ agent, workspace, modelRef, providerRef, config, resumedCo
243
245
  engageKill("Ctrl+K");
244
246
  return;
245
247
  }
248
+ // Ctrl+P opens the command palette — fuzzy-find commands and checkpoints.
249
+ // Idle prompt only, like Ctrl+E: opening it mid-turn would race the agent,
250
+ // and the kill switch above must keep its chord unshared.
251
+ if (key.ctrl && _input === "p") {
252
+ if (phase === "input") {
253
+ setPaletteFilter("");
254
+ setPhase("palette");
255
+ }
256
+ else if (phase === "palette") {
257
+ setPhase("input");
258
+ }
259
+ return;
260
+ }
246
261
  if (key.escape && phase === "working") {
247
262
  setActivity("cancelling…");
248
263
  abortRef.current?.abort();
@@ -315,6 +330,13 @@ export function App({ agent, workspace, modelRef, providerRef, config, resumedCo
315
330
  setResumeFilter((f) => f + _input);
316
331
  }
317
332
  }
333
+ if (phase === "palette") {
334
+ if (key.backspace || key.delete)
335
+ setPaletteFilter((f) => f.slice(0, -1));
336
+ else if (_input && !key.upArrow && !key.downArrow && !key.return && !key.escape && !key.tab) {
337
+ setPaletteFilter((f) => f + _input);
338
+ }
339
+ }
318
340
  if (suggestions.length) {
319
341
  if (key.upArrow)
320
342
  setCmdIndex((i) => (i - 1 + suggestions.length) % suggestions.length);
@@ -385,6 +407,24 @@ export function App({ agent, workspace, modelRef, providerRef, config, resumedCo
385
407
  };
386
408
  void runCommand(cmd, ctx);
387
409
  };
410
+ // Command palette selection. Commands are inserted into the input line —
411
+ // never executed blind: a fuzzy finder that runs on Enter is how
412
+ // conversations get cleared by accident. Checkpoints rewind immediately;
413
+ // the name is complete and unambiguous, exactly like /rewind <name>.
414
+ const onPaletteSelect = (value) => {
415
+ if (value.startsWith("checkpoint:")) {
416
+ const name = value.slice("checkpoint:".length);
417
+ setPhase("input");
418
+ handleSlash(`/rewind ${name}`);
419
+ return;
420
+ }
421
+ if (value.startsWith("cmd:")) {
422
+ setInput(`${value.slice("cmd:".length)} `);
423
+ setInputKey((k) => k + 1);
424
+ setPhase("input");
425
+ }
426
+ };
427
+ const paletteItems = buildPaletteItems(allCommands, agent.listCheckpoints(), paletteFilter);
388
428
  const expandMentions = async (text) => {
389
429
  const mentions = [...new Set([...text.matchAll(MENTION_ALL_RE)].map((m) => m[1]))];
390
430
  let extra = "";
@@ -517,7 +557,7 @@ export function App({ agent, workspace, modelRef, providerRef, config, resumedCo
517
557
  label: s.name || s.title,
518
558
  value: s.file,
519
559
  hint: `${s.shortId} · ${s.date} · ${s.count} msgs`,
520
- })), onSelect: onResumeSelect, onCancel: () => onResumeSelect("") })] })), phase === "input" && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { color: "green", children: "\u276F " }), _jsx(TextInput, { value: input, onChange: (v) => {
560
+ })), onSelect: onResumeSelect, onCancel: () => onResumeSelect("") })] })), phase === "palette" && (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [_jsxs(Text, { bold: true, color: "cyan", children: ["Command palette", " ", _jsx(Text, { dimColor: true, children: "(type to filter \u00B7 \u2191\u2193 navigate \u00B7 Enter selects \u00B7 Esc closes \u00B7 Ctrl+P toggles)" })] }), paletteFilter ? (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "filter: " }), paletteFilter] })) : null, _jsx(SelectList, { items: paletteItems, onSelect: onPaletteSelect, onCancel: () => setPhase("input") })] })), phase === "input" && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { color: "green", children: "\u276F " }), _jsx(TextInput, { value: input, onChange: (v) => {
521
561
  setInput(v.replace(/\t/g, ""));
522
562
  setCmdIndex(0);
523
563
  setFileIndex(0);
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Subsequence match, case-insensitive: "rwd" matches "/rewind", "exmd"
3
+ * matches "/export". Order matters ("mre" does not match "/rename") but
4
+ * contiguity doesn't — this is what makes a palette feel like a palette
5
+ * rather than the `/` prefix completion.
6
+ */
7
+ export function fuzzyMatch(haystack, needle) {
8
+ const h = haystack.toLowerCase();
9
+ const n = needle.trim().toLowerCase();
10
+ if (!n)
11
+ return true;
12
+ let i = 0;
13
+ for (const ch of h) {
14
+ if (ch === n[i])
15
+ i++;
16
+ if (i === n.length)
17
+ return true;
18
+ }
19
+ return false;
20
+ }
21
+ /**
22
+ * The palette's rows: every command first, then every checkpoint. Values are
23
+ * prefixed so the selection handler knows what was picked without a second
24
+ * lookup — `cmd:/rename` inserts the command into the input line (never runs
25
+ * it blind: a fuzzy finder that executes on Enter is how conversations get
26
+ * cleared by accident), `checkpoint:<name>` rewinds to that checkpoint.
27
+ */
28
+ export function buildPaletteItems(commands, checkpoints, filter) {
29
+ const items = [];
30
+ for (const c of commands) {
31
+ if (!fuzzyMatch(`${c.name} ${c.description}`, filter))
32
+ continue;
33
+ items.push({ label: c.name, value: `cmd:${c.name}`, hint: c.description });
34
+ }
35
+ for (const cp of checkpoints) {
36
+ if (!fuzzyMatch(`checkpoint ${cp.name}`, filter))
37
+ continue;
38
+ items.push({
39
+ label: `checkpoint: ${cp.name}`,
40
+ value: `checkpoint:${cp.name}`,
41
+ hint: `rewind · saved ${new Date(cp.createdAt).toLocaleTimeString()}`,
42
+ });
43
+ }
44
+ return items;
45
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kritya",
3
- "version": "0.8.39-beta",
3
+ "version": "0.8.40-beta",
4
4
  "description": "Kritya — Superintelligence at your command line",
5
5
  "type": "module",
6
6
  "publishConfig": {