@ftrdotdev/pi-inquiry

Interactive questions for the pi coding agent: a single `question` tool renders option lists, tabbed forms, and free-text answers

Packages

Package details

extension

Install @ftrdotdev/pi-inquiry from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@ftrdotdev/pi-inquiry
Package
@ftrdotdev/pi-inquiry
Version
0.5.1
Published
Sep 19, 2026
Downloads
469/mo · 17/wk
Author
felixnorden
License
MIT
Types
extension
Size
199.9 KB
Dependencies
2 dependencies · 3 peers
Pi manifest JSON
{
  "extensions": [
    "./index.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-inquiry

Interactive questions for the pi coding agent. Registers a single question tool that asks the user one or more multiple-choice questions: a selectable option list for a single question, a tabbed form with a submit tab for several. Every question keeps an open "Type something." path, so the user can type a custom answer when the listed options do not fit.

Package: @ftrdotdev/pi-inquiry · MIT

What it is

inquiry is a pi extension. It is a package named @ftrdotdev/pi-inquiry and registers exactly one tool: question.

The question tool asks the user to decide: pick between options, or confirm a decision. It renders as a selectable list (single question) or a tabbed form (multiple questions). Use it instead of ending a reply with a question in plain text — the model guidance is contributed through promptSnippet and promptGuidelines at tool registration, so the agent knows when to call it and how to present questions.

The tool renders in pi's TUI. Outside TUI mode it returns an error instead of showing a question.

Installation

Install from npm:

pi install npm:@ftrdotdev/pi-inquiry

From git or a local checkout:

pi install git:github.com/felixnorden/pi-mono
pi install ./path/to/pi-mono/packages/inquiry

To try the package without installing it, use -e (temporary, current run only):

pi -e npm:@ftrdotdev/pi-inquiry

Registration lives in package.json under the pi field:

"pi": {
  "extensions": ["./src/tool/question.ts"]
}

The extension entry is src/tool/question.ts. It wires the tool definition (name, JSON Schema parameters, prompt contributions, renderers) to pi's ExtensionAPI. The core logic lives in pure Effect modules under src/core/, with pi-specific adapters under src/sdk/.

Using the question tool

The model calls question whenever it needs the user to decide something, pick between options, or confirm a decision. Ask all open questions in one question call. Put one question per entry in questions[].

Parameters

{
  "questions": [
    {
      "prompt": "The full question text to display",
      "options": [{ "label": "Short label", "description": "Optional one-line description" }],
      "id": "optional-unique-id", // defaults to q1, q2, ...
      "label": "Scope", // short tab-bar label, defaults to Q1, Q2
      "allowOther": true, // defaults to true; false forces the listed options
      "multiple": false // defaults to false; true asks for one or more answers
    }
  ]
}
  • questions — one or more questions. A single question shows a simple option list; multiple questions show a tabbed interface with a submit tab.
  • options — 2–5 short options per question, each a label plus an optional one-line description.
  • allowOther — when true (default), the user can also type a free-text answer ("Type something."). Set it to false only when one of the listed options is required.
  • multiple — when true, the question is multi-select: the options render as checkboxes and the user picks one or more of them, then presses Enter to confirm the selection instead of choosing a single one. Space toggles a checkbox on or off; Enter confirms. This is the natural way to ask "which of these apply?" without turning each option into its own binary question.

Multi-select answers

A multi-select question keeps an "Add your own answer" entry (when allowOther is true) so the user can type additional alternatives; each typed answer becomes one more chosen value. Space toggles each option and removes a typed chip; Enter confirms and records the whole selection. As with a single-select question, pressing Enter on the "Add your own answer" row opens the type mode (Space does too). In the result, a multi-select question contributes one Answer per chosen option or typed alternative, all sharing the question's id.

Result

The tool returns its answers both as human-readable content and in details:

{
  questions: [...],                  // the normalized questions asked
  answers: [
    {
      id: "q1",
      label: "the chosen option or typed answer",
      wasCustom: false,              // true when the user typed a free-text answer
      index: 2                       // option index, absent for custom answers
    }
    // A multi-select question contributes one entry per chosen value.
  ],
  cancelled: false                   // true when the user dismissed the form
}

The tool groups a question's answers together in its output, so a multi-select question reads as e.g. Q1: user selected: 1. A, 2. B, user wrote: X rather than several detached rows.

Treat a cancelled result as a declined answer. Do not re-ask unless the answer is needed for the next step. When you re-ask, change the form.

Pairing with the tracker

question is a decision tool, not a tracker. Pair it with pi's tracker tool when a session spans several pieces of work. Keep one tracker list per piece of work, and use question when you need the user to steer the session:

  • which task list to work on next, when several pieces of work are open
  • which option or approach to take for the current item
  • whether to change scope, reprioritize, or drop an item
  • confirm a decision instead of guessing

Group related decisions into one question call with multiple entries in questions[]. They render as a single tabbed form with a submit tab, so the user resolves several open points in one pass.

Architecture

The core is a pure Effect toolkit with zero pi imports; all pi interaction lives on the fringe.

  • src/core/ — pure Effect, no pi imports
    • domain.ts — Effect Schema wire contract. The single source of truth: the tool's parameters JSON Schema is generated from it, and decodeParams validates every call.
    • machine.ts — pure reducer step(state, key). Exhaustive tests pin the legacy UX contract (tab navigation, editor mode, cancel semantics).
    • flow.ts — the interaction program: keyboard events in, machine steps, result out, via the Keyboard and Renderer services.
    • keyboard.ts, editor.ts, scene.ts, text.ts — key event model, code-point editor, style-token scene model, width-aware wrapping.
  • src/sdk/ — the pi fringe (all pi imports live here)
    • pi-ui.tsctx.ui.custom bridge: key events via a queue, scene painting at the current width, result completion.
    • keys.ts — raw pi key data to core KeyEvents.
    • painter.ts — scene style tokens to pi theme ANSI.
    • schema.test.ts — pins pi's runtime validation against the generated schema.
  • src/tool/question.ts — tool registration: discovery content (promptSnippet/promptGuidelines), the legacy prepareArguments shim, renderers.

Development

bun install
bun run test       # vitest (pure tests + @effect/vitest flow tests)
bun run lint       # oxlint
bunx tsc --noEmit  # typecheck