pi-ask-user 0.10.0 → 0.11.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 (3) hide show
  1. package/README.md +1 -1
  2. package/index.ts +57 -12
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -129,7 +129,7 @@ While an `ask_user` prompt is open:
129
129
  | `ctrl+g` (configurable via `commentToggleKey`) | Toggle the optional comment/extra-context row (when `allowComment: true`). |
130
130
  | `enter` | Confirm the focused option, submit a freeform response, or submit/skip an optional comment. |
131
131
  | `esc` | Clear the search filter, exit freeform/comment mode, or cancel the prompt. |
132
- | `↑` / `↓` | Navigate options. |
132
+ | `↑` / `↓`, `ctrl+k` / `ctrl+j` | Navigate options. `ctrl+k` / `ctrl+j` (vim-style) work while typing in searchable prompts without disturbing the filter. |
133
133
 
134
134
  If you prefer never to see the overlay, set `displayMode: "inline"` per call or `PI_ASK_USER_DISPLAY_MODE=inline` globally.
135
135
 
package/index.ts CHANGED
@@ -52,6 +52,36 @@ function StringEnum<const T extends readonly string[]>(
52
52
  });
53
53
  }
54
54
 
55
+ /**
56
+ * `getMarkdownTheme()` returns a bag of closures that read through a Proxy
57
+ * over the host's theme singleton. The Proxy only throws on property access,
58
+ * not when the bag itself is constructed — so a naive
59
+ * `try { getMarkdownTheme() } catch {}` silently lets a broken bag escape
60
+ * and crashes mid-render the first time pi-tui's Markdown calls
61
+ * `mdTheme.bold(...)`.
62
+ *
63
+ * That broken-bag scenario shows up whenever this extension's bundled copy
64
+ * of `@earendil-works/pi-coding-agent` is a different module instance than
65
+ * the host's — e.g. an older Pi still on the legacy
66
+ * `@mariozechner/pi-coding-agent` scope (≤ 0.73.1) where npm cannot dedupe
67
+ * across scopes, so our copy's theme singleton is never initialised
68
+ * (`globalThis[Symbol.for("@earendil-works/pi-coding-agent:theme")]` is
69
+ * undefined). See https://github.com/edlsh/pi-ask-user/issues/17.
70
+ *
71
+ * Probe `bold("")` to force the Proxy lookup eagerly; on throw, callers
72
+ * fall back to plain `Text` rendering for context blocks.
73
+ */
74
+ function safeMarkdownTheme(): MarkdownTheme | undefined {
75
+ try {
76
+ const md = getMarkdownTheme();
77
+ if (!md) return undefined;
78
+ md.bold("");
79
+ return md;
80
+ } catch {
81
+ return undefined;
82
+ }
83
+ }
84
+
55
85
  type AskOptionInput = QuestionOption | string;
56
86
 
57
87
  type AskDisplayMode = "overlay" | "inline";
@@ -320,6 +350,27 @@ const COMMENT_TOGGLE_LABEL = "Add extra context after selection";
320
350
  const DEFAULT_OVERLAY_TOGGLE_KEY = "alt+o";
321
351
  const DEFAULT_COMMENT_TOGGLE_KEY = "ctrl+g";
322
352
 
