npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nguyenquangthai/pi-ask

v0.1.14

Published

Keyboard-first structured questions with review for Pi Coding Agent.

Readme

pi-ask

Interactive keyboard-first questionnaire for Pi Coding Agent. Presents the user 1–4 structured questions with options, custom answers, and a review tab before final submission.

pi-ask preview

Inspired by the AskUserQuestion tool from Claude Code / OpenCode / Codex CLI.

Install

# Install from npm (recommended)
pi install npm:@nguyenquangthai/pi-ask

# Or pin a Git commit/tag
pi install git:github.com/QuangThai/[email protected]

# Local development
pi install ./

Prerequisites: Pi >=0.80.7 and Node.js >=20.6.0.

What it looks like

The tool (ask_user_question) makes the LLM pause and show a keyboard-driven dialog:

  • Questions tab — each question has a header, optional context, and 2–4 options
  • Recommended — options marked recommended: true are moved to the top and show a (Recommended) hint; they are never pre-selected
  • Other — pick "Other — add your own answer" to enter free text via the inline editor; Enter saves and continues
  • Multi-selectSpace to toggle, Enter to confirm
  • Pick & continue — choosing a single-choice option (Space/Enter) saves and moves to the next tab automatically
  • Review tab — see all answers before submitting; navigate back to any tab to edit
  • Keyboard navigation↑↓ move, Space/Enter choose & continue, ←→/Tab switch tabs, Esc dismiss

Tool call (transcript)

ask_user_question 2 questions (Storage, UI)
  ↓
Storage: Tool details
UI: Review tab, Custom answer

Usage for the LLM

When facing ambiguity, the model calls ask_user_question. Example:

{
  "questions": [
    {
      "id": "persistence",
      "header": "Persist",
      "question": "How should session state be persisted?",
      "context": "Answers must survive pi /tree and /fork operations.",
      "multiSelect": false,
      "required": false,
      "options": [
        { "value": "details", "label": "Tool result details", "recommended": true },
        { "value": "file",    "label": "File" },
        { "value": "env",     "label": "Environment variable" }
      ]
    }
  ]
}

Conditional follow-up example

Use showWhen to ask a follow-up only when it is relevant:

{
  "questions": [
    {
      "id": "stack",
      "header": "Stack",
      "question": "What are you building?",
      "context": "Selecting Backend reveals a database question.",
      "multiSelect": false,
      "options": [
        { "value": "frontend", "label": "Frontend UI" },
        { "value": "backend", "label": "Backend API", "recommended": true }
      ]
    },
    {
      "id": "db",
      "header": "DB",
      "question": "Which database?",
      "multiSelect": false,
      "showWhen": { "questionId": "stack", "equals": "backend" },
      "options": [
        { "value": "postgres", "label": "PostgreSQL" },
        { "value": "sqlite", "label": "SQLite" }
      ]
    }
  ]
}

| User picks… | Behavior | |-------------|----------| | Frontend UI | DB tab hidden — submit only shows { stack: frontend } | | Backend API | DB tab appears — user picks a database; submit shows { stack: backend, db: postgres } | | Backend → picks Postgres → reopens and switches to Frontend | DB answer cleared and removed from the result; hidden required children never block submit |

When to use: Prefer showWhen over separate ask_user_question calls. One dialog with a conditional chain is faster and less disruptive than asking multiple times.

Rules:

  • id must be unique per question; value must be unique per option
  • value, label, id, and header are required in the public schema, but the tool registers a prepareArguments() hook that derives anything the model omits before Pi's validation runs — so LLM calls never hit a hard framework validation error. Missing value → slug of the label (e.g. Chọn mẫu tối giảnchon-mau-toi-gian), missing idquestion-N, missing question → the header, missing header → the question text (truncated)
  • Providing value/label explicitly is preferred: value is the stable key returned to you, label is the text shown to the user
  • A single question/option object is auto-wrapped into an array; null/non-object entries are dropped; string booleans ("false", "no", "yes", …) are normalized
  • required defaults to true; set required: false to let the user explicitly skip a question
  • showWhen: { questionId, equals } shows a follow-up only after the parent is confirmed with that option value (one level deep; Other text never matches)
  • Use recommended: true on the best option (moved to the top with a hint; user must select it explicitly)
  • Do not include a custom "Other" option — it is automatic
  • header ≤ 12 characters (longer headers are truncated, never rejected)
  • Free-text Other answers are capped at 4,000 characters; terminal control characters are removed

Key bindings

