pi-ask-user 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +35 -4
  2. package/index.ts +156 -8
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -17,6 +17,7 @@ High-quality video: [ask-user-demo.mp4](./media/ask-user-demo.mp4)
17
17
  - User-toggleable extra context on structured selections
18
18
  - Context display support
19
19
  - Configurable display mode: `overlay` (modal, default) or `inline` (rendered directly in the flow)
20
+ - Runtime overlay toggle: press the configured overlay-toggle key (`alt+o` by default, configurable per call or via env var) while the prompt is open to temporarily hide/show the popup so you can read prior agent output, then press it again to bring it back
20
21
  - Pi-TUI-aligned keybinding and editor behavior
21
22
  - Custom TUI rendering for tool calls and results
22
23
  - System prompt integration via `promptSnippet` and `promptGuidelines`
@@ -65,6 +66,8 @@ The registered tool name is:
65
66
  | `allowFreeform` | `boolean?` | `true` | Add a "Type something" freeform option |
66
67
  | `allowComment` | `boolean?` | `false` | Expose a user-toggleable extra-context option in the custom UI (`ctrl+g` or the toggle row) and collect an optional comment in fallback dialogs |
67
68
  | `displayMode` | `"overlay" \| "inline"?` | env var or `"overlay"` | Controls custom UI rendering: `overlay` shows the centered modal (current behavior), `inline` renders without overlay framing |
69
+ | `overlayToggleKey` | `string?` | env var or `"alt+o"` | Shortcut for hiding/showing the overlay popup (overlay mode only). Pi-TUI key spec, e.g. `"alt+o"`, `"ctrl+shift+h"`. Pass `"off"` to disable. |
70
+ | `commentToggleKey` | `string?` | env var or `"ctrl+g"` | Shortcut for toggling the optional comment/extra-context row when `allowComment: true`. Pass `"off"` to disable. |
68
71
  | `timeout` | `number?` | — | Auto-dismiss after N ms and return `null` if the prompt times out |
69
72
 
70
73
  ## Example usage shape
@@ -86,22 +89,50 @@ The registered tool name is:
86
89
 
87
90
  `displayMode: "inline"` uses the same interaction logic but skips overlay mode when calling `ctx.ui.custom(...)`. RPC/headless fallback behavior is unchanged.
88
91
 
89
- ## Personal display mode preference
92
+ ## Personal preferences via environment variables
90
93
 
91
- Set the `PI_ASK_USER_DISPLAY_MODE` environment variable to configure your preferred default globally. Add it to your shell profile (`~/.zshrc`, `~/.bash_profile`, etc.):
94
+ Configure your defaults globally by setting these in your shell profile (`~/.zshrc`, `~/.bash_profile`, etc.):
92
95
 
93
96
  ```bash
94
97
  export PI_ASK_USER_DISPLAY_MODE=inline
98
+ export PI_ASK_USER_OVERLAY_TOGGLE_KEY=alt+h
99
+ export PI_ASK_USER_COMMENT_TOGGLE_KEY=alt+c
95
100
  ```
96
101
 
97
- Effective behavior order:
102
+ ### Display mode
103
+
104
+ Effective order:
98
105
 
99
106
  1. Per-call `displayMode` parameter (if provided)
100
- 2. `PI_ASK_USER_DISPLAY_MODE` environment variable (if set to `"overlay"` or `"inline"`)
107
+ 2. `PI_ASK_USER_DISPLAY_MODE` (if set to `"overlay"` or `"inline"`)
101
108
  3. Fallback default: `"overlay"`
102
109
 
103
110
  Unrecognised values are silently ignored and fall back to `"overlay"`.
104
111
 
