sparrow/cmd_handlers/
setup_cmd.rs1use sparrow::auth::store::ChainedAuthStore;
4use sparrow::auth::{AuthStore, Credential};
5use sparrow::config::{Config, ConfigStore, FsConfigStore, ProviderConfig};
6
7pub async fn handle_setup(config: &Config, store: &FsConfigStore) -> anyhow::Result<()> {
8 use sparrow::tui::theme::boot_sequence;
9 use std::io::{self, Write};
10
11 for line in boot_sequence() {
12 println!("{}", line);
13 }
14 println!();
15 println!("═══ SPARROW SETUP ═══");
16 println!();
17 println!("Sparrow setup configures providers, model routing, budget, and autonomy.");
18 println!();
19 println!("Current configuration:");
20 println!(" Config dir : {:?}", config.config_dir);
21 println!(" State dir : {:?}", config.state_dir);
22 println!(" Autonomy : {:?}", config.defaults.autonomy);
23 println!(
24 " Budget : ${}/day, ${}/session",
25 config.budget.daily_usd, config.budget.session_usd
26 );
27 println!();
28
29 let effective = sparrow::config::effective_provider_configs(config);
30 if effective.is_empty() {
31 println!("No provider detected yet.");
32 } else {
33 println!("Detected/configured providers:");
34 for (name, pconfig) in &effective {
35 println!(" {} (adapter: {})", name, pconfig.adapter);
36 for model in &pconfig.models {
37 println!(" - {}", model);
38 }
39 }
40 }
41
42 println!();
43 println!("Recommended first setup:");
44 println!(" - local/free: ollama");
45 println!(" - cheap cloud: nvidia");
46 println!(" - strong cloud: anthropic");
47 println!();
48 print!("Configure or update a provider now? [Y/n] ");
49 io::stdout().flush().ok();
50 let mut answer = String::new();
51 io::stdin().read_line(&mut answer)?;
52 if matches!(answer.trim().to_lowercase().as_str(), "n" | "no" | "non") {
53 println!("Setup left unchanged. Run 'sparrow console' for the WebView config panel.");
54 return Ok(());
55 }
56
57 let registry = sparrow::config::providers::provider_registry();
58 println!("\nAvailable providers:");
59 for def in registry.iter().take(18) {
60 let env_state = def
61 .api_key_env
62 .as_ref()
63 .map(|env| {
64 if std::env::var(env)
65 .map(|v| !v.trim().is_empty())
66 .unwrap_or(false)
67 {
68 "env found"
69 } else {
70 "env missing"
71 }
72 })
73 .unwrap_or("no key needed");
74 println!(" {:18} {:22} {}", def.id, def.label, env_state);
75 }
76 println!(" custom Custom Endpoint");
77
78 print!("\nProvider id [nvidia]: ");
79 io::stdout().flush().ok();
80 let mut provider_id = String::new();
81 io::stdin().read_line(&mut provider_id)?;
82 let provider_id = provider_id.trim();
83 let provider_id = if provider_id.is_empty() {
84 "nvidia"
85 } else {
86 provider_id
87 };
88 let Some(def) = sparrow::config::providers::find_provider(provider_id) else {
89 anyhow::bail!(
90 "Unknown provider '{}'. Use 'sparrow model --list' or the WebView config panel.",
91 provider_id
92 );
93 };
94
95 let default_models = sparrow::config::providers::default_models(&def.id);
96 let default_model = default_models
97 .first()
98 .cloned()
99 .unwrap_or_else(|| "model".into());
100 print!("Model [{}]: ", default_model);
101 io::stdout().flush().ok();
102 let mut model = String::new();
103 io::stdin().read_line(&mut model)?;
104 let model = model.trim();
105 let model = if model.is_empty() {
106 default_model
107 } else {
108 model.to_string()
109 };
110
111 let mut next = config.clone();
112 next.providers.insert(
113 def.id.clone(),
114 ProviderConfig {
115 adapter: def.adapter.clone(),
116 base_url: Some(def.base_url.clone()),
117 models: vec![model],
118 api_key_env: def.api_key_env.clone(),
119 },
120 );
121
122 print!(
123 "Default routing provider for medium tasks [{}]? [Y/n] ",
124 def.id
125 );
126 io::stdout().flush().ok();
127 let mut route_answer = String::new();
128 io::stdin().read_line(&mut route_answer)?;
129 if !matches!(
130 route_answer.trim().to_lowercase().as_str(),
131 "n" | "no" | "non"
132 ) {
133 next.routing.policy.insert("medium".into(), def.id.clone());
134 if def.tags.iter().any(|t| t == "strong" || t == "code") {
135 next.routing.policy.insert("small".into(), def.id.clone());
136 }
137 }
138
139 if let Some(env_name) = &def.api_key_env {
140 if std::env::var(env_name)
141 .map(|v| !v.trim().is_empty())
142 .unwrap_or(false)
143 {
144 println!(
145 "Credential: {} is already present in environment.",
146 env_name
147 );
148 } else {
149 print!(
150 "Paste API key for {} now, or leave empty to use env later: ",
151 def.label
152 );
153 io::stdout().flush().ok();
154 let mut key = String::new();
155 io::stdin().read_line(&mut key)?;
156 let key = key.trim();
157 if !key.is_empty() {
158 let auth = ChainedAuthStore::new(next.config_dir.clone());
159 auth.set(&def.id, Credential::api_key(key.to_string()))?;
160 println!("Credential stored for {}.", def.id);
161 }
162 }
163 }
164
165 store.save(&next)?;
166 println!("\nSetup saved.");
167 println!("Run 'sparrow doctor' to verify or 'sparrow console' for the graphical WebView.");
168
169 Ok(())
170}