@geoqiao/pi-ask 1.1.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 (59) hide show
  1. package/CHANGELOG.md +194 -0
  2. package/LICENSE +22 -0
  3. package/README.md +282 -0
  4. package/docs/README.md +33 -0
  5. package/docs/configuration.md +406 -0
  6. package/docs/contract.md +309 -0
  7. package/docs/remote-events.md +187 -0
  8. package/package.json +130 -0
  9. package/skills/ask-user/SKILL.md +110 -0
  10. package/src/answer-commands.ts +361 -0
  11. package/src/answer-extraction.ts +354 -0
  12. package/src/ask-payload-store.ts +86 -0
  13. package/src/ask-settings-command.ts +14 -0
  14. package/src/ask-tool-helpers.ts +172 -0
  15. package/src/ask-tool.ts +84 -0
  16. package/src/config/defaults.ts +216 -0
  17. package/src/config/migrate.ts +70 -0
  18. package/src/config/migrations/index.ts +139 -0
  19. package/src/config/migrations/types.ts +10 -0
  20. package/src/config/schema.ts +287 -0
  21. package/src/config/store.ts +227 -0
  22. package/src/constants/keymaps.ts +721 -0
  23. package/src/constants/text.ts +12 -0
  24. package/src/constants/ui.ts +22 -0
  25. package/src/index.ts +30 -0
  26. package/src/math.ts +3 -0
  27. package/src/notifications.ts +119 -0
  28. package/src/remote-ask.ts +563 -0
  29. package/src/result-format.ts +157 -0
  30. package/src/result.ts +23 -0
  31. package/src/schema.ts +74 -0
  32. package/src/state/answers.ts +251 -0
  33. package/src/state/create.ts +18 -0
  34. package/src/state/editor.ts +70 -0
  35. package/src/state/navigation.ts +86 -0
  36. package/src/state/normalize.ts +326 -0
  37. package/src/state/question-type.ts +128 -0
  38. package/src/state/result.ts +263 -0
  39. package/src/state/selectors.ts +135 -0
  40. package/src/state/transitions.ts +330 -0
  41. package/src/state/view.ts +28 -0
  42. package/src/text.ts +98 -0
  43. package/src/types.ts +169 -0
  44. package/src/ui/auto-submit.ts +36 -0
  45. package/src/ui/autocomplete.ts +52 -0
  46. package/src/ui/controller.ts +645 -0
  47. package/src/ui/dismiss-guard.ts +26 -0
  48. package/src/ui/input.ts +160 -0
  49. package/src/ui/render-frame.ts +235 -0
  50. package/src/ui/render-helpers.ts +385 -0
  51. package/src/ui/render-question.ts +288 -0
  52. package/src/ui/render-submit.ts +168 -0
  53. package/src/ui/render-types.ts +33 -0
  54. package/src/ui/render.ts +53 -0
  55. package/src/ui/review-shortcuts.ts +43 -0
  56. package/src/ui/settings-list.ts +461 -0
  57. package/src/ui/show-settings.ts +37 -0
  58. package/src/ui/view-models/question.ts +203 -0
  59. package/src/ui/view-models/review.ts +100 -0
@@ -0,0 +1,406 @@
1
+ # pi-ask configuration
2
+
3
+ This file is the source of truth for configuring `@eko24ive/pi-ask`.
4
+
5
+ When changing pi-ask settings:
6
+
7
+ 1. Edit the config file.
8
+ 2. Validate keymaps against the rules below.
9
+ 3. Run `/reload` or restart pi so the new config is picked up.
10
+
11
+ pi-ask treats this file as user-owned config. It does not rewrite or back up the file just because it was loaded, migrated, or found invalid. Settings toggled in `/ask-settings` are saved only when the file is writable; if saving fails, pi-ask reverts the toggle and shows the config path to edit manually.
12
+
13
+ ## Config file path
14
+
15
+ Default path:
16
+
17
+ `~/.pi/agent/extensions/eko24ive-pi-ask.json`
18
+
19
+ If the file does not exist yet, pi-ask attempts to create it with the current default settings the first time the ask flow is used. If the config location is read-only or managed outside pi-ask, pi-ask uses built-in defaults for the session and leaves disk unchanged.
20
+
21
+ Older pi-ask versions wrote this file at `~/.pi/agent/eko24ive-pi-ask.json`. If that legacy file exists and the extensions config does not, pi-ask reads the legacy file as a fallback and leaves disk unchanged. If both files exist, pi-ask uses the extensions config and leaves the legacy root file untouched.
22
+
23
+ ## Config versions and migrations
24
+
25
+ `schemaVersion` identifies the persisted config shape. pi-ask migrates older supported schema versions forward in memory before validation. Migrations preserve existing user-provided values and add new fields from defaults when needed, but loading a config does not rewrite the file.
26
+
27
+ Unsupported future versions or invalid files are left unchanged and defaults are loaded for the current session. Fix the file, then run `/reload` or restart pi.
28
+
29
+ ## Config shape
30
+
31
+ ```json
32
+ {
33
+ "schemaVersion": 5,
34
+ "answer": {
35
+ "extractionModels": [
36
+ { "provider": "openai-codex", "id": "<model-id>" },
37
+ { "provider": "github-copilot", "id": "<model-id>" },
38
+ { "provider": "anthropic", "id": "<model-id>" }
39
+ ],
40
+ "extractionTimeoutMs": 30000,
41
+ "extractionRetries": 1
42
+ },
43
+ "behaviour": {
44
+ "autoSubmitWhenAnsweredWithoutNotes": false,
45
+ "confirmDismissWhenDirty": true,
46
+ "doublePressReviewShortcuts": true,
47
+ "presentSingleAsMulti": false,
48
+ "showFooterHints": true
49
+ },
50
+ "keymaps": {
51
+ "global": { "dismiss": ["ctrl+c"], "settings": ["?"] },
52
+ "main": {
53
+ "confirm": ["enter"],
54
+ "cancel": ["esc"],
55
+ "toggle": ["space"],
56
+ "changeQuestionType": ["t"],
57
+ "nextTab": ["tab", "right"],
58
+ "previousTab": ["shift+tab", "left"],
59
+ "nextOption": ["down"],
60
+ "previousOption": ["up"],
61
+ "optionNote": ["n"],
62
+ "questionNote": ["shift+n"]
63
+ },
64
+ "editor": {
65
+ "submit": ["enter"],
66
+ "close": ["esc"],
67
+ "nextTabWhenEmpty": ["tab", "right"],
68
+ "previousTabWhenEmpty": ["shift+tab", "left"],
69
+ "nextOptionWhenEmpty": ["down"],
70
+ "previousOptionWhenEmpty": ["up"]
71
+ },
72
+ "noteEditor": {
73
+ "save": ["enter"],
74
+ "close": ["esc"],
75
+ "nextTabWhenEmpty": ["tab", "right"],
76
+ "previousTabWhenEmpty": ["shift+tab", "left"],
77
+ "nextOptionWhenEmpty": ["down"],
78
+ "previousOptionWhenEmpty": ["up"]
79
+ },
80
+ "settingsModal": {
81
+ "close": ["esc", "ctrl+c", "?"],
82
+ "nextOption": ["down"],
83
+ "previousOption": ["up"],
84
+ "toggle": ["enter", "space"]
85
+ }
86
+ },
87
+ "notifications": {
88
+ "enabled": true,
89
+ "channels": ["bell"]
90
+ }
91
+ }
92
+ ```
93
+
94
+ ## Answer extraction
95
+
96
+ These settings affect only the `/answer` command. Normal `ask_user` tool calls do not use an extraction model.
97
+
98
+ ### `answer.extractionModels`
99
+
100
+ - type: array of `{ "provider": string, "id": string }`
101
+ - default: lightweight OpenAI Codex, GitHub Copilot, and Anthropic models
102
+ - effect: `/answer` tries configured models in order and uses the first model with available auth
103
+ - fallback: if no configured model is usable, `/answer` tries the current chat model after validating its auth
104
+
105
+ ### `answer.extractionTimeoutMs`
106
+
107
+ - type: positive number
108
+ - default: `30000`
109
+ - effect: per-attempt extraction timeout in milliseconds
110
+
111
+ ### `answer.extractionRetries`
112
+
113
+ - type: integer from `0` to `3`
114
+ - default: `1`
115
+ - effect: number of retry attempts after raw JSON parsing fails; retries include the parse error and previous response as feedback
116
+
117
+ ## Behaviour
118
+
119
+ ### `behaviour.autoSubmitWhenAnsweredWithoutNotes`
120
+
121
+ - type: boolean
122
+ - default: `false`
123
+ - effect: when enabled, a fully answered ask flow with no notes can auto-submit from the review tab
124
+
125
+ ### `behaviour.confirmDismissWhenDirty`
126
+
127
+ - type: boolean
128
+ - default: `true`
129
+ - effect: when enabled, discarding a dirty ask flow requires a second cancel/dismiss action
130
+ - the warning stays visible until the user changes tabs in the ask flow
131
+ - dirty means there are saved answers/notes or unsaved editor draft text
132
+
133
+ ### `behaviour.doublePressReviewShortcuts`
134
+
135
+ - type: boolean
136
+ - default: `true`
137
+ - effect: when enabled, review-tab number shortcuts (`1`, `2`, `3`) only trigger after pressing the same key twice
138
+ - the review screen shows an inline hint and keeps the pending shortcut armed until another review shortcut is pressed or the user leaves the tab
139
+
140
+ ### `behaviour.presentSingleAsMulti`
141
+
142
+ - type: boolean
143
+ - default: `false`
144
+ - effect: future ask flows render requested `single` questions as `multi` questions
145
+ - result: the question keeps `type: "single"` and adds `presentedType: "multi"`; result text uses one compact note when any answered questions were presented differently
146
+ - scope: this is a default presentation policy for new/replayed ask flows; it is not hot-applied to the current ask flow
147
+ - current-flow override: use the configurable main-flow `changeQuestionType` hotkey (default `t`) to change the active question type live: non-preview questions toggle `single <-> multi`; preview questions toggle `preview <-> multi`
148
+ - destructive confirmation: changing `multi -> single` with multiple selected answers requires pressing the type hotkey again; the pending confirmation has no timeout and clears when another navigation/action is used
149
+
150
+ ### `behaviour.showFooterHints`
151
+
152
+ - type: boolean
153
+ - default: `true`
154
+ - effect: when disabled, the ask flow hides the footer keymap hints
155
+
156
+ ## Notifications
157
+
158
+ Notifications are best-effort external alerts emitted once per ask session, when the ask UI opens and is waiting for input.
159
+
160
+ ### `notifications.enabled`
161
+
162
+ - type: boolean
163
+ - default: `true`
164
+ - effect: enables or disables external ask notifications
165
+ - settings UI: this is the only notification field toggled individually by `/ask-settings` or `?` in the ask flow
166
+
167
+ ### `notifications.channels`
168
+
169
+ - type: array
170
+ - default: `["bell"]`
171
+ - supported channels:
172
+ - `"bell"` writes BEL (`\u0007`)
173
+ - `"osc9"` writes an OSC 9 terminal notification
174
+ - `"osc777"` writes an OSC 777 title/body notification
175
+ - `{ "type": "command", "command": string }` runs a shell command
176
+ - effect: channels run in order; failures are ignored and never fail the ask flow
177
+ - invalid channels are skipped; if none are valid, pi-ask falls back to `["bell"]`
178
+
179
+ Notification text:
180
+
181
+ ```txt
182
+ Question waiting: <label or prompt>
183
+ ```
184
+
185
+ Command channels receive these environment variables:
186
+
187
+ ```sh
188
+ ASK_NOTIFY_EVENT=question.waiting
189
+ ASK_NOTIFY_TITLE="pi ask"
190
+ ASK_NOTIFY_MESSAGE="Question waiting: <label or prompt>"
191
+ ```
192
+
193
+ cmux example:
194
+
195
+ ```json
196
+ {
197
+ "type": "command",
198
+ "command": "cmux notify --title \"$ASK_NOTIFY_TITLE\" --body \"$ASK_NOTIFY_MESSAGE\""
199
+ }
200
+ ```
201
+
202
+ ## Keymaps
203
+
204
+ `keymaps` is context-aware. Each action accepts either one key id string or an array of key id strings.
205
+ Arrays are aliases: any listed key triggers the same action.
206
+
207
+ ### Defaults
208
+
209
+ ```json
210
+ "keymaps": {
211
+ "global": {
212
+ "dismiss": ["ctrl+c"],
213
+ "settings": ["?"]
214
+ },
215
+ "main": {
216
+ "confirm": ["enter"],
217
+ "cancel": ["esc"],
218
+ "changeQuestionType": ["t"],
219
+ "toggle": ["space"],
220
+ "nextTab": ["tab", "right"],
221
+ "previousTab": ["shift+tab", "left"],
222
+ "nextOption": ["down"],
223
+ "previousOption": ["up"],
224
+ "optionNote": ["n"],
225
+ "questionNote": ["shift+n"]
226
+ },
227
+ "editor": {
228
+ "submit": ["enter"],
229
+ "close": ["esc"],
230
+ "nextTabWhenEmpty": ["tab", "right"],
231
+ "previousTabWhenEmpty": ["shift+tab", "left"],
232
+ "nextOptionWhenEmpty": ["down"],
233
+ "previousOptionWhenEmpty": ["up"]
234
+ },
235
+ "noteEditor": {
236
+ "save": ["enter"],
237
+ "close": ["esc"],
238
+ "nextTabWhenEmpty": ["tab", "right"],
239
+ "previousTabWhenEmpty": ["shift+tab", "left"],
240
+ "nextOptionWhenEmpty": ["down"],
241
+ "previousOptionWhenEmpty": ["up"]
242
+ },
243
+ "settingsModal": {
244
+ "close": ["esc", "ctrl+c", "?"],
245
+ "nextOption": ["down"],
246
+ "previousOption": ["up"],
247
+ "toggle": ["enter", "space"]
248
+ }
249
+ }
250
+ ```
251
+
252
+ ### Contexts
253
+
254
+ - `global`: active in the main ask flow and editors; duplicates with those contexts are invalid
255
+ - `main`: question and review flow
256
+ - `editor`: custom answer editor
257
+ - `noteEditor`: question/option note editor
258
+ - `settingsModal`: `/ask-settings` and `?` settings overlay
259
+
260
+ ### Allowed bindings
261
+
262
+ Each action accepts any `pi-tui` key id string, as long as it is:
263
+
264
+ - supported by `pi-tui`
265
+ - not a fixed numeric shortcut (`1` through `9`)
266
+ - not duplicated within the same context
267
+ - not duplicated between `global` and `main`, `editor`, or `noteEditor`
268
+
269
+ Examples of valid bindings:
270
+
271
+ - `esc`
272
+ - `ctrl+c`
273
+ - `space`
274
+ - `enter`
275
+ - `n`
276
+ - `shift+n`
277
+ - `alt+f7`
278
+ - `ctrl+[`
279
+ - `ctrl+shift+p`
280
+ - `super+k`
281
+
282
+ ### Accepted alias normalization
283
+
284
+ Common aliases are normalized to canonical `pi-tui`-style strings.
285
+
286
+ Examples:
287
+
288
+ - `escape` -> `esc`
289
+ - `return` -> `enter`
290
+ - `control+c` -> `ctrl+c`
291
+ - `Shift+N` -> `shift+n`
292
+ - `pageup` -> `pageUp`
293
+ - `pagedown` -> `pageDown`
294
+
295
+ ### Fixed bindings
296
+
297
+ These are intentionally not configurable:
298
+
299
+ - `1..9` triggers option/review shortcuts
300
+ - when `behaviour.doublePressReviewShortcuts` is enabled, review-tab shortcuts `1`, `2`, and `3` require the same key twice
301
+ - `@` remains the file-reference affordance in editors
302
+
303
+ ## Invalid keymaps behavior
304
+
305
+ If configured keymaps are invalid:
306
+
307
+ - valid `behaviour`, `notifications`, and `answer` settings still load
308
+ - invalid `keymaps` fall back to default keymaps for the current session
309
+ - ask remains usable
310
+ - a warning notice is shown
311
+ - after fixing the file, run `/reload` or restart pi
312
+
313
+ Invalid keymaps include:
314
+
315
+ - missing one of the required contexts or actions
316
+ - unsupported key syntax
317
+ - duplicate bindings within one context
318
+ - duplicate bindings between `global` and `main`, `editor`, or `noteEditor`
319
+ - use of fixed numeric shortcuts (`1` through `9`)
320
+
321
+ ## Example custom config
322
+
323
+ ```json
324
+ {
325
+ "schemaVersion": 5,
326
+ "answer": {
327
+ "extractionRetries": 1,
328
+ "extractionTimeoutMs": 30000,
329
+ "extractionModels": [
330
+ { "provider": "openai-codex", "id": "<model-id>" }
331
+ ]
332
+ },
333
+ "behaviour": {
334
+ "autoSubmitWhenAnsweredWithoutNotes": true,
335
+ "confirmDismissWhenDirty": true,
336
+ "doublePressReviewShortcuts": true,
337
+ "presentSingleAsMulti": false,
338
+ "showFooterHints": false
339
+ },
340
+ "keymaps": {
341
+ "global": {
342
+ "dismiss": ["ctrl+c"],
343
+ "settings": ["?"]
344
+ },
345
+ "main": {
346
+ "confirm": ["ctrl+k"],
347
+ "cancel": ["q"],
348
+ "toggle": ["ctrl+t"],
349
+ "changeQuestionType": ["t"],
350
+ "nextTab": ["tab", "right"],
351
+ "previousTab": ["shift+tab", "left"],
352
+ "nextOption": ["down"],
353
+ "previousOption": ["up"],
354
+ "optionNote": ["x"],
355
+ "questionNote": ["shift+x"]
356
+ },
357
+ "editor": {
358
+ "submit": ["ctrl+k"],
359
+ "close": ["q"],
360
+ "nextTabWhenEmpty": ["tab", "right"],
361
+ "previousTabWhenEmpty": ["shift+tab", "left"],
362
+ "nextOptionWhenEmpty": ["down"],
363
+ "previousOptionWhenEmpty": ["up"]
364
+ },
365
+ "noteEditor": {
366
+ "save": ["ctrl+k"],
367
+ "close": ["q"],
368
+ "nextTabWhenEmpty": ["tab", "right"],
369
+ "previousTabWhenEmpty": ["shift+tab", "left"],
370
+ "nextOptionWhenEmpty": ["down"],
371
+ "previousOptionWhenEmpty": ["up"]
372
+ },
373
+ "settingsModal": {
374
+ "close": ["esc", "ctrl+c", "?"],
375
+ "nextOption": ["j", "down"],
376
+ "previousOption": ["k", "up"],
377
+ "toggle": ["enter", "space"]
378
+ }
379
+ },
380
+ "notifications": {
381
+ "enabled": true,
382
+ "channels": [
383
+ {
384
+ "type": "command",
385
+ "command": "cmux notify --title \"$ASK_NOTIFY_TITLE\" --body \"$ASK_NOTIFY_MESSAGE\""
386
+ }
387
+ ]
388
+ }
389
+ }
390
+ ```
391
+
392
+ ## Agent editing rule
393
+
394
+ When editing this config for a user:
395
+
396
+ - preserve unrelated fields
397
+ - keep `schemaVersion` at `5`
398
+ - preserve `answer.extractionModels` as explicit provider/id pairs
399
+ - keep `answer.extractionRetries` between `0` and `3`
400
+ - do not assign fixed numeric shortcuts (`1` through `9`) to configurable actions
401
+ - do not create duplicate bindings within a context or between `global` and `main`, `editor`, or `noteEditor`
402
+ - preserve existing `notifications.channels` unless the user asks to change notification targets
403
+ - only toggle `notifications.enabled` unless the user asks to configure channels
404
+ - use the settings reset action only when the user explicitly asks to restore pi-ask defaults
405
+ - use a `cmux notify` command channel when the user asks for cmux notifications
406
+ - after changing the file, tell the user to run `/reload` or restart pi