112
+ ### Shortcuts
113
+
114
+ Effective order for both `overlayToggleKey` and `commentToggleKey`:
115
+
116
+ 1. Per-call parameter (if provided)
117
+ 2. Matching env var (`PI_ASK_USER_OVERLAY_TOGGLE_KEY` / `PI_ASK_USER_COMMENT_TOGGLE_KEY`)
118
+ 3. Built-in defaults: `alt+o` and `ctrl+g`
119
+
120
+ Pass `"off"`, `"none"`, or `"disabled"` (at any level) to disable the shortcut entirely. Invalid specs are silently dropped and the next source is used. Specs follow the Pi-TUI [`KeyId`](https://github.com/badlogic/pi-mono/blob/main/packages/tui/src/keys.ts) format: `[mod+]...key` where modifiers are `ctrl`, `shift`, `alt`, `super`, in any order, joined by `+` (e.g. `ctrl+g`, `alt+shift+x`, `escape`, `tab`).
121
+
122
+ ## Controls
123
+
124
+ While an `ask_user` prompt is open:
125
+
126
+ | Key | Action |
127
+ |-----|--------|
128
+ | `alt+o` (configurable via `overlayToggleKey`) | Hide/show the overlay popup so you can read the agent's prior output. Available in `overlay` mode only. The first time you hide it, a notification reminds you which key brings it back. |
129
+ | `ctrl+g` (configurable via `commentToggleKey`) | Toggle the optional comment/extra-context row (when `allowComment: true`). |
130
+ | `enter` | Confirm the focused option, submit a freeform response, or submit/skip an optional comment. |
131
+ | `esc` | Clear the search filter, exit freeform/comment mode, or cancel the prompt. |
132
+ | `↑` / `↓` | Navigate options. |
133
+
134
+ If you prefer never to see the overlay, set `displayMode: "inline"` per call or `PI_ASK_USER_DISPLAY_MODE=inline` globally.
135
+
105
136
  ## Result details
106
137
 
107
138
  All tool results include a structured `details` object for rendering and session state reconstruction:
package/index.ts CHANGED
@@ -21,6 +21,7 @@ import {
21
21
  Markdown,
22
22
  type MarkdownTheme,
23
23
  matchesKey,
24
+ type OverlayHandle,
24
25
  Spacer,
25
26
  Text,
26
27
  type TUI,
@@ -63,6 +64,8 @@ interface AskParams {
63
64
  allowFreeform?: boolean;
64
65
  allowComment?: boolean;
65
66
  displayMode?: AskDisplayMode;
67
+ overlayToggleKey?: string | null;
68
+ commentToggleKey?: string | null;
66
69
  timeout?: number;
67
70
  }
68
71
 
@@ -244,8 +247,63 @@ function literalHint(theme: Theme, key: string, description: string): string {
244
247
  return `${theme.fg("dim", key)}${theme.fg("muted", ` ${description}`)}`;
245
248
  }
246
249
 
247
- function isCommentToggleKey(data: string): boolean {
248
- return matchesKey(data, Key.ctrl("g"));
250
+ type ResolvedShortcut =
251
+ | { disabled: false; spec: string; matches: (data: string) => boolean }
252
+ | { disabled: true; spec: null; matches: (data: string) => false };
253
+
254
+ interface ResolvedAskShortcuts {
255
+ overlayToggle: ResolvedShortcut;
256
+ commentToggle: ResolvedShortcut;
257
+ }
258
+
259
+ const DISABLED_SHORTCUT: ResolvedShortcut = {
260
+ disabled: true,
261
+ spec: null,
262
+ matches: ((_data: string) => false) as (data: string) => false,
263
+ };
264
+
265
+ const SHORTCUT_DISABLE_VALUES = new Set(["off", "none", "disabled", ""]);
266
+
267
+ function normalizeShortcutSpec(value: string | null | undefined): string | null | undefined {
268
+ if (value === undefined) return undefined;
269
+ if (value === null) return null;
270
+ const trimmed = value.trim().toLowerCase();
271
+ if (SHORTCUT_DISABLE_VALUES.has(trimmed)) return null;
272
+ return trimmed;
273
+ }
274
+
275
+ function isValidShortcutSpec(spec: string): boolean {
276
+ // KeyId is canonical lowercase: modifiers (`ctrl|shift|alt|super`) joined by `+`,
277
+ // plus a base key. We do a light syntactic sanity check; matchesKey() does the rest.
278
+ if (!spec) return false;
279
+ if (!/^[a-z0-9+_\-!@#$%^&*()|~`'":;,./<>?[\]{}=\\]+$/i.test(spec)) return false;
280
+ if (spec.startsWith("+") || spec.endsWith("+")) return false;
281
+ if (spec.includes("++")) return false;
282
+ return true;
283
+ }
284
+
285
+ function buildShortcut(spec: string): ResolvedShortcut {
286
+ return {
287
+ disabled: false,
288
+ spec,
289
+ matches: (data: string) => matchesKey(data, spec as any),
290
+ };
291
+ }
292
+
293
+ function resolveShortcut(
294
+ paramValue: string | null | undefined,
295
+ envValue: string | undefined,
296
+ defaultSpec: string,
297
+ ): ResolvedShortcut {
298
+ const candidates: Array<string | null | undefined> = [paramValue, envValue, defaultSpec];
299
+ for (const raw of candidates) {
300
+ const normalized = normalizeShortcutSpec(raw);
301
+ if (normalized === undefined) continue; // not provided, fall through
302
+ if (normalized === null) return DISABLED_SHORTCUT; // explicit disable
303
+ if (isValidShortcutSpec(normalized)) return buildShortcut(normalized);
304
+ // Invalid spec: silently fall through to next candidate.
305
+ }
306
+ return DISABLED_SHORTCUT;
249
307
  }
250
308
 
251
309
  type AskMode = "select" | "freeform" | "comment";
@@ -259,8 +317,13 @@ const SINGLE_SELECT_SPLIT_PANE_RIGHT_MIN_WIDTH = 28;
259
317
  const SINGLE_SELECT_SPLIT_PANE_SEPARATOR = " │ ";
260
318
  const FREEFORM_SENTINEL = "\u270f\ufe0f Type custom response...";
261
319
  const COMMENT_TOGGLE_LABEL = "Add extra context after selection";
320
+ const DEFAULT_OVERLAY_TOGGLE_KEY = "alt+o";
321
+ const DEFAULT_COMMENT_TOGGLE_KEY = "ctrl+g";
262
322
 
263
- function buildCustomUIOptions(displayMode: AskDisplayMode) {
323
+ function buildCustomUIOptions(
324
+ displayMode: AskDisplayMode,
325
+ onHandle?: (handle: OverlayHandle) => void,
326
+ ) {
264
327
  switch (displayMode) {
265
328
  case "inline":
266
329
  return undefined;
@@ -274,6 +337,7 @@ function buildCustomUIOptions(displayMode: AskDisplayMode) {
274
337
  maxHeight: "85%",
275
338
  margin: 1,
276
339
  },
340
+ ...(onHandle ? { onHandle } : {}),
277
341
  };
278
342
  default: {
279
343
  const _exhaustive: never = displayMode;
@@ -287,6 +351,7 @@ function buildCustomUIOptions(displayMode: AskDisplayMode) {
287
351
  maxHeight: "85%",
288
352
  margin: 1,
289
353
  },
354
+ ...(onHandle ? { onHandle } : {}),
290
355
  };
291
356
  }
292
357
  }
@@ -298,6 +363,7 @@ class MultiSelectList implements Component {
298
363
  private allowComment: boolean;
299
364
  private theme: Theme;
300
365
  private keybindings: KeybindingsManager;
366
+ private commentToggle: ResolvedShortcut;
301
367
  private selectedIndex = 0;
302
368
  private checked = new Set<number>();
303
369
  private commentEnabled = false;
@@ -314,12 +380,14 @@ class MultiSelectList implements Component {
314
380
  allowComment: boolean,
315
381
  theme: Theme,
316
382
  keybindings: KeybindingsManager,
383
+ commentToggle: ResolvedShortcut,
317
384
  ) {
318
385
  this.options = options;
319
386
  this.allowFreeform = allowFreeform;
320
387
  this.allowComment = allowComment;
321
388
  this.theme = theme;
322
389
  this.keybindings = keybindings;
390
+ this.commentToggle = commentToggle;
323
391
  }
324
392
 
325
393
  public isCommentEnabled(): boolean {
@@ -376,7 +444,7 @@ class MultiSelectList implements Component {
376
444
  return;
377
445
  }
378
446
 
379
- if (this.allowComment && isCommentToggleKey(data)) {
447
+ if (this.allowComment && !this.commentToggle.disabled && this.commentToggle.matches(data)) {
380
448
  this.toggleComment();
381
449
  return;
382
450
  }
@@ -520,6 +588,7 @@ class WrappedSingleSelectList implements Component {
520
588
  private allowComment: boolean;
521
589
  private theme: Theme;
522
590
  private keybindings: KeybindingsManager;
591
+ private commentToggle: ResolvedShortcut;
523
592
  private selectedIndex = 0;
524
593
  private searchQuery = "";
525
594
  private commentEnabled = false;
@@ -537,12 +606,14 @@ class WrappedSingleSelectList implements Component {
537
606
  allowComment: boolean,
538
607
  theme: Theme,
539
608
  keybindings: KeybindingsManager,
609
+ commentToggle: ResolvedShortcut,
540
610
  ) {
541
611
  this.options = options;
542
612
  this.allowFreeform = allowFreeform;
543
613
  this.allowComment = allowComment;
544
614
  this.theme = theme;
545
615
  this.keybindings = keybindings;
616
+ this.commentToggle = commentToggle;
546
617
  }
547
618
 
548
619
  public isCommentEnabled(): boolean {
@@ -763,7 +834,7 @@ class WrappedSingleSelectList implements Component {
763
834
  return;
764
835
  }
765
836
 
766
- if (this.allowComment && isCommentToggleKey(data)) {
837
+ if (this.allowComment && !this.commentToggle.disabled && this.commentToggle.matches(data)) {
767
838
  this.toggleComment();
768
839
  return;
769
840
  }
@@ -868,9 +939,11 @@ class AskComponent extends Container {
868
939
  private allowMultiple: boolean;
869
940
  private allowFreeform: boolean;
870
941
  private allowComment: boolean;
942
+ private displayMode: AskDisplayMode;
871
943
  private tui: TUI;
872
944
  private theme: Theme;
873
945
  private keybindings: KeybindingsManager;
946
+ private shortcuts: ResolvedAskShortcuts;
874
947
  private onDone: (result: AskUIResult | null) => void;
875
948
 
876
949
  private mode: AskMode = "select";
@@ -909,9 +982,11 @@ class AskComponent extends Container {
909
982
  allowMultiple: boolean,
910
983
  allowFreeform: boolean,
911
984
  allowComment: boolean,
985
+ displayMode: AskDisplayMode,
912
986
  tui: TUI,
913
987
  theme: Theme,
914
988
  keybindings: KeybindingsManager,
989
+ shortcuts: ResolvedAskShortcuts,
915
990
  onDone: (result: AskUIResult | null) => void,
916
991
  ) {
917
992
  super();
@@ -922,9 +997,11 @@ class AskComponent extends Container {
922
997
  this.allowMultiple = allowMultiple;
923
998
  this.allowFreeform = allowFreeform;
924
999
  this.allowComment = allowComment;
1000
+ this.displayMode = displayMode;
925
1001
  this.tui = tui;
926
1002
  this.theme = theme;
927
1003
  this.keybindings = keybindings;
1004
+ this.shortcuts = shortcuts;
928
1005
  this.onDone = onDone;
929
1006
 
930
1007
  // Layout skeleton
@@ -1044,6 +1121,12 @@ class AskComponent extends Container {
1044
1121
 
1045
1122
  private updateHelpText(): void {
1046
1123
  const theme = this.theme;
1124
+ const overlayHint = this.displayMode === "overlay" && !this.shortcuts.overlayToggle.disabled
1125
+ ? literalHint(theme, this.shortcuts.overlayToggle.spec, "hide")
1126
+ : null;
1127
+ const commentHint = this.allowComment && !this.shortcuts.commentToggle.disabled
1128
+ ? literalHint(theme, this.shortcuts.commentToggle.spec, "toggle context")
1129
+ : null;
1047
1130
  if (this.mode === "freeform" || this.mode === "comment") {
1048
1131
  const alternateCancelKeys = this.keybindings
1049
1132
  .getKeys("tui.select.cancel")
@@ -1052,6 +1135,7 @@ class AskComponent extends Container {
1052
1135
  keybindingHint(theme, this.keybindings, "tui.input.submit", this.mode === "comment" ? "submit/skip" : "submit"),
1053
1136
  keybindingHint(theme, this.keybindings, "tui.input.newLine", "newline"),
1054
1137
  literalHint(theme, "esc", "back"),
1138
+ overlayHint,
1055
1139
  alternateCancelKeys.length > 0 ? literalHint(theme, formatKeyList(alternateCancelKeys), "cancel") : null,
1056
1140
  ]
1057
1141
  .filter((hint): hint is string => !!hint)
@@ -1064,7 +1148,8 @@ class AskComponent extends Container {
1064
1148
  const hints = [
1065
1149
  literalHint(theme, "↑↓", "navigate"),
1066
1150
  literalHint(theme, "space", "toggle"),
1067
- this.allowComment ? literalHint(theme, "ctrl+g", "toggle context") : null,
1151
+ commentHint,
1152
+ overlayHint,
1068
1153
  keybindingHint(theme, this.keybindings, "tui.select.confirm", "submit"),
1069
1154
  keybindingHint(theme, this.keybindings, "tui.select.cancel", "cancel"),
1070
1155
  ]
@@ -1079,7 +1164,8 @@ class AskComponent extends Container {
1079
1164
  literalHint(theme, "type", "filter"),
1080
1165
  keybindingHint(theme, this.keybindings, "tui.editor.deleteCharBackward", "erase"),
1081
1166
  literalHint(theme, "↑↓", "navigate"),
1082
- this.allowComment ? literalHint(theme, "ctrl+g", "toggle context") : null,
1167
+ commentHint,
1168
+ overlayHint,
1083
1169
  keybindingHint(theme, this.keybindings, "tui.select.confirm", "select"),
1084
1170
  literalHint(theme, "esc", "clear/cancel"),
1085
1171
  alternateCancelKeys.length > 0
@@ -1101,6 +1187,7 @@ class AskComponent extends Container {
1101
1187
  this.allowComment,
1102
1188
  this.theme,
1103
1189
  this.keybindings,
1190
+ this.shortcuts.commentToggle,
1104
1191
  );
1105
1192
  list.onSubmit = (result) => this.handleSelectionSubmit([result], list.isCommentEnabled());
1106
1193
  list.onCancel = () => this.onDone(null);
@@ -1119,6 +1206,7 @@ class AskComponent extends Container {
1119
1206
  this.allowComment,
1120
1207
  this.theme,
1121
1208
  this.keybindings,
1209
+ this.shortcuts.commentToggle,
1122
1210
  );
1123
1211
  list.onCancel = () => this.onDone(null);
1124
1212
  list.onSubmit = (result) => this.handleSelectionSubmit(result, list.isCommentEnabled());
@@ -1389,6 +1477,18 @@ export default function(pi: ExtensionAPI) {
1389
1477
  description: "UI rendering mode. 'overlay' shows a centered modal, 'inline' renders in-place. Default: PI_ASK_USER_DISPLAY_MODE env var if set, otherwise 'overlay'. Omit to respect the user's configured preference.",
1390
1478
  }),
1391
1479
  ),
1480
+ overlayToggleKey: Type.Optional(
1481
+ Type.String({
1482
+ description:
1483
+ "Shortcut for hiding/showing the overlay popup (overlay mode only), e.g. 'alt+o' or 'ctrl+shift+h'. Pass 'off' to disable. Default: PI_ASK_USER_OVERLAY_TOGGLE_KEY env var if set, otherwise 'alt+o'.",
1484
+ }),
1485
+ ),
1486
+ commentToggleKey: Type.Optional(
1487
+ Type.String({
1488
+ description:
1489
+ "Shortcut for toggling the optional comment/extra-context row when allowComment is true, e.g. 'ctrl+g'. Pass 'off' to disable. Default: PI_ASK_USER_COMMENT_TOGGLE_KEY env var if set, otherwise 'ctrl+g'.",
1490
+ }),
1491
+ ),
1392
1492
  timeout: Type.Optional(
1393
1493
  Type.Number({ description: "Auto-dismiss after N milliseconds. Returns null (cancelled) when expired." }),
1394
1494
  ),
@@ -1410,12 +1510,26 @@ export default function(pi: ExtensionAPI) {
1410
1510
  allowFreeform = true,
1411
1511
  allowComment = false,
1412
1512
  displayMode,
1513
+ overlayToggleKey,
1514
+ commentToggleKey,
1413
1515
  timeout,
1414
1516
  } = params as AskParams;
1415
1517
  const envMode = process.env.PI_ASK_USER_DISPLAY_MODE;
1416
1518
  const envDisplayMode: AskDisplayMode | undefined =
1417
1519
  envMode === "overlay" || envMode === "inline" ? envMode : undefined;
1418
1520
  const effectiveDisplayMode: AskDisplayMode = displayMode ?? envDisplayMode ?? "overlay";
1521
+ const shortcuts: ResolvedAskShortcuts = {
1522
+ overlayToggle: resolveShortcut(
1523
+ overlayToggleKey,
1524
+ process.env.PI_ASK_USER_OVERLAY_TOGGLE_KEY,
1525
+ DEFAULT_OVERLAY_TOGGLE_KEY,
1526
+ ),
1527
+ commentToggle: resolveShortcut(
1528
+ commentToggleKey,
1529
+ process.env.PI_ASK_USER_COMMENT_TOGGLE_KEY,
1530
+ DEFAULT_COMMENT_TOGGLE_KEY,
1531
+ ),
1532
+ };
1419
1533
  const options = normalizeOptions(rawOptions);
1420
1534
  const normalizedContext = context?.trim() || undefined;
1421
1535
 
@@ -1461,6 +1575,9 @@ export default function(pi: ExtensionAPI) {
1461
1575
  });
1462
1576
 
1463
1577
  let result: AskUIResult | null;
1578
+ let overlayHandle: OverlayHandle | undefined;
1579
+ let removeOverlayInputListener: (() => void) | undefined;
1580
+ let hasAnnouncedHide = false;
1464
1581
  try {
1465
1582
  const customFactory = (tui: TUI, theme: Theme, keybindings: KeybindingsManager, done: (result: AskUIResult | null) => void) => {
1466
1583
  if (signal) {
@@ -1479,14 +1596,43 @@ export default function(pi: ExtensionAPI) {
1479
1596
  allowMultiple,
1480
1597
  allowFreeform,
1481
1598
  allowComment,
1599
+ effectiveDisplayMode,
1482
1600
  tui,
1483
1601
  theme,
1484
1602
  keybindings,
1603
+ shortcuts,
1485
1604
  done,
1486
1605
  );
1487
1606
  };
1488
1607
 
1489
- const customResult = await ctx.ui.custom<AskUIResult | null>(customFactory, buildCustomUIOptions(effectiveDisplayMode));
1608
+ // Register a raw terminal input listener for the overlay-toggle key so the
1609
+ // overlay can be toggled even while it is hidden (hidden overlays do not
1610
+ // receive input). Inline mode does not need this because the prompt is
1611
+ // already non-modal. Skipped entirely if the user disabled the shortcut.
1612
+ const overlayToggle = shortcuts.overlayToggle;
1613
+ if (
1614
+ effectiveDisplayMode === "overlay"
1615
+ && !overlayToggle.disabled
1616
+ && typeof ctx.ui.onTerminalInput === "function"
1617
+ ) {
1618
+ removeOverlayInputListener = ctx.ui.onTerminalInput((data) => {
1619
+ if (!overlayToggle.matches(data) || !overlayHandle) return undefined;
1620
+ const nextHidden = !overlayHandle.isHidden();
1621
+ overlayHandle.setHidden(nextHidden);
1622
+ if (nextHidden && !hasAnnouncedHide) {
1623
+ hasAnnouncedHide = true;
1624
+ ctx.ui.notify?.(`ask_user hidden — press ${overlayToggle.spec} to reopen`, "info");
1625
+ }
1626
+ return { consume: true };
1627
+ });
1628
+ }
1629
+
1630
+ const customResult = await ctx.ui.custom<AskUIResult | null>(
1631
+ customFactory,
1632
+ buildCustomUIOptions(effectiveDisplayMode, (handle) => {
1633
+ overlayHandle = handle;
1634
+ }),
1635
+ );
1490
1636
 
1491
1637
  if (customResult !== undefined) {
1492
1638
  result = customResult;
@@ -1502,6 +1648,8 @@ export default function(pi: ExtensionAPI) {
1502
1648
  isError: true,
1503
1649
  details: { error: message },
1504
1650
  };
1651
+ } finally {
1652
+ removeOverlayInputListener?.();
1505
1653
  }
1506
1654
 
1507
1655
  if (result === null) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ask-user",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
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": [