Class: Rubino::CLI::OnboardingWizard

Inherits:
Object
  • Object
show all
Defined in:
lib/rubino/cli/onboarding_wizard.rb

Overview

First-run onboarding (#93). A small, skippable interactive wizard that takes a brand-new user from an empty home to a working model: pick a provider/model, paste the key (written to .env, never echoed back), and persist the matching model.default / model.provider / providers.<name> block to config.yml. The catalog mirrors DOCS-BLUEPRINT models-and-keys.

It is only invoked when no usable credential is configured AND we are on a real TTY (ChatCommand#ensure_model_configured!); non-interactive contexts get the actionable guidance instead. #run returns true on a completed setup, false if the user skipped — the caller re-checks usability either way, so a partial/declined run safely falls through to the guidance+exit.

Constant Summary collapse

PROVIDERS =

Each provider: the model.provider to write, a default model id, the .env key var, and any providers.<name> config block to persist. Ordered so the recommended default comes first and matches the seeded config default (config/defaults.rb model.default => openai/gpt-4.1), keeping the from-zero experience consistent between the wizard and the non-interactive fail-fast guidance. OpenAI is the recommended default (maintainer directive); MiniMax stays a first-class selectable option — listed but NOT pushed or auto-selected — and carries the anthropic_compatible + base_url wiring it needs so picking it still yields a first-turn-working config.

[
  {
    key: "openai",
    label: "OpenAI (GPT) — recommended default",
    provider: "openai",
    model: "gpt-4.1",
    env_var: "OPENAI_API_KEY",
    config: {}
  },
  {
    key: "minimax",
    label: "MiniMax (Anthropic-compatible)",
    provider: "minimax",
    model: "MiniMax-M3",
    env_var: "MINIMAX_API_KEY",
    config: {
      "anthropic_compatible" => true,
      "base_url" => "https://api.minimax.io/anthropic",
      "api_key" => "${MINIMAX_API_KEY}"
    }
  },
  {
    key: "anthropic",
    label: "Anthropic (Claude)",
    provider: "anthropic",
    model: "claude-sonnet-4-5",
    env_var: "ANTHROPIC_API_KEY",
    config: {}
  },
  {
    key: "gemini",
    label: "Google (Gemini)",
    provider: "google",
    model: "gemini-2.5-pro",
    env_var: "GEMINI_API_KEY",
    config: {}
  },
  {
    key: "gateway",
    label: "OpenAI-compatible gateway",
    provider: "gateway",
    model: "auto",
    env_var: "OPENAI_API_KEY",
    config: {
      "openai_compatible" => true,
      "assume_model_exists" => true,
      "base_url" => nil # filled in interactively
    }
  }
].freeze

Instance Method Summary collapse

Constructor Details

#initialize(ui: Rubino.ui, input: $stdin, output: $stdout) ⇒ OnboardingWizard

Returns a new instance of OnboardingWizard.



80
81
82
83
84
# File 'lib/rubino/cli/onboarding_wizard.rb', line 80

def initialize(ui: Rubino.ui, input: $stdin, output: $stdout)
  @ui     = ui
  @input  = input
  @output = output
end

Instance Method Details

#runObject

Drives the wizard. Returns true when a provider was configured, false when the user skipped (empty/‘s`/`skip` at the provider prompt).

A Ctrl-C MID-wizard — after picking a provider, before pasting the key —used to escape as a raw ‘Interrupt` backtrace out of `gets`/`noecho` (H2). Catch it here and abort CLEANLY: print “Setup cancelled.” and exit 130 (the conventional SIGINT code). Nothing is half-written — #persist! (the only writer of the provider’s model.* / .env key) runs ONLY after a non-empty key is obtained, several lines below the interrupt point, so an abort leaves the config at the seeded defaults and re-running ‘setup` works. The base config.yml/.env `setup` materialized before onboarding are the intended seed files, not partial wizard state.



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/rubino/cli/onboarding_wizard.rb', line 98

def run
  @ui.blank_line
  @ui.info("Welcome to rubino — let's get you connected to a model.")
  @ui.status("No API key is configured yet. Pick a provider (or press Enter to skip).")
  @ui.blank_line

  choice = ask_provider
  return false unless choice

  # When the provider was just CONFIRMED via its already-present env key
  # (F3), that key is the one to use — don't re-ask "use the detected key?"
  # one line later. Otherwise prompt/paste as usual.
  api_key = ask_api_key(choice, skip_env_prompt: @confirmed_env_key)
  return false if api_key.nil? || api_key.empty?

  base_url = ask_base_url(choice)

  persist!(choice, api_key, base_url)
  Rubino.reload_configuration!

  @ui.blank_line
  @ui.success("Configured #{choice[:label]} with model #{choice[:model]}.")
  @ui.status("Saved to #{config_loader.config_path} and #{config_loader.env_path}.")
  @ui.blank_line
  true
rescue Interrupt
  @output.puts
  @ui.warning("Setup cancelled.")
  exit(130)
end