Skip to content
swhittPublic

About

Simple and beautiful terminal prompts for Ruby. Port of @clack/prompts.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation


 C L A C K 

CLI prompts for Ruby. Zero dependencies.

A faithful Ruby port of @clack/prompts

Gem Version Tests Ruby 3.2+ MIT License

A create-app flow built with Clack: text prompt, select, multiselect, spinner, outro
Recorded from examples/media/hero_demo.rb

See it in your own terminal:

gem install clack && clack-demo

Why

The usual starting point:

print "Pick a framework (1=Rails, 2=Sinatra, 3=Hanami): "
framework = gets.chomp.to_i

No navigation, no validation, no feedback, and Ctrl+C ends in a stack trace. Clack replaces this with prompts that draw themselves, validate as the user types, and put the terminal back the way they found it.

Install

gem "clack"

Ruby 3.2 or newer. No runtime dependencies, no curses.

Quick start

Thirteen prompts, plus spinners, progress bars, task lists, and styled output, all under one module. A complete flow:

require "clack"

Clack.intro "create-app"

project = Clack.group do |g|
  g.prompt(:name) { Clack.text(message: "Project name?", placeholder: "my-app") }
  g.prompt(:framework) do
    Clack.select(message: "Pick a framework",
      options: {rails: "Rails", sinatra: "Sinatra", hanami: "Hanami"})
  end
  g.prompt(:extras) do
    Clack.multiselect(message: "Select extras",
      options: {rspec: "RSpec", rubocop: "RuboCop"}, required: false)
  end
end
exit if Clack.cancel?(project)

Clack.spin("Installing dependencies...") { sleep 2 }

Clack.outro "You're all set!"

Clack.group collects answers into a Hash keyed by prompt name (project[:framework] # => :sinatra). Later prompts can read earlier answers (g.prompt(:port) { |answers| ... }), and Clack.group(on_cancel: ->(partial) { ... }) sees whatever was collected before the user bailed.

Prompts

Every prompt takes message: plus its own options and returns the answer, or Clack::CANCEL when the user backs out.

Prompt Returns Worth knowing
Clack.text String placeholder:, default_value:, initial_value:, tab completions:
Clack.multiline_text String Enter inserts a newline, Ctrl+D submits
Clack.password String masked input, custom mask:
Clack.confirm Boolean active:/inactive: labels, vertical: layout
Clack.select one value per-option hint:/disabled:, initial_value:, max_items: scrolling
Clack.multiselect Array Space toggles, a selects all, i inverts, required:
Clack.autocomplete one value fuzzy type-to-filter across label, value, and hint; custom filter:
Clack.autocomplete_multiselect Array fuzzy filter plus checkboxes
Clack.select_key one value answer with a single keypress, case_sensitive:
Clack.group_multiselect Array checkboxes under group headings, selectable_groups:
Clack.path String filesystem browser, root:, only_directories:
Clack.date Date segmented picker, :iso/:us/:eu formats, min:/max:
Clack.range Numeric slider over min:/max:/step:, fractional steps stay exact
Text prompt with placeholder on the guide rail
Clack.text
Select prompt with a highlighted option, dim hint, and keyboard hint footer
Clack.select
Multiselect with two options checked and a keyboard hint footer
Clack.multiselect
Confirm prompt with a Yes/No toggle
Clack.confirm

Option lists accept a plain Hash ({pg: "PostgreSQL"} returns :pg) or full specs with hints:

db = Clack.select(
  message: "Pick a database",
  options: [
    {value: :pg, label: "PostgreSQL", hint: "recommended"},
    {value: :sqlite, label: "SQLite"},
    {value: :mysql, label: "MySQL", disabled: true}
  ]
)

Clack.group_multiselect takes a Hash of groups the same way:

addons = Clack.group_multiselect(
  message: "Add-ons",
  options: {
    "Observability" => {prom: "Prometheus", dd: "Datadog"},
    "Storage" => {s3: "S3", gcs: "GCS"}
  }
)

Every option of every prompt is covered in the prompt reference.

Cancellation

Escape and Ctrl+C never raise mid-prompt. The prompt returns the Clack::CANCEL sentinel, the terminal is restored (raw mode, cursor, half-drawn frames), and the script decides what happens next. CANCEL is a sentinel object, never nil, so an empty answer can't masquerade as a cancellation:

name = Clack.text(message: "Project name?")
exit 1 if Clack.handle_cancel(name)  # prints "Cancelled", returns true

name = "my-app" if Clack.cancel?(name)  # or check without printing

The same cleanup runs on exit, uncaught exceptions, and signals, so a crashed script never leaves the terminal in raw mode with a hidden cursor.

Validation

validate: takes a proc, a Regexp, a built-in Symbol, or an Array combining them. Return a String to reject, or Clack.warning for a soft failure the user can push through with a second Enter:

Clack.text(
  message: "Project name?",
  validate: [:required, /\A[a-z0-9_-]+\z/],
  transform: :strip
)

Clack.text(
  message: "Port?",
  validate: ->(v) { Clack.warning("Ports below 1024 need root") if v.to_i < 1024 }
)

A text prompt showing a yellow validation error after an empty submit

Seventeen built-in validators, eleven transformers, and the full shorthand catalog live in validation and transforms.

Spinners, progress, tasks

Clack.spin("Installing dependencies...", success: "Installed") do |s|
  sleep 1
  s.message "Compiling assets..."
  sleep 1
end

A spinner mid-animation on the guide rail

The block can exit, break, raise, or be interrupted; the spinner always prints a final line instead of leaving a half-drawn frame, even in forked children. Clack.spinner gives the same thing under manual start/stop/error control, and Clack.progress(total: 100) draws a determinate bar.

Clack.tasks runs a list sequentially, one spinner per step:

Clack.tasks(tasks: [
  {title: "Fetching sources", task: -> { sleep 1 }},
  {title: "Linking binaries", task: -> { sleep 1 }},
  {title: "Deploying", task: -> { sleep 1 }, enabled: ENV["DEPLOY"] == "1"}
])

Styled output

Clack.log.info "Fetching origin"
Clack.log.success "Build passed"
Clack.log.warn "Cache is stale"
Clack.note "Run bin/setup to finish.", title: "Next steps"
Clack.stream.command("bundle install")   # stream a subprocess line by line
Helper Does
Clack.intro / outro / cancel open, close, and abort the session rail
Clack.log info, success, step, warn, error, message one-liners
Clack.note / Clack.box bordered blocks, alignment and width options
Clack.stream line-by-line output from enumerables, IOs, and shell commands
Clack.task_log scrolling log that clears itself on success, stays visible on failure
Clack.group collect prompts into one Hash with a single cancel path

Testing

Prompts are drivable from tests, no TTY required:

require "clack/testing"

result = Clack::Testing.simulate(Clack.method(:select),
  message: "Pick", options: %w[a b c]) do |p|
  p.down
  p.submit
end
result # => "b"

The driver speaks arrows, typing, toggling, and cancellation, and works with RSpec, Minitest, or anything else. Details in testing.

CI mode

Clack.update_settings(ci_mode: :auto)

With :auto, prompts submit their defaults whenever input is not a TTY or a CI environment is detected, so the same script runs interactively and in a pipeline. Without it, prompting on piped stdin raises Clack::NotATerminalError with an actionable message rather than hanging.

Configuration

Clack.update_settings(
  with_guide: false,                        # hide the guide rail
  show_instructions: false,                 # hide keyboard hint footers
  messages: {cancel: "Abgebrochen"},        # localize built-in strings
  aliases: {"y" => :enter, "n" => :cancel}  # extra key bindings
)

Both toggles also work per call, and instructions: replaces any prompt's hint text. Colors follow NO_COLOR and FORCE_COLOR, CLACK_UNICODE=0 forces the ASCII symbol set, and CLACK_ESCAPE_TIMEOUT widens the escape-detection window for laggy SSH links. Settings, key aliases, and environment variables are covered in configuration.

Real terminals

  • Width is measured, not guessed: CJK text, fullwidth forms, emoji, and combining marks all line up.
  • Arrow keys survive tmux and a vim exit: SS3 sequences are normalized, and the various Home/End encodings fold to one code per key.
  • Narrow windows get a width warning rather than a mangled frame, and hint footers soft-wrap instead of corrupting the layout.

When something still looks wrong: troubleshooting.

Relationship to @clack/prompts

Clack tracks upstream @clack/prompts: the same components, the same look, the same cancel-anywhere contract. On top of that, additions that only make sense in Ruby: the range slider, the validator and transformer toolkit, Clack::Testing, and CI mode.

Coming from tty-prompt? examples/migration_from_tty_prompt.rb maps the common patterns side by side.

Documentation

More runnable scripts live in examples/, including the demo behind clack-demo and the full-feature showcase.


Ported from @clack/prompts by Nate Moore and contributors. MIT.

About

Simple and beautiful terminal prompts for Ruby. Port of @clack/prompts.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages