Skip to content

Commit 1fbe892

Browse files
authored
Merge pull request #77 from wafflestudio/feature/interactive-onboarding
feat: Add interactive onboarding wizard with API key and config manag…
2 parents 29a7e69 + a5cb7f4 commit 1fbe892

File tree

10 files changed

+936
-78
lines changed

10 files changed

+936
-78
lines changed

Cargo.lock

Lines changed: 60 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ regex = "1.10"
4343
lazy_static = "1.4"
4444
syntect = "5.2"
4545
terminal_size = "0.3"
46+
toml = "0.8"
4647

4748
[features]
4849
default = ["reqwest"]

src/cli/config.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
use anyhow::Result;
2+
use console::style;
3+
4+
use crate::config::Config;
5+
6+
/// Handle config get command
7+
pub async fn handle_config_get(key: String) -> Result<()> {
8+
let config = Config::load()?;
9+
10+
if let Some(value) = config.get(&key) {
11+
println!("{value}");
12+
Ok(())
13+
} else {
14+
anyhow::bail!("Config key '{key}' not found");
15+
}
16+
}
17+
18+
/// Handle config set command
19+
pub async fn handle_config_set(key: String, value: String) -> Result<()> {
20+
let mut config = Config::load()?;
21+
22+
config.set(&key, value.clone())?;
23+
config.save()?;
24+
25+
println!(
26+
"{} Config '{}' set successfully",
27+
style("✓").green(),
28+
style(&key).cyan()
29+
);
30+
println!(
31+
" Saved to: {}",
32+
style(Config::get_config_path()?.display()).dim()
33+
);
34+
35+
Ok(())
36+
}
37+
38+
/// Handle config unset command
39+
pub async fn handle_config_unset(key: String) -> Result<()> {
40+
let mut config = Config::load()?;
41+
42+
config.unset(&key)?;
43+
config.save()?;
44+
45+
println!(
46+
"{} Config '{}' removed",
47+
style("✓").green(),
48+
style(&key).cyan()
49+
);
50+
51+
Ok(())
52+
}
53+
54+
/// Handle config list command
55+
pub async fn handle_config_list() -> Result<()> {
56+
let config = Config::load()?;
57+
let items = config.list();
58+
59+
if items.is_empty() {
60+
println!("{}", style("No configuration set.").dim());
61+
println!();
62+
println!("💡 Set a config value:");
63+
println!(
64+
" {}",
65+
style("retrochat config set google-ai-api-key YOUR_KEY").cyan()
66+
);
67+
} else {
68+
println!("{}", style("Configuration:").bold());
69+
println!();
70+
for (key, value) in items {
71+
println!(" {} = {}", style(key).cyan(), style(value).dim());
72+
}
73+
println!();
74+
println!(
75+
" Config file: {}",
76+
style(Config::get_config_path()?.display()).dim()
77+
);
78+
}
79+
80+
Ok(())
81+
}
82+
83+
/// Handle config path command
84+
pub async fn handle_config_path() -> Result<()> {
85+
let config_path = Config::get_config_path()?;
86+
println!("{}", config_path.display());
87+
Ok(())
88+
}

