Ruby  Coverage

inquirex-tty

Terminal adapter for the Inquirex questionnaire engine. Renders flow definitions as interactive ANSI terminal wizards using tty-prompt, with ASCII-art headers, styled boxes, and automatic widget selection based on data types.

Ships as a CLI (inquirex-tty) with commands to run flows interactively, validate definitions, and export Mermaid diagrams.

Inquirex CLI Shorthand Alias

This gem also installs a shorter command inqt which you can run in place of inquirex-tty.

Installation

gem "inquirex-tty"

The gem installs an inquirex-tty executable.

Quick Start

Write a flow definition in Ruby:

# my_flow.rb
require "inquirex"

Inquirex.define id: "hello", version: "1.0.0" do
  meta title: "Hello World"
  start :name

  ask :name do
    type :string
    question "What is your name?"
    transition to: :age
  end

  ask :age do
    type :integer
    question "How old are you?"
    transition to: :farewell
  end

  say :farewell do
    text "Thanks for chatting!"
  end
end

Run it:

inqt run my_flow.rb

The CLI walks the user through each step, selecting the appropriate TTY widget for each data type, and prints collected answers as JSON when the flow completes.

CLI Commands

inquirex-tty run <flow_file>

Execute a flow interactively. Each step is rendered with the appropriate tty-prompt widget based on the node's data type and widget hints.

# run it and then dump answers as json to stdout
inqt run examples/08_tax_preparer.rb

# run it and save the anawers to a json file
inqt run examples/08_tax_preparer.rb \
  --output answers.json

Options:

Flag Description
--output, -o Write JSON results to a file instead of stderr

On completion, outputs a JSON summary:

{
  "flow_file": "examples/08_tax_preparer.rb",
  "path_taken": [
    "intro", "filing_status", "dependents", "income_types",
    "state_filing", "foreign_accounts", "deduction_types",
    "client_name", "client_email", "thanks"
  ],
  "answers": {
    "filing_status": "single",
    "dependents": 2,
    "income_types": ["W2", "1099"],
    "state_filing": ["California"],
    "foreign_accounts": "no",
    "deduction_types": ["Medical"],
    "client_name": "Konstantin Gredeskoul",
    "client_email": "kigster@gmail.com"
  },
  "steps_completed": 10,
  "completed_at": "2026-04-13T23:51:33-07:00"
}

inquirex-tty validate <flow_file>

Check that a flow definition is well-formed without running it. Validates:

  • Start step exists in the step list
  • All transition targets reference known steps
  • All steps are reachable from the start step (detects orphans)
inqt validate examples/08_tax_preparer.rb

inquirex-tty graph <flow_file>

Export the flow as a Mermaid diagram source, an image, or both.

inqt graph examples/08_tax_preparer.rb                                       # Mermaid source to stdout
inqt graph examples/08_tax_preparer.rb --output flow.mmd                     # write source to a file
inqt graph examples/08_tax_preparer.rb --format image -o flow.svg            # SVG via mmdc
inqt graph examples/08_tax_preparer.rb --format both --output ~/Desktop      # source + image into a directory
inqt graph examples/08_tax_preparer.rb --format image --open                 # SVG + open in viewer

Options:

Flag Description
--output, -o Output file or directory (default: stdout)
--format, -f source (default), image (SVG via mmdc), or both
--open, -p Open the generated image in the system viewer (default: false)

Image generation requires mermaid-cli (npm install -g @mermaid-js/mermaid-cli). The command attempts to install it automatically if mmdc is not on your PATH.

inquirex-tty export <flow_file>

Export the flow definition as JSON or YAML. Useful for serving flows to frontend adapters (the JS widget, Rails API, etc.) or for inspecting the wire format.

inqt export examples/08_tax_preparer.rb                          # pretty JSON to stdout
inqt export examples/08_tax_preparer.rb -f yml                   # YAML to stdout
inqt export examples/08_tax_preparer.rb -o .                     # write 08_tax_preparer.json to cwd
inqt export examples/08_tax_preparer.rb -f yml -o ~/flows        # write 08_tax_preparer.yml to ~/flows
inqt export examples/08_tax_preparer.rb -o out.json              # write to named file
inqt export examples/08_tax_preparer.rb -f yml -o out            # appends .yml → out.yml

