configwizard

package
v0.1.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 8, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// Background colors (kept for selected/interactive elements only)
	BaseBackground   = lipgloss.AdaptiveColor{Light: "#f8f8f2", Dark: "#1e1e2e"}
	PanelBackground  = lipgloss.AdaptiveColor{Light: "#ffffff", Dark: "#313244"}
	AltRowBackground = lipgloss.AdaptiveColor{Light: "#eff0eb", Dark: "#45475a"}

	// Text colors
	PrimaryText   = lipgloss.AdaptiveColor{Light: "#282a36", Dark: "#cdd6f4"}
	SecondaryText = lipgloss.AdaptiveColor{Light: "#6272a4", Dark: "#a6adc8"}
	HeaderText    = lipgloss.AdaptiveColor{Light: "#bd93f9", Dark: "#cba6f7"}

	// Accent colors
	SelectionAccent = lipgloss.AdaptiveColor{Light: "#8be9fd", Dark: "#89b4fa"}
	BorderColor     = lipgloss.AdaptiveColor{Light: "#44475a", Dark: "#585b70"}

	// Status colors
	SuccessColor  = lipgloss.AdaptiveColor{Light: "#2e7d32", Dark: "#a6e3a1"}
	SuccessBorder = lipgloss.AdaptiveColor{Light: "#1b5e20", Dark: "#4e8254"}
	ErrorColor    = lipgloss.AdaptiveColor{Light: "#c62828", Dark: "#f38ba8"}
	ErrorBorder   = lipgloss.AdaptiveColor{Light: "#8e0000", Dark: "#b34d4d"}
	WarningColor  = lipgloss.AdaptiveColor{Light: "#e65100", Dark: "#fab387"}
	InfoColor     = lipgloss.AdaptiveColor{Light: "#1565c0", Dark: "#89b4fa"}
)

Wizard color palette - Adaptive (light/dark terminal themes)