src/cli/help.rs

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -175,15 +175,82 @@ pub fn print_environment_config() {
175175

176176
/// Print full getting started guide for TUI
177177
pub fn print_full_getting_started() {
178-
println!("Getting Started:");
178+
use console::style;
179+
179180
println!();
180-
print_import_help();
181-
print_supported_formats();
182-
println!("3. Launch TUI:");
183-
println!(" $ retrochat tui");
181+
println!(
182+
"{}",
183+
style("────────────────────────────────────────────────────────────────────────────").dim()
184+
);
185+
println!(
186+
" {} {}",
187+
style("📂").bold(),
188+
style("Import Chat Files").bold().cyan()
189+
);
190+
println!(
191+
"{}",
192+
style("────────────────────────────────────────────────────────────────────────────").dim()
193+
);
194+
println!();
195+
println!("From providers:");
196+
println!(
197+
" {} {}",
198+
style("$").dim(),
199+
style("retrochat import claude").cyan()
200+
);
201+
println!(
202+
" {} {}",
203+
style("$").dim(),
204+
style("retrochat import gemini").cyan()
205+
);
206+
println!(
207+
" {} {}",
208+
style("$").dim(),
209+
style("retrochat import codex").cyan()
210+
);
211+
println!(
212+
" {} {} {}",
213+
style("$").dim(),
214+
style("retrochat import all").cyan(),
215+
style("# All at once").dim()
216+
);
184217
println!();
185-
println!("4. Generate insights:");
186-
println!(" $ retrochat analytics insights");
218+
println!("From custom path:");
219+
println!(
220+
" {} {}",
221+
style("$").dim(),
222+
style("retrochat import --path <directory>").cyan()
223+
);
224+
println!();
225+
println!("Supported: Claude Code, Gemini CLI, Codex");
226+
println!();
227+
println!();
228+
println!(
229+
"{}",
230+
style("────────────────────────────────────────────────────────────────────────────").dim()
231+
);
232+
println!(
233+
" {} {}",
234+
style("🚀").bold(),
235+
style("Next Steps").bold().cyan()
236+
);
237+
println!(
238+
"{}",
239+
style("────────────────────────────────────────────────────────────────────────────").dim()
240+
);
241+
println!();
242+
println!(
243+
" {} {} {}",
244+
style("$").dim(),
245+
style("retrochat tui").cyan(),
246+
style("# Launch TUI").dim()
247+
);
248+
println!(
249+
" {} {} {}",
250+
style("$").dim(),
251+
style("retrochat analytics execute").cyan(),
252+
style("# Generate insights").dim()
253+
);
187254
println!();
188255
}
189256

src/cli/mod.rs

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod analytics;
2+
pub mod config;
23
pub mod help;
34
pub mod import;
45
pub mod init;
@@ -11,7 +12,6 @@ use clap::{Parser, Subcommand};
1112
use std::sync::Arc;
1213
use tokio::runtime::Runtime;
1314

14-
use crate::env::apis as env_vars;
1515
use crate::models::Provider;
1616

1717
#[derive(Parser)]
@@ -138,6 +138,15 @@ pub enum Commands {
138138
#[arg(long)]
139139
no_tool: bool,
140140
},
141+
142+
/// Interactive setup wizard for first-time users
143+
Setup,
144+
145+
/// Manage configuration settings
146+
Config {
147+
#[command(subcommand)]
148+
command: ConfigCommands,
149+
},
141150
}
142151

143152
#[derive(Subcommand)]
@@ -198,6 +207,31 @@ pub enum AnalysisCommands {
198207
},
199208
}
200209

210+
#[derive(Subcommand)]
211+
pub enum ConfigCommands {
212+
/// Get a configuration value
213+
Get {
214+
/// Configuration key to get
215+
key: String,
216+
},
217+
/// Set a configuration value
218+
Set {
219+
/// Configuration key to set
220+
key: String,
221+
/// Value to set
222+
value: String,
223+
},
224+
/// Remove a configuration value
225+
Unset {
226+
/// Configuration key to remove
227+
key: String,
228+
},
229+
/// List all configuration values
230+
List,
231+
/// Show the path to the config file
232+
Path,
233+
}
234+
201235
impl Cli {
202236
pub fn run(self) -> anyhow::Result<()> {
203237
let rt = Runtime::new()?;
@@ -224,6 +258,11 @@ impl Cli {
224258
}
225259

226260
// After setup (or if DB already exists), launch TUI
261+
println!("{}",console::style("────────────────────────────────────────────────────────────────────────────").dim());
262+
println!(" {} {}", console::style("🚀").bold(), console::style("Launching TUI").bold().cyan());
263+
println!("{}", console::style("────────────────────────────────────────────────────────────────────────────").dim());
264+
println!();
265+
227266
return tui::handle_tui_command().await;
228267
}
229268
Some(cmd) => cmd,
@@ -347,6 +386,21 @@ impl Cli {
347386
})
348387
.await
349388
}
389+
390+
// ═══════════════════════════════════════════════════
391+
// Setup & Configuration
392+
// ═══════════════════════════════════════════════════
393+
Commands::Setup => setup::run_setup_wizard().await,
394+
395+
Commands::Config { command } => match command {
396+
ConfigCommands::Get { key } => config::handle_config_get(key).await,
397+
ConfigCommands::Set { key, value } => {
398+
config::handle_config_set(key, value).await
399+
}
400+
ConfigCommands::Unset { key } => config::handle_config_unset(key).await,
401+
ConfigCommands::List => config::handle_config_list().await,
402+
ConfigCommands::Path => config::handle_config_path().await,
403+
},
350404
}
351405
})
352406
}
@@ -365,7 +419,8 @@ impl Cli {
365419
let db_path = crate::database::config::get_default_db_path()?;
366420
let db_manager = rt.block_on(async { DatabaseManager::new(&db_path).await })?;
367421

368-
let api_key = std::env::var(env_vars::GOOGLE_AI_API_KEY).unwrap_or_else(|_| "".to_string());
422+
// Get API key with priority: environment variable > config file
423+
let api_key = crate::config::get_google_ai_api_key()?.unwrap_or_default();
369424

370425
let google_ai_config = if api_key.is_empty() {
371426
GoogleAiConfig::default()

0 commit comments

Comments
 (0)