@d3ara1n/pi-ask-user 1.0.0 → 2.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 (3) hide show
  1. package/README.md +112 -44
  2. package/package.json +3 -2
  3. package/src/index.ts +740 -311
package/README.md CHANGED
@@ -4,24 +4,20 @@ A collapsible **ask-user** tool for [pi](https://github.com/earendil-works/pi).
4
4
 
5
5
  ## Why
6
6
 
7
- Other ask-user tools render a panel that covers the transcript, and even when
8
- they "collapse" they keep keyboard focus locked on the panel — so you **can't
7
+ Most ask-user tools render a panel that covers the transcript, so you **can't
9
8
  scroll the conversation** to read the analysis that should inform your choice.
10
9
  You end up choosing blind.
11
10
 
12
- This tool fixes that:
11
+ This tool fixes that by **not** using a screen overlay. The panel renders into
12
+ pi's bottom `editorContainer` slot (the same path `ctx.ui.select()` /
13
+ `ctx.ui.input()` take), so the transcript stays visible **above** the panel and
14
+ remains scrollable via the terminal's native scrollback — mouse wheel,
15
+ `Shift+PgUp`, `Cmd+↑`. This works because pi's TUI never enters alt-screen and
16
+ never tracks the mouse, so every rendered chat line lives in the terminal
17
+ buffer and can be scrolled back at any time, no focus gymnastics needed.
13
18
 
14
- - **Collapse actually releases focus.** Press `Ctrl+\` and the panel shrinks to
15
- a single status row while `OverlayHandle.unfocus()` hands focus back to the
16
- transcript. Your normal scroll mechanisms work again — mouse wheel,
17
- `Shift+PgUp`, `Ctrl+Left`/`Ctrl+Right` tree nav, `/tree`.
18
- - **Re-expand works from anywhere.** A global shortcut (`pi.registerShortcut`)
19
- captures `Ctrl+\` from the editor, so pressing it again re-expands and
20
- re-focuses the panel — no matter where focus currently is.
21
-
22
- > **Note:** Because pi removes the editor from the UI tree while an overlay is
23
- > active, full transcript scrolling while collapsed depends on pi's overlay
24
- > behavior. The collapse affordance is kept as best-effort.
19
+ - **Collapse** (`Ctrl+\`) shrinks the panel to a single status row, leaving
20
+ even more of the transcript on screen while you decide.
25
21
 
26
22
  ## Tool: `ask_user`
27
23
 
@@ -30,11 +26,11 @@ This tool fixes that:
30
26
  "questions": [
31
27
  {
32
28
  "header": "Which layout?",
33
- "label": "layout",
29
+ "tab": "layout",
34
30
  "prompt": "Pick the layout for the new settings page.",
35
31
  "options": [
36
- { "value": "sidebar", "label": "Sidebar", "description": "Nav on the left…" },
37
- { "value": "tabs", "label": "Tabs", "description": "Top tabs…" }
32
+ { "label": "Sidebar", "description": "Nav on the left…" },
33
+ { "label": "Tabs", "description": "Top tabs…" }
38
34
  ]
39
35
  }
40
36
  ]
@@ -48,10 +44,9 @@ This tool fixes that:
48
44
  | Field | Type | Required | Description |
49
45
  |-------|------|----------|-------------|
50
46
  | `header` | string | yes | Short title shown in the panel header |
51
- | `label` | string | yes | Short keyword shown on the tab bar and returned to identify this question. Must be unique across all questions in one call |
47
+ | `tab` | string | yes | Short keyword identifying this question. Shown on the tab bar when there are multiple questions, and returned in the result as the answer's prefix. Write it in the user's language, not as a programmatic identifier. Must be unique across all questions in one call |
52
48
  | `options` | array | yes | 2–4 options |
53
49
  | `prompt` | string | no | Longer body text under the header |
54
- | `allowOther` | boolean | no | Allow "Type something." custom input. Default `true` |
55
50
  | `multiSelect` | boolean | no | Check multiple options. Default `false` |
56
51
  | `allowSkip` | boolean | no | If `false`, the user MUST answer before proceeding. Default `true` |
57
52
 
@@ -60,9 +55,8 @@ This tool fixes that:
60
55
  | Field | Type | Required | Description |
61
56
  |-------|------|----------|-------------|
62
57
  | `label` | string | yes | Display label |
63
- | `value` | string | no | Returned value. Defaults to `label` if omitted |
64
58
  | `description` | string | no | Short explanation under the label (wraps). Add one when the label alone isn't self-explanatory |
65
- | `preview` | string | no | Only add when `description` alone can't make the option clear — ASCII layout demo, code snippet, or detailed reasoning. Most options need only `description`. Triggers a side column when present |
59
+ | `preview` | string | no | Use when `description` (a short one-liner) isn't enough and the user genuinely benefits from more detail in a side column — ASCII layout demo, code skeleton, Pro/Cons breakdown, or the reasoning behind the option and what choosing it entails. Rendered verbatim. Don't treat it as extra text capacity — every line competes for the user's attention. If a short `description` already conveys the option, leave empty. Most options need only `description` |
66
60
 
67
61
  ### Icons
68
62
 
@@ -84,8 +78,9 @@ Moving `↑`/`↓` only moves the cursor; selection is committed separately.
84
78
 
85
79
  ### Custom input ("Type something.")
86
80
 
87
- When `allowOther` is `true` (default), a "Type something." row appears. Press
88
- `Enter` on it to open a text editor:
81
+ Every question always shows a "Type something." row (this cannot be turned off),
82
+ so the user is never locked into the provided options. Press `Enter` on it to
83
+ open a text editor:
89
84
 
90
85
  - After submitting, the row displays the committed text with a filled glyph
91
86
  (`◉ ✎ your text`).
@@ -94,11 +89,22 @@ When `allowOther` is `true` (default), a "Type something." row appears. Press
94
89
  - `Esc` discards the edit (the original answer is kept); `Enter` confirms the
95
90
  change (the answer is updated).
96
91
 
92
+ **Multi-select + custom input.** In `multiSelect` mode the custom text is an
93
+ *extra* entry kept alongside the checked options — it never overwrites them.
94
+ You can check several options, then open "Type something.", type a value, and
95
+ submit: both the checks and the custom text are preserved and returned
96
+ together. The same holds in reverse — edit the question later from the review
97
+ screen to add or remove checks and the committed custom text stays intact
98
+ (and vice versa). Submitting an empty custom value clears only the custom
99
+ entry; any remaining checks are kept.
100
+
97
101
  ### Required questions
98
102
 
99
103
  Set `allowSkip: false` to force an answer. The user cannot advance forward
100
- (`Tab`/`→`) until they answer. Backward navigation (`Shift+Tab`/`←`) is always
101
- allowed so they can review/edit earlier questions.
104
+ (`Tab`/`→`) until they answer; the built-in "Type something." row always lets
105
+ them supply a custom answer, so they're never trapped by options they dislike.
106
+ Backward navigation (`Shift+Tab`/`←`) is always allowed so they can review/edit
107
+ earlier questions.
102
108
 
103
109
  ### Rich previews
104
110
 
@@ -111,19 +117,17 @@ cursor updates the right pane. Ideal for comparing ASCII layouts / code samples:
111
117
  ```jsonc