View Source
var (
	// Main container
	MainContainerStyle = lipgloss.NewStyle().
						Foreground(PrimaryText).
						Width(64).
						Border(lipgloss.RoundedBorder()).
						BorderForeground(BorderColor).
						Padding(1, 2)

	// Title style
	TitleStyle = lipgloss.NewStyle().
				Bold(true).
				Foreground(HeaderText).
				Width(60).
				Align(lipgloss.Center)

	// Section header
	SectionHeaderStyle = lipgloss.NewStyle().
						Bold(true).
						Foreground(HeaderText).
						Padding(0, 1)

	// Menu item - unselected
	MenuItemStyle = lipgloss.NewStyle().
					Foreground(PrimaryText).
					Padding(0, 2)

	// Menu item - selected (keeps background for visual distinction)
	MenuItemSelectedStyle = lipgloss.NewStyle().
							Foreground(PrimaryText).
							Background(SelectionAccent).
							Bold(true).
							Padding(0, 2)

	// Menu item - dimmed (for info display)
	MenuItemDimmedStyle = lipgloss.NewStyle().
						Foreground(SecondaryText).
						Padding(0, 2)

	// Menu item description (used when item is selected)
	MenuItemDescriptionStyle = lipgloss.NewStyle().
								Foreground(PrimaryText).
								Background(SelectionAccent).
								Padding(0, 2)

	// Input field
	InputFieldStyle = lipgloss.NewStyle().
					Foreground(PrimaryText).
					Border(lipgloss.NormalBorder()).
					BorderForeground(BorderColor).
					Padding(0, 1)

	// Input field focused
	InputFieldFocusedStyle = lipgloss.NewStyle().
							Foreground(PrimaryText).
							Border(lipgloss.NormalBorder()).
							BorderForeground(SelectionAccent).
							Padding(0, 1)

	// Button - unselected
	ButtonStyle = lipgloss.NewStyle().
				Foreground(PrimaryText).
				Background(PanelBackground).
				Border(lipgloss.NormalBorder()).
				BorderForeground(BorderColor).
				Padding(0, 2)

	// Button - selected/active (keeps background)
	ButtonActiveStyle = lipgloss.NewStyle().
						Foreground(PrimaryText).
						Background(SelectionAccent).
						Bold(true).
						Border(lipgloss.NormalBorder()).
						BorderForeground(SelectionAccent).
						Padding(0, 2)

	// Primary button (keeps background)
	ButtonPrimaryStyle = lipgloss.NewStyle().
						Foreground(BaseBackground).
						Background(SuccessColor).
						Bold(true).
						Border(lipgloss.NormalBorder()).
						BorderForeground(SuccessBorder).
						Padding(0, 2)

	// Danger button (keeps background)
	ButtonDangerStyle = lipgloss.NewStyle().
						Foreground(BaseBackground).
						Background(ErrorColor).
						Bold(true).
						Border(lipgloss.NormalBorder()).
						BorderForeground(ErrorBorder).
						Padding(0, 2)

	// Checkbox - unchecked
	CheckboxUncheckedStyle = lipgloss.NewStyle().
							Foreground(SecondaryText).
							SetString("[ ]")

	// Checkbox - checked
	CheckboxCheckedStyle = lipgloss.NewStyle().
							Foreground(SuccessColor).
							SetString("[✓]")

	// Checkbox - unchecked + focused (cyan highlight)
	CheckboxUncheckedFocusedStyle = lipgloss.NewStyle().
									Foreground(SelectionAccent).
									Bold(true).
									SetString("[ ]")

	// Checkbox - checked + focused (cyan highlight)
	CheckboxCheckedFocusedStyle = lipgloss.NewStyle().
								Foreground(SelectionAccent).
								Bold(true).
								SetString("[✓]")

	// Input field - disabled (dimmed, no border)
	InputFieldDisabledStyle = lipgloss.NewStyle().
							Foreground(SecondaryText).
							Padding(0, 1)

	// Focused row highlight background
	FocusedRowStyle = lipgloss.NewStyle().
					Background(AltRowBackground).
					Padding(0, 1)

	// Status indicators
	StatusOKStyle      = lipgloss.NewStyle().Foreground(SuccessColor)
	StatusErrorStyle   = lipgloss.NewStyle().Foreground(ErrorColor)
	StatusPendingStyle = lipgloss.NewStyle().Foreground(WarningColor).SetString("⟳")

	// List item - unselected
	ListItemStyle = lipgloss.NewStyle().
					Foreground(PrimaryText).
					Padding(0, 1)

	// List item - selected (keeps background)
	ListItemSelectedStyle = lipgloss.NewStyle().
							Foreground(PrimaryText).
							Background(SelectionAccent).
							Padding(0, 1)

	// List item - invalid (provider/model not found)
	ListItemInvalidStyle = lipgloss.NewStyle().
							Foreground(ErrorColor).
							Padding(0, 1)

	// List item - invalid + selected
	ListItemInvalidSelectedStyle = lipgloss.NewStyle().
									Foreground(ErrorColor).
									Background(SelectionAccent).
									Padding(0, 1)

	// Table header
	TableHeaderStyle = lipgloss.NewStyle().
						Bold(true).
						Foreground(HeaderText).
						Border(lipgloss.NormalBorder()).
						BorderBottom(true).
						BorderForeground(BorderColor)

	// Table row
	TableRowStyle = lipgloss.NewStyle().
					Foreground(PrimaryText)

	// Table row - alternate (keeps background for visual distinction)
	TableRowAltStyle = lipgloss.NewStyle().
						Foreground(PrimaryText).
						Background(AltRowBackground)

	// Modal/Overlay
	ModalStyle = lipgloss.NewStyle().
				Foreground(PrimaryText).
				Background(PanelBackground).
				Width(50).
				Height(9).
				Border(lipgloss.RoundedBorder()).
				BorderForeground(HeaderText).
				Padding(1, 2).
				Align(lipgloss.Center)

	// Dropdown floating panel (keeps background for overlay distinction)
	DropdownStyle = lipgloss.NewStyle().
					Border(lipgloss.RoundedBorder()).
					BorderForeground(SelectionAccent).
					Background(PanelBackground).
					Padding(0, 1).
					Width(42)

	// Error message
	ErrorStyle = lipgloss.NewStyle().
				Foreground(ErrorColor).
				Padding(0, 1)

	// Warning message (inline, no padding)
	WarningStyle = lipgloss.NewStyle().
					Foreground(ErrorColor)

	// Help text
	HelpTextStyle = lipgloss.NewStyle().
					Foreground(SecondaryText).
					Padding(0, 1)

	// Key hint
	KeyHintStyle = lipgloss.NewStyle().
					Foreground(SelectionAccent).
					SetString("[" + "key" + "]")

	// Profile tab styles
	TabStyle = lipgloss.NewStyle().
				Foreground(SecondaryText).
				Padding(0, 1).
				MarginRight(1)

	TabActiveStyle = lipgloss.NewStyle().
					Foreground(PrimaryText).
					Background(SelectionAccent).
					Bold(true).
					Padding(0, 1).
					MarginRight(1)

	TabAddStyle = lipgloss.NewStyle().
				Foreground(HeaderText).
				Padding(0, 1).
				MarginRight(1)

	TabAddActiveStyle = lipgloss.NewStyle().
						Foreground(PrimaryText).
						Background(HeaderText).
						Bold(true).
						Padding(0, 1).
						MarginRight(1)

	// Launch profile indicator (★)
	LaunchProfileIndicator = lipgloss.NewStyle().
							Foreground(SuccessColor).
							SetString("★")

	// Profile modal styles
	ProfileModalStyle = lipgloss.NewStyle().
						Foreground(PrimaryText).
						Background(PanelBackground).
						Width(56).
						Border(lipgloss.RoundedBorder()).
						BorderForeground(HeaderText).
						Padding(1, 2)
)

