@alexleekt/pi-ask-user-glimpse 0.2.1 → 0.3.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.
@@ -2,168 +2,190 @@ import type { ExtensionUIContext } from "@earendil-works/pi-coding-agent";
2
2
  import type { AskUserPayload, Question } from "../shared/ask-user.js";
3
3
 
4
4
  export async function terminalPrompt(
5
- payload: AskUserPayload,
6
- ui: ExtensionUIContext | undefined,
5
+ payload: AskUserPayload,
6
+ ui: ExtensionUIContext | undefined,
7
7
  ): Promise<Record<string, unknown> | null> {
8
- if (!ui) {
9
- return null;
10
- }
11
-
12
- // Questionnaire mode: structured questions with per-question options
13
- if (payload.questions && payload.questions.length > 0) {
14
- return questionnaireFallback(payload.questions, payload.allowComment, ui, payload.context);
15
- }
16
-
17
- // Legacy flat options mode
18
- return flatOptionsFallback(payload, ui);
8
+ if (!ui) {
9
+ return null;
10
+ }
11
+
12
+ // Questionnaire mode: structured questions with per-question options
13
+ if (payload.questions && payload.questions.length > 0) {
14
+ return questionnaireFallback(
15
+ payload.questions,
16
+ payload.allowComment,
17
+ ui,
18
+ payload.context,
19
+ );
20
+ }
21
+
22
+ // Legacy flat options mode
23
+ return flatOptionsFallback(payload, ui);
19
24
  }
20
25
 
21
26
  type QuestionnaireAnswer = {
22
- question: string;
23
- answer: string;
24
- kind: "selection" | "freeform";
25
- comment?: string;
27
+ question: string;
28
+ answer: string;
29
+ kind: "selection" | "freeform";
30
+ comment?: string;
26
31
  };
27
32
 