Options:

Flag Description
--format, -f json (default), yaml, or yml
--output, -o Output file or directory (default: stdout)

Output path rules:

  • No --output → print to stdout
  • --output <dir> (existing directory) → write <flow-basename>.<ext> inside it
  • --output <file> → use that filename; if the extension is missing or mismatched, the appropriate one (.json/.yml) is substituted

inquirex-tty version

Print version information for the TTY adapter and its dependencies.

inqt version

Example Session

Running the tax preparation intake example:

$ inquirex-tty run examples/08_tax_preparer.rb

  _____      _     __  __   ____    ____    _____   ____       _      ____       _      _____   ___    ___    _   _    ___   _   _   _____      _      _  __  _____
 |_   _|    / \    \ \/ /  |  _ \  |  _ \  | ____| |  _ \     / \    |  _ \     / \    |_   _| |_ _|  / _ \  | \ | |  |_ _| | \ | | |_   _|    / \    | |/ / | ____|
   | |     / _ \    \  /   | |_) | | |_) | |  _|   | |_) |   / _ \   | |_) |   / _ \     | |    | |  | | | | |  \| |   | |  |  \| |   | |     / _ \   | ' /  |  _|
   | |    / ___ \   /  \   |  __/  |  _ <  | |___  |  __/   / ___ \  |  _ <   / ___ \    | |    | |  | |_| | | |\  |   | |  | |\  |   | |    / ___ \  | . \  | |___
   |_|   /_/   \_\ /_/\_\  |_|     |_| \_\ |_____| |_|     /_/   \_\ |_| \_\ /_/   \_\   |_|   |___|  \___/  |_| \_|  |___| |_| \_|   |_|   /_/   \_\ |_|\_\ |_____|

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Step 2: intro
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Please describe your tax situation in a few sentences.
Do not under any circumstances provide personal information,
such as your address or social security number.

Example: I have two W-2s from my two jobs, a rental property, and a side business.
Press any key to continue...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Step 3: filing_status
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
What is your filing status for 2025? single
Step 4: dependents
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
How many dependents do you have? 2
Step 5: income_types
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Select all income types that apply to you in 2025. W2, 1099
Step 6: state_filing
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Which states do you need to file in? California
Step 7: foreign_accounts
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Do you have any foreign financial accounts? no
Step 8: deduction_types
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Which additional deductions apply to you? Medical
Step 9: client_name
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Your name, please: Konstantin Gredeskoul
Step 10: client_email
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Your email address: kigster@gmail.com
Step 11: thanks
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Thank you! We will review your information and send you a
tax preparation estimate within 1-2 business days.
Press any key to continue...

Widget Mapping

The renderer selects a tty-prompt method for each node based on the :tty widget hint from WidgetRegistry:

Widget Hint tty-prompt Method Used For
text_input prompt.ask :string, :date, :email, :phone
multiline prompt.multiline :text
number_input prompt.ask (with convert:) :integer, :decimal, :currency
yes_no prompt.yes? :boolean / confirm
select prompt.select :enum
multi_select prompt.multi_select :multi_enum
enum_select prompt.enum_select Numbered menu variant
mask prompt.mask Password/hidden input
slider prompt.slider Numeric range

You can override the default by setting an explicit :tty widget hint in the DSL:

ask :priority do
  type :enum
  question "How urgent?"
  options low: "Low", medium: "Medium", high: "High"
  widget target: :tty, type: :enum_select   # numbered menu instead of arrow-key select
  transition to: :next_step
end

Display Verbs

Verb Rendering
header Large ASCII-art text via TTY::Font (falls back to TTY::Box)
say Plain text with "Press any key to continue..."
btw Info-styled box (blue border)
warning Warning-styled box (yellow/red)

LLM Integration