Base styles (no backgrounds on non-interactive elements — terminal native bg shows through)

View Source
var LogDestinationOptions = []string{"stdout", "stderr", "file"}

LogDestinationOptions are the available log destination options.

View Source
var LogLevelOptions = []string{"debug", "info", "warn", "error"}

LogLevelOptions are the available log level options.

View Source
var PredefinedRouteNames = []string{
	"default",
	"background",
	"think",
	"thinkMore",
	"ultrathink",
	"longContext",
	"image",
	"webSearch",
}

PredefinedRouteNames are the built-in route names.

View Source
var ProviderPresets = map[string]ProviderPreset{
	"alicloud": {
		BaseURL:     "https://coding.dashscope.aliyuncs.com/apps/anthropic",
		Transformer: "glm_anthropic",
		Models:      []string{"MiniMax-M2.5", "kimi-k2.5", "qwen3-coder-plus", "glm-5", "glm-4.7"},
	},
	"anthropic": {
		BaseURL:     "https://api.anthropic.com",
		Transformer: "anthropic",
		Models:      []string{"claude-haiku-4.5", "claude-sonnet-4.6", "claude-opus-4.5", "claude-opus-4.6"},
	},
	"bigmodel": {
		BaseURL:     "https://open.bigmodel.cn/api",
		Transformer: "glm_anthropic",
		Models:      []string{"glm-4.6v", "glm-4.7", "glm-5-turbo", "glm-5v-turbo", "glm-5.1"},
	},
	"openrouter": {
		BaseURL:     "https://openrouter.ai/api",
		Transformer: "anthropic",
		Models:      []string{"openai/gpt-5.4", "openai/gpt-5.4-mini", "openai/gpt-5.3-codex", "google/gemini-2.5-flash", "google/gemini-2.5-pro"},
	},
	"openrouter-openai": {
		BaseURL:     "https://openrouter.ai/api",
		Transformer: "anthropic",
		Models:      []string{"openai/gpt-5.4", "openai/gpt-5.4-mini", "openai/gpt-5.3-codex"},
	},
	"openrouter-anthropic": {
		BaseURL:     "https://openrouter.ai/api",
		Transformer: "anthropic",
		Models:      []string{"anthropic/claude-haiku-4.5", "anthropic/claude-sonnet-4.5", "anthropic/claude-sonnet-4.6", "anthropic/claude-opus-4.5", "anthropic/claude-opus-4.6"},
	},
}

ProviderPresets contains all available provider presets.

View Source
var TransformerOptions = []string{"anthropic", "openai", "glm_anthropic", "gemini"}

TransformerOptions are the available transformer options.

Functions

func GenerateEnvVarName

func GenerateEnvVarName(providerName string) string

GenerateEnvVarName generates the environment variable name for a provider.

func GenerateExportLine

func GenerateExportLine(providerName, apiKey string) string