28
33
  async function questionnaireFallback(
29
- questions: Question[],
30
- allowComment: boolean,
31
- ui: ExtensionUIContext,
32
- context?: string,
34
+ questions: Question[],
35
+ allowComment: boolean,
36
+ ui: ExtensionUIContext,
37
+ context?: string,
33
38
  ): Promise<Record<string, unknown> | null> {
34
- const answers: QuestionnaireAnswer[] = [];
35
-
36
- for (const q of questions) {
37
- const prompt = context
38
- ? `${q.title}\n\nContext: ${context}`
39
- : q.title;
40
- let answer: string | undefined;
41
-
42
- if (q.options && q.options.length > 0) {
43
- const labels = q.options.map((opt, i) => `${i + 1}. ${opt.title}`);
44
-
45
- if (q.allowMultiple) {
46
- const selections: string[] = [];
47
- while (true) {
48
- const remaining = labels.filter((_, i) => !selections.includes(q.options![i].title));
49
- if (remaining.length === 0) break;
50
-
51
- const choice = await ui.select(
52
- `${prompt}\nSelected: ${selections.join(", ") || "none"}\nChoose one (or cancel to finish)`,
53
- remaining,
54
- );
55
- if (choice === undefined) break;
56
-
57
- const idx = labels.indexOf(choice);
58
- const title = q.options[idx]?.title;
59
- if (title && !selections.includes(title)) {
60
- selections.push(title);
61
- }
62
- }
63
- answer = selections.join(", ");
64
- } else {
65
- const choice = await ui.select(prompt, labels);
66
- if (choice === undefined) return null;
67
- const idx = labels.indexOf(choice);
68
- answer = q.options[idx]?.title;
69
- }
70
- } else {
71
- answer = await ui.input(prompt + (q.description ? `\n${q.description}` : ""));
72
- }
73
-
74
- if (answer === undefined) return null;
75
-
76
- let comment: string | undefined;
77
- if (allowComment) {
78
- comment = (await ui.input(`Comment for "${q.title}" (press Enter to skip):`)) ?? undefined;
79
- }
80
-
81
- answers.push({
82
- question: q.title,
83
- answer,
84
- kind: (q.options && q.options.length > 0 ? "selection" : "freeform"),
85
- comment,
86
- });
87
- }
88
-
89
- return {
90
- kind: "questionnaire",
91
- selections: answers.map((a) => `${a.question}: ${a.answer}`),
92
- questionnaireDetails: answers,
93
- };
39
+ const answers: QuestionnaireAnswer[] = [];
40
+
41
+ for (const q of questions) {
42
+ const prompt = context ? `${q.title}\n\nContext: ${context}` : q.title;
43
+ let answer: string | undefined;
44
+
45
+ if (q.options && q.options.length > 0) {
46
+ const labels = q.options.map((opt, i) => `${i + 1}. ${opt.title}`);
47
+
48
+ if (q.allowMultiple) {
49
+ const selections: string[] = [];
50
+ while (true) {
51
+ const remaining = labels.filter((_, i) => {
52
+ const title = q.options?.[i]?.title;
53
+ return title ? !selections.includes(title) : false;
54
+ });
55
+ if (remaining.length === 0) break;
56
+
57
+ const choice = await ui.select(
58
+ `${prompt}\nSelected: ${selections.join(", ") || "none"}\nChoose one (or cancel to finish)`,
59
+ remaining,
60
+ );
61
+ if (choice === undefined) break;
62
+
63
+ const idx = labels.indexOf(choice);
64
+ const title = q.options[idx]?.title;
65
+ if (title && !selections.includes(title)) {
66
+ selections.push(title);
67
+ }
68
+ }
69
+ answer = selections.join(", ");
70
+ } else {
71
+ const choice = await ui.select(prompt, labels);
72
+ if (choice === undefined) return null;
73
+ const idx = labels.indexOf(choice);
74
+ answer = q.options[idx]?.title;
75
+ }
76
+ } else {
77
+ answer = await ui.input(
78
+ prompt + (q.description ? `\n${q.description}` : ""),
79
+ );
80
+ }
81
+
82
+ if (answer === undefined) return null;
83
+
84
+ let comment: string | undefined;
85
+ if (allowComment) {
86
+ comment =
87
+ (await ui.input(
88
+ `Comment for "${q.title}" (press Enter to skip):`,
89
+ )) ?? undefined;
90
+ }
91
+
92
+ answers.push({
93
+ question: q.title,
94
+ answer,
95
+ kind: q.options && q.options.length > 0 ? "selection" : "freeform",
96
+ comment,
97
+ });
98
+ }
99
+
100
+ return {
101
+ kind: "questionnaire",
102
+ selections: answers.map((a) => `${a.question}: ${a.answer}`),
103
+ questionnaireDetails: answers,
104
+ };
94
105
  }
95
106
 