When a flow contains inquirex-llm verbs (clarify, describe, summarize, detour), the inquirex-tty run command automatically:

  1. Loads .env files, walking up from the current directory and the flow file's directory. Shell-set values take precedence; empty-string keys are treated as unset.
  2. Picks an adapter based on available credentials (see "Adapter selection" below).
  3. Shows a 🧠 Thinking — asking <provider> to extract structured data… banner while the LLM call is in flight.
  4. For clarify steps, splats the extracted fields into the engine's top-level answers via Engine#prefill!, so any downstream step with skip_if not_empty(:field) is auto-skipped.
  5. Prints a / extraction table showing which fields the LLM filled in vs. which ones will still be asked.

Adapter selection (first match wins)

Condition Adapter used
INQUIREX_LLM_ADAPTER=null Inquirex::LLM::NullAdapter
INQUIREX_LLM_ADAPTER=anthropic Inquirex::LLM::AnthropicAdapter
INQUIREX_LLM_ADAPTER=openai Inquirex::LLM::OpenAIAdapter
ANTHROPIC_API_KEY is set Inquirex::LLM::AnthropicAdapter
OPENAI_API_KEY is set Inquirex::LLM::OpenAIAdapter
nothing set Inquirex::LLM::NullAdapter (demo)

End-to-end example

examples/09_tax_preparer_llm.rb is a complete LLM-assisted tax intake. The user types one free-text description of their tax situation; the LLM extracts filing_status, dependents, income_types, state_filing; the wizard only asks for whatever the LLM couldn't determine, then runs a final summarize for a complexity / fee-estimate write-up.

# examples/09_tax_preparer_llm.rb (excerpt)
require "inquirex"
require "inquirex/llm"

Inquirex.define id: "tax-preparer-llm-2025", version: "1.0.0" do
  meta title: "Tax Prep Intake (LLM-assisted)"
  start :describe

  ask :describe do
    type :text
    question "Describe your 2025 tax situation in your own words…"
    widget target: :tty, type: :multiline
    transition to: :extracted
  end

  clarify :extracted do
    from :describe
    prompt <<~PROMPT
      Extract tax intake fields. Use these EXACT value conventions:
      filing_status: "single" | "married_filing_jointly" | …
      income_types:  array of "W2" | "1099" | "Business" | "Investment" | "Rental" | "Retirement"
      Use "" / 0 / [] for anything the client did not mention.
    PROMPT
    schema filing_status: :string,
           dependents:    :integer,
           income_types:  :multi_enum,
           state_filing:  :string
    model :claude_sonnet
    temperature 0.0
    transition to: :filing_status
  end

  ask :filing_status do
    type :enum
    question "What is your filing status?"
    options(%w[single married_filing_jointly married_filing_separately head_of_household widowed])
    skip_if not_empty(:filing_status)
    transition to: :dependents
  end

  # …same pattern for :dependents, :income_types, :state_filing…

  ask :client_contact do
    type :string
    question "Your name and email?"
    transition to: :summary
  end

  summarize :summary do
    from_all
    prompt "Return JSON: { complexity, fee_estimate_low, fee_estimate_high, red_flags, notes }"
    transition to: :done
  end

  say :done do
    text "Thank you! A tax professional will review your intake and reach out."
  end
end

Run it:

# Put your key in any .env up the tree — OPENAI_API_KEY or ANTHROPIC_API_KEY
echo 'OPENAI_API_KEY=sk-…' >> ../.env

inqt run examples/09_tax_preparer_llm.rb

A typical session (input shortened):

> I'm MFJ with two kids. I'm W-2 at Google, my wife runs a consulting LLC,
  we have a rental in Oakland and some Coinbase crypto. We live in California.

🧠 Thinking — asking GPT to extract structured data…
📋 LLM extracted:
  ✅ filing_status: "married_filing_jointly"
  ✅ dependents: 2
  ✅ income_types: ["W2", "Business", "Rental", "Investment"]
  ✅ state_filing: "California"
# :filing_status, :dependents, :income_types, :state_filing all auto-skipped.
# User only gets asked for :client_contact before the summary.