112
118
  {
113
119
  "header": "Which layout?",
114
- "label": "layout",
120
+ "tab": "layout",
115
121
  "options": [
116
122
  {
117
123
  "label": "Sidebar",
118
- "value": "sidebar",
119
124
  "description": "Left-side navigation with the main content to its right.",
120
- "preview": "┌──┬────────┐\n│导│ 正文 │\n│航│ │\n└──┴────────┘\n左侧固定导航"
125
+ "preview": "┌──┬────────┐\n│NA│ body │\n│V │ │\n└──┴────────┘\nleft sidebar nav"
121
126
  },
122
127
  {
123
128
  "label": "Top bar",
124
- "value": "topbar",
125
129
  "description": "Top horizontal nav with the main content below.",
126
- "preview": "┌──────────────┐\n│ 导航条 │\n├──────────────┤\n│ 正文 │\n└──────────────┘\n顶部横向导航"
130
+ "preview": "┌──────────────┐\n│ nav bar │\n├──────────────┤\n│ body │\n└──────────────┘\ntop horizontal nav"
127
131
  }
128
132
  ]
129
133
  }
@@ -136,26 +140,90 @@ contains a newline renders verbatim as a fixed-width block.
136
140
 
137
141
  After the last question is answered, a **review screen** lists every question
138
142
  and its answer (multi-select answers are comma-joined and truncated with `…`
139
- when too long; skipped questions show `(skipped)`). Each entry spans two rows:
140
- the question header and, below it, the answer:
143
+ when too long; skipped questions show `(skipped)`). Each question entry spans
144
+ two rows (header + answer), followed by a trailing **note entry**:
141
145
 
142
146
  ```
143
- ▸ Which layout?
144
- Sidebar
145
- Which database?
146
- Postgres
147
+ 1. Which layout?
148
+ Sidebar
149
+ 2. Which database?
150
+ Postgres
151
+
152
+ ✎ Note to assistant
153
+ (optional — Tab to add a note)
147
154
  ```
148
155
 
