{"id":36220797,"url":"https://github.com/swhitt/clackrb","last_synced_at":"2026-04-01T23:52:26.829Z","repository":{"id":331579676,"uuid":"1128060313","full_name":"swhitt/clackrb","owner":"swhitt","description":"Simple and beautiful terminal prompts for Ruby. Port of @clack/prompts.","archived":false,"fork":false,"pushed_at":"2026-03-22T18:19:32.000Z","size":4480,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-23T09:09:12.044Z","etag":null,"topics":["cli","command-line","gem","menu","prompt","ruby","terminal","tui"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/swhitt.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-05T04:40:55.000Z","updated_at":"2026-03-22T18:19:36.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/swhitt/clackrb","commit_stats":null,"previous_names":["swhitt/clackrb"],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/swhitt/clackrb","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swhitt%2Fclackrb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swhitt%2Fclackrb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swhitt%2Fclackrb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swhitt%2Fclackrb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/swhitt","download_url":"https://codeload.github.com/swhitt/clackrb/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swhitt%2Fclackrb/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31293126,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T21:15:39.731Z","status":"ssl_error","status_checked_at":"2026-04-01T21:15:34.046Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","command-line","gem","menu","prompt","ruby","terminal","tui"],"created_at":"2026-01-11T04:56:30.347Z","updated_at":"2026-04-01T23:52:26.817Z","avatar_url":"https://github.com/swhitt.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cbr\u003e\n  \u003cbr\u003e\n  \u003ccode\u003e\u0026nbsp;C\u0026nbsp;L\u0026nbsp;A\u0026nbsp;C\u0026nbsp;K\u0026nbsp;\u003c/code\u003e\n  \u003cbr\u003e\n  \u003cbr\u003e\n  \u003ci\u003eCLI prompts for Ruby. Zero dependencies.\u003c/i\u003e\n  \u003cbr\u003e\n  \u003cbr\u003e\n  \u003ca href=\"https://rubygems.org/gems/clack\"\u003e\u003cimg src=\"https://img.shields.io/gem/v/clack?style=flat-square\u0026color=cc342d\" alt=\"Gem Version\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/swhitt/clackrb/actions\"\u003e\u003cimg src=\"https://img.shields.io/github/actions/workflow/status/swhitt/clackrb/ci.yml?style=flat-square\u0026label=tests\" alt=\"Tests\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.ruby-lang.org\"\u003e\u003cimg src=\"https://img.shields.io/badge/ruby-3.2%2B-cc342d?style=flat-square\" alt=\"Ruby 3.2+\"\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-MIT-blue?style=flat-square\" alt=\"MIT License\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"examples/demo.gif?v=2\" width=\"640\" alt=\"Clack demo showing beautiful terminal prompts\"\u003e\n\u003c/p\u003e\n\n---\n\n## Why Clack?\n\nThe standard approach:\n\n```ruby\nprint \"What is your project named? \"\nname = gets.chomp\nprint \"Pick a framework (1=Rails, 2=Sinatra, 3=Roda): \"\nframework = gets.chomp.to_i\n```\n\nNo navigation, no validation, no visual feedback. With Clack:\n\n```ruby\nrequire \"clack\"\n\nClack.intro \"create-app\"\n\nname = Clack.text(message: \"What is your project named?\", placeholder: \"my-app\")\n# =\u003e Renders a gorgeous, navigable text input with placeholder text\n\nframework = Clack.select(\n  message: \"Pick a framework\",\n  options: [\n    { value: \"rails\", label: \"Ruby on Rails\", hint: \"recommended\" },\n    { value: \"sinatra\", label: \"Sinatra\" },\n    { value: \"roda\", label: \"Roda\" }\n  ]\n)\n# =\u003e Arrow-key navigation, vim bindings, instant submit\n\nClack.outro \"You're all set!\"\n```\n\n---\n\n## Installation\n\n```ruby\n# Gemfile\ngem \"clack\"\n```\n\n```bash\n# Or install directly\ngem install clack\n```\n\n---\n\n## Quick Start\n\n```ruby\nrequire \"clack\"\n\nClack.intro \"create-app\"\n\nresult = Clack.group do |g|\n  g.prompt(:name) { Clack.text(message: \"Project name?\", placeholder: \"my-app\") }\n\n  g.prompt(:framework) do\n    Clack.select(\n      message: \"Pick a framework\",\n      options: [\n        { value: \"rails\", label: \"Ruby on Rails\", hint: \"recommended\" },\n        { value: \"sinatra\", label: \"Sinatra\" },\n        { value: \"roda\", label: \"Roda\" }\n      ]\n    )\n  end\n\n  g.prompt(:features) do\n    Clack.multiselect(\n      message: \"Select features\",\n      options: %w[api auth admin websockets]\n    )\n  end\nend\n\nif Clack.cancel?(result)\n  Clack.cancel(\"Setup cancelled\")\n  exit 1\nend\n\nClack.outro \"You're all set!\"\n```\n\n---\n\n## Prompts\n\nAll prompts return the user's input, or `Clack::CANCEL` if the user pressed Escape/Ctrl+C.\n\n### Text\n\nSingle-line text input with placeholders, defaults, validation, and tab completion.\n\n\u003cimg src=\"examples/images/text.svg\" alt=\"Text prompt\"\u003e\n\n```ruby\nname = Clack.text(\n  message: \"What is your project named?\",\n  placeholder: \"my-project\",       # Shown when empty (dim)\n  default_value: \"untitled\",       # Used if submitted empty\n  initial_value: \"hello-world\",    # Pre-filled, editable\n  validate: -\u003e(v) { \"Required!\" if v.empty? },\n  help: \"Letters, numbers, and dashes only\"  # Contextual help text\n)\n```\n\n**Tab completion** -- press `Tab` to fill the longest common prefix of matching candidates:\n\n```ruby\n# Static list\ncmd = Clack.text(\n  message: \"Command?\",\n  completions: %w[build test deploy lint format]\n)\n\n# Dynamic completions\nfile = Clack.text(\n  message: \"File?\",\n  completions: -\u003e(input) { Dir.glob(\"#{input}*\") }\n)\n```\n\n### Password\n\nMasked text input for secrets and API keys.\n\n\u003cimg src=\"examples/images/password.svg\" alt=\"Password prompt\"\u003e\n\n```ruby\nsecret = Clack.password(\n  message: \"Enter your API key\",\n  mask: \"*\"  # Default: \"▪\"\n)\n```\n\n### Multiline Text\n\nFor when one line isn't enough. Enter inserts a newline, Ctrl+D submits.\n\n```ruby\nbio = Clack.multiline_text(\n  message: \"Tell us about yourself\",\n  initial_value: \"Hello!\\n\",\n  validate: -\u003e(v) { \"Too short\" if v.strip.length \u003c 10 }\n)\n```\n\n### Confirm\n\nYes/no toggle with customizable labels.\n\n\u003cimg src=\"examples/images/confirm.svg\" alt=\"Confirm prompt\"\u003e\n\n```ruby\nproceed = Clack.confirm(\n  message: \"Deploy to production?\",\n  active: \"Yes, ship it!\",\n  inactive: \"No, abort\",\n  initial_value: false\n)\n```\n\n### Select\n\nPick one from a list. Navigate with arrow keys or `hjkl`.\n\n\u003cimg src=\"examples/images/select.svg\" alt=\"Select prompt\"\u003e\n\n```ruby\ndb = Clack.select(\n  message: \"Choose a database\",\n  options: [\n    { value: \"pg\", label: \"PostgreSQL\", hint: \"recommended\" },\n    { value: \"mysql\", label: \"MySQL\" },\n    { value: \"sqlite\", label: \"SQLite\", disabled: true }\n  ],\n  initial_value: \"pg\",\n  max_items: 5  # Enable scrolling\n)\n```\n\n### Multiselect\n\nPick many. Toggle with Space. Select all with `a`. Invert with `i`.\n\n\u003cimg src=\"examples/images/multiselect.svg\" alt=\"Multiselect prompt\"\u003e\n\n```ruby\nfeatures = Clack.multiselect(\n  message: \"Select features to install\",\n  options: [\n    { value: \"api\", label: \"API Mode\" },\n    { value: \"auth\", label: \"Authentication\" },\n    { value: \"jobs\", label: \"Background Jobs\" }\n  ],\n  initial_values: [\"api\"],\n  required: true,     # Must select at least one\n  max_items: 5        # Enable scrolling\n)\n```\n\n**Shortcuts:** `Space` toggle | `a` all | `i` invert\n\n### Autocomplete\n\nType to filter with fuzzy matching -- \"fb\" matches \"foobar\". Pass `filter:` to override with custom logic.\n\n```ruby\ncolor = Clack.autocomplete(\n  message: \"Pick a color\",\n  options: %w[red orange yellow green blue indigo violet],\n  placeholder: \"Type to search...\",\n  max_items: 5  # Default; scrollable via up/down arrows\n)\n\n# Custom filter logic (receives option hash and query string)\ncmd = Clack.autocomplete(\n  message: \"Select command\",\n  options: commands,\n  filter: -\u003e(opt, query) { opt[:label].start_with?(query) }\n)\n```\n\n\u003e Vim-style `j`/`k`/`h`/`l` navigation is not available in autocomplete -- all keyboard input feeds into the search field. Use arrow keys to navigate results.\n\n### Autocomplete Multiselect\n\nType-to-filter with multi-selection support.\n\n```ruby\ncolors = Clack.autocomplete_multiselect(\n  message: \"Pick colors\",\n  options: %w[red orange yellow green blue indigo violet],\n  placeholder: \"Type to filter...\",\n  required: true,              # At least one selection required\n  initial_values: [\"red\"],     # Pre-selected values\n  max_items: 5                 # Default; scrollable via up/down arrows\n)\n```\n\n**Shortcuts:** `Space` toggle | `Enter` confirm\n\n\u003e The `a` (select all), `i` (invert), and `j`/`k`/`h`/`l` shortcuts from Multiselect are not available here -- all keyboard input feeds into the search field instead. Use arrow keys to navigate.\n\n### Path\n\nFilesystem navigation with Tab completion and arrow key selection.\n\n```ruby\nproject_dir = Clack.path(\n  message: \"Where should we create your project?\",\n  only_directories: true,  # Only show directories\n  root: \".\"               # Starting directory\n)\n```\n\n**Navigation:** Type to filter | `Tab` to autocomplete | up/down arrows to select\n\n### Date\n\nSegmented date picker with three format modes.\n\n```ruby\ndate = Clack.date(\n  message: \"Release date?\",\n  format: :us,                        # :iso (YYYY-MM-DD), :us (MM/DD/YYYY), :eu (DD/MM/YYYY)\n  initial_value: Date.today + 7,\n  min: Date.today,\n  max: Date.today + 365,\n  validate: -\u003e(d) { \"Not a Friday\" unless d.friday? }\n)\n```\n\n**Navigation:** `Tab`/left-right arrows between segments | up/down arrows to adjust value | type digits directly\n\n### Range\n\nVisual slider for numeric selection.\n\n```ruby\nvolume = Clack.range(\n  message: \"Set volume\",\n  min: 0,\n  max: 100,\n  step: 5,\n  initial_value: 50\n)\n# Navigate with arrow keys or hjkl\n```\n\n### Select Key\n\nInstant selection via keyboard shortcuts. No arrow key navigation needed.\n\n```ruby\naction = Clack.select_key(\n  message: \"What would you like to do?\",\n  options: [\n    { value: \"create\", label: \"Create new project\", key: \"c\" },\n    { value: \"open\", label: \"Open existing\", key: \"o\" },\n    { value: \"quit\", label: \"Quit\", key: \"q\" }\n  ]\n)\n```\n\n### Group Multiselect\n\nMultiselect with options organized into named categories.\n\n```ruby\nfeatures = Clack.group_multiselect(\n  message: \"Select features\",\n  options: [\n    {\n      label: \"Frontend\",\n      options: [\n        { value: \"hotwire\", label: \"Hotwire\" },\n        { value: \"stimulus\", label: \"Stimulus\" }\n      ]\n    },\n    {\n      label: \"Background\",\n      options: [\n        { value: \"sidekiq\", label: \"Sidekiq\" },\n        { value: \"solid_queue\", label: \"Solid Queue\" }\n      ]\n    }\n  ],\n  selectable_groups: true,  # Toggle all options in a group at once\n  group_spacing: 1          # Blank lines between groups\n)\n```\n\n### Spinner\n\nNon-blocking animated indicator for async work.\n\n\u003cimg src=\"examples/images/spinner.svg\" alt=\"Spinner\"\u003e\n\n```ruby\nspinner = Clack.spinner\nspinner.start(\"Installing dependencies...\")\n\n# Do your work...\nsleep 2\n\nspinner.stop(\"Dependencies installed!\")\n# Or: spinner.error(\"Installation failed\")\n# Or: spinner.cancel(\"Cancelled\")\n```\n\n**Block form** -- wraps a block with automatic success/error handling:\n\n```ruby\nresult = Clack.spin(\"Installing dependencies...\") { system(\"npm install\") }\n\n# With custom messages\nClack.spin(\"Compiling...\", success: \"Build complete!\") { build_project }\n\n# Access the spinner inside the block\nClack.spin(\"Working...\") do |s|\n  s.message \"Step 1...\"\n  do_step_1\n  s.message \"Step 2...\"\n  do_step_2\nend\n```\n\n### Progress\n\nA visual progress bar for measurable operations.\n\n```ruby\nprogress = Clack.progress(total: 100, message: \"Downloading...\")\nprogress.start\n\nfiles.each_with_index do |file, i|\n  download(file)\n  progress.update(i + 1)\nend\n\nprogress.stop(\"Download complete!\")\n```\n\n### Tasks\n\nRun multiple tasks sequentially with status indicators.\n\n```ruby\nresults = Clack.tasks(tasks: [\n  { title: \"Checking dependencies\", task: -\u003e { check_deps } },\n  { title: \"Building project\", task: -\u003e { build } },\n  { title: \"Running tests\", task: -\u003e { run_tests } }\n])\n\n# Update the spinner message mid-task\nClack.tasks(tasks: [\n  { title: \"Installing\", task: -\u003e(message) {\n    message.call(\"Fetching packages...\")\n    fetch_packages\n    message.call(\"Compiling...\")\n    compile\n  }}\n])\n\n# Conditionally skip tasks with enabled:\nClack.tasks(tasks: [\n  { title: \"Lint\", task: -\u003e { lint } },\n  { title: \"Deploy\", task: -\u003e { deploy }, enabled: ENV[\"DEPLOY\"] == \"true\" }\n])\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eQuick Reference Table\u003c/strong\u003e (click to expand)\u003c/summary\u003e\n\n| Prompt | Method | Key Options | Defaults |\n|--------|--------|-------------|----------|\n| Text | `Clack.text` | `placeholder:`, `default_value:`, `initial_value:`, `completions:` | -- |\n| Password | `Clack.password` | `mask:`, `validate:` | `mask: \"▪\"` |\n| Confirm | `Clack.confirm` | `active:`, `inactive:`, `initial_value:` | `active: \"Yes\"`, `inactive: \"No\"`, `initial_value: true` |\n| Select | `Clack.select` | `options:`, `initial_value:`, `max_items:` | -- |\n| Multiselect | `Clack.multiselect` | `options:`, `initial_values:`, `required:`, `cursor_at:` | `required: true` |\n| Group Multiselect | `Clack.group_multiselect` | `options:` (nested), `selectable_groups:`, `group_spacing:` | `selectable_groups: false` |\n| Autocomplete | `Clack.autocomplete` | `options:`, `placeholder:`, `filter:`, `max_items:` | `max_items: 5` |\n| Autocomplete Multiselect | `Clack.autocomplete_multiselect` | `options:`, `required:`, `initial_values:`, `filter:` | `required: true`, `max_items: 5` |\n| Select Key | `Clack.select_key` | `options:` (with `:key`) | -- |\n| Path | `Clack.path` | `root:`, `only_directories:` | `root: \".\"` |\n| Date | `Clack.date` | `format:`, `initial_value:`, `min:`, `max:` | `format: :iso` |\n| Range | `Clack.range` | `min:`, `max:`, `step:`, `initial_value:` | `min: 0`, `max: 100`, `step: 1` |\n| Multiline Text | `Clack.multiline_text` | `initial_value:`, `validate:` | Submit with **Ctrl+D** |\n| Spinner | `Clack.spinner` / `Clack.spin` | block form auto-handles success/error | -- |\n| Tasks | `Clack.tasks` | `tasks:` (`{title:, task:, enabled:}`) | `enabled: true` |\n| Progress | `Clack.progress` | `total:`, `message:` | -- |\n\nAll prompts accept `message:`, `validate:`, `help:`, and return `Clack::CANCEL` on Escape/Ctrl+C.\n\n\u003c/details\u003e\n\n---\n\n## Cancellation\n\n```ruby\nresult = Clack.text(message: \"Name?\")\nexit 1 if Clack.cancel?(result)\n\n# Or the one-liner version (prints \"Cancelled\" and returns true)\nresult = Clack.text(message: \"Name?\")\nexit 1 if Clack.handle_cancel(result)\n\n# With a custom message\nexit 1 if Clack.handle_cancel(result, \"Aborted by user\")\n```\n\n---\n\n## Validation and Transforms\n\nEvery prompt supports `validate:` and `transform:`. The pipeline looks like this:\n\n```\nUser Input --\u003e Validation (raw) --\u003e Transform (if valid) --\u003e Final Value\n```\n\nValidation returns an error message, a `Clack::Warning`, or `nil` to pass.\n\n**Validation results:**\n- `nil` or `false` -- passes validation\n- String -- shows error (red), user must fix input\n- `Clack::Warning.new(message)` -- shows warning (yellow), user can confirm with Enter or edit\n\n```ruby\n# Symbol shortcuts (clean and idiomatic)\nname = Clack.text(message: \"Name?\", transform: :strip)\ncode = Clack.text(message: \"Code?\", transform: :upcase)\n\n# Chain multiple transforms\nusername = Clack.text(\n  message: \"Username?\",\n  transform: Clack::Transformers.chain(:strip, :downcase)\n)\n\n# Combine validation and transform\namount = Clack.text(\n  message: \"Amount?\",\n  validate: -\u003e(v) { \"Must be a number\" unless v.match?(/\\A\\d+\\z/) },\n  transform: :to_integer\n)\n\n# Warning validation (soft failure -- user can confirm or edit)\nfile = Clack.text(\n  message: \"Output file?\",\n  validate: -\u003e(v) { Clack::Warning.new(\"File exists. Overwrite?\") if File.exist?(v) }\n)\n```\n\n### Built-in Validators\n\n```ruby\nClack::Validators.required              # Non-empty input\nClack::Validators.min_length(3)         # Minimum character count\nClack::Validators.max_length(100)       # Maximum character count\nClack::Validators.format(/\\A[a-z]+\\z/, \"Only lowercase\")\nClack::Validators.email                 # Email format (user@host.tld)\nClack::Validators.url                   # URL format (http/https)\nClack::Validators.integer               # Integer string (\"-5\", \"42\")\nClack::Validators.in_range(1..100)      # Numeric range (parses as int)\nClack::Validators.one_of(%w[a b c])     # Allowlist check\nClack::Validators.path_exists           # File/dir exists on disk\nClack::Validators.directory_exists      # Directory exists on disk\nClack::Validators.future_date           # Date strictly after today\nClack::Validators.past_date             # Date strictly before today\nClack::Validators.date_range(min: d1, max: d2)  # Date within range\nClack::Validators.combine(v1, v2)       # First error/warning wins\n\n# Warning validators -- allow user to confirm or edit\nClack::Validators.file_exists_warning   # For file overwrite confirmations\nClack::Validators.as_warning(validator) # Convert any validator to warning\n```\n\n### Built-in Transformers\n\n```ruby\n:strip / :trim      # Remove leading/trailing whitespace\n:downcase / :upcase # Change case\n:capitalize         # \"hello world\" -\u003e \"Hello world\"\n:titlecase          # \"hello world\" -\u003e \"Hello World\"\n:squish             # Collapse whitespace to single spaces\n:compact            # Remove all whitespace\n:to_integer         # Parse as integer\n:to_float           # Parse as float\n:digits_only        # Extract only digits\n```\n\n---\n\n## Prompt Groups\n\nChain multiple prompts and collect results in a hash. If the user cancels any prompt, the whole group returns `Clack::CANCEL`.\n\n```ruby\nresult = Clack.group do |g|\n  g.prompt(:name) { Clack.text(message: \"Your name?\") }\n  g.prompt(:email) { Clack.text(message: \"Your email?\") }\n  g.prompt(:confirm) { |r| Clack.confirm(message: \"Create account for #{r[:email]}?\") }\nend\n\nreturn if Clack.cancel?(result)\n\nputs \"Welcome, #{result[:name]}!\"\n```\n\nHandle cancellation with a callback:\n\n```ruby\nClack.group(on_cancel: -\u003e(r) { cleanup(r) }) do |g|\n  # prompts...\nend\n```\n\n---\n\n## Pretty Printing\n\n### Logging\n\n```ruby\nClack.log.info(\"Starting build...\")\nClack.log.success(\"Build completed!\")\nClack.log.warn(\"Cache is stale\")\nClack.log.error(\"Build failed\")\nClack.log.step(\"Running migrations\")\nClack.log.message(\"Custom message\")\n```\n\n### Stream\n\nStream output from iterables, enumerables, or shell commands:\n\n```ruby\n# Stream from an array or enumerable\nClack.stream.info([\"Line 1\", \"Line 2\", \"Line 3\"])\nClack.stream.step([\"Step 1\", \"Step 2\", \"Step 3\"])\n\n# Stream from a shell command (returns true/false for success)\nsuccess = Clack.stream.command(\"npm install\", type: :info)\n\n# Stream from any IO or StringIO\nClack.stream.success(io_stream)\n```\n\n### Note\n\nDisplay important information in a box:\n\n```ruby\nClack.note(\u003c\u003c~MSG, title: \"Next Steps\")\n  cd my-project\n  bundle install\n  bin/rails server\nMSG\n```\n\n### Box\n\nRender a customizable bordered box:\n\n```ruby\nClack.box(\"Hello, World!\", title: \"Greeting\")\n\n# With options\nClack.box(\n  \"Centered content\",\n  title: \"My Box\",\n  content_align: :center,    # :left, :center, :right\n  title_align: :center,\n  width: 40,                 # or :auto to fit content\n  rounded: true              # rounded or square corners\n)\n```\n\n### Task Log\n\nStreaming log that clears on success and shows full output on failure. Great for build output:\n\n```ruby\ntl = Clack.task_log(title: \"Building...\", limit: 10)\n\ntl.message(\"Compiling file 1...\")\ntl.message(\"Compiling file 2...\")\n\n# On success: clears the log\ntl.success(\"Build complete!\")\n\n# On error: keeps the log visible\n# tl.error(\"Build failed!\")\n```\n\n---\n\n## Session Markers\n\n```ruby\nClack.intro(\"my-cli v1.0\")  # ┌ my-cli v1.0\n# ... your prompts ...\nClack.outro(\"Done!\")        # └ Done!\n\n# Or on error:\nClack.cancel(\"Aborted\")     # └ Aborted (red)\n```\n\n---\n\n## Configuration\n\n```ruby\n# Add custom key bindings (merged with defaults)\nClack.update_settings(aliases: { \"y\" =\u003e :enter, \"n\" =\u003e :cancel })\n\n# Disable guide bars\nClack.update_settings(with_guide: false)\n\n# CI / non-interactive mode (prompts auto-submit with defaults)\nClack.update_settings(ci_mode: true)       # Always on\nClack.update_settings(ci_mode: :auto)      # Auto-detect (non-TTY or CI env vars)\n```\n\nWhen CI mode is active, prompts immediately submit with their default values instead of waiting for input. Useful for CI pipelines and scripted environments where stdin is not a TTY.\n\nClack also warns when terminal width is below 40 columns, since prompts may not render cleanly in very narrow terminals.\n\n---\n\n## Testing\n\nClack ships with first-class test helpers. Require `clack/testing` explicitly (it is not auto-loaded):\n\n```ruby\nrequire \"clack/testing\"\n\n# Simulate a text prompt\nresult = Clack::Testing.simulate(Clack.method(:text), message: \"Name?\") do |prompt|\n  prompt.type(\"Alice\")\n  prompt.submit\nend\n# =\u003e \"Alice\"\n\n# Capture rendered output alongside the result\nresult, output = Clack::Testing.simulate_with_output(Clack.method(:confirm), message: \"Sure?\") do |prompt|\n  prompt.left   # switch to \"No\"\n  prompt.submit\nend\n```\n\nThe `PromptDriver` yielded to the block provides these methods:\n\n| Method | Description |\n|---|---|\n| `type(text)` | Type a string character by character |\n| `submit` | Press Enter |\n| `cancel` | Press Escape |\n| `up` / `down` / `left` / `right` | Arrow keys |\n| `toggle` | Press Space (for multiselect) |\n| `tab` | Press Tab |\n| `backspace` | Press Backspace |\n| `ctrl_d` | Press Ctrl+D (submit multiline text) |\n| `key(sym_or_char)` | Press an arbitrary key by symbol (e.g. `:escape`) or raw character |\n\n---\n\n## Try It\n\n```bash\nruby examples/full_demo.rb\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eRecording the demo GIF\u003c/summary\u003e\n\nRequires [asciinema](https://asciinema.org/), [agg](https://github.com/asciinema/agg), and [expect](https://core.tcl-lang.org/expect/index):\n\n```bash\n# Record the demo (automated via expect script)\nasciinema rec examples/demo.cast --command \"expect examples/demo.exp\" --overwrite -q\n\n# Split batched frames to show typing (Ruby buffers terminal output)\nruby examples/split_cast.rb\n\n# Convert to GIF\nagg examples/demo.cast examples/demo.gif --font-size 18 --cols 80 --rows 28 --speed 0.6\n```\n\u003c/details\u003e\n\n---\n\n## Requirements\n\n- Ruby 3.2+\n- No runtime dependencies\n- Unicode terminal recommended (ASCII fallbacks included)\n\n### Development\n\n```bash\nbundle install\nbundle exec rake        # Lint + tests\nbundle exec rake spec   # Tests only\nCOVERAGE=true bundle exec rake spec  # With coverage\n```\n\n### Roadmap\n\n- **Wizard mode** -- Multi-step flows with back navigation (`Clack.wizard`). The current `Clack.group` runs prompts as sequential Ruby code, so there's no way to \"go back\" and re-answer a previous question. A declarative wizard API would define steps as a graph with branching and let the engine handle forward/back navigation.\n\n---\n\n## Credits\n\nThis is a Ruby port of [Clack](https://github.com/bombshell-dev/clack), created by [Nate Moore](https://github.com/natemoo-re) and the [Astro](https://astro.build) team.\n\n## License\n\nMIT -- See [LICENSE](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswhitt%2Fclackrb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fswhitt%2Fclackrb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswhitt%2Fclackrb/lists"}