Troubleshooting

  • "the null adapter" shown in the thinking banner → no API key was found. Check OPENAI_API_KEY / ANTHROPIC_API_KEY in your shell or in a .env up the directory tree.
  • NoMethodError: undefined method 'prefill!' → you're running the inquirex-tty binary against an older rubygems-installed copy of the core gem. exe/inquirex-tty now bootstraps Bundler against the repo's Gemfile automatically, but a global rake install'd copy will still be stale — run rake install in ../inquirex to update, or run from a checkout so the shim's Bundler bootstrap takes over.

Examples

The gem ships with 10 examples of increasing complexity:

Example Description Steps Features
01_hello_world.rb Minimal flow 3 String and integer input
02_yes_or_no.rb Boolean branching 3 confirm, equals rule
03_food_preferences.rb Multi-select branching 6 multi_enum, contains rule
04_event_registration.rb Two-level branching 9 Nested conditionals
05_job_application.rb Composed rules 13 all(), any(), greater_than
06_health_assessment.rb Three-level branching 18 Complex composed rules
07_loan_application.rb Real-world loan intake 20+ Currency, 3-level branching
08_tax_preparer.rb Full tax preparation wizard 18+ All data types, deep branching
09_tax_preparer_llm.rb LLM-assisted tax intake 9 clarify + summarize, skip_if not_empty, auto-prefill
10_real_tax_preparer.rb Realistic tax preparer flow 20+ Full intake variant

Run any example:

inqt run examples/01_hello_world.rb
inqt run examples/08_tax_preparer.rb
inqt run examples/09_tax_preparer_llm.rb

Validate all examples:

for f in examples/*.rb; do inquirex-tty validate "$f"; done

Architecture

inquirex-tty/
├── exe/inquirex-tty                # CLI entry point (dry-cli)
└── lib/inquirex/tty/
    ├── commands/
    │   ├── run.rb                  # Interactive flow execution
    │   ├── validate.rb             # Definition validation
    │   ├── graph.rb                # Mermaid diagram export
    │   ├── export.rb               # JSON / YAML serialization
    │   └── version.rb              # Print version info
    ├── renderer.rb                 # Node → tty-prompt widget dispatcher
    ├── flow_loader.rb              # Load .rb flow definitions
    ├── output_path.rb              # Shared -o/--output path resolution
    ├── ui_helper.rb                # TTY::Box/Pastel/Screen helpers
    └── commands.rb                 # dry-cli command registry

Renderer

Inquirex::TTY::Renderer is the core class that maps nodes to tty-prompt calls. It reads the :tty widget hint from each node (via WidgetRegistry defaults or explicit DSL hints) and dispatches to the matching render_* method.

The TTY::Prompt instance is injectable for testing:

prompt = TTY::Prompt::Test.new
renderer = Inquirex::TTY::Renderer.new(prompt:)

FlowLoader

Loads a .rb file and evaluates it to produce an Inquirex::Definition. The file must contain an Inquirex.define call that returns the definition.

UIHelper

A mixin module providing styled output helpers (box, info, success, error, warning, sep, next_step) built on TTY::Box, Pastel, and TTY::Screen. Included automatically in all CLI commands and the Renderer.

Development

bin/setup                           # install dependencies
bundle exec rspec                   # run tests
bundle exec rspec --format doc      # verbose test output
bundle exec rubocop                 # lint

Useful just tasks

just test                           # run full test suite with coverage
just lint                           # rubocop
just format                         # rubocop --auto-correct
just run examples/01_hello_world.rb # run a flow
just validate examples/01_hello_world.rb
just graph examples/01_hello_world.rb
just examples                       # validate all examples
just ci                             # tests + lint

Writing a Custom Flow

Any .rb file that calls Inquirex.define and returns a Definition works:

require "inquirex"

Inquirex.define id: "my-flow", version: "1.0.0" do
  meta title: "My Flow"
  start :first_question

  ask :first_question do
    type :enum
    question "Pick one:"
    options a: "Option A", b: "Option B", c: "Option C"
    widget target: :tty, type: :select
    transition to: :detail, if_rule: equals(:first_question, "a")
    transition to: :done
  end

  ask :detail do
    type :text
    question "Tell me more about A:"
    transition to: :done
  end

  say :done do
    text "All done!"
  end
end

License

MIT. See LICENSE.txt.

Copyright 2026 Konstantin Gredeskoul & Inquirex.