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
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?"
low: "Low", medium: "Medium", high: "High"
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:
- Loads
.envfiles, walking up from the current directory and the flow file's directory. Shell-set values take precedence; empty-string keys are treated as unset. - Picks an adapter based on available credentials (see "Adapter selection" below).
- Shows a
🧠 Thinking — asking <provider> to extract structured data…banner while the LLM call is in flight. - For
clarifysteps, splats the extracted fields into the engine's top-level answers viaEngine#prefill!, so any downstream step withskip_if not_empty(:field)is auto-skipped. - 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
title: "Tax Prep Intake (LLM-assisted)"
start :describe
ask :describe do
type :text
question "Describe your 2025 tax situation in your own words…"
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?"
(%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_KEYin your shell or in a.envup the directory tree. NoMethodError: undefined method 'prefill!'→ you're running theinquirex-ttybinary against an older rubygems-installed copy of the core gem.exe/inquirex-ttynow bootstraps Bundler against the repo's Gemfile automatically, but a globalrake install'd copy will still be stale — runrake installin../inquirexto 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
title: "My Flow"
start :first_question
ask :first_question do
type :enum
question "Pick one:"
a: "Option A", b: "Option B", c: "Option C"
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.