GenerateExportLine generates the shell export line for an API key.

func GetBackLabel

func GetBackLabel(screen Screen) string

GetBackLabel returns the back label for a screen.

func GetExportPreview

func GetExportPreview(providerName, apiKey string) string

GetExportPreview returns a preview of what will be added to the shell config.

func GetScreenTitle

func GetScreenTitle(screen Screen) string

GetScreenTitle returns the title for a screen.

func ValidateHost

func ValidateHost(host string) bool

ValidateHost validates that the host is not empty.

func ValidatePort

func ValidatePort(port string) bool

ValidatePort validates that the port is in the valid range.

Types

type ProviderPreset

type ProviderPreset struct {
	BaseURL     string
	Transformer string
	Models      []string
}

ProviderPreset defines preset provider configurations.

type Screen

type Screen int

Screen represents the current screen in the wizard.

const (
	ScreenMainMenu Screen = iota
	ScreenProviders
	ScreenAddProvider1
	ScreenAddProvider2
	ScreenRoutes
	ScreenEditRoute
	ScreenCreateProfile
	ScreenEditProfile
	ScreenServer
	ScreenLogging
	ScreenViewConfig
	ScreenTestConnection
)

type ShellConfig

type ShellConfig struct {
	ShellPath  string
	RCFilePath string
	ExportLine string
}

ShellConfig handles shell configuration for API keys.

func GetShellConfig

func GetShellConfig() (*ShellConfig, error)

GetShellConfig returns the appropriate shell configuration.

func (*ShellConfig) AddToShellConfig

func (s *ShellConfig) AddToShellConfig(providerName, apiKey string) error

AddToShellConfig adds the API key export to the shell RC file. Uses a two-phase approach: remove all existing ccrouter entries for this provider, then append a single fresh comment+export pair.

func (*ShellConfig) RemoveFromShellConfig

func (s *ShellConfig) RemoveFromShellConfig(providerName string) error

RemoveFromShellConfig removes the API key export for a provider from the RC file. Uses the same Phase 1 filtering as AddToShellConfig but without Phase 2 append.

func (*ShellConfig) SourceAllNow

func (s *ShellConfig) SourceAllNow(apiKeys map[string]string)

SourceAllNow exports all API keys in the current process environment.

func (*ShellConfig) SourceNow

func (s *ShellConfig) SourceNow(providerName, apiKey string) error

SourceNow exports the API key in the current process environment.

func (*ShellConfig) SyncAllShellExports

func (s *ShellConfig) SyncAllShellExports(apiKeys map[string]string) error

SyncAllShellExports removes ALL ccrouter entries from the RC file, then re-adds entries for the given apiKeys map (provider name → real API key value). This ensures the RC file is fully reconciled with the current config.

func (*ShellConfig) WriteEnvFile

func (s *ShellConfig) WriteEnvFile(apiKeys map[string]string) (string, error)

WriteEnvFile writes a shell env file at ~/.cc-modelrouter/shell_env.sh containing export lines for the given API keys. Returns the file path.

type TestConnectionResult

type TestConnectionResult struct {
	Success      bool
	Latency      time.Duration
	Error        string
	InputTokens  int
	OutputTokens int
	CostEstimate string
}

TestConnectionResult contains the result of a connection test.

func TestProviderConnection

func TestProviderConnection(providerName string, providerCfg config.ProviderConfig, model string) *TestConnectionResult

TestProviderConnection tests connectivity to a provider with a specific model.

type TestConnectionResultMsg

type TestConnectionResultMsg struct {
	Success bool
	Latency time.Duration
	Error   string
}

type WizardKeyMap

type WizardKeyMap struct {
	Up     key.Binding
	Down   key.Binding
	Enter  key.Binding
	Escape key.Binding
	Delete key.Binding
	Tab    key.Binding
}

WizardKeyMap defines the key bindings for the wizard.

func DefaultKeyMap

func DefaultKeyMap() WizardKeyMap

DefaultKeyMap returns the default key bindings.

type WizardModel

type WizardModel struct {
	// contains filtered or unexported fields
}

WizardModel is the main Bubble Tea model for the wizard.

func NewWizardModel

func NewWizardModel(cfg *config.Config, configPath string) *WizardModel