353
+ // Vim-style aliases for navigating option lists. ctrl+j/k are safe in the
354
+ // searchable single-select because they don't collide with fuzzy-search input.
355
+ const VIM_SELECT_UP_KEY = Key.ctrl("k");
356
+ const VIM_SELECT_DOWN_KEY = Key.ctrl("j");
357
+
358
+ function matchesSelectUp(data: string, keybindings: KeybindingsManager): boolean {
359
+ return (
360
+ keybindings.matches(data, "tui.select.up") ||
361
+ matchesKey(data, Key.shift("tab")) ||
362
+ matchesKey(data, VIM_SELECT_UP_KEY)
363
+ );
364
+ }
365
+
366
+ function matchesSelectDown(data: string, keybindings: KeybindingsManager): boolean {
367
+ return (
368
+ keybindings.matches(data, "tui.select.down") ||
369
+ matchesKey(data, Key.tab) ||
370
+ matchesKey(data, VIM_SELECT_DOWN_KEY)
371
+ );
372
+ }
373
+
323
374
  function buildCustomUIOptions(
324
375
  displayMode: AskDisplayMode,
325
376
  onHandle?: (handle: OverlayHandle) => void,
@@ -449,13 +500,13 @@ class MultiSelectList implements Component {
449
500
  return;
450
501
  }
451
502
 
452
- if (this.keybindings.matches(data, "tui.select.up") || matchesKey(data, Key.shift("tab"))) {
503
+ if (matchesSelectUp(data, this.keybindings)) {
453
504
  this.selectedIndex = this.selectedIndex === 0 ? count - 1 : this.selectedIndex - 1;
454
505
  this.invalidate();
455
506
  return;
456
507
  }
457
508
 
458
- if (this.keybindings.matches(data, "tui.select.down") || matchesKey(data, Key.tab)) {
509
+ if (matchesSelectDown(data, this.keybindings)) {
459
510
  this.selectedIndex = this.selectedIndex === count - 1 ? 0 : this.selectedIndex + 1;
460
511
  this.invalidate();
461
512
  return;
@@ -764,10 +815,7 @@ class WrappedSingleSelectList implements Component {
764
815
  private buildPreviewLines(width: number, filteredOptions: QuestionOption[], maxLines: number): string[] {
765
816
  if (maxLines <= 0) return [];
766
817
 
767
- let mdTheme: MarkdownTheme | undefined;
768
- try {
769
- mdTheme = getMarkdownTheme();
770
- } catch { }
818
+ const mdTheme = safeMarkdownTheme();
771
819
 
772
820
  let md = "";
773
821
 
@@ -842,13 +890,13 @@ class WrappedSingleSelectList implements Component {
842
890
  const filteredOptions = this.getFilteredOptions();
843
891
  const count = this.getItemCount(filteredOptions);
844
892
 
845
- if ((this.keybindings.matches(data, "tui.select.up") || matchesKey(data, Key.shift("tab"))) && count > 0) {
893
+ if (matchesSelectUp(data, this.keybindings) && count > 0) {
846
894
  this.selectedIndex = this.selectedIndex === 0 ? count - 1 : this.selectedIndex - 1;
847
895
  this.invalidate();
848
896
  return;
849
897
  }
850
898
 
851
- if ((this.keybindings.matches(data, "tui.select.down") || matchesKey(data, Key.tab)) && count > 0) {
899
+ if (matchesSelectDown(data, this.keybindings) && count > 0) {
852
900
  this.selectedIndex = this.selectedIndex === count - 1 ? 0 : this.selectedIndex + 1;
853
901
  this.invalidate();
854
902
  return;
@@ -1021,10 +1069,7 @@ class AskComponent extends Container {
1021
1069
 
1022
1070
  if (this.context) {
1023
1071
  this.addChild(new Spacer(1));
1024
- let mdTheme: MarkdownTheme | undefined;
1025
- try {
1026
- mdTheme = getMarkdownTheme();
1027
- } catch { }
1072
+ const mdTheme = safeMarkdownTheme();
1028
1073
  if (mdTheme) {
1029
1074
  this.contextComponent = new Markdown("", 1, 0, mdTheme);
1030
1075
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ask-user",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
4
4
  "description": "Interactive ask_user tool for pi-coding-agent with searchable split-pane selection UI, multi-select, and freeform input",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -44,8 +44,8 @@
44
44
  "check": "npm pack --dry-run"
45
45
  },
46
46
  "peerDependencies": {
47
- "@earendil-works/pi-coding-agent": "*",
48
- "@earendil-works/pi-tui": "*",
47
+ "@earendil-works/pi-coding-agent": ">=0.74.0",
48
+ "@earendil-works/pi-tui": ">=0.74.0",
49
49
  "@sinclair/typebox": "*"
50
50
  }
51
51
  }