mirror of
https://github.com/instructkr/claw-code.git
synced 2026-06-10 00:12:16 +08:00
Merge pull request #3227 from TheArchitectit/worktree-wizard-entry-points
feat: wizard entry points — /setup command, claw setup subcommand (rebased)
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
mod init;
|
||||
mod input;
|
||||
mod render;
|
||||
mod setup_wizard;
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::env;
|
||||
@@ -1095,6 +1096,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
CliAction::SessionList { output_format } => run_session_list(output_format)?,
|
||||
CliAction::State { output_format } => run_worker_state(output_format)?,
|
||||
CliAction::Init { output_format } => run_init(output_format)?,
|
||||
CliAction::Setup { output_format: _ } => run_setup()?,
|
||||
// #146: dispatch pure-local introspection. Text mode uses existing
|
||||
// render_config_report/render_diff_report; JSON mode uses the
|
||||
// corresponding _json helpers already exposed for resume sessions.
|
||||
@@ -1238,6 +1240,9 @@ enum CliAction {
|
||||
Init {
|
||||
output_format: CliOutputFormat,
|
||||
},
|
||||
Setup {
|
||||
output_format: CliOutputFormat,
|
||||
},
|
||||
// #146: `claw config` and `claw diff` are pure-local read-only
|
||||
// introspection commands; wire them as standalone CLI subcommands.
|
||||
Config {
|
||||
@@ -1301,6 +1306,7 @@ enum LocalHelpTopic {
|
||||
Model,
|
||||
Settings,
|
||||
Diff,
|
||||
Setup,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -1765,6 +1771,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
"doctor" => Some(LocalHelpTopic::Doctor),
|
||||
"acp" => Some(LocalHelpTopic::Acp),
|
||||
"init" => Some(LocalHelpTopic::Init),
|
||||
"setup" => Some(LocalHelpTopic::Setup),
|
||||
"state" => Some(LocalHelpTopic::State),
|
||||
"resume" => Some(LocalHelpTopic::Resume),
|
||||
"session" => Some(LocalHelpTopic::Session),
|
||||
@@ -2144,6 +2151,15 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
}
|
||||
Ok(CliAction::Init { output_format })
|
||||
}
|
||||
"setup" => {
|
||||
if rest.len() > 1 {
|
||||
let extra = rest[1..].join(" ");
|
||||
return Err(format!(
|
||||
"unexpected extra arguments after `claw setup`: {extra}\nUsage: claw setup"
|
||||
));
|
||||
}
|
||||
Ok(CliAction::Setup { output_format })
|
||||
}
|
||||
"export" => parse_export_args(&rest[1..], output_format),
|
||||
"prompt" => {
|
||||
let mut read_stdin = false;
|
||||
@@ -2271,6 +2287,7 @@ fn parse_local_help_action(
|
||||
"doctor" => LocalHelpTopic::Doctor,
|
||||
"acp" => LocalHelpTopic::Acp,
|
||||
"init" => LocalHelpTopic::Init,
|
||||
"setup" => LocalHelpTopic::Setup,
|
||||
"state" => LocalHelpTopic::State,
|
||||
"export" => LocalHelpTopic::Export,
|
||||
"version" => LocalHelpTopic::Version,
|
||||
@@ -2316,7 +2333,7 @@ fn parse_single_word_command_alias(
|
||||
let verb = &rest[0];
|
||||
let is_diagnostic = matches!(
|
||||
verb.as_str(),
|
||||
"help" | "version" | "status" | "sandbox" | "doctor" | "state"
|
||||
"help" | "version" | "status" | "sandbox" | "doctor" | "setup" | "state"
|
||||
);
|
||||
|
||||
if is_diagnostic && rest.len() > 1 {
|
||||
@@ -2336,6 +2353,7 @@ fn parse_single_word_command_alias(
|
||||
"doctor" => Some(LocalHelpTopic::Doctor),
|
||||
"acp" => Some(LocalHelpTopic::Acp),
|
||||
"init" => Some(LocalHelpTopic::Init),
|
||||
"setup" => Some(LocalHelpTopic::Setup),
|
||||
"state" => Some(LocalHelpTopic::State),
|
||||
"export" => Some(LocalHelpTopic::Export),
|
||||
"version" => Some(LocalHelpTopic::Version),
|
||||
@@ -2390,6 +2408,7 @@ fn parse_single_word_command_alias(
|
||||
"doctor" => Some(LocalHelpTopic::Doctor),
|
||||
"acp" => Some(LocalHelpTopic::Acp),
|
||||
"init" => Some(LocalHelpTopic::Init),
|
||||
"setup" => Some(LocalHelpTopic::Setup),
|
||||
"state" => Some(LocalHelpTopic::State),
|
||||
"export" => Some(LocalHelpTopic::Export),
|
||||
"version" => Some(LocalHelpTopic::Version),
|
||||
@@ -2452,6 +2471,7 @@ fn parse_single_word_command_alias(
|
||||
.map(PermissionModeProvenance::from_flag)
|
||||
.unwrap_or_else(permission_mode_provenance_for_current_dir),
|
||||
})),
|
||||
"setup" => Some(Ok(CliAction::Setup { output_format })),
|
||||
"state" => Some(Ok(CliAction::State { output_format })),
|
||||
// #146: let `config` and `diff` fall through to parse_subcommand
|
||||
// where they are wired as pure-local introspection, instead of
|
||||
@@ -2744,6 +2764,7 @@ fn suggest_similar_subcommand(input: &str) -> Option<Vec<String>> {
|
||||
"status",
|
||||
"sandbox",
|
||||
"doctor",
|
||||
"setup",
|
||||
"state",
|
||||
"dump-manifests",
|
||||
"bootstrap-plan",
|
||||
@@ -3729,6 +3750,11 @@ fn run_doctor(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run the interactive setup wizard to configure provider, API key, and model.
|
||||
fn run_setup() -> Result<(), Box<dyn std::error::Error>> {
|
||||
setup_wizard::run_setup_wizard()
|
||||
}
|
||||
|
||||
/// Starts a minimal Model Context Protocol server that exposes claw's
|
||||
/// built-in tools over stdio.
|
||||
///
|
||||
@@ -6900,7 +6926,8 @@ fn run_resume_command(
|
||||
| SlashCommand::Tag { .. }
|
||||
| SlashCommand::OutputStyle { .. }
|
||||
| SlashCommand::AddDir { .. }
|
||||
| SlashCommand::Team { .. } => Err("unsupported resumed slash command".into()),
|
||||
| SlashCommand::Team { .. }
|
||||
| SlashCommand::Setup => Err("unsupported resumed slash command".into()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8161,6 +8188,12 @@ impl LiveCli {
|
||||
);
|
||||
false
|
||||
}
|
||||
SlashCommand::Setup => {
|
||||
if let Err(e) = setup_wizard::run_setup_wizard() {
|
||||
eprintln!("Setup wizard failed: {e}");
|
||||
}
|
||||
false
|
||||
}
|
||||
SlashCommand::History { count } => {
|
||||
self.print_prompt_history(count.as_deref());
|
||||
false
|
||||
@@ -10230,6 +10263,13 @@ fn render_help_topic(topic: LocalHelpTopic) -> String {
|
||||
Formats text (default), json
|
||||
Related /diff · ROADMAP #148"
|
||||
.to_string(),
|
||||
LocalHelpTopic::Setup => "Setup
|
||||
Usage claw setup
|
||||
Aliases /setup (inside the REPL)
|
||||
Purpose run the interactive provider setup wizard to configure API key, model, and base URL
|
||||
Output writes provider settings to ~/.claw/settings.json (0600 permissions)
|
||||
Related /model · /config · claw doctor"
|
||||
.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10257,6 +10297,7 @@ fn local_help_topic_command(topic: LocalHelpTopic) -> &'static str {
|
||||
LocalHelpTopic::Model => "models",
|
||||
LocalHelpTopic::Settings => "settings",
|
||||
LocalHelpTopic::Diff => "diff",
|
||||
LocalHelpTopic::Setup => "setup",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,10 @@ const DEFAULT_BASE_URLS: &[(&str, &str)] = &[
|
||||
("anthropic", "https://api.anthropic.com"),
|
||||
("xai", "https://api.x.ai/v1"),
|
||||
("openai", "https://api.openai.com/v1"),
|
||||
("dashscope", "https://dashscope.aliyuncs.com/compatible-mode/v1"),
|
||||
(
|
||||
"dashscope",
|
||||
"https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||
),
|
||||
];
|
||||
|
||||
const API_KEY_ENV_VARS: &[(&str, &str)] = &[
|
||||
@@ -51,12 +54,7 @@ pub fn run_setup_wizard() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let model = prompt_model(&kind, ¤t)?;
|
||||
let fast_model = prompt_fast_model(¤t, model.as_deref())?;
|
||||
|
||||
save_user_provider_settings(
|
||||
&kind,
|
||||
&api_key,
|
||||
base_url.as_deref(),
|
||||
model.as_deref(),
|
||||
)?;
|
||||
save_user_provider_settings(&kind, &api_key, base_url.as_deref(), model.as_deref())?;
|
||||
|
||||
if let Some(fast) = &fast_model {
|
||||
save_settings_field("subagentModel", fast)?;
|
||||
@@ -64,7 +62,10 @@ pub fn run_setup_wizard() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
println!();
|
||||
println!(" \x1b[32mProvider saved to ~/.claw/settings.json\x1b[0m");
|
||||
println!(" Run \x1b[1m/model {}\x1b[0m or restart claw to activate.", model.as_deref().unwrap_or(&kind));
|
||||
println!(
|
||||
" Run \x1b[1m/model {}\x1b[0m or restart claw to activate.",
|
||||
model.as_deref().unwrap_or(&kind)
|
||||
);
|
||||
println!();
|
||||
|
||||
Ok(())
|
||||
@@ -82,7 +83,11 @@ fn prompt_provider(current: &RuntimeProviderConfig) -> Result<String, Box<dyn st
|
||||
let current_kind = current.kind().unwrap_or("anthropic");
|
||||
println!(" \x1b[1mProvider\x1b[0m");
|
||||
for (num, label, kind) in PROVIDERS {
|
||||
let marker = if *kind == current_kind { " (current)" } else { "" };
|
||||
let marker = if *kind == current_kind {
|
||||
" (current)"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
println!(" [{num}] {label}{marker}");
|
||||
}
|
||||
let default = PROVIDERS
|
||||
@@ -129,9 +134,7 @@ fn prompt_api_key(
|
||||
};
|
||||
|
||||
// Check if env var is already set
|
||||
let env_set = std::env::var(env_var)
|
||||
.ok()
|
||||
.is_some_and(|v| !v.is_empty());
|
||||
let env_set = std::env::var(env_var).ok().is_some_and(|v| !v.is_empty());
|
||||
if env_set {
|
||||
println!(" {env_var} is set in environment (will take priority over stored key)");
|
||||
}
|
||||
@@ -144,7 +147,9 @@ fn prompt_api_key(
|
||||
};
|
||||
|
||||
if key.is_empty() && !env_set {
|
||||
eprintln!(" \x1b[33mWarning: no API key configured. Set {env_var} or re-run setup.\x1b[0m");
|
||||
eprintln!(
|
||||
" \x1b[33mWarning: no API key configured. Set {env_var} or re-run setup.\x1b[0m"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(key)
|
||||
@@ -174,9 +179,7 @@ fn prompt_base_url(
|
||||
"dashscope" => "DASHSCOPE_BASE_URL",
|
||||
_ => "BASE_URL",
|
||||
};
|
||||
let env_set = std::env::var(env_var)
|
||||
.ok()
|
||||
.is_some_and(|v| !v.is_empty());
|
||||
let env_set = std::env::var(env_var).ok().is_some_and(|v| !v.is_empty());
|
||||
if env_set {
|
||||
println!(" {env_var} is set in environment (will take priority over stored URL)");
|
||||
}
|
||||
@@ -203,7 +206,9 @@ fn prompt_model(
|
||||
.find(|(k, _)| *k == kind)
|
||||
.map_or(empty, |(_, models)| *models);
|
||||
|
||||
let current_model = current.model().unwrap_or(aliases.first().copied().unwrap_or(""));
|
||||
let current_model = current
|
||||
.model()
|
||||
.unwrap_or(aliases.first().copied().unwrap_or(""));
|
||||
|
||||
println!(" \x1b[1mModel\x1b[0m");
|
||||
if !aliases.is_empty() {
|
||||
@@ -235,12 +240,16 @@ fn prompt_fast_model(
|
||||
println!(" Press Enter to skip (agents will use your main model).");
|
||||
|
||||
let current_fast = load_current_settings_field("subagentModel");
|
||||
let default_hint = current_fast
|
||||
.as_deref()
|
||||
.or(main_model)
|
||||
.unwrap_or("");
|
||||
let default_hint = current_fast.as_deref().or(main_model).unwrap_or("");
|
||||
|
||||
let input = read_line(&format!(" Fast model [{}]: ", if default_hint.is_empty() { "same as main" } else { default_hint }))?;
|
||||
let input = read_line(&format!(
|
||||
" Fast model [{}]: ",
|
||||
if default_hint.is_empty() {
|
||||
"same as main"
|
||||
} else {
|
||||
default_hint
|
||||
}
|
||||
))?;
|
||||
if input.trim().is_empty() {
|
||||
Ok(current_fast)
|
||||
} else {
|
||||
@@ -269,7 +278,10 @@ fn save_settings_field(field: &str, value: &str) -> Result<(), Box<dyn std::erro
|
||||
};
|
||||
|
||||
if let Some(obj) = settings.as_object_mut() {
|
||||
obj.insert(field.to_string(), serde_json::Value::String(value.to_string()));
|
||||
obj.insert(
|
||||
field.to_string(),
|
||||
serde_json::Value::String(value.to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
std::fs::create_dir_all(&settings_dir)?;
|
||||
|
||||
Reference in New Issue
Block a user