@capdiem/pi-ask-user 0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 capdiem
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # Pi Ask User
2
+
3
+ An interactive `ask_user` form tool for the [Pi coding agent](https://pi.dev/).
4
+
5
+ When the model needs your decision, preference, or input — especially during a
6
+ grilling / design-interview session — it calls `ask_user` and you answer in an
7
+ interactive form instead of reading a plain-text `Q1..QN` block and replying
8
+ with numbered text.
9
+
10
+ ## Features
11
+
12
+ - **One call, many questions** — pass the whole round (a grilling frontier, a
13
+ clarification batch) as a single form with up to 10 questions.
14
+ - **Choice and free-text** — each question is `type: "choice"` (options list,
15
+ with an optional "Type something" free-text escape) or `type: "text"`.
16
+ - **Question numbering (optional)** — set `numbered: true` to label questions `Q1`, `Q2`, … in the form body with an optional short title (`Q1 - Scope:`), mirroring the original grilling format. Ordinary (non-grill) forms show just the prompt.
17
+ - **Recommended-answer hints** — each question may carry a `recommendation` (the grilling skill's `➡️ recommended answer`). When it matches one of a choice question's options, that option is marked with a **`★`** between the option number and the label (bold label), and its description (muted) plus the recommendation detail (default + bold, wrapped as `(推荐:…)`) are shown together on one line — a leading title in the recommendation is stripped (grill shape `<标题> - <详情>`, taking only the `<详情>` after the first dash). Otherwise the recommendation appears dimmed under the question as `Recommended: …`.
18
+ - **TUI mode** — a full-screen tabbed form (↑↓ select, Tab/←→ switch, Enter
19
+ confirm, Esc cancel) via `ctx.ui.custom()`.
20
+ - **RPC mode** — the same questions as sequential `select`/`input` dialogs over
21
+ the [extension UI protocol](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/rpc.md#extension-ui-protocol).
22
+ The recommended option's label carries the `(推荐:…)` detail merged in,
23
+ and the selected display string is reverse-mapped back to the original option.
24
+ - **Graceful fallback** — in `print`/`json` modes it returns the questions as
25
+ numbered text so the model asks in plain text, exactly like the old format.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pi install npm:pi-ask-user
31
+ ```
32
+
33
+ Or load it directly from source during development:
34
+
35
+ ```bash
36
+ pi -e ./extensions/pi-ask-user/index.ts
37
+ ```
38
+
39
+ ## Tool: `ask_user`
40
+
41
+ The model calls `ask_user` with a `questions` array. Each question:
42
+
43
+ ```json
44
+ {
45
+ "id": "q1",
46
+ "title": "Scope",
47
+ "prompt": "Should the scope include the migration path?",
48
+ "type": "choice",
49
+ "options": [
50
+ { "value": "include", "label": "Include it", "description": "Safer, bigger" },
51
+ { "value": "exclude", "label": "Exclude it", "description": "Faster to ship" }
52
+ ],
53
+ "allowOther": true,
54
+ "recommendation": "include — deferring it now is much harder to reverse later"
55
+ }
56
+ ```
57
+
58
+ For free-text questions use `type: "text"` (no `options` needed).
59
+
60
+ The top-level call accepts an optional `numbered` flag (default `false`):
61
+
62
+ - `numbered: true` — grilling style: each question is prefixed `Q1`, `Q2`, …
63
+ in the form body (`Q1 - Scope: …`) and in results.
64
+ - omitted/false — ordinary form: only the prompt is shown (plus the optional
65
+ `title`, e.g. `Scope: …`).
66
+
67
+ ## Wiring it into grilling skills
68
+
69
+ `ask_user` ships `promptGuidelines` that tell the model to prefer the form over
70
+ plain-text `Q1..QN`. To make a grilling skill (e.g. Matt Pocock's
71
+ `/grilling`) deterministic, add one line to the skill:
72
+
73
+ > Present each round's frontier via the `ask_user` tool as a form. If the
74
+ > `ask_user` tool is not available, fall back to numbered plain-text questions.
75
+
76
+ ## License
77
+
78
+ MIT
package/index.min.js ADDED
@@ -0,0 +1,13 @@
1
+ import{StringEnum as Gj}from"@earendil-works/pi-ai";import{Editor as Hj,Key as O,matchesKey as g,Text as i,visibleWidth as Jj,wrapTextWithAnsi as a}from"@earendil-works/pi-tui";import{Type as B}from"typebox";var Vj=B.Object({value:B.String({description:"Value returned to the LLM when this option is selected"}),label:B.String({description:"Display label shown to the user"}),description:B.Optional(B.String({description:"Optional description shown under the label"}))}),Xj=B.Object({id:B.String({description:"Unique question id (e.g. q1, q2, scope)"}),title:B.Optional(B.String({description:"Optional short title shown after the question number, e.g. 'Scope'"})),prompt:B.String({description:"Full question text to display"}),type:Gj(["choice","text"],{description:"'choice' for an options list, 'text' for free-form input"}),options:B.Optional(B.Array(Vj,{description:"Options for type=choice (required for choice)"})),allowOther:B.Optional(B.Boolean({description:"Choice questions: allow free-text 'Type something' (default true)"})),recommendation:B.Optional(B.String({description:"Your recommended answer (the grilling '➡️ recommended answer'). When it matches an option's value or label, that option is highlighted in the form."}))}),Yj=B.Object({questions:B.Array(Xj,{description:"One or more questions to ask the user in a single interactive form",minItems:1,maxItems:10}),numbered:B.Optional(B.Boolean({description:"Prefix each question with its number (Q1, Q2…) in grilling style. Default: false."}))});function Zj(J){return J.questions.map((z,H)=>({...z,allowOther:z.allowOther!==!1,options:z.type==="choice"?z.options??[]:void 0,number:H+1,numbered:J.numbered===!0}))}function I(J){if(J.numbered)return`Q${J.number}${J.title?` - ${J.title}`:""}`;return J.title??""}function _j(J,z){let H=J.recommendation?.trim().toLowerCase();if(!H)return!1;let j=(z.value??"").trim().toLowerCase(),V=(z.label??"").trim().toLowerCase();if(j&&H===j)return!0;if(V&&H===V)return!0;if(j.length>=3&&H.includes(j))return!0;if(V.length>=3&&H.includes(V))return!0;if(H.length>=3){if(j&&j.includes(H))return!0;if(V&&V.includes(H))return!0}return!1}function p(J){if(!J.recommendation||J.type!=="choice")return-1;let z=J.options??[];for(let H=0;H<z.length;H++)if(_j(J,z[H]))return H;return-1}function h(J,z){let H=J.trim();if(z)for(let V of[z.label,z.value]){let Y=V?.trim();if(!Y)continue;if(H.toLowerCase().startsWith(Y.toLowerCase())){H=H.slice(Y.length).trimStart();break}}let j=H.match(/[\u2014\u2013]| - |-/);if(j&&typeof j.index==="number")H=H.slice(j.index+j[0].length).replace(/^[\s::,,、.…-]+/,"");return H.trim()||J.trim()}function $j(J,z,H=!0){return{content:[{type:"text",text:J}],details:{questions:z,answers:[],cancelled:H}}}function t(J,z){return z.map((j)=>{let V=J.find((N)=>N.id===j.id),Y=V?I(V)||`Q${V.number}`:j.id;if(j.wasCustom)return`${Y}: user wrote: ${j.label}`;let F=j.index?` ${j.index}.`:"";return`${Y}: user selected:${F} ${j.label}`}).join(`
2
+ `)}function Dj(J){return{borderColor:(z)=>J.fg("accent",z),selectList:{selectedPrefix:(z)=>J.fg("accent",z),selectedText:(z)=>J.fg("accent",z),description:(z)=>J.fg("muted",z),scrollInfo:(z)=>J.fg("dim",z),noMatch:(z)=>J.fg("warning",z)}}}async function Fj(J,z){return J.ui.custom((H,j,V,Y)=>{let F=z.length>1,N=z.length,G=0,$=0,U=!1,D=null,k,A=new Map,P=new Hj(H,Dj(j));function v(){k=void 0,H.requestRender()}function W(X){Y({questions:z,answers:Array.from(A.values()),cancelled:X})}function y(){return z[G]}function m(){let X=y();if(!X||X.type!=="choice")return[];let Z=[...X.options??[]];if(X.allowOther)Z.push({value:"__other__",label:"Type something.",isOther:!0});return Z}function c(){return z.every((X)=>A.has(X.id))}function o(){if(!F){W(!1);return}if(G<N-1)G++;else G=N;$=0,U=!1,D=null,v()}function r(X,Z,L,K,C){A.set(X,{id:X,value:Z,label:L,wasCustom:K,index:C})}function d(X){U=!0,D=X,P.setText(""),v()}P.onSubmit=(X)=>{if(!D)return;let Z=X.trim()||"(no response)";r(D,Z,Z,!0),U=!1,D=null,P.setText(""),o()};function e(X){if(U){if(g(X,O.escape)){U=!1,D=null,P.setText(""),v();return}P.handleInput(X),v();return}if(F){if(g(X,O.tab)||g(X,O.right)){G=(G+1)%(N+1),$=0,v();return}if(g(X,O.shift("tab"))||g(X,O.left)){G=(G-1+N+1)%(N+1),$=0,v();return}}let Z=y();if(G===N){if(g(X,O.enter)&&c())W(!1);else if(g(X,O.escape))W(!0);return}if(!Z)return;if(Z.type==="text"){if(g(X,O.enter))d(Z.id);else if(g(X,O.escape))W(!0);return}let L=m();if(g(X,O.up)){$=Math.max(0,$-1),v();return}if(g(X,O.down)){$=Math.min(L.length-1,$+1),v();return}if(g(X,O.enter)){let K=L[$];if(!K)return;if(K.isOther){d(Z.id);return}r(Z.id,K.value,K.label,!1,$+1),o();return}if(g(X,O.escape))W(!0)}function jj(X){if(k)return k;let Z=[],L=Math.max(1,X);function K(_){Z.push(...a(_,L))}function C(_,E){let Q=Jj(_);if(Q>=L){K(_+E);return}let S=a(E,L-Q),M=" ".repeat(Q);for(let R=0;R<S.length;R++)Z.push(`${R===0?_:M}${S[R]}`)}function n(_){let E=p(_)>=0,Q=I(_),S=Q?`${j.fg("accent",j.bold(`${Q}:`))} ${j.fg("text",_.prompt)}`:j.fg("text",_.prompt);if(C(" ",S),_.recommendation&&!E)Z.push(""),C(" ",j.fg("dim",`Recommended: ${h(_.recommendation)}`));Z.push("")}if(Z.push(j.fg("accent","─".repeat(L))),F){let _=["← "];for(let R=0;R<z.length;R++){let T=R===G,f=A.has(z[R].id),q=z[R].title??`Q${R+1}`,b=f?"■":"□",u=f?"success":"muted",x=` ${b} ${q} `,l=T?j.bg("selectedBg",j.fg("text",x)):j.fg(u,x);_.push(`${l} `)}let E=c(),Q=G===N,S=" ✓ Submit ",M=Q?j.bg("selectedBg",j.fg("text",S)):j.fg(E?"success":"dim",S);_.push(`${M} →`),C(" ",_.join("")),Z.push("")}function s(){let _=m(),E=y(),Q=E?p(E):-1;for(let S=0;S<_.length;S++){let M=_[S],R=S===$,T=M.isOther===!0,f=!T&&S===Q,q=R?j.fg("accent","> "):" ",b=`${S+1}. ${f?"★ ":""}${M.label}${T&&U?" ✎":""}`,u=f?j.bold(b):b,x=R||T&&U?"accent":"text";if(C(q,j.fg(x,u)),f&&E?.recommendation){let l=h(E.recommendation,M),zj=M.description?j.fg("muted",M.description):"";C(" ",zj+j.bold(`(推荐:${l})`))}else if(M.description)C(" ",j.fg("muted",M.description))}}let w=y();if(U&&w){if(n(w),w.type==="choice")s();Z.push(""),C(" ",j.fg("muted","Your answer:"));for(let _ of P.render(Math.max(1,L-2)))Z.push(` ${_}`);Z.push(""),C(" ",j.fg("dim","Enter to submit • Esc to cancel"))}else if(G===N){C(" ",j.fg("accent",j.bold("Ready to submit"))),Z.push("");for(let _ of z){let E=A.get(_.id);if(E){let Q=E.wasCustom?"(wrote) ":"",S=`${j.fg("muted",`${I(_)}: `)}${j.fg("text",Q+E.label)}`;C(" ",S)}}if(Z.push(""),c())C(" ",j.fg("success","Press Enter to submit"));else{let _=z.filter((E)=>!A.has(E.id)).map((E)=>I(E)).join(", ");C(" ",j.fg("warning",`Unanswered: ${_}`))}}else if(w)if(n(w),w.type==="choice")s();else{let _=A.get(w.id);if(C(" ",j.fg("muted","Free-form answer")),Z.push(""),_)C(" ",j.fg("text",` ${_.label}`)),Z.push(""),C(" ",j.fg("dim","Enter to edit • Esc cancel"));else C(" ",j.fg("dim","Press Enter to type your answer"))}if(Z.push(""),!U){let _=F?"Tab/←→ navigate • ↑↓ select • Enter confirm • Esc cancel":"↑↓ select • Enter confirm • Esc cancel";C(" ",j.fg("dim",_))}return Z.push(j.fg("accent","─".repeat(L))),k=Z,Z}return{render:jj,invalidate:()=>{k=void 0},handleInput:e}})}async function Nj(J,z){let H=[],j=!1;for(let V of z)if(V.type==="choice"){let Y=V.options??[],F=p(V),N=Y.map((D,k)=>({index:k,label:k===F&&V.recommendation?`${D.label}(推荐:${h(V.recommendation,D)})`:D.label}));if(V.allowOther)N.push({index:-1,label:"Type something..."});let G=I(V),$=G?`${G}: ${V.prompt}`:V.prompt,U=await J.ui.select($,N.map((D)=>D.label));if(U===void 0){j=!0;break}if(U==="Type something..."){let D=await J.ui.input(V.prompt,"Type your answer");if(D===void 0){j=!0;break}H.push({id:V.id,value:D,label:D,wasCustom:!0})}else{let D=N.find((P)=>P.label===U),k=D?D.index:-1,A=k>=0?Y[k]:void 0;H.push({id:V.id,value:A?.value??U,label:A?.label??U,wasCustom:!1,index:k>=0?k+1:void 0})}}else{let Y=await J.ui.input(V.prompt,I(V)||V.prompt);if(Y===void 0){j=!0;break}H.push({id:V.id,value:Y,label:Y,wasCustom:!0})}return{questions:z,answers:H,cancelled:j}}function Uj(J){J.registerTool({name:"ask_user",label:"Ask User",description:"Ask the user one or more questions as an interactive form (choice options or free-text). "+"Use when you need the user's decision, preference, or input to continue — especially to "+"present a round of design/planning questions with your recommended answer for each. Each question may include a 'recommendation'; when it matches one of the options, that option is highlighted.",promptSnippet:"Ask the user questions through an interactive form",promptGuidelines:["Use ask_user to put questions to the user as an interactive form instead of printing plain-text Q1..QN blocks.","When a single turn has multiple related questions (e.g. a grilling round's frontier), pass them all in one ask_user call — one question per entry, with type 'choice' or 'text'.","For each question you can include a 'recommendation' with your recommended answer. When it matches an option's value or label, that option is highlighted in the form; otherwise it is shown as a hint under the question.","For grilling-style rounds set numbered: true so the questions are prefixed Q1/Q2. For ordinary questions omit it — the form then shows just the prompt (plus an optional title).","ask_user works in TUI mode (full form) and RPC mode (sequential dialogs). In print/json mode it returns the questions as text so you can ask them in plain text.","If ask_user reports 'cancelled', stop and let the user redirect instead of re-asking the same questions."],parameters:Yj,executionMode:"sequential",async execute(z,H,j,V,Y){if(j?.aborted)return $j("Cancelled",[],!0);let F=Zj(H);if(Y.mode==="tui"){let G=await Fj(Y,F);if(G.cancelled)return{content:[{type:"text",text:"User cancelled the questions"}],details:G};return{content:[{type:"text",text:t(F,G.answers)}],details:G}}if(Y.mode==="rpc"){let G=await Nj(Y,F);if(G.cancelled)return{content:[{type:"text",text:"User cancelled the questions"}],details:G};return{content:[{type:"text",text:t(F,G.answers)}],details:G}}return{content:[{type:"text",text:`Interactive form unavailable in this mode. Ask the user the following questions as plain text (numbered Q1..QN):
3
+
4
+ `+F.map((G)=>{let $=G.recommendation?`
5
+ Recommended: ${G.recommendation}`:"",U=G.type==="choice"?`
6
+ Options: ${(G.options??[]).map((k)=>`${k.label}`).join(" | ")}`:"",D=I(G);return`${D?`${D}: `:""}${G.prompt}${U}${$}`}).join(`
7
+
8
+ `)}],details:{questions:F,answers:[],cancelled:!1}}},renderCall(z,H,j){let V=j.lastComponent??new i("",0,0),Y=Array.isArray(z.questions)?z.questions:[],F=Y.map((G,$)=>G.title?`Q${$+1} - ${G.title}`:G.id||`Q${$+1}`).join(", "),N=H.fg("toolTitle",H.bold("ask_user "));if(N+=H.fg("muted",`${Y.length} question${Y.length!==1?"s":""}`),F)N+=H.fg("dim",` (${F})`);return V.setText(N),V},renderResult(z,H,j,V){let Y=V.lastComponent??new i("",0,0),F=z.details;if(!F){let G=z.content.filter(($)=>$.type==="text").map(($)=>$.text).join(`
9
+ `);return Y.setText(j.fg("warning",G||"ask_user")),Y}if(F.cancelled)return Y.setText(j.fg("warning","Cancelled")),Y;let N=F.answers.map((G)=>{let $=F.questions.find((k)=>k.id===G.id),U=$?I($)||`Q${$.number}`:G.id;if(G.wasCustom)return`${j.fg("success","✓ ")}${j.fg("accent",U)}: ${j.fg("muted","(wrote) ")}${G.label}`;let D=G.index?`${G.index}. ${G.label}`:G.label;return`${j.fg("success","✓ ")}${j.fg("accent",U)}: ${D}`});return Y.setText(N.join(`
10
+ `)),Y}})}export{Uj as default};
11
+
12
+ //# debugId=26EC0FD3FCF63F7E64756E2164756E21
13
+ //# sourceMappingURL=index.min.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["..\\..\\extensions\\pi-ask-user\\index.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * Ask User Questions - interactive question form tool\n *\n * Lets the LLM ask the user one or more questions as an interactive form\n * (choice + free-text), instead of dumping plain-text \"Q1..QN\" blocks.\n *\n * Mode behavior:\n * - TUI mode: full-screen tabbed form via ctx.ui.custom()\n * - RPC mode: per-question select/input dialogs over the extension UI protocol\n * - print/json mode: structured fallback so the LLM can ask in plain text\n *\n * Each question carries a \"recommendation\" hint (the grilling skill's\n * \"➡️ recommended answer\"). When it matches one of a choice question's\n * options, it is highlighted on that option; otherwise it is shown dimmed\n * under the prompt.\n */\n\nimport type { ExtensionAPI, ExtensionContext, Theme } from \"@earendil-works/pi-coding-agent\";\nimport { StringEnum } from \"@earendil-works/pi-ai\";\nimport {\n Editor,\n type EditorTheme,\n Key,\n matchesKey,\n Text,\n visibleWidth,\n wrapTextWithAnsi,\n} from \"@earendil-works/pi-tui\";\nimport { Type } from \"typebox\";\n\n// ---------- Types ----------\n\ninterface QuestionOption {\n value: string;\n label: string;\n description?: string;\n}\n\ninterface Question {\n id: string;\n title?: string;\n prompt: string;\n type: \"choice\" | \"text\";\n options?: QuestionOption[];\n allowOther: boolean;\n recommendation?: string;\n number: number;\n numbered: boolean;\n}\n\ninterface Answer {\n id: string;\n value: string;\n label: string;\n wasCustom: boolean;\n index?: number;\n}\n\ninterface AskUserResult {\n questions: Question[];\n answers: Answer[];\n cancelled: boolean;\n}\n\n// ---------- Schema ----------\n\nconst QuestionOptionSchema = Type.Object({\n value: Type.String({ description: \"Value returned to the LLM when this option is selected\" }),\n label: Type.String({ description: \"Display label shown to the user\" }),\n description: Type.Optional(\n Type.String({ description: \"Optional description shown under the label\" }),\n ),\n});\n\nconst QuestionSchema = Type.Object({\n id: Type.String({ description: \"Unique question id (e.g. q1, q2, scope)\" }),\n title: Type.Optional(\n Type.String({ description: \"Optional short title shown after the question number, e.g. 'Scope'\" }),\n ),\n prompt: Type.String({ description: \"Full question text to display\" }),\n type: StringEnum([\"choice\", \"text\"] as const, {\n description: \"'choice' for an options list, 'text' for free-form input\",\n }),\n options: Type.Optional(\n Type.Array(QuestionOptionSchema, { description: \"Options for type=choice (required for choice)\" }),\n ),\n allowOther: Type.Optional(\n Type.Boolean({ description: \"Choice questions: allow free-text 'Type something' (default true)\" }),\n ),\n recommendation: Type.Optional(\n Type.String({\n description:\n \"Your recommended answer (the grilling '➡️ recommended answer'). When it matches an option's value or label, that option is highlighted in the form.\",\n }),\n ),\n});\n\nconst AskUserParams = Type.Object({\n questions: Type.Array(QuestionSchema, {\n description: \"One or more questions to ask the user in a single interactive form\",\n minItems: 1,\n maxItems: 10,\n }),\n numbered: Type.Optional(\n Type.Boolean({\n description: \"Prefix each question with its number (Q1, Q2…) in grilling style. Default: false.\",\n }),\n ),\n});\n\n// ---------- Helpers ----------\n\n/** Raw question shape as accepted by the tool (optional fields not yet normalized). */\ninterface RawQuestion {\n id: string;\n title?: string;\n prompt: string;\n type: \"choice\" | \"text\";\n options?: QuestionOption[];\n allowOther?: boolean;\n recommendation?: string;\n}\n\nfunction defaultQuestions(params: {\n questions: RawQuestion[];\n numbered?: boolean;\n}): Question[] {\n return params.questions.map((q, i) => ({\n ...q,\n allowOther: q.allowOther !== false,\n options: q.type === \"choice\" ? q.options ?? [] : undefined,\n number: i + 1,\n numbered: params.numbered === true,\n }));\n}\n\n/**\n * Question label: \"Q1 - Scope\" when numbered, \"Scope\" when not. Empty string\n * when not numbered and no title is set.\n */\nfunction questionLabel(q: Pick<Question, \"number\" | \"numbered\" | \"title\">): string {\n if (q.numbered) return `Q${q.number}${q.title ? ` - ${q.title}` : \"\"}`;\n return q.title ?? \"\";\n}\n\n/** Whether a question's recommendation points at a specific option (heuristic match). */\nfunction recommendationMatch(q: Question, opt: QuestionOption): boolean {\n const rec = q.recommendation?.trim().toLowerCase();\n if (!rec) return false;\n const value = (opt.value ?? \"\").trim().toLowerCase();\n const label = (opt.label ?? \"\").trim().toLowerCase();\n if (value && rec === value) return true;\n if (label && rec === label) return true;\n if (value.length >= 3 && rec.includes(value)) return true;\n if (label.length >= 3 && rec.includes(label)) return true;\n if (rec.length >= 3) {\n if (value && value.includes(rec)) return true;\n if (label && label.includes(rec)) return true;\n }\n return false;\n}\n\n/**\n * Index of the option a question's recommendation points at, or -1 when the\n * recommendation is absent or matches no option. Returns the first match.\n */\nfunction recommendedIndex(q: Question): number {\n if (!q.recommendation || q.type !== \"choice\") return -1;\n const opts = q.options ?? [];\n for (let i = 0; i < opts.length; i++) {\n if (recommendationMatch(q, opts[i])) return i;\n }\n return -1;\n}\n\n/**\n * Reduce a recommendation to its \"detail\" for display.\n * Grill recommendation shape is \"<标题> - <详情>\"; we take only the <详情>\n * after the first dash separator (em/en dash, spaced hyphen, fullwidth\n * hyphen-minus), so a leading echo of the option title is dropped even when\n * the title carries extra text like \"(现状)\". A leading echo of the option\n * label/value is also stripped for the no-dash case.\n */\nfunction stripRecommendationTitle(rec: string, opt?: QuestionOption): string {\n let cleaned = rec.trim();\n if (opt) {\n for (const token of [opt.label, opt.value]) {\n const t = token?.trim();\n if (!t) continue;\n if (cleaned.toLowerCase().startsWith(t.toLowerCase())) {\n cleaned = cleaned.slice(t.length).trimStart();\n break;\n }\n }\n }\n const m = cleaned.match(/[\\u2014\\u2013]| - |-/);\n if (m && typeof m.index === \"number\") {\n cleaned = cleaned\n .slice(m.index + m[0].length)\n .replace(/^[\\s::,,、.…-]+/, \"\");\n }\n return cleaned.trim() || rec.trim();\n}\n\nfunction textResult(\n message: string,\n questions: Question[],\n cancelled = true,\n): { content: { type: \"text\"; text: string }[]; details: AskUserResult } {\n return {\n content: [{ type: \"text\", text: message }],\n details: { questions, answers: [], cancelled },\n };\n}\n\nfunction formatAnswers(questions: Question[], answers: Answer[]): string {\n const lines = answers.map((a) => {\n const q = questions.find((x) => x.id === a.id);\n const label = q ? questionLabel(q) || `Q${q.number}` : a.id;\n if (a.wasCustom) return `${label}: user wrote: ${a.label}`;\n const idx = a.index ? ` ${a.index}.` : \"\";\n return `${label}: user selected:${idx} ${a.label}`;\n });\n return lines.join(\"\\n\");\n}\n\n// ---------- TUI form ----------\n\nfunction editorTheme(theme: Theme): EditorTheme {\n return {\n borderColor: (s) => theme.fg(\"accent\", s),\n selectList: {\n selectedPrefix: (t) => theme.fg(\"accent\", t),\n selectedText: (t) => theme.fg(\"accent\", t),\n description: (t) => theme.fg(\"muted\", t),\n scrollInfo: (t) => theme.fg(\"dim\", t),\n noMatch: (t) => theme.fg(\"warning\", t),\n },\n };\n}\n\nasync function presentTuiForm(\n ctx: ExtensionContext,\n questions: Question[],\n): Promise<AskUserResult> {\n return ctx.ui.custom<AskUserResult>((tui, theme, _kb, done) => {\n const isMulti = questions.length > 1;\n const submitTab = questions.length;\n\n let currentTab = 0;\n let optionIndex = 0;\n let inputMode = false;\n let inputQuestionId: string | null = null;\n let cachedLines: string[] | undefined;\n const answers = new Map<string, Answer>();\n\n const editor = new Editor(tui, editorTheme(theme));\n\n // ---------- helpers ----------\n\n function refresh() {\n cachedLines = undefined;\n tui.requestRender();\n }\n\n function submit(cancelled: boolean) {\n done({ questions, answers: Array.from(answers.values()), cancelled });\n }\n\n function currentQuestion(): Question | undefined {\n return questions[currentTab];\n }\n\n function currentOptions(): Array<QuestionOption & { isOther?: boolean }> {\n const q = currentQuestion();\n if (!q || q.type !== \"choice\") return [];\n const opts: Array<QuestionOption & { isOther?: boolean }> = [...(q.options ?? [])];\n if (q.allowOther) {\n opts.push({ value: \"__other__\", label: \"Type something.\", isOther: true });\n }\n return opts;\n }\n\n function allAnswered(): boolean {\n return questions.every((q) => answers.has(q.id));\n }\n\n function advanceAfterAnswer() {\n if (!isMulti) {\n submit(false);\n return;\n }\n if (currentTab < submitTab - 1) {\n currentTab++;\n } else {\n currentTab = submitTab;\n }\n optionIndex = 0;\n inputMode = false;\n inputQuestionId = null;\n refresh();\n }\n\n function saveAnswer(\n questionId: string,\n value: string,\n label: string,\n wasCustom: boolean,\n index?: number,\n ) {\n answers.set(questionId, { id: questionId, value, label, wasCustom, index });\n }\n\n function openInput(questionId: string) {\n inputMode = true;\n inputQuestionId = questionId;\n editor.setText(\"\");\n refresh();\n }\n\n editor.onSubmit = (value) => {\n if (!inputQuestionId) return;\n const trimmed = value.trim() || \"(no response)\";\n saveAnswer(inputQuestionId, trimmed, trimmed, true);\n inputMode = false;\n inputQuestionId = null;\n editor.setText(\"\");\n advanceAfterAnswer();\n };\n\n // ---------- input ----------\n\n function handleInput(data: string) {\n if (inputMode) {\n if (matchesKey(data, Key.escape)) {\n inputMode = false;\n inputQuestionId = null;\n editor.setText(\"\");\n refresh();\n return;\n }\n editor.handleInput(data);\n refresh();\n return;\n }\n\n // Tab navigation (multi-question only)\n if (isMulti) {\n if (matchesKey(data, Key.tab) || matchesKey(data, Key.right)) {\n currentTab = (currentTab + 1) % (submitTab + 1);\n optionIndex = 0;\n refresh();\n return;\n }\n if (matchesKey(data, Key.shift(\"tab\")) || matchesKey(data, Key.left)) {\n currentTab = (currentTab - 1 + submitTab + 1) % (submitTab + 1);\n optionIndex = 0;\n refresh();\n return;\n }\n }\n\n const q = currentQuestion();\n\n // Submit tab\n if (currentTab === submitTab) {\n if (matchesKey(data, Key.enter) && allAnswered()) {\n submit(false);\n } else if (matchesKey(data, Key.escape)) {\n submit(true);\n }\n return;\n }\n\n if (!q) return;\n\n if (q.type === \"text\") {\n if (matchesKey(data, Key.enter)) {\n openInput(q.id);\n } else if (matchesKey(data, Key.escape)) {\n submit(true);\n }\n return;\n }\n\n // Choice navigation\n const opts = currentOptions();\n if (matchesKey(data, Key.up)) {\n optionIndex = Math.max(0, optionIndex - 1);\n refresh();\n return;\n }\n if (matchesKey(data, Key.down)) {\n optionIndex = Math.min(opts.length - 1, optionIndex + 1);\n refresh();\n return;\n }\n if (matchesKey(data, Key.enter)) {\n const opt = opts[optionIndex];\n if (!opt) return;\n if (opt.isOther) {\n openInput(q.id);\n return;\n }\n saveAnswer(q.id, opt.value, opt.label, false, optionIndex + 1);\n advanceAfterAnswer();\n return;\n }\n if (matchesKey(data, Key.escape)) {\n submit(true);\n }\n }\n\n // ---------- render ----------\n\n function render(width: number): string[] {\n if (cachedLines) return cachedLines;\n\n const lines: string[] = [];\n const renderWidth = Math.max(1, width);\n\n function addWrapped(text: string) {\n lines.push(...wrapTextWithAnsi(text, renderWidth));\n }\n\n function addWrappedWithPrefix(prefix: string, text: string) {\n const prefixWidth = visibleWidth(prefix);\n if (prefixWidth >= renderWidth) {\n addWrapped(prefix + text);\n return;\n }\n const wrapped = wrapTextWithAnsi(text, renderWidth - prefixWidth);\n const continuationPrefix = \" \".repeat(prefixWidth);\n for (let i = 0; i < wrapped.length; i++) {\n lines.push(`${i === 0 ? prefix : continuationPrefix}${wrapped[i]}`);\n }\n }\n\n function renderPromptAndRecommendation(q: Question) {\n const recOnOption = recommendedIndex(q) >= 0;\n const label = questionLabel(q);\n const head = label\n ? `${theme.fg(\"accent\", theme.bold(`${label}:`))} ${theme.fg(\"text\", q.prompt)}`\n : theme.fg(\"text\", q.prompt);\n addWrappedWithPrefix(\" \", head);\n if (q.recommendation && !recOnOption) {\n lines.push(\"\");\n addWrappedWithPrefix(\n \" \",\n theme.fg(\"dim\", `Recommended: ${stripRecommendationTitle(q.recommendation)}`),\n );\n }\n lines.push(\"\");\n }\n\n lines.push(theme.fg(\"accent\", \"─\".repeat(renderWidth)));\n\n // Tab bar (multi-question only)\n if (isMulti) {\n const tabs: string[] = [\"← \"];\n for (let i = 0; i < questions.length; i++) {\n const isActive = i === currentTab;\n const isAnswered = answers.has(questions[i].id);\n const lbl = questions[i].title ?? `Q${i + 1}`;\n const box = isAnswered ? \"■\" : \"□\";\n const color = isAnswered ? \"success\" : \"muted\";\n const text = ` ${box} ${lbl} `;\n const styled = isActive\n ? theme.bg(\"selectedBg\", theme.fg(\"text\", text))\n : theme.fg(color, text);\n tabs.push(`${styled} `);\n }\n const canSubmit = allAnswered();\n const isSubmitTab = currentTab === submitTab;\n const submitText = \" ✓ Submit \";\n const submitStyled = isSubmitTab\n ? theme.bg(\"selectedBg\", theme.fg(\"text\", submitText))\n : theme.fg(canSubmit ? \"success\" : \"dim\", submitText);\n tabs.push(`${submitStyled} →`);\n addWrappedWithPrefix(\" \", tabs.join(\"\"));\n lines.push(\"\");\n }\n\n // Render an option list, highlighting the recommended option if any.\n function renderOptions() {\n const opts = currentOptions();\n const q = currentQuestion();\n const recIdx = q ? recommendedIndex(q) : -1;\n for (let i = 0; i < opts.length; i++) {\n const opt = opts[i];\n const selected = i === optionIndex;\n const isOther = opt.isOther === true;\n const recommended = !isOther && i === recIdx;\n const prefix = selected ? theme.fg(\"accent\", \"> \") : \" \";\n const labelBase = `${i + 1}. ${recommended ? \"★ \" : \"\"}${opt.label}${isOther && inputMode ? \" ✎\" : \"\"}`;\n const label = recommended ? theme.bold(labelBase) : labelBase;\n const color = selected || (isOther && inputMode) ? \"accent\" : \"text\";\n addWrappedWithPrefix(prefix, theme.fg(color, label));\n if (recommended && q?.recommendation) {\n // Recommended option: description (muted) then the recommendation\n // detail (default + bold) wrapped as (推荐:<详情>), same line.\n const detail = stripRecommendationTitle(q.recommendation, opt);\n const desc = opt.description ? theme.fg(\"muted\", opt.description) : \"\";\n addWrappedWithPrefix(\" \", desc + theme.bold(`(推荐:${detail})`));\n } else if (opt.description) {\n addWrappedWithPrefix(\" \", theme.fg(\"muted\", opt.description));\n }\n }\n }\n\n const q = currentQuestion();\n\n // Content\n if (inputMode && q) {\n renderPromptAndRecommendation(q);\n if (q.type === \"choice\") renderOptions();\n lines.push(\"\");\n addWrappedWithPrefix(\" \", theme.fg(\"muted\", \"Your answer:\"));\n for (const line of editor.render(Math.max(1, renderWidth - 2))) {\n lines.push(` ${line}`);\n }\n lines.push(\"\");\n addWrappedWithPrefix(\" \", theme.fg(\"dim\", \"Enter to submit • Esc to cancel\"));\n } else if (currentTab === submitTab) {\n addWrappedWithPrefix(\" \", theme.fg(\"accent\", theme.bold(\"Ready to submit\")));\n lines.push(\"\");\n for (const question of questions) {\n const answer = answers.get(question.id);\n if (answer) {\n const prefix = answer.wasCustom ? \"(wrote) \" : \"\";\n const summary = `${theme.fg(\"muted\", `${questionLabel(question)}: `)}${theme.fg(\"text\", prefix + answer.label)}`;\n addWrappedWithPrefix(\" \", summary);\n }\n }\n lines.push(\"\");\n if (allAnswered()) {\n addWrappedWithPrefix(\" \", theme.fg(\"success\", \"Press Enter to submit\"));\n } else {\n const missing = questions\n .filter((x) => !answers.has(x.id))\n .map((x) => questionLabel(x))\n .join(\", \");\n addWrappedWithPrefix(\" \", theme.fg(\"warning\", `Unanswered: ${missing}`));\n }\n } else if (q) {\n renderPromptAndRecommendation(q);\n if (q.type === \"choice\") {\n renderOptions();\n } else {\n const answer = answers.get(q.id);\n addWrappedWithPrefix(\" \", theme.fg(\"muted\", \"Free-form answer\"));\n lines.push(\"\");\n if (answer) {\n addWrappedWithPrefix(\" \", theme.fg(\"text\", ` ${answer.label}`));\n lines.push(\"\");\n addWrappedWithPrefix(\" \", theme.fg(\"dim\", \"Enter to edit • Esc cancel\"));\n } else {\n addWrappedWithPrefix(\" \", theme.fg(\"dim\", \"Press Enter to type your answer\"));\n }\n }\n }\n\n lines.push(\"\");\n if (!inputMode) {\n const help = isMulti\n ? \"Tab/←→ navigate • ↑↓ select • Enter confirm • Esc cancel\"\n : \"↑↓ select • Enter confirm • Esc cancel\";\n addWrappedWithPrefix(\" \", theme.fg(\"dim\", help));\n }\n lines.push(theme.fg(\"accent\", \"─\".repeat(renderWidth)));\n\n cachedLines = lines;\n return lines;\n }\n\n return {\n render,\n invalidate: () => {\n cachedLines = undefined;\n },\n handleInput,\n };\n });\n}\n\n// ---------- RPC fallback (per-question dialogs) ----------\n\nasync function presentRpcDialogs(\n ctx: ExtensionContext,\n questions: Question[],\n): Promise<AskUserResult> {\n const answers: Answer[] = [];\n let cancelled = false;\n\n for (const q of questions) {\n if (q.type === \"choice\") {\n // Merge the recommendation detail into the recommended option's label\n // (e.g. \"保留 fallback 行(推荐:…)\"), then reverse-map the selected\n // display string back to the original option index.\n const opts = q.options ?? [];\n const recIdx = recommendedIndex(q);\n const display: { index: number; label: string }[] = opts.map((o, idx) => ({\n index: idx,\n label:\n idx === recIdx && q.recommendation\n ? `${o.label}(推荐:${stripRecommendationTitle(q.recommendation, o)})`\n : o.label,\n }));\n if (q.allowOther) display.push({ index: -1, label: \"Type something...\" });\n const label = questionLabel(q);\n const title = label ? `${label}: ${q.prompt}` : q.prompt;\n const choice = await ctx.ui.select(title, display.map((d) => d.label));\n if (choice === undefined) {\n cancelled = true;\n break;\n }\n if (choice === \"Type something...\") {\n const value = await ctx.ui.input(q.prompt, \"Type your answer\");\n if (value === undefined) {\n cancelled = true;\n break;\n }\n answers.push({ id: q.id, value, label: value, wasCustom: true });\n } else {\n const found = display.find((d) => d.label === choice);\n const idx = found ? found.index : -1;\n const opt = idx >= 0 ? opts[idx] : undefined;\n answers.push({\n id: q.id,\n value: opt?.value ?? choice,\n label: opt?.label ?? choice,\n wasCustom: false,\n index: idx >= 0 ? idx + 1 : undefined,\n });\n }\n } else {\n const value = await ctx.ui.input(q.prompt, questionLabel(q) || q.prompt);\n if (value === undefined) {\n cancelled = true;\n break;\n }\n answers.push({ id: q.id, value, label: value, wasCustom: true });\n }\n }\n\n return { questions, answers, cancelled };\n}\n\n// ---------- Extension ----------\n\nexport default function askUserExtension(pi: ExtensionAPI): void {\n pi.registerTool({\n name: \"ask_user\",\n label: \"Ask User\",\n description:\n \"Ask the user one or more questions as an interactive form (choice options or free-text). \" +\n \"Use when you need the user's decision, preference, or input to continue — especially to \" +\n \"present a round of design/planning questions with your recommended answer for each. \" +\n \"Each question may include a 'recommendation'; when it matches one of the options, that option is highlighted.\",\n promptSnippet: \"Ask the user questions through an interactive form\",\n promptGuidelines: [\n \"Use ask_user to put questions to the user as an interactive form instead of printing plain-text Q1..QN blocks.\",\n \"When a single turn has multiple related questions (e.g. a grilling round's frontier), pass them all in one ask_user call — one question per entry, with type 'choice' or 'text'.\",\n \"For each question you can include a 'recommendation' with your recommended answer. When it matches an option's value or label, that option is highlighted in the form; otherwise it is shown as a hint under the question.\",\n \"For grilling-style rounds set numbered: true so the questions are prefixed Q1/Q2. For ordinary questions omit it — the form then shows just the prompt (plus an optional title).\",\n \"ask_user works in TUI mode (full form) and RPC mode (sequential dialogs). In print/json mode it returns the questions as text so you can ask them in plain text.\",\n \"If ask_user reports 'cancelled', stop and let the user redirect instead of re-asking the same questions.\",\n ],\n parameters: AskUserParams,\n executionMode: \"sequential\",\n\n async execute(_toolCallId, params, signal, _onUpdate, ctx) {\n if (signal?.aborted) {\n return textResult(\"Cancelled\", [], true);\n }\n const questions = defaultQuestions(params);\n\n if (ctx.mode === \"tui\") {\n const result = await presentTuiForm(ctx, questions);\n if (result.cancelled) {\n return {\n content: [{ type: \"text\", text: \"User cancelled the questions\" }],\n details: result,\n };\n }\n return {\n content: [{ type: \"text\", text: formatAnswers(questions, result.answers) }],\n details: result,\n };\n }\n\n if (ctx.mode === \"rpc\") {\n const result = await presentRpcDialogs(ctx, questions);\n if (result.cancelled) {\n return {\n content: [{ type: \"text\", text: \"User cancelled the questions\" }],\n details: result,\n };\n }\n return {\n content: [{ type: \"text\", text: formatAnswers(questions, result.answers) }],\n details: result,\n };\n }\n\n // Non-interactive modes: structured fallback so the LLM asks in plain text.\n const fallback = questions\n .map((q) => {\n const rec = q.recommendation ? `\\n Recommended: ${q.recommendation}` : \"\";\n const opts =\n q.type === \"choice\"\n ? `\\n Options: ${(q.options ?? []).map((o) => `${o.label}`).join(\" | \")}`\n : \"\";\n const label = questionLabel(q);\n return `${label ? `${label}: ` : \"\"}${q.prompt}${opts}${rec}`;\n })\n .join(\"\\n\\n\");\n return {\n content: [\n {\n type: \"text\",\n text:\n \"Interactive form unavailable in this mode. Ask the user the following questions as plain text (numbered Q1..QN):\\n\\n\" +\n fallback,\n },\n ],\n details: { questions, answers: [], cancelled: false },\n };\n },\n\n renderCall(args, theme, context) {\n const text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n const qs = Array.isArray(args.questions) ? (args.questions as Question[]) : [];\n const labels = qs\n .map((q, i) => (q.title ? `Q${i + 1} - ${q.title}` : q.id || `Q${i + 1}`))\n .join(\", \");\n let content = theme.fg(\"toolTitle\", theme.bold(\"ask_user \"));\n content += theme.fg(\"muted\", `${qs.length} question${qs.length !== 1 ? \"s\" : \"\"}`);\n if (labels) content += theme.fg(\"dim\", ` (${labels})`);\n text.setText(content);\n return text;\n },\n\n renderResult(result, _options, theme, context) {\n const text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n const details = result.details as AskUserResult | undefined;\n if (!details) {\n const out = result.content\n .filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n .map((c) => c.text)\n .join(\"\\n\");\n text.setText(theme.fg(\"warning\", out || \"ask_user\"));\n return text;\n }\n if (details.cancelled) {\n text.setText(theme.fg(\"warning\", \"Cancelled\"));\n return text;\n }\n const lines = details.answers.map((a) => {\n const q = details.questions.find((x) => x.id === a.id);\n const label = q ? questionLabel(q) || `Q${q.number}` : a.id;\n if (a.wasCustom) {\n return `${theme.fg(\"success\", \"✓ \")}${theme.fg(\"accent\", label)}: ${theme.fg(\"muted\", \"(wrote) \")}${a.label}`;\n }\n const display = a.index ? `${a.index}. ${a.label}` : a.label;\n return `${theme.fg(\"success\", \"✓ \")}${theme.fg(\"accent\", label)}: ${display}`;\n });\n text.setText(lines.join(\"\\n\"));\n return text;\n },\n });\n}\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "26EC0FD3FCF63F7E64756E2164756E21",
9
+ "names": []
10
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@capdiem/pi-ask-user",
3
+ "version": "0.1.0",
4
+ "description": "An interactive ask_user form tool for the Pi coding agent: choice or free-text questions with recommended-answer hints, in TUI (full form) and RPC (sequential dialogs)",
5
+ "type": "module",
6
+ "pi": {
7
+ "extensions": [
8
+ "./index.min.js"
9
+ ]
10
+ },
11
+ "keywords": [
12
+ "pi-package",
13
+ "pi",
14
+ "coding-agent",
15
+ "ask-user",
16
+ "questionnaire",
17
+ "form",
18
+ "grilling"
19
+ ],
20
+ "author": "capdiem <capdiem@live.com>",
21
+ "license": "MIT",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/capdiem/pi-extensions.git",
25
+ "directory": "extensions/pi-ask-user"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public",
29
+ "registry": "https://registry.npmjs.org/"
30
+ },
31
+ "peerDependencies": {
32
+ "@earendil-works/pi-ai": "*",
33
+ "@earendil-works/pi-coding-agent": "*",
34
+ "@earendil-works/pi-tui": "*",
35
+ "typebox": "*"
36
+ },
37
+ "files": [
38
+ "LICENSE",
39
+ "README.md",
40
+ "index.min.js",
41
+ "index.min.js.map"
42
+ ]
43
+ }