@xynogen/pix-ask 0.2.2 → 0.2.5

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
@@ -4,7 +4,7 @@ Pi tool — structured questionnaire UI (`ask_user`).
4
4
 
5
5
  ## What it does
6
6
 
7
- Registers the `ask_user` tool in Pi. When the agent needs to resolve ambiguous requirements, it presents up to 4 structured multiple-choice questions in a TUI dialog. Each question requires 2–4 options with labels and descriptions; supports multi-select and markdown side-by-side previews for richer context. Single-select questions auto-append a "Type something." row for free-form input and always include "Chat about this" as an escape hatch. In non-interactive (RPC/JSON) mode, falls back to text-based prompts. The agent uses this tool before proceeding rather than guessing at intent.
7
+ Registers the `ask_user` tool in Pi. When the agent needs to resolve ambiguous requirements, it presents up to 4 structured multiple-choice questions in a TUI dialog. Each question requires 2–4 options with labels and descriptions; supports multi-select and markdown side-by-side previews for richer context. Single-select questions (without a preview) auto-append a "Type something." row for free-form input; multi-select questions append a "Next" row to advance. Single-select questions with a `preview` skip the free-form row to make room for the side-by-side layout. In non-interactive (RPC/JSON) mode, falls back to text-based prompts. The agent uses this tool before proceeding rather than guessing at intent.
8
8
 
9
9
  ## Install
10
10
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xynogen/pix-ask",
3
- "version": "0.2.2",
4
- "description": "Pi tool \u2014 structured questionnaire UI (ask_user)",
3
+ "version": "0.2.5",
4
+ "description": "Pi tool structured questionnaire UI (ask_user)",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
7
7
  "scripts": {
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "typebox": "^1.1.38",
38
- "@xynogen/pix-pretty": "*"
38
+ "@xynogen/pix-pretty": "^1.7.9"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "@earendil-works/pi-coding-agent": "*",
package/src/once.ts CHANGED
@@ -14,7 +14,8 @@
14
14
  */
15
15
  export function once(pi: object, key: string, fn: () => void): void {
16
16
  const g = globalThis as { __pixOnce?: WeakMap<object, Set<string>> };
17
- const registry = (g.__pixOnce ??= new WeakMap<object, Set<string>>());
17
+ if (!g.__pixOnce) g.__pixOnce = new WeakMap<object, Set<string>>();
18
+ const registry = g.__pixOnce;
18
19
  let loaded = registry.get(pi);
19
20
  if (!loaded) {
20
21
  loaded = new Set<string>();
@@ -65,7 +65,9 @@ export class AskQuestionnaire extends Container {
65
65
  // ── Accessors ──────────────────────────────────────────────────────
66
66
 
67
67
  private get currentQ(): QuestionData {
68
- return this.params.questions[this.currentIndex]!;
68
+ const q = this.params.questions[this.currentIndex];
69
+ if (!q) throw new Error("currentIndex out of bounds");
70
+ return q;
69
71
  }
70
72
 
71
73
  private get filteredOptions(): OptionData[] {
@@ -209,7 +211,7 @@ export class AskQuestionnaire extends Container {
209
211
  const q = this.currentQ;
210
212
  if (prev.kind === "multi") {
211
213
  for (let i = 0; i < q.options.length; i++) {
212
- if (prev.selected?.includes(q.options[i]!.label)) {
214
+ if (prev.selected?.includes(q.options[i]?.label ?? "")) {
213
215
  this.multiChecked.add(i);
214
216
  }
215
217
  }
@@ -354,7 +356,8 @@ export class AskQuestionnaire extends Container {
354
356
  this.selectedOptionIndex = Math.min(idx, this.totalItems - 1);
355
357
  this.refresh();
356
358
  } else {
357
- const opt = this.filteredOptions[idx]!;
359
+ const opt = this.filteredOptions[idx];
360
+ if (!opt) return;
358
361
  this.recordAnswer("option", opt.label, undefined, opt.preview);
359
362
  this.nextQuestion();
360
363
  }
@@ -434,7 +437,8 @@ export class AskQuestionnaire extends Container {
434
437
  const pad = " ".repeat(LABEL_COL);
435
438
 
436
439
  for (let i = start; i < end; i++) {
437
- const item = items[i]!;
440
+ const item = items[i];
441
+ if (!item) continue;
438
442
  const sel = i === this.selectedOptionIndex;
439
443
  const ptr = sel ? t.fg("accent", "→") : " ";
440
444
 
package/src/rpc.ts CHANGED
@@ -6,14 +6,18 @@ import type { QuestionAnswer, QuestionnaireResult } from "./types.js";
6
6
  // Used when ctx.hasUI is false (headless / JSON / print mode).
7
7
 
8
8
  export async function rpcFallback(
9
- ui: { select: Function; input: Function },
9
+ ui: {
10
+ select(title: string, options: string[]): Promise<string | undefined>;
11
+ input(title: string, placeholder?: string): Promise<string | undefined>;
12
+ },
10
13
  params: Params,
11
14
  ): Promise<QuestionnaireResult> {
12
15
  const answers: QuestionAnswer[] = [];
13
16
  let cancelled = false;
14
17
 
15
18
  for (let i = 0; i < params.questions.length; i++) {
16
- const q = params.questions[i]!;
19
+ const q = params.questions[i];
20
+ if (!q) break;
17
21
  const header = q.header;
18
22
 
19
23
  if (q.multiSelect) {
@@ -69,7 +73,7 @@ export async function rpcFallback(
69
73
  const opt = q.options.find(
70
74
  (o) =>
71
75
  chosen === o.label || `${o.label} — ${o.description}` === chosen,
72
- )!;
76
+ );
73
77
  answers.push({
74
78
  questionIndex: i,
75
79
  question: q.question,