96
107
  async function flatOptionsFallback(
97
- payload: AskUserPayload,
98
- ui: ExtensionUIContext,
108
+ payload: AskUserPayload,
109
+ ui: ExtensionUIContext,
99
110
  ): Promise<Record<string, unknown> | null> {
100
- const { question, context, options, allowMultiple, allowFreeform, allowComment } = payload;
101
-
102
- const prompt = context ? `${question}\n\nContext: ${context}` : question;
103
-
104
- if (options.length === 0) {
105
- const text = await ui.input(prompt);
106
- if (text === undefined) return null;
107
- return { kind: "freeform", text };
108
- }
109
- const optionLabels = options.map((opt, i) => `${i + 1}. ${opt.title}`);
110
- if (allowFreeform) {
111
- optionLabels.push("Other (freeform)");
112
- }
113
-
114
- if (allowMultiple) {
115
- const selections: string[] = [];
116
- while (true) {
117
- const remaining = optionLabels.filter(
118
- (_, i) => !selections.includes(options[i]?.title ?? ""),
119
- );
120
- if (remaining.length === 0) break;
121
-
122
- const choice = await ui.select(
123
- `${prompt}\nSelected: ${selections.join(", ") || "none"}\nChoose one (or cancel to finish)`,
124
- remaining,
125
- );
126
- if (choice === undefined) break;
127
-
128
- const idx = optionLabels.indexOf(choice);
129
- if (idx >= options.length) {
130
- const text = await ui.input("Enter your answer:");
131
- if (text !== undefined && text.trim()) {
132
- selections.push(`Other: ${text.trim()}`);
133
- }
134
- continue;
135
- }
136
- const title = options[idx]?.title;
137
- if (title && !selections.includes(title)) {
138
- selections.push(title);
139
- }
140
- }
141
-
142
- let comment: string | undefined;
143
- if (allowComment && selections.length > 0) {
144
- comment = (await ui.input("Optional comment (press Enter to skip):")) ?? undefined;
145
- }
146
-
147
- return { kind: "selection", selections, comment };
148
- } else {
149
- const choice = await ui.select(prompt, optionLabels);
150
- if (choice === undefined) return null;
151
-
152
- const idx = optionLabels.indexOf(choice);
153
- if (idx >= options.length) {
154
- const text = await ui.input("Enter your answer:");
155
- if (text === undefined) return null;
156
- return { kind: "freeform", text };
157
- }
158
-
159
- const title = options[idx]?.title;
160
- if (!title) return null;
161
-
162
- let comment: string | undefined;
163
- if (allowComment) {
164
- comment = (await ui.input("Optional comment (press Enter to skip):")) ?? undefined;
165
- }
166
-
167
- return { kind: "selection", selections: [title], comment };
168
- }
111
+ const {
112
+ question,
113
+ context,
114
+ options,
115
+ allowMultiple,
116
+ allowFreeform,
117
+ allowComment,
118
+ } = payload;
119
+
120
+ const prompt = context ? `${question}\n\nContext: ${context}` : question;
121
+
122
+ if (options.length === 0) {
123
+ const text = await ui.input(prompt);
124
+ if (text === undefined) return null;
125
+ return { kind: "freeform", text };
126
+ }
127
+ const optionLabels = options.map((opt, i) => `${i + 1}. ${opt.title}`);
128
+ if (allowFreeform) {
129
+ optionLabels.push("Other (freeform)");
130
+ }
131
+
132
+ if (allowMultiple) {
133
+ const selections: string[] = [];
134
+ while (true) {
135
+ const remaining = optionLabels.filter(
136
+ (_, i) => !selections.includes(options[i]?.title ?? ""),
137
+ );
138
+ if (remaining.length === 0) break;
139
+
140
+ const choice = await ui.select(
141
+ `${prompt}\nSelected: ${selections.join(", ") || "none"}\nChoose one (or cancel to finish)`,
142
+ remaining,
143
+ );
144
+ if (choice === undefined) break;
145
+
146
+ const idx = optionLabels.indexOf(choice);
147
+ if (idx >= options.length) {
148
+ const text = await ui.input("Enter your answer:");
149
+ if (text?.trim()) {
150
+ selections.push(`Other: ${text.trim()}`);
151
+ }
152
+ continue;
153
+ }
154
+ const title = options[idx]?.title;
155
+ if (title && !selections.includes(title)) {
156
+ selections.push(title);
157
+ }
158
+ }
159
+
160
+ let comment: string | undefined;
161
+ if (allowComment && selections.length > 0) {
162
+ comment =
163
+ (await ui.input("Optional comment (press Enter to skip):")) ??
164
+ undefined;
165
+ }
166
+
167
+ return { kind: "selection", selections, comment };
168
+ } else {
169
+ const choice = await ui.select(prompt, optionLabels);
170
+ if (choice === undefined) return null;
171
+
172
+ const idx = optionLabels.indexOf(choice);
173
+ if (idx >= options.length) {
174
+ const text = await ui.input("Enter your answer:");
175
+ if (text === undefined) return null;
176
+ return { kind: "freeform", text };
177
+ }
178
+
179
+ const title = options[idx]?.title;
180
+ if (!title) return null;
181
+
182
+ let comment: string | undefined;
183
+ if (allowComment) {
184
+ comment =
185
+ (await ui.input("Optional comment (press Enter to skip):")) ??
186
+ undefined;
187
+ }
188
+
189
+ return { kind: "selection", selections: [title], comment };
190
+ }
169
191
  }