Understand how questions are loaded, validated, presented in Ink, and written back out as markdown.
The questions CLI expects a JSON array of question definitions and uses Ink to walk a human operator through them. Users typically pipe JSON or point the CLI at a file (questions sample_questions.json). The entrypoint normalizes argv, reads the question set, validates each prompt, and boots the Ink app. Inside the TUI, the operator navigates with [/h and ]/l, arrow keys or j/k, and Enter/Space depending on the question type. Yes/no prompts always include a third "Other" row that opens a custom input so operators can supply free-form context without toggling configuration. Answers are cached locally until the final submit screen, where the UI is replaced with the generated markdown; the CLI simultaneously writes the same payload to --output when set (and only logs directly to stdout when running non-interactively). The finalOutput state doubles as the "submitted" flagβonce it holds a string, prior screens unmount and the only available action is confirming exit.
src/cli.tsx β parses CLI flags, loads question JSON from stdin/file, validates structure, and renders the Ink app.src/components/App.tsx β orchestrates per-question state, keyboard handling, navigation, and submission lifecycle.src/components/{SingleSelect,MultiSelect,FreeForm,YesNo}.tsx β renderers for each supported question type, including custom-option UX.src/components/Header.tsx β displays global navigation affordances and current progress counter.src/components/SubmitScreen.tsx β review + submit view that surfaces every answer and triggers finalizing logic on Enter.src/utils/markdown.ts β turns (questions, answers) into the markdown payload written to stdout/file.sample_questions.json β reference dataset demonstrating the JSON schema accepted by the CLI.Question union (src/types.ts) β guarantees each prompt carries id, question, and type-specific metadata (options, suggestions).Answer (src/types.ts) β union used across the UI to store the current value for each question id.parseQuestions(input) (src/cli.tsx) β runtime guard that enforces the schema before Ink ever mounts.generateMarkdown(questions, answers) (src/utils/markdown.ts) β shared formatter used by the submit handler to keep stdout and file output in sync.src/cli.tsx inspects process.argv, supports -f/--file and positional file args, falls back to piped stdin, or exits with help if no payload exists.id, question, and a supported type, plus options/suggested arrays where required. Failures exit early so the Ink UI never loads bad data.render(<App ... />) seeds per-question UI state (selection indexes, custom strings, multi-select sets) and tracks currentStep to know which component to show.useInput inside App centralizes keyboard handling ([/h and ]/l to move steps, arrows or j/k to change the highlighted option, Space for multi-select toggles, Enter/Space to confirm). Components receive state slices to display cursors, custom text fields, and selection markers.saveAnswer() maps current UI state into the canonical Answer union per question id; multi-select answers are arrays, custom entries persist when Enter/Space is pressed, yes/no returns booleans for Yes/No or a custom string when "Other" is confirmed, and skipped questions remain null.SubmitScreen shows a summary. Pressing Enter/Space once generates the markdown via generateMarkdown, stores it in finalOutput, and swaps the UI to a read-only Markdown view; finalOutput !== null now indicates the session is locked and no prior step can be revisited. Any subsequent Enter/Space (or key press) triggers the final onSubmit/exit path, which writes to --output (or stdout) and terminates the Ink app.options arrays throw descriptive errors before entering the interactive flow._none selected_/(none selected) in markdown so downstream readers know it was intentional.Bun.write; the CLI surfaces them after printing to stdout so callers can retry another path.sample_questions.json, or fetch remote question sets?