| Key | Context | Action | |-----|---------|--------| | | Options list | Move cursor | | Space / Enter | Required single-select option | Select and continue to next tab | | Enter | Optional question with no answer | Skip and confirm | | Space | Multi-select option | Toggle option (stay on tab) | | Enter | Selected multi-select options | Confirm question | | Enter / Space | "Other — add your own answer" | Open inline editor | | Enter | Inline editor (with text) | Save, confirm, and continue | | Esc | Inline editor | Cancel | | / Tab | Multi-question tabs | Switch tabs | | Enter | Review tab | Submit all | | Esc | Anywhere | Cancel / dismiss |

Submission guarantees

  • An unanswered required question can be visited in Review but cannot be submitted; Enter is a no-op until every visible question is confirmed.
  • An optional question can be explicitly skipped with Enter; it is omitted from the submitted answers array.
  • A required multi-select question with no checked option and no Other text cannot be confirmed.
  • Saving an Other answer (Enter in the inline editor) confirms the question and continues immediately; the answer is shown in the Review tab and when navigating back.
  • Saving blank Other text clears it. If that leaves no answer, the question becomes unconfirmed and blocks Submit.
  • Editing a selected answer or Other text unconfirms that question until the user confirms it again.
  • A showWhen follow-up is hidden until its parent is confirmed with the matching option value; hidden questions are omitted from tabs, Review, and answers.
  • Editing or unconfirming a parent clears and hides dependent children; a hidden required child does not block submit.
  • Multi-select answers are serialized in the original option order, regardless of the order in which options were toggled.
  • A submitted answer may carry selectedValues and customText together; the LLM transcript preserves both.
  • Terminal exit/abort, user dismissal, invalid input, and unavailable UI have distinct result statuses: aborted, dismissed, invalid, and unavailable.

Architecture

src/
├── index.ts       # Tool registration, non‑TUI fallback, renderCall/renderResult
├── schema.ts      # TypeBox schemas + validation
├── state.ts       # Reducer: navigation, selection, confirm, toResult
├── component.ts   # QuestionnaireComponent (pi-tui, no pi-coding-agent import)
tests/
├── state.test.ts      # reducer and result-contract tests
├── component.test.ts  # keyboard and rendering tests
└── tool.test.ts       # runtime validation and lifecycle tests

Key design decisions:

  1. Built-in "Other" row — Pi's LLM should not add its own "Other" option; the component adds "Other — add your own answer" automatically. For multi-select questions, the custom text supplements selected options.
  2. Result by question ID, not text — answers map via stable questionId/value, avoiding duplicate-text collisions.
  3. State in tool result details — answers persist in the Pi session JSONL via built-in toolResult.details. Branch tracking is automatic: /tree or /fork uses the correct branch's answers. No appendEntry, no external state.
  4. Non-TUI = disabled — in ctx.mode !== "tui", returns status: "unavailable" and deactivates itself so the model won't retry.
  5. Keyboard-first WCAG — all actions work with ↑↓ Enter Space Esc ←→; no mouse dependency; color is never the sole indicator.
  6. No dead renderingrender() caches by width and invalidates on state/theme change.
  7. TUI-only custom componentctx.ui.custom() opens only in ctx.mode === "tui"; RPC, JSON, and print modes return an explicit unavailable result.
  8. Terminal and IME safety — rendered lines are clamped to the supplied display width; the questionnaire forwards focus to its inline Editor for IME-aware terminals.

Reliability

npm run check covers reducer invariants, keyboard flows, review navigation, Other editing, narrow terminal widths, runtime validation, non-TUI fallback, and aborts before and after opening the dialog. The package tarball includes only runtime source and release metadata.

GitHub Actions runs this check, a production dependency audit, package dry-run, and a clean tarball-install smoke test on Node 20 and 22.

Implementation choices are verified against:

  • Pi extension API and lifecycle: https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/extensions.md
  • Pi custom-component, focus, keyboard, and width contract: https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/tui.md
  • Reference questionnaire test coverage: https://github.com/ghoseb/pi-askuserquestion
  • Alternative ask contract (free text, review disposition, dismissal): https://github.com/IgorWarzocha/howaboua-pi-stuff/tree/main/packages/pi-ask

Pi package gallery readiness

pi.dev/packages indexes npm packages tagged with the pi-package keyword; it does not accept a separate package upload. This package is prepared with the required keyword and its preview asset is hosted at a stable GitHub URL through pi.image in package.json. It will appear in the gallery only after a future npm publish.

Development

npm install
npm test                 # unit and integration tests
npm run typecheck        # tsc --noEmit
npm run lint             # biome check
npm run pack:dry         # verify package contents

Test interactively:

pi -e ./src/index.ts --model sonnet

Support and security

License

MIT © 2026 QuangThai