NewWizardModel creates a new wizard model.

func (*WizardModel) Init

func (m *WizardModel) Init() tea.Cmd

Init initializes the wizard.

func (*WizardModel) ResolvedKeys

func (m *WizardModel) ResolvedKeys() map[string]string

ResolvedKeys returns the resolved API keys map from the wizard model.

func (*WizardModel) Update

func (m *WizardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd)

Update handles messages.

func (*WizardModel) View

func (m *WizardModel) View() string

View renders the wizard UI.

type WizardState

type WizardState struct {
	// Navigation
	CurrentScreen  Screen
	PreviousScreen Screen

	// Config being edited
	Config      *config.Config
	ConfigPath  string
	HasChanges  bool
	OriginalCfg *config.Config // For detecting changes

	// Main menu state
	MainMenuCursor int // saved main menu cursor when entering sub-screens

	// Provider list screen state
	ProviderCursor int

	// Add/Edit provider state (Step 1)
	NewProviderName        string
	NewProviderBaseURL     string
	NewProviderTransformer string
	NewProviderModels      string
	ProviderPreset         string // "anthropic", "openrouter", "bigmodel", "gemini", "custom"
	EditingProvider        bool   // true when editing an existing provider

	// Add provider state (Step 2)
	NewProviderAPIKey string
	AddToShellConfig  bool
	SourceImmediately bool

	// Routes screen state
	RouteCursor int

	// Edit route state
	EditRouteName        string
	EditRouteChain       []config.RouteTarget
	EditRouteChainCursor int // selected chain item index
	SelectedProvider     string
	SelectedModel        string

	// Server screen state
	ServerHost  string
	ServerPort  string
	PortStatus  string // non-empty = port availability status (warning or success)
	PortTesting bool   // true while port availability test is running

	// Logging screen state
	LoggingEnabled     bool
	LoggingLevel       string
	LoggingDestination string
	LoggingFilePath    string

	// Logging dropdown state
	ShowLogLevelDropdown   bool
	LogLevelDropdownCursor int
	ShowLogDestDropdown    bool
	LogDestDropdownCursor  int

	// Test connection state
	TestProvider string
	TestModel    string
	TestStatus   string // "testing", "success", "error"
	TestError    string
	TestLatency  float64

	// Modal/Confirmation
	ShowConfirm    bool
	ConfirmMessage string
	ConfirmAction  func() bool
	ConfirmCursor  int // 0 = Yes focused, 1 = No focused

	// Resolved API keys (real values, not ${CCROUTER_...} placeholders)
	ResolvedAPIKeys      map[string]string
	OriginalResolvedKeys map[string]string // snapshot for change detection

	// Error message
	ErrorMessage string

	// Dropdown state (Add Provider screen)
	ShowDropdown   bool
	DropdownCursor int

	// Model dropdown state (Add Provider screen)
	ShowModelDropdown   bool
	ModelDropdownCursor int

	// Route name dropdown state (Edit Route screen)
	ShowRouteNameDropdown   bool
	RouteNameDropdownCursor int

	// Profile tabs state (Routes screen)
	ProfileTabIndex int      // Current tab index (0 = legacy, 1 = default, 2 = ...)
	ProfileTabKeys  []string // Tab keys in order: ["legacy", "default", "cost-opt", ...]

	// Profile edit state (when creating/editing profile metadata)
	EditProfileKey       string // Profile key being edited (empty when creating new)
	EditProfileName      string // Profile display name (for rename/create)
	EditProfileDesc      string // Profile description
	ShowProfileEditModal bool   // Show profile name/description edit modal (deprecated - use ScreenEditProfile instead)
	IsCreatingProfile    bool   // true when creating new profile, false when editing existing

	// Migration state (when creating first profile with legacy routes)
	ShowMigrationModal bool // Show migration confirmation modal
	MigrationChoice    int  // 0 = copy routes, 1 = start empty
}

WizardState holds all state for the configuration wizard.

func NewWizardState

func NewWizardState(cfg *config.Config, configPath string) *WizardState

NewWizardState creates a new wizard state with defaults.

func (*WizardState) HasUnsavedChanges

func (s *WizardState) HasUnsavedChanges() bool

HasUnsavedChanges returns true if the config has been modified.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL