@oh-my-pi/user-prompt 0.8.3 → 1.3.371

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
@@ -39,13 +39,8 @@ Asks the user questions during execution and returns their response. Useful for:
39
39
 
40
40
  ```json
41
41
  {
42
- "question": "Which database would you like to use?",
43
- "options": [
44
- { "label": "PostgreSQL (Recommended)" },
45
- { "label": "MySQL" },
46
- { "label": "SQLite" },
47
- { "label": "MongoDB" }
48
- ]
42
+ "question": "Which database would you like to use?",
43
+ "options": [{ "label": "PostgreSQL (Recommended)" }, { "label": "MySQL" }, { "label": "SQLite" }, { "label": "MongoDB" }]
49
44
  }
50
45
  ```
51
46
 
@@ -53,15 +48,15 @@ Asks the user questions during execution and returns their response. Useful for:
53
48
 
54
49
  ```json
55
50
  {
56
- "question": "Which features should I implement?",
57
- "options": [
58
- { "label": "Authentication" },
59
- { "label": "API endpoints" },
60
- { "label": "Database models" },
61
- { "label": "Unit tests" },
62
- { "label": "Documentation" }
63
- ],
64
- "multi": true
51
+ "question": "Which features should I implement?",
52
+ "options": [
53
+ { "label": "Authentication" },
54
+ { "label": "API endpoints" },
55
+ { "label": "Database models" },
56
+ { "label": "Unit tests" },
57
+ { "label": "Documentation" }
58
+ ],
59
+ "multi": true
65
60
  }
66
61
  ```
67
62
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/user-prompt",
3
- "version": "0.8.3",
3
+ "version": "1.3.371",
4
4
  "description": "Interactive user prompting tool for gathering user input during execution",
5
5
  "keywords": [
6
6
  "omp-plugin",
@@ -17,12 +17,7 @@
17
17
  "directory": "plugins/user-prompt"
18
18
  },
19
19
  "omp": {
20
- "install": [
21
- {
22
- "src": "tools/user-prompt/index.ts",
23
- "dest": "agent/tools/user-prompt/index.ts"
24
- }
25
- ]
20
+ "tools": "tools"
26
21
  },
27
22
  "files": [
28
23
  "tools"
package/tools/index.ts ADDED
@@ -0,0 +1,245 @@
1
+ /**
2
+ * User Prompt Tool - Ask questions to the user during execution
3
+ *
4
+ * Use this tool when you need to ask the user questions during execution.
5
+ * This allows you to:
6
+ * 1. Gather user preferences or requirements
7
+ * 2. Clarify ambiguous instructions
8
+ * 3. Get decisions on implementation choices as you work
9
+ * 4. Offer choices to the user about what direction to take
10
+ *
11
+ * Usage notes:
12
+ * - Users will always be able to select "Other" to provide custom text input
13
+ * - Use multi: true to allow multiple answers to be selected for a question
14
+ * - If you recommend a specific option, make that the first option in the list
15
+ * and add "(Recommended)" at the end of the label
16
+ */
17
+
18
+ import type { CustomAgentTool, CustomToolFactory, ToolAPI } from '@mariozechner/pi-coding-agent'
19
+ import { Text } from '@mariozechner/pi-tui'
20
+ import { Type } from '@sinclair/typebox'
21
+
22
+ // =============================================================================
23
+ // Tool Definition
24
+ // =============================================================================
25
+
26
+ const OTHER_OPTION = 'Other (type your own)'
27
+
28
+ const OptionItem = Type.Object({
29
+ label: Type.String({ description: 'Display label for this option' }),
30
+ })
31
+
32
+ const UserPromptParams = Type.Object({
33
+ question: Type.String({ description: 'The question to ask the user' }),
34
+ options: Type.Array(OptionItem, {
35
+ description: 'Available options for the user to choose from.',
36
+ minItems: 1,
37
+ }),
38
+ multi: Type.Optional(
39
+ Type.Boolean({
40
+ description: 'Allow multiple options to be selected (default: false)',
41
+ default: false,
42
+ })
43
+ ),
44
+ })
45
+
46
+ interface UserPromptDetails {
47
+ question: string
48
+ options: string[]
49
+ multi: boolean
50
+ selectedOptions: string[]
51
+ customInput?: string
52
+ }
53
+
54
+ const DESCRIPTION = `Use this tool when you need to ask the user questions during execution. This allows you to:
55
+ 1. Gather user preferences or requirements
56
+ 2. Clarify ambiguous instructions
57
+ 3. Get decisions on implementation choices as you work
58
+ 4. Offer choices to the user about what direction to take.
59
+
60
+ Usage notes:
61
+ - Users will always be able to select "Other" to provide custom text input
62
+ - Use multi: true to allow multiple answers to be selected for a question
63
+ - If you recommend a specific option, make that the first option in the list and add "(Recommended)" at the end of the label
64
+
65
+ Example usage:
66
+
67
+ <example>
68
+ assistant: Let me ask which features you want to include.
69
+ assistant: Uses the user_prompt tool:
70
+ {
71
+ "question": "Which features should I implement?",
72
+ "options": [
73
+ {"label": "Authentication"},
74
+ {"label": "API endpoints"},
75
+ {"label": "Database models"},
76
+ {"label": "Unit tests"},
77
+ {"label": "Documentation"}
78
+ ],
79
+ "multi": true
80
+ }
81
+ </example>`
82
+
83
+ const factory: CustomToolFactory = (pi: ToolAPI) => {
84
+ const tool: CustomAgentTool<typeof UserPromptParams, UserPromptDetails> = {
85
+ name: 'user_prompt',
86
+ label: 'User Prompt',
87
+ description: DESCRIPTION,
88
+ parameters: UserPromptParams,
89
+
90
+ async execute(_toolCallId, params, _signal, _onUpdate) {
91
+ const { question, options, multi = false } = params
92
+ const optionLabels = options.map(o => o.label)
93
+
94
+ if (!pi.hasUI) {
95
+ return {
96
+ content: [
97
+ {
98
+ type: 'text',
99
+ text: 'Error: User prompt requires interactive mode',
100
+ },
101
+ ],
102
+ details: {
103
+ question,
104
+ options: optionLabels,
105
+ multi,
106
+ selectedOptions: [],
107
+ },
108
+ }
109
+ }
110
+
111
+ let selectedOptions: string[] = []
112
+ let customInput: string | undefined
113
+
114
+ if (multi) {
115
+ // Multi-select: show checkboxes in the label to indicate selection state
116
+ const DONE = '✓ Done selecting'
117
+ const selected = new Set<string>()
118
+
119
+ while (true) {
120
+ // Build options with checkbox indicators
121
+ const opts: string[] = []
122
+
123
+ // Add "Done" option if any selected
124
+ if (selected.size > 0) {
125
+ opts.push(DONE)
126
+ }
127
+
128
+ // Add all options with [X] or [ ] prefix
129
+ for (const opt of optionLabels) {
130
+ const checkbox = selected.has(opt) ? '[X]' : '[ ]'
131
+ opts.push(`${checkbox} ${opt}`)
132
+ }
133
+
134
+ // Add "Other" option
135
+ opts.push(OTHER_OPTION)
136
+
137
+ const prefix = selected.size > 0 ? `(${selected.size} selected) ` : ''
138
+ const choice = await pi.ui.select(`${prefix}${question}`, opts)
139
+
140
+ if (choice === null || choice === DONE) break
141
+
142
+ if (choice === OTHER_OPTION) {
143
+ const input = await pi.ui.input('Enter your response:')
144
+ if (input) customInput = input
145
+ break
146
+ }
147
+
148
+ // Toggle selection - extract the actual option name
149
+ const optMatch = choice.match(/^\[.\] (.+)$/)
150
+ if (optMatch) {
151
+ const opt = optMatch[1]
152
+ if (selected.has(opt)) {
153
+ selected.delete(opt)
154
+ } else {
155
+ selected.add(opt)
156
+ }
157
+ }
158
+ }
159
+ selectedOptions = Array.from(selected)
160
+ } else {
161
+ // Single select with "Other" option
162
+ const choice = await pi.ui.select(question, [...optionLabels, OTHER_OPTION])
163
+ if (choice === OTHER_OPTION) {
164
+ const input = await pi.ui.input('Enter your response:')
165
+ if (input) customInput = input
166
+ } else if (choice) {
167
+ selectedOptions = [choice]
168
+ }
169
+ }
170
+
171
+ const details: UserPromptDetails = {
172
+ question,
173
+ options: optionLabels,
174
+ multi,
175
+ selectedOptions,
176
+ customInput,
177
+ }
178
+
179
+ let responseText: string
180
+ if (customInput) {
181
+ responseText = `User provided custom input: ${customInput}`
182
+ } else if (selectedOptions.length > 0) {
183
+ responseText = multi ? `User selected: ${selectedOptions.join(', ')}` : `User selected: ${selectedOptions[0]}`
184
+ } else {
185
+ responseText = 'User cancelled the selection'
186
+ }
187
+
188
+ return { content: [{ type: 'text', text: responseText }], details }
189
+ },
190
+
191
+ renderCall(args, t) {
192
+ if (!args.question) {
193
+ return new Text(t.fg('error', 'user_prompt: no question provided'), 0, 0)
194
+ }
195
+
196
+ const multiTag = args.multi ? t.fg('muted', ' [multi-select]') : ''
197
+ let text = t.fg('toolTitle', '? ') + t.fg('accent', args.question) + multiTag
198
+
199
+ if (args.options?.length) {
200
+ for (const opt of args.options) {
201
+ text += `\n${t.fg('dim', ' ○ ')}${t.fg('muted', opt.label)}`
202
+ }
203
+ text += `\n${t.fg('dim', ' ○ ')}${t.fg('muted', 'Other (custom input)')}`
204
+ }
205
+
206
+ return new Text(text, 0, 0)
207
+ },
208
+
209
+ renderResult(result, _opts, t) {
210
+ const { details } = result
211
+ if (!details) {
212
+ const txt = result.content[0]
213
+ return new Text(txt?.type === 'text' ? txt.text : '', 0, 0)
214
+ }
215
+
216
+ let text = t.fg('toolTitle', '? ') + t.fg('accent', details.question)
217
+
218
+ if (details.customInput) {
219
+ // Custom input provided
220
+ text += `\n${t.fg('dim', ' ⎿ ')}${t.fg('success', details.customInput)}`
221
+ } else if (details.selectedOptions.length > 0) {
222
+ // Show only selected options
223
+ const selected = details.selectedOptions
224
+ if (selected.length === 1) {
225
+ text += `\n${t.fg('dim', ' ⎿ ')}${t.fg('success', selected[0])}`
226
+ } else {
227
+ // Multiple selections
228
+ for (let i = 0; i < selected.length; i++) {
229
+ const isLast = i === selected.length - 1
230
+ const branch = isLast ? '└─' : '├─'
231
+ text += `\n${t.fg('dim', ` ${branch} `)}${t.fg('success', selected[i])}`
232
+ }
233
+ }
234
+ } else {
235
+ text += `\n${t.fg('dim', ' ⎿ ')}${t.fg('warning', 'Cancelled')}`
236
+ }
237
+
238
+ return new Text(text, 0, 0)
239
+ },
240
+ }
241
+
242
+ return tool
243
+ }
244
+
245
+ export default factory
@@ -1,263 +0,0 @@
1
- /**
2
- * User Prompt Tool - Ask questions to the user during execution
3
- *
4
- * Use this tool when you need to ask the user questions during execution.
5
- * This allows you to:
6
- * 1. Gather user preferences or requirements
7
- * 2. Clarify ambiguous instructions
8
- * 3. Get decisions on implementation choices as you work
9
- * 4. Offer choices to the user about what direction to take
10
- *
11
- * Usage notes:
12
- * - Users will always be able to select "Other" to provide custom text input
13
- * - Use multi: true to allow multiple answers to be selected for a question
14
- * - If you recommend a specific option, make that the first option in the list
15
- * and add "(Recommended)" at the end of the label
16
- */
17
-
18
- import { Type } from "@sinclair/typebox";
19
- import { Text } from "@mariozechner/pi-tui";
20
- import type {
21
- CustomAgentTool,
22
- CustomToolFactory,
23
- ToolAPI,
24
- } from "@mariozechner/pi-coding-agent";
25
-
26
- // =============================================================================
27
- // Tool Definition
28
- // =============================================================================
29
-
30
- const OTHER_OPTION = "Other (type your own)";
31
-
32
- const OptionItem = Type.Object({
33
- label: Type.String({ description: "Display label for this option" }),
34
- });
35
-
36
- const UserPromptParams = Type.Object({
37
- question: Type.String({ description: "The question to ask the user" }),
38
- options: Type.Array(OptionItem, {
39
- description: "Available options for the user to choose from.",
40
- minItems: 1,
41
- }),
42
- multi: Type.Optional(
43
- Type.Boolean({
44
- description: "Allow multiple options to be selected (default: false)",
45
- default: false,
46
- }),
47
- ),
48
- });
49
-
50
- interface UserPromptDetails {
51
- question: string;
52
- options: string[];
53
- multi: boolean;
54
- selectedOptions: string[];
55
- customInput?: string;
56
- }
57
-
58
- const DESCRIPTION = `Use this tool when you need to ask the user questions during execution. This allows you to:
59
- 1. Gather user preferences or requirements
60
- 2. Clarify ambiguous instructions
61
- 3. Get decisions on implementation choices as you work
62
- 4. Offer choices to the user about what direction to take.
63
-
64
- Usage notes:
65
- - Users will always be able to select "Other" to provide custom text input
66
- - Use multi: true to allow multiple answers to be selected for a question
67
- - If you recommend a specific option, make that the first option in the list and add "(Recommended)" at the end of the label
68
-
69
- Example usage:
70
-
71
- <example>
72
- assistant: Let me ask which features you want to include.
73
- assistant: Uses the user_prompt tool:
74
- {
75
- "question": "Which features should I implement?",
76
- "options": [
77
- {"label": "Authentication"},
78
- {"label": "API endpoints"},
79
- {"label": "Database models"},
80
- {"label": "Unit tests"},
81
- {"label": "Documentation"}
82
- ],
83
- "multi": true
84
- }
85
- </example>`;
86
-
87
- const factory: CustomToolFactory = (pi: ToolAPI) => {
88
- const tool: CustomAgentTool<typeof UserPromptParams, UserPromptDetails> = {
89
- name: "user_prompt",
90
- label: "User Prompt",
91
- description: DESCRIPTION,
92
- parameters: UserPromptParams,
93
-
94
- async execute(_toolCallId, params, _signal, _onUpdate) {
95
- const { question, options, multi = false } = params;
96
- const optionLabels = options.map((o) => o.label);
97
-
98
- if (!pi.hasUI) {
99
- return {
100
- content: [
101
- {
102
- type: "text",
103
- text: "Error: User prompt requires interactive mode",
104
- },
105
- ],
106
- details: {
107
- question,
108
- options: optionLabels,
109
- multi,
110
- selectedOptions: [],
111
- },
112
- };
113
- }
114
-
115
- let selectedOptions: string[] = [];
116
- let customInput: string | undefined;
117
-
118
- if (multi) {
119
- // Multi-select: show checkboxes in the label to indicate selection state
120
- const DONE = "✓ Done selecting";
121
- const selected = new Set<string>();
122
-
123
- while (true) {
124
- // Build options with checkbox indicators
125
- const opts: string[] = [];
126
-
127
- // Add "Done" option if any selected
128
- if (selected.size > 0) {
129
- opts.push(DONE);
130
- }
131
-
132
- // Add all options with [X] or [ ] prefix
133
- for (const opt of optionLabels) {
134
- const checkbox = selected.has(opt) ? "[X]" : "[ ]";
135
- opts.push(`${checkbox} ${opt}`);
136
- }
137
-
138
- // Add "Other" option
139
- opts.push(OTHER_OPTION);
140
-
141
- const prefix =
142
- selected.size > 0 ? `(${selected.size} selected) ` : "";
143
- const choice = await pi.ui.select(`${prefix}${question}`, opts);
144
-
145
- if (choice === null || choice === DONE) break;
146
-
147
- if (choice === OTHER_OPTION) {
148
- const input = await pi.ui.input("Enter your response:");
149
- if (input) customInput = input;
150
- break;
151
- }
152
-
153
- // Toggle selection - extract the actual option name
154
- const optMatch = choice.match(/^\[.\] (.+)$/);
155
- if (optMatch) {
156
- const opt = optMatch[1];
157
- if (selected.has(opt)) {
158
- selected.delete(opt);
159
- } else {
160
- selected.add(opt);
161
- }
162
- }
163
- }
164
- selectedOptions = Array.from(selected);
165
- } else {
166
- // Single select with "Other" option
167
- const choice = await pi.ui.select(question, [
168
- ...optionLabels,
169
- OTHER_OPTION,
170
- ]);
171
- if (choice === OTHER_OPTION) {
172
- const input = await pi.ui.input("Enter your response:");
173
- if (input) customInput = input;
174
- } else if (choice) {
175
- selectedOptions = [choice];
176
- }
177
- }
178
-
179
- const details: UserPromptDetails = {
180
- question,
181
- options: optionLabels,
182
- multi,
183
- selectedOptions,
184
- customInput,
185
- };
186
-
187
- let responseText: string;
188
- if (customInput) {
189
- responseText = `User provided custom input: ${customInput}`;
190
- } else if (selectedOptions.length > 0) {
191
- responseText = multi
192
- ? `User selected: ${selectedOptions.join(", ")}`
193
- : `User selected: ${selectedOptions[0]}`;
194
- } else {
195
- responseText = "User cancelled the selection";
196
- }
197
-
198
- return { content: [{ type: "text", text: responseText }], details };
199
- },
200
-
201
- renderCall(args, t) {
202
- if (!args.question) {
203
- return new Text(
204
- t.fg("error", "user_prompt: no question provided"),
205
- 0,
206
- 0,
207
- );
208
- }
209
-
210
- const multiTag = args.multi ? t.fg("muted", " [multi-select]") : "";
211
- let text =
212
- t.fg("toolTitle", "? ") + t.fg("accent", args.question) + multiTag;
213
-
214
- if (args.options?.length) {
215
- for (const opt of args.options) {
216
- text += "\n" + t.fg("dim", " ○ ") + t.fg("muted", opt.label);
217
- }
218
- text +=
219
- "\n" + t.fg("dim", " ○ ") + t.fg("muted", "Other (custom input)");
220
- }
221
-
222
- return new Text(text, 0, 0);
223
- },
224
-
225
- renderResult(result, { expanded }, t) {
226
- const { details } = result;
227
- if (!details) {
228
- const txt = result.content[0];
229
- return new Text(txt?.type === "text" ? txt.text : "", 0, 0);
230
- }
231
-
232
- let text = t.fg("toolTitle", "? ") + t.fg("accent", details.question);
233
-
234
- if (details.customInput) {
235
- // Custom input provided
236
- text +=
237
- "\n" + t.fg("dim", " ⎿ ") + t.fg("success", details.customInput);
238
- } else if (details.selectedOptions.length > 0) {
239
- // Show only selected options
240
- const selected = details.selectedOptions;
241
- if (selected.length === 1) {
242
- text += "\n" + t.fg("dim", " ⎿ ") + t.fg("success", selected[0]);
243
- } else {
244
- // Multiple selections
245
- for (let i = 0; i < selected.length; i++) {
246
- const isLast = i === selected.length - 1;
247
- const branch = isLast ? "└─" : "├─";
248
- text +=
249
- "\n" + t.fg("dim", ` ${branch} `) + t.fg("success", selected[i]);
250
- }
251
- }
252
- } else {
253
- text += "\n" + t.fg("dim", " ⎿ ") + t.fg("warning", "Cancelled");
254
- }
255
-
256
- return new Text(text, 0, 0);
257
- },
258
- };
259
-
260
- return tool;
261
- };
262
-
263
- export default factory;