pub use crate::tui::provider_selector::EXISTING_KEY_SENTINEL;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SttProvider {
#[default]
Off,
Groq,
Local,
OpenAiCompatible,
Voicebox,
}
impl SttProvider {
pub const ALL: [SttProvider; 5] = [
SttProvider::Off,
SttProvider::Groq,
SttProvider::Local,
SttProvider::OpenAiCompatible,
SttProvider::Voicebox,
];
pub fn available(is_local_available: bool) -> &'static [SttProvider] {
if is_local_available {
&Self::ALL
} else {
&[
Self::Off,
Self::Groq,
Self::OpenAiCompatible,
Self::Voicebox,
]
}
}
pub fn label(&self) -> &'static str {
match self {
Self::Off => "Off",
Self::Groq => "API (Groq Whisper)",
Self::Local => "Local (Whisper — runs on device)",
Self::OpenAiCompatible => "OpenAI-compatible (custom endpoint)",
Self::Voicebox => "Voicebox",
}
}
pub fn next(self, available: &[SttProvider]) -> Self {
available
.iter()
.cycle()
.skip_while(|&&v| v != self)
.nth(1)
.copied()
.unwrap_or(Self::Off)
}
pub fn prev(self, available: &[SttProvider]) -> Self {
available
.iter()
.rev()
.cycle()
.skip_while(|&&v| v != self)
.nth(1)
.copied()
.unwrap_or(Self::Off)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TtsProvider {
#[default]
Off,
OpenAi,
Local,
OpenAiCompatible,
Voicebox,
}
impl TtsProvider {
pub const ALL: [TtsProvider; 5] = [
TtsProvider::Off,
TtsProvider::OpenAi,
TtsProvider::Local,
TtsProvider::OpenAiCompatible,
TtsProvider::Voicebox,
];
pub fn available(is_local_available: bool) -> &'static [TtsProvider] {
if is_local_available {
&Self::ALL
} else {
&[
Self::Off,
TtsProvider::OpenAi,
TtsProvider::OpenAiCompatible,
TtsProvider::Voicebox,
]
}
}
pub fn label(&self) -> &'static str {
match self {
Self::Off => "Off",
Self::OpenAi => "API (OpenAI TTS — uses OpenAI key)",
Self::Local => "Local (Piper — runs on device, free)",
Self::OpenAiCompatible => "OpenAI-compatible (custom endpoint)",
Self::Voicebox => "Voicebox",
}
}
pub fn next(self, available: &[TtsProvider]) -> Self {
available
.iter()
.cycle()
.skip_while(|&&v| v != self)
.nth(1)
.copied()
.unwrap_or(Self::Off)
}
pub fn prev(self, available: &[TtsProvider]) -> Self {
available
.iter()
.rev()
.cycle()
.skip_while(|&&v| v != self)
.nth(1)
.copied()
.unwrap_or(Self::Off)
}
}
pub const PROVIDERS: &[ProviderInfo] = &[
ProviderInfo {
id: "anthropic",
name: "Anthropic Claude",
models: &[], key_label: "Setup Token",
help_lines: &[
"Claude Max / Code: run 'claude setup-token'",
"Or paste API key from console.anthropic.com",
],
},
ProviderInfo {
id: "openai",
name: "OpenAI",
models: &[],
key_label: "API Key",
help_lines: &["Get key from platform.openai.com"],
},
ProviderInfo {
id: "github",
name: "GitHub Copilot",
models: &[],
key_label: "OAuth",
help_lines: &["Sign in with GitHub to use your Copilot subscription"],
},
ProviderInfo {
id: "gemini",
name: "Google Gemini",
models: &[],
key_label: "API Key",
help_lines: &["Get key from aistudio.google.com"],
},
ProviderInfo {
id: "openrouter",
name: "OpenRouter",
models: &[],
key_label: "API Key",
help_lines: &["Get key from openrouter.ai/keys"],
},
ProviderInfo {
id: "minimax",
name: "Minimax",
models: &[], key_label: "API Key",
help_lines: &["Get key from platform.minimax.io"],
},
ProviderInfo {
id: "zhipu",
name: "z.ai GLM",
models: &[], key_label: "API Key",
help_lines: &["Get key from open.bigmodel.cn"],
},
ProviderInfo {
id: "claude-cli",
name: "Claude CLI",
models: &["sonnet", "opus", "haiku"],
key_label: "",
help_lines: &[
"Uses local 'claude' CLI subprocess — no API key needed",
"Requires: npm install -g @anthropic-ai/claude-code",
],
},
ProviderInfo {
id: "opencode-cli",
name: "OpenCode CLI",
models: &[],
key_label: "",
help_lines: &[
"Uses local 'opencode' CLI subprocess — free models, no API key needed",
"Requires: curl -fsSL https://opencode.ai/install | bash",
],
},
ProviderInfo {
id: "codex-cli",
name: "Codex CLI",
models: &[
"gpt-5.5",
"gpt-5.4",
"gpt-5.4-mini",
"gpt-5.3-codex",
"gpt-5.3-codex-spark",
"gpt-5.2",
],
key_label: "",
help_lines: &[
"Uses local 'codex' CLI subprocess — ChatGPT/Codex auth, no API key needed",
"Requires: npm install -g @openai/codex (then run 'codex' once to sign in)",
],
},
ProviderInfo {
id: "codex",
name: "Codex",
models: &[
"gpt-5.5",
"gpt-5.4",
"gpt-5.4-mini",
"gpt-5.3-codex",
"gpt-5.3-codex-spark",
"gpt-5.2",
"gpt-4o",
"o3",
"o4-mini",
],
key_label: "OAuth",
help_lines: &[
"Authenticate with OpenAI Codex subscription via device-code flow",
"No CLI needed — sign in through OpenCrabs onboarding",
],
},
ProviderInfo {
id: "opencode",
name: "OpenCode",
models: &[], key_label: "API Key",
help_lines: &[
"Go and Zen plans — opencode.ai",
"Get key from opencode.ai/settings",
],
},
ProviderInfo {
id: "qwen",
name: "Qwen",
models: &[
"qwen3.6-plus",
"qwen3-max",
"qwen3-coder-plus",
"qwen3.5-plus",
"qwen-max",
"qwen-plus",
"qwen-flash",
],
key_label: "API Key",
help_lines: &[
"DashScope OpenAI-compatible API (Alibaba Cloud Model Studio)",
"Get key from bailian.console.aliyun.com or qwen.ai/apiplatform",
],
},
ProviderInfo {
id: "ollama",
name: "Ollama",
models: &[], key_label: "API Key",
help_lines: &[
"Local: runs on localhost:11434 — no key needed",
"Cloud: set base_url + optional API key",
"Requires: ollama serve (local) or cloud endpoint",
],
},
ProviderInfo {
id: "xiaomi",
name: "Xiaomi",
models: &[], key_label: "",
help_lines: &[
"Free during the opencrabs x Xiaomi launch (no API key needed)",
"Default provider — just continue to start chatting",
],
},
ProviderInfo {
id: "", name: "Custom OpenAI-Compatible",
models: &[],
key_label: "API Key",
help_lines: &["Enter your own API endpoint"],
},
];
pub struct ProviderInfo {
pub id: &'static str,
pub name: &'static str,
pub models: &'static [&'static str],
pub key_label: &'static str,
pub help_lines: &'static [&'static str],
}
pub const CHANNEL_NAMES: &[(&str, &str)] = &[
("Telegram", "Bot token (via @BotFather)"),
("Discord", "Bot token (via Developer Portal)"),
("WhatsApp", "QR code pairing"),
("Slack", "Socket Mode (bot + app tokens)"),
("Trello", "API Key + Token from trello.com/power-ups/admin"),
];
pub const TEMPLATE_FILES: &[(&str, &str)] = &[
(
"SOUL.md",
include_str!("../../docs/reference/templates/SOUL.md"),
),
(
"USER.md",
include_str!("../../docs/reference/templates/USER.md"),
),
(
"AGENTS.md",
include_str!("../../docs/reference/templates/AGENTS.md"),
),
(
"TOOLS.md",
include_str!("../../docs/reference/templates/TOOLS.md"),
),
(
"MEMORY.md",
include_str!("../../docs/reference/templates/MEMORY.md"),
),
(
"CODE.md",
include_str!("../../docs/reference/templates/CODE.md"),
),
(
"SECURITY.md",
include_str!("../../docs/reference/templates/SECURITY.md"),
),
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OnboardingStep {
ModeSelect,
Workspace,
ProviderAuth,
Channels,
TelegramSetup,
DiscordSetup,
WhatsAppSetup,
SlackSetup,
TrelloSetup,
VoiceSetup,
ImageSetup,
Daemon,
HealthCheck,
BrainSetup,
Complete,
}
impl OnboardingStep {
pub fn number(&self) -> usize {
match self {
Self::ModeSelect => 1,
Self::Workspace => 2,
Self::ProviderAuth => 3,
Self::Channels => 4,
Self::TelegramSetup => 4, Self::DiscordSetup => 4, Self::WhatsAppSetup => 4, Self::SlackSetup => 4, Self::TrelloSetup => 4, Self::VoiceSetup => 5,
Self::ImageSetup => 6,
Self::Daemon => 7,
Self::HealthCheck => 8,
Self::BrainSetup => 9,
Self::Complete => 10,
}
}
pub fn total() -> usize {
9
}
pub fn title(&self) -> &'static str {
match self {
Self::ModeSelect => "Pick Your Vibe",
Self::Workspace => "Home Base",
Self::ProviderAuth => "Brain Fuel",
Self::Channels => "Chat Me Anywhere",
Self::TelegramSetup => "Telegram Bot",
Self::DiscordSetup => "Discord Bot",
Self::WhatsAppSetup => "WhatsApp",
Self::SlackSetup => "Slack Bot",
Self::TrelloSetup => "Trello",
Self::VoiceSetup => "Voice Superpowers",
Self::ImageSetup => "Image Handling",
Self::Daemon => "Always On",
Self::HealthCheck => "Vibe Check",
Self::BrainSetup => "Make It Yours",
Self::Complete => "Let's Go!",
}
}
pub fn subtitle(&self) -> &'static str {
match self {
Self::ModeSelect => "Quick and easy or full control — your call",
Self::Workspace => "Where my brain lives on disk",
Self::ProviderAuth => "Pick your AI model and drop your key",
Self::Channels => "Chat with me from your phone — Telegram, WhatsApp, whatever",
Self::TelegramSetup => "Hook up your Telegram bot token",
Self::DiscordSetup => "Hook up your Discord bot token",
Self::WhatsAppSetup => "Scan the QR code with your phone",
Self::SlackSetup => "Hook up your Slack bot and app tokens",
Self::TrelloSetup => "Hook up your Trello API Key and Token",
Self::VoiceSetup => "Talk to me, literally",
Self::ImageSetup => "Vision and image generation via Google Gemini",
Self::Daemon => "Keep me running in the background",
Self::HealthCheck => "Making sure everything's wired up right",
Self::BrainSetup => "Make me yours, drop some context so I actually get you",
Self::Complete => "You're all set — let's build something cool",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WizardMode {
QuickStart,
Advanced,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HealthStatus {
Pending,
Running,
Pass,
Fail(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthField {
Provider,
ApiKey,
Model,
CustomName,
CustomBaseUrl,
CustomApiKey,
CustomModel,
CustomContextWindow,
ZhipuEndpointType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiscordField {
BotToken,
ChannelID,
AllowedList,
RespondTo,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SlackField {
BotToken,
AppToken,
ChannelID,
AllowedList,
RespondTo,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TelegramField {
BotToken,
UserID,
RespondTo,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WhatsAppField {
Connection,
PhoneAllowlist,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TrelloField {
ApiKey,
ApiToken,
BoardId,
AllowedUsers,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ChannelTestStatus {
Idle,
Testing,
Success,
Failed(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VoiceField {
SttModeSelect,
GroqApiKey,
LocalModelSelect,
SttOpenaiCompatSelect,
SttOpenaiCompatUrl,
SttOpenaiCompatModel,
SttOpenaiCompatKey,
SttVoiceboxSelect,
SttVoiceboxUrl,
TtsModeSelect,
TtsLocalVoiceSelect,
TtsOpenaiCompatSelect,
TtsOpenaiCompatUrl,
TtsOpenaiCompatModel,
TtsOpenaiCompatVoice,
TtsOpenaiCompatKey,
TtsVoiceboxSelect,
TtsVoiceboxUrl,
TtsVoiceboxProfileId,
TtsVoiceboxEngine,
Continue,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ImageField {
VisionToggle,
GenerationToggle,
GenerationModel,
ApiKey,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GitHubDeviceFlowStatus {
Idle,
WaitingForUser,
Complete,
Failed(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum CodexDeviceFlowStatus {
#[default]
Idle,
WaitingForUser,
Complete,
Failed(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BrainField {
AboutMe,
AboutAgent,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WizardAction {
None,
Cancel,
Complete,
GenerateBrain,
FetchModels,
WhatsAppConnect,
TestTelegram,
TestDiscord,
TestSlack,
TestWhatsApp,
TestTrello,
DownloadWhisperModel,
DownloadPiperVoice,
GitHubDeviceFlow,
CodexDeviceFlow,
QuickJumpDone,
}