feat: add --quick flag to onboard for non-interactive setup
- zeroclaw onboard --quick: generates config with sensible defaults, zero prompts - zeroclaw onboard --quick --api-key sk-... --provider anthropic: one-liner setup - Fixes wizard hanging in non-TTY / IDE terminals - Scaffolds workspace files, prints summary, shows next steps - 1,017 tests, 0 clippy warnings
This commit is contained in:
parent
59bab7c554
commit
589921bbf8
3 changed files with 159 additions and 6 deletions
31
src/main.rs
31
src/main.rs
|
|
@ -46,7 +46,19 @@ struct Cli {
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
enum Commands {
|
enum Commands {
|
||||||
/// Initialize your workspace and configuration
|
/// Initialize your workspace and configuration
|
||||||
Onboard,
|
Onboard {
|
||||||
|
/// Skip interactive prompts — generate config with sensible defaults
|
||||||
|
#[arg(long)]
|
||||||
|
quick: bool,
|
||||||
|
|
||||||
|
/// API key (used with --quick)
|
||||||
|
#[arg(long)]
|
||||||
|
api_key: Option<String>,
|
||||||
|
|
||||||
|
/// Provider name (used with --quick, default: openrouter)
|
||||||
|
#[arg(long)]
|
||||||
|
provider: Option<String>,
|
||||||
|
},
|
||||||
|
|
||||||
/// Start the AI agent loop
|
/// Start the AI agent loop
|
||||||
Agent {
|
Agent {
|
||||||
|
|
@ -210,9 +222,18 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
|
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
|
||||||
|
|
||||||
// Onboard runs the interactive wizard — no existing config needed
|
// Onboard runs the wizard or quick setup — no existing config needed
|
||||||
if matches!(cli.command, Commands::Onboard) {
|
if let Commands::Onboard {
|
||||||
let config = onboard::run_wizard()?;
|
quick,
|
||||||
|
api_key,
|
||||||
|
provider,
|
||||||
|
} = &cli.command
|
||||||
|
{
|
||||||
|
let config = if *quick {
|
||||||
|
onboard::run_quick_setup(api_key.as_deref(), provider.as_deref())?
|
||||||
|
} else {
|
||||||
|
onboard::run_wizard()?
|
||||||
|
};
|
||||||
// Auto-start channels if user said yes during wizard
|
// Auto-start channels if user said yes during wizard
|
||||||
if std::env::var("ZEROCLAW_AUTOSTART_CHANNELS").as_deref() == Ok("1") {
|
if std::env::var("ZEROCLAW_AUTOSTART_CHANNELS").as_deref() == Ok("1") {
|
||||||
channels::start_channels(config).await?;
|
channels::start_channels(config).await?;
|
||||||
|
|
@ -224,7 +245,7 @@ async fn main() -> Result<()> {
|
||||||
let config = Config::load_or_init()?;
|
let config = Config::load_or_init()?;
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Commands::Onboard => unreachable!(),
|
Commands::Onboard { .. } => unreachable!(),
|
||||||
|
|
||||||
Commands::Agent {
|
Commands::Agent {
|
||||||
message,
|
message,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
pub mod wizard;
|
pub mod wizard;
|
||||||
|
|
||||||
pub use wizard::run_wizard;
|
pub use wizard::{run_quick_setup, run_wizard};
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,138 @@ pub fn run_wizard() -> Result<Config> {
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Quick setup (zero prompts) ───────────────────────────────────
|
||||||
|
|
||||||
|
/// Non-interactive setup: generates a sensible default config instantly.
|
||||||
|
/// Use `zeroclaw onboard --quick` or `zeroclaw onboard --quick --api-key sk-... --provider openrouter`
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
pub fn run_quick_setup(api_key: Option<&str>, provider: Option<&str>) -> Result<Config> {
|
||||||
|
println!("{}", style(BANNER).cyan().bold());
|
||||||
|
println!(
|
||||||
|
" {}",
|
||||||
|
style("Quick Setup — generating config with sensible defaults...")
|
||||||
|
.white()
|
||||||
|
.bold()
|
||||||
|
);
|
||||||
|
println!();
|
||||||
|
|
||||||
|
let home = directories::UserDirs::new()
|
||||||
|
.map(|u| u.home_dir().to_path_buf())
|
||||||
|
.context("Could not find home directory")?;
|
||||||
|
let zeroclaw_dir = home.join(".zeroclaw");
|
||||||
|
let workspace_dir = zeroclaw_dir.join("workspace");
|
||||||
|
let config_path = zeroclaw_dir.join("config.toml");
|
||||||
|
|
||||||
|
fs::create_dir_all(&workspace_dir).context("Failed to create workspace directory")?;
|
||||||
|
|
||||||
|
let provider_name = provider.unwrap_or("openrouter").to_string();
|
||||||
|
let model = default_model_for_provider(&provider_name);
|
||||||
|
|
||||||
|
let config = Config {
|
||||||
|
workspace_dir: workspace_dir.clone(),
|
||||||
|
config_path: config_path.clone(),
|
||||||
|
api_key: api_key.map(String::from),
|
||||||
|
default_provider: Some(provider_name.clone()),
|
||||||
|
default_model: Some(model.clone()),
|
||||||
|
default_temperature: 0.7,
|
||||||
|
observability: ObservabilityConfig::default(),
|
||||||
|
autonomy: AutonomyConfig {
|
||||||
|
level: AutonomyLevel::Full,
|
||||||
|
workspace_only: false,
|
||||||
|
..AutonomyConfig::default()
|
||||||
|
},
|
||||||
|
runtime: RuntimeConfig::default(),
|
||||||
|
heartbeat: HeartbeatConfig::default(),
|
||||||
|
channels_config: ChannelsConfig::default(),
|
||||||
|
memory: MemoryConfig::default(),
|
||||||
|
tunnel: crate::config::TunnelConfig::default(),
|
||||||
|
gateway: crate::config::GatewayConfig::default(),
|
||||||
|
composio: ComposioConfig::default(),
|
||||||
|
secrets: SecretsConfig::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
config.save()?;
|
||||||
|
|
||||||
|
// Scaffold minimal workspace files
|
||||||
|
let default_ctx = ProjectContext {
|
||||||
|
user_name: std::env::var("USER").unwrap_or_else(|_| "User".into()),
|
||||||
|
timezone: "UTC".into(),
|
||||||
|
agent_name: "ZeroClaw".into(),
|
||||||
|
communication_style: "Direct and concise".into(),
|
||||||
|
};
|
||||||
|
scaffold_workspace(&workspace_dir, &default_ctx)?;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
" {} Workspace: {}",
|
||||||
|
style("✓").green().bold(),
|
||||||
|
style(workspace_dir.display()).green()
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" {} Provider: {}",
|
||||||
|
style("✓").green().bold(),
|
||||||
|
style(&provider_name).green()
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" {} Model: {}",
|
||||||
|
style("✓").green().bold(),
|
||||||
|
style(&model).green()
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" {} API Key: {}",
|
||||||
|
style("✓").green().bold(),
|
||||||
|
if api_key.is_some() {
|
||||||
|
style("set").green()
|
||||||
|
} else {
|
||||||
|
style("not set (use --api-key or edit config.toml)").yellow()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" {} Security: {}",
|
||||||
|
style("✓").green().bold(),
|
||||||
|
style("Full Autonomy").green()
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" {} Memory: {}",
|
||||||
|
style("✓").green().bold(),
|
||||||
|
style("sqlite (auto-save)").green()
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" {} Secrets: {}",
|
||||||
|
style("✓").green().bold(),
|
||||||
|
style("encrypted").green()
|
||||||
|
);
|
||||||
|
println!();
|
||||||
|
println!(
|
||||||
|
" {} {}",
|
||||||
|
style("Config saved:").white().bold(),
|
||||||
|
style(config_path.display()).green()
|
||||||
|
);
|
||||||
|
println!();
|
||||||
|
println!(" Next steps:");
|
||||||
|
if api_key.is_none() {
|
||||||
|
println!(" 1. Set your API key: export OPENROUTER_API_KEY=\"sk-...\"");
|
||||||
|
println!(" 2. Or edit: ~/.zeroclaw/config.toml");
|
||||||
|
println!(" 3. Run: zeroclaw agent -m \"Hello!\"");
|
||||||
|
} else {
|
||||||
|
println!(" Run: zeroclaw agent -m \"Hello!\"");
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pick a sensible default model for the given provider.
|
||||||
|
fn default_model_for_provider(provider: &str) -> String {
|
||||||
|
match provider {
|
||||||
|
"anthropic" => "claude-sonnet-4-20250514".into(),
|
||||||
|
"openai" => "gpt-4o".into(),
|
||||||
|
"ollama" => "llama3.2".into(),
|
||||||
|
"groq" => "llama-3.3-70b-versatile".into(),
|
||||||
|
"deepseek" => "deepseek-chat".into(),
|
||||||
|
_ => "anthropic/claude-sonnet-4-20250514".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Step helpers ─────────────────────────────────────────────────
|
// ── Step helpers ─────────────────────────────────────────────────
|
||||||
|
|
||||||
fn print_step(current: u8, total: u8, title: &str) {
|
fn print_step(current: u8, total: u8, title: &str) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue