{"id":31271768,"url":"https://github.com/scientist-labs/thor-interactive","last_synced_at":"2026-04-02T14:22:40.848Z","repository":{"id":313633622,"uuid":"1051827978","full_name":"scientist-labs/thor-interactive","owner":"scientist-labs","description":"Turn any Thor CLI into a TUI with persistent state, auto-completion, and configurable default handlers for unrecognized input.","archived":false,"fork":false,"pushed_at":"2026-03-26T23:27:43.000Z","size":1253,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-27T09:42:46.264Z","etag":null,"topics":["cli","ruby","thor"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/scientist-labs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","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":"2025-09-06T19:59:56.000Z","updated_at":"2026-03-26T23:40:31.000Z","dependencies_parsed_at":"2025-09-07T14:15:37.808Z","dependency_job_id":"8c028a29-1d4e-4b12-b7e8-5f930a588cdf","html_url":"https://github.com/scientist-labs/thor-interactive","commit_stats":null,"previous_names":["scientist-labs/thor-interactive"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/scientist-labs/thor-interactive","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scientist-labs%2Fthor-interactive","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scientist-labs%2Fthor-interactive/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scientist-labs%2Fthor-interactive/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scientist-labs%2Fthor-interactive/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/scientist-labs","download_url":"https://codeload.github.com/scientist-labs/thor-interactive/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scientist-labs%2Fthor-interactive/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31307829,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: 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","ruby","thor"],"created_at":"2025-09-23T20:02:12.870Z","updated_at":"2026-04-02T14:22:40.840Z","avatar_url":"https://github.com/scientist-labs.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"\u003cimg src=\"/docs/assets/thor-interactive-wide.png\" alt=\"thor-interactive\" height=\"80px\"\u003e\n\nTurn any Thor CLI into an interactive terminal application with persistent state, auto-completion, and a rich TUI powered by [ratatui_ruby](https://www.ratatui-ruby.dev/).\n\n\u003cp\u003e\n  \u003cimg src=\"/docs/assets/screenshot.png\" alt=\"thor-interactive TUI screenshot\" width=\"600\"\u003e\u003cbr\u003e\n  \u003csub\u003eScreenshot from \u003ca href=\"https://github.com/scientist-labs/ragnar-cli\"\u003eRagnar\u003c/a\u003e, a RAG pipeline built with thor-interactive.\u003c/sub\u003e\n\u003c/p\u003e\n\nThor::Interactive converts your existing Thor command-line applications into interactive sessions — a Claude Code-like terminal UI with multi-line input, a status bar, animated spinners, and theming. Perfect for RAG pipelines, database tools, or any CLI that benefits from persistent connections and cached state.\n\n## Features\n\n- **TUI Mode**: Rich terminal UI with multi-line input, status bar, spinner, tab completion overlay, and theming — powered by Rust via [ratatui_ruby](https://www.ratatui-ruby.dev/)\n- **State Persistence**: Maintains class variables and instance state between commands\n- **Auto-completion**: Tab completion for command names, options, and paths\n- **Default Handlers**: Configurable fallback for non-command input (great for natural language interfaces)\n- **Command History**: Persistent history with up/down arrow navigation\n- **Graceful Degradation**: Falls back to a Reline-based REPL if `ratatui_ruby` is not installed\n\n## Quick Start\n\n### Installation\n\nAdd to your application's Gemfile:\n\n```ruby\ngem 'thor-interactive'\ngem 'ratatui_ruby', '~\u003e 1.4'  # Enables TUI mode\n```\n\n### Basic Usage\n\n```ruby\nrequire 'thor'\nrequire 'thor/interactive'\n\nclass MyApp \u003c Thor\n  include Thor::Interactive::Command\n\n  configure_interactive(\n    ui_mode: :tui,\n    prompt: \"myapp\u003e \"\n  )\n\n  desc \"hello NAME\", \"Say hello\"\n  def hello(name)\n    puts \"Hello #{name}!\"\n  end\n\n  desc \"search QUERY\", \"Search for something\"\n  def search(query)\n    puts \"Searching for: #{query}\"\n  end\nend\n\nMyApp.start(ARGV)\n```\n\nThen `bundle install` and run:\n\n```bash\n# Normal CLI usage (unchanged)\nbundle exec ruby myapp.rb hello World\n\n# Interactive TUI mode\nbundle exec ruby myapp.rb interactive\n```\n\n\u003e **Note:** `bundle exec` ensures `ratatui_ruby` is loaded. Without it, you'll get the basic Reline REPL instead of the TUI.\n\n### Key Bindings\n\n| Key | Action |\n|-----|--------|\n| Enter | Submit input |\n| Shift+Enter | Insert newline (Kitty protocol terminals) |\n| Ctrl+N | Toggle multi-line mode (fallback for older terminals) |\n| Ctrl+J | Always submit (even in multi-line mode) |\n| Tab | Auto-complete commands |\n| Ctrl+C | Clear input / double-tap to exit |\n| Ctrl+D | Exit |\n| Escape | Clear input / exit multi-line mode |\n| Up/Down | History navigation (single-line) or cursor movement (multi-line) |\n\n## State Persistence\n\nThe key benefit of interactive mode is maintaining state between commands. In normal CLI mode, each invocation starts fresh. In interactive mode, a single instance persists — so expensive connections, caches, and counters survive between commands:\n\n```ruby\nclass ProjectApp \u003c Thor\n  include Thor::Interactive::Command\n\n  @@db = nil\n  @@tasks = []\n\n  configure_interactive(\n    ui_mode: :tui,\n    prompt: \"project\u003e \"\n  )\n\n  desc \"connect HOST\", \"Connect to database\"\n  def connect(host)\n    @@db = Database.new(host)  # Expensive — only done once\n    puts \"Connected to #{host}\"\n  end\n\n  desc \"add TASK\", \"Add a task\"\n  def add(task)\n    @@tasks \u003c\u003c {name: task, created_at: Time.now}\n    puts \"Added: #{task} (#{@@tasks.size} total)\"\n  end\n\n  desc \"list\", \"Show all tasks\"\n  def list\n    @@tasks.each_with_index do |t, i|\n      puts \"#{i + 1}. #{t[:name]}\"\n    end\n  end\n\n  desc \"status\", \"Show connection and task count\"\n  def status\n    puts \"Database: #{@@db ? 'connected' : 'not connected'}\"\n    puts \"Tasks: #{@@tasks.size}\"\n  end\nend\n```\n\n```bash\nproject\u003e /connect localhost\nConnected to localhost\n\nproject\u003e /add \"Design API\"\nAdded: Design API (1 total)\n\nproject\u003e /add \"Write tests\"\nAdded: Write tests (2 total)\n\nproject\u003e /status\nDatabase: connected    # Still connected — same instance!\nTasks: 2\n```\n\n## Configuration\n\n### TUI Options\n\n```ruby\nconfigure_interactive(\n  ui_mode: :tui,\n  prompt: \"myapp\u003e \",\n  theme: :dark,                          # :default, :dark, :light, :minimal, or custom hash\n  status_bar: {\n    left: -\u003e(instance) { \" MyApp\" },     # Left section\n    right: -\u003e(instance) { \" v1.0 \" }     # Right section\n  },\n  spinner_messages: [                     # Custom spinner messages (optional)\n    \"Thinking\", \"Brewing\", \"Crunching\"\n  ]\n)\n```\n\n### Custom Theme\n\n```ruby\nconfigure_interactive(\n  ui_mode: :tui,\n  theme: {\n    error_fg: :light_red,\n    input_border: :cyan,\n    status_bar_fg: :white,\n    status_bar_bg: :dark_gray\n  }\n)\n```\n\nSee `Thor::Interactive::TUI::Theme::THEMES` for the full list of configurable color keys.\n\n### Default Handlers\n\nRoute unrecognized input to a command automatically:\n\n```ruby\nconfigure_interactive(\n  ui_mode: :tui,\n  prompt: \"myapp\u003e \",\n  default_handler: proc do |input, thor_instance|\n    thor_instance.search(input)\n  end\n)\n```\n\n```bash\nmyapp\u003e /search thor interactive\nSearching for: thor interactive\n\nmyapp\u003e some random text\nSearching for: some random text    # No slash needed — default handler kicks in\n```\n\n**Important:** Always use direct method calls in default handlers, not `invoke()`. Thor's `invoke` has internal deduplication that silently prevents repeated calls to the same method.\n\n### All Options\n\n```ruby\nconfigure_interactive(\n  ui_mode: :tui,                         # :tui for TUI mode (omit for basic REPL)\n  prompt: \"myapp\u003e \",                     # Custom prompt\n  theme: :dark,                          # TUI theme\n  status_bar: { left: ..., right: ... }, # TUI status bar sections\n  spinner_messages: [...],               # TUI spinner messages\n  history_file: \"~/.myapp_history\",      # Custom history file location\n  allow_nested: false,                   # Prevent nested sessions (default)\n  default_handler: proc { |input, i| },  # Handle non-command input\n  ctrl_c_behavior: :clear_prompt,        # :clear_prompt, :show_help, or :silent\n  double_ctrl_c_timeout: 0.5            # Seconds for double Ctrl+C exit\n)\n```\n\n## Basic REPL Mode (without ratatui_ruby)\n\nIf you don't need the TUI, or `ratatui_ruby` isn't available, thor-interactive provides a Reline-based REPL. Just omit `ui_mode: :tui`:\n\n```ruby\nclass MyApp \u003c Thor\n  include Thor::Interactive::Command\n\n  configure_interactive(prompt: \"myapp\u003e \")\n\n  desc \"hello NAME\", \"Say hello\"\n  def hello(name)\n    puts \"Hello #{name}!\"\n  end\nend\n```\n\nOr start programmatically:\n\n```ruby\nThor::Interactive.start(MyApp, prompt: \"custom\u003e \")\n```\n\n## Development\n\n```bash\nbundle install           # Install dependencies\nbundle exec rspec        # Run tests (446 examples)\nbundle exec rake build   # Build gem\n```\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/scientist-labs/thor-interactive.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscientist-labs%2Fthor-interactive","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fscientist-labs%2Fthor-interactive","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscientist-labs%2Fthor-interactive/lists"}