149
- - `↑`/`↓` move the cursor between questions
150
- - `PgUp`/`PgDn` scroll by page (when there are more questions than rows)
151
- - `Tab` jump to the focused question to edit it (returns to the review after)
152
- - `Enter` confirm and submit all answers
156
+ Each title carries a fixed-width marker (`1.`/`2.`… for questions, `✎ ` for
157
+ the note) so every title aligns; the body is indented one level deeper to keep
158
+ header vs content visually distinct. An empty line sets the note apart from
159
+ the Q&A list above it.
160
+
161
+ Each title carries a fixed-width marker (`1.`/`2.`… for questions, `✎ ` for
162
+ the note) so every title aligns; the body is indented one level deeper to keep
163
+ header vs content visually distinct.
164
+
165
+ - `↑`/`↓` — move the cursor between entries (questions + the note)
166
+ - `PgUp`/`PgDn` — scroll by page (when there are more entries than rows)
167
+ - `Tab` — edit the focused entry: a question (returns to review after) or the
168
+ note (opens a free-form editor)
169
+ - `Enter` — confirm and submit all answers. `Enter` is always "submit" on the
170
+ review screen, never "edit" — this deliberately differs from the question
171
+ screens (where `Enter` edits/advances) so you can never submit by
172
+ double-tapping `Enter` while trying to edit something. Use `Tab` to edit.
153
173
  - `Esc` — cancel
154
174
 
175
+ ### Note to assistant
176
+
177
+ The review screen ends with a **note** entry — a free-form message the user
178
+ can attach for the assistant, about anything *beyond* the specific questions
179
+ (overall direction, pacing, priorities, a correction to the premise, …).
180
+
181
+ - Move the cursor to the note row and press `Tab` to open the editor.
182
+ - `Enter` saves the note (empty = no note); `Esc` returns to the review without
183
+ saving (the in-progress draft is kept).
184
+ - The note is **out-of-band**: it is not part of `questions`/`options` and the
185
+ assistant cannot request or pre-fill it. It surfaces only in the tool result
186
+ as `message`, and only when non-empty.
187
+
188
+ Because the note can reframe or override the answers, the assistant is told to
189
+ treat it as high-priority context.
190
+
155
191
  ### Result
156
192
 
157
- The tool returns a summary plus structured `answers`. If the user cancelled
158
- mid-way, the message notes how many questions were answered before cancellation.
193
+ The tool result returned to the model is **JSON**, shaped symmetrically with
194
+ the questions schema so the model can correlate each answer back to its own
195
+ question by the `tab` key:
196
+
197
+ ```json
198
+ {
199
+ "cancelled": false,
200
+ "answers": [
201
+ { "tab": "layout", "answer": "Sidebar" },
202
+ { "tab": "extras", "answers": ["dark-mode"], "custom": "also add export-to-pdf" },
203
+ { "tab": "db", "skipped": true }
204
+ ],
205
+ "message": "leaning towards the minimal option"
206
+ }
207
+ ```
208
+
209
+ Only the relevant fields appear per answer (no noise):
210
+
211
+ | Situation | Fields |
212
+ |-----------|--------|
213
+ | single-select, option picked | `answer` |
214
+ | single-select, custom typed | `custom` |
215
+ | multi-select, options picked | `answers: [...]` |
216
+ | multi-select, options + custom | `answers` + `custom` |
217
+ | multi-select, custom only | `custom` |
218
+ | multi-select, empty commit (skippable, submitted with nothing) | `answers: []` |
219
+ | any question, Tab-skipped | `skipped: true` |
220
+
221
+ `custom` is always a sibling of `answer`/`answers`, never mixed in — it signals
222
+ the user typed something outside the offered options. The top-level `message`
223
+ (the user's review-screen note) appears only when non-empty. `cancelled: true`
224
+ means the user pressed `Esc`; `answers` still lists whatever was answered
225
+ before cancellation. This JSON shape replaces the old `"tab: answer"` text
226
+ format, which could break when a custom answer contained a colon or newline.
159
227
 
160
228
  ## Keys
161
229
 
@@ -164,8 +232,8 @@ mid-way, the message notes how many questions were answered before cancellation.
164
232
  | `↑` `↓` / `PgUp` `PgDn` | Move cursor / scroll options |
165
233
  | `Space` | Commit selection (single: select-only; multi: toggle) |
166
234
  | `Enter` | Confirm & advance (single) / commit checked (multi) / enter custom input |
167
- | `Tab` / `Shift+Tab` | Next / previous question (option list only — not hijacked inside the editor) |
168
- | `→` / `←` | Same as `Tab` / `Shift+Tab` |
235
+ | `Tab` / `Shift+Tab` | Next / previous question, **cycling** (last → first). Option list only — not hijacked inside the editor |
236
+ | `→` / `←` | Next / previous question, but **stop at the boundary** (no cycle) — safer than Tab when there are many questions |
169
237
  | `Esc` | Cancel (or exit custom-input editor without saving) |
170
238
  | `Ctrl+\` | Collapse / expand the panel |
171
239
 
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@d3ara1n/pi-ask-user",
3
- "version": "1.0.0",
4
- "description": "Ask-user tool for pi — collapsible question overlay that releases focus so you can scroll the transcript while deciding",
3
+ "version": "2.1.0",
4
+ "type": "module",
5
+ "description": "Ask-user tool for pi — renders in the bottom editor slot (not a screen overlay), so the transcript stays visible and scrollable above the panel while you decide",
5
6
  "main": "src/index.ts",
6
7
  "keywords": [
7
8
  "pi-package",