From 17fba63a66c7b42c9c37f7fc7943a9b264b0c940 Mon Sep 17 00:00:00 2001 From: Duyet Le Date: Mon, 14 Sep 2026 15:47:08 +0000 Subject: [PATCH] chore: fix baseline clippy warnings in src/ - help.rs: const thread_local initializer - spawn.rs: derive Default; use !contains_key; struct shorthand in tests - term.rs: div_ceil; writeln! instead of write with newline - tui/view.rs: to_string() instead of format! - upgrade.rs: unwrap_or_default() - http.rs: rename to snake_case - cmd/{config_tui,dispatch,keys,menu,models}.rs: &PathBuf -> &Path - cmd/dispatch.rs: match -> matches! Co-Authored-By: Claude Code --- README.md | 2 +- src/cmd/config_tui.rs | 34 +++++++++++++++++++++++----------- src/cmd/dispatch.rs | 16 ++++++++-------- src/cmd/keys.rs | 4 ++-- src/cmd/launch.rs | 2 +- src/cmd/menu.rs | 40 ++++++++++++++++++++++++---------------- src/cmd/models.rs | 14 +++++--------- src/config.rs | 6 ++++-- src/demo.rs | 2 +- src/help.rs | 10 ++++------ src/http.rs | 4 ++-- src/key.rs | 17 ++++++++++++++--- src/spawn.rs | 36 ++++++++++++++---------------------- src/term.rs | 4 ++-- src/tui/view.rs | 2 +- src/upgrade.rs | 4 ++-- tests/cli.rs | 39 +++++++++++++++++++++++++++++++++++++++ 17 files changed, 147 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index a08dfc0..1c7629a 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ anyr account use work # switch saved profiles anyr onboard impl # paste-ready prompt for a coding agent anyr plan # migration plan prompt (no code changes) anyr fix # repair LLM wiring through AnyRouter -anyr claude --ok # launch through the gateway +anyr claude --ok # launch through the gateway (model: anyrouter/auto) anyr claude --install --ok # install Claude Code first if missing anyr codex --ok anyr opencode --ok diff --git a/src/cmd/config_tui.rs b/src/cmd/config_tui.rs index 1763df9..cb698b5 100644 --- a/src/cmd/config_tui.rs +++ b/src/cmd/config_tui.rs @@ -1,5 +1,5 @@ use std::collections::BTreeMap; -use std::path::PathBuf; +use std::path::Path; use crate::config::{valid_account_name, write_config, Profile}; use crate::http::{fetch_credits, fetch_me}; @@ -35,7 +35,7 @@ use crate::cmd::usage::run_usage; pub(crate) fn print_config_status( parsed: &ParsedArgs, env: &BTreeMap, - path: &PathBuf, + path: &Path, ) -> Result<(), String> { let cfg = load_config_if_present(path).unwrap_or_default(); let profile = cfg.profiles.get(&cfg.active_profile); @@ -57,7 +57,11 @@ pub(crate) fn print_config_status( } else { format!("{account} {extra_email}") }; - let model = session_model_label(profile.map(|p| p.default_model()).unwrap_or("auto")); + let model = session_model_label( + profile + .map(|p| p.default_model()) + .unwrap_or(crate::config::DEFAULT_MODEL), + ); let agent = profile .and_then(|p| p.default_tool.clone()) .or_else(|| cfg.last_tool.clone()) @@ -203,7 +207,7 @@ pub(crate) fn settings_tab_names() -> Vec { pub(crate) fn config_settings_frame( parsed: &ParsedArgs, env: &BTreeMap, - path: &PathBuf, + path: &Path, online: bool, cache: &mut CreditsCache, tab: usize, @@ -276,7 +280,7 @@ pub(crate) fn config_settings_frame( pub(crate) fn fill_general_settings( rows: &mut Vec, kinds: &mut Vec>, - path: &PathBuf, + path: &Path, parsed: &ParsedArgs, env: &BTreeMap, profile: Option<&Profile>, @@ -339,7 +343,11 @@ pub(crate) fn fill_general_settings( rows, kinds, "default", - session_model_label(profile.map(|p| p.default_model()).unwrap_or("auto")), + session_model_label( + profile + .map(|p| p.default_model()) + .unwrap_or(crate::config::DEFAULT_MODEL), + ), Tone::Model, SettingKind::Model("default"), ); @@ -412,7 +420,7 @@ pub(crate) fn fill_general_settings( pub(crate) fn fill_agent_settings( rows: &mut Vec, kinds: &mut Vec>, - path: &PathBuf, + path: &Path, env: &BTreeMap, profile: Option<&Profile>, id: &'static str, @@ -703,7 +711,7 @@ pub(crate) fn slot_current_opt(profile: Option<&Profile>, slot: &str) -> String pub(crate) fn config_settings_loop( parsed: &ParsedArgs, env: &BTreeMap, - path: &PathBuf, + path: &Path, ) -> Result { let mut cache = CreditsCache::fresh(); let mut tab = 0usize; @@ -750,7 +758,7 @@ pub(crate) fn config_settings_loop( pub(crate) fn config_edit_row( parsed: &ParsedArgs, env: &BTreeMap, - path: &PathBuf, + path: &Path, kind: SettingKind, ) -> Result { match kind { @@ -1125,7 +1133,7 @@ pub(crate) fn config_reset_row(path: &std::path::Path, kind: SettingKind) -> Res pub(crate) fn config_menu_loop_legacy( parsed: &ParsedArgs, env: &BTreeMap, - path: &PathBuf, + path: &Path, ) -> Result { let items = vec![ "Switch key".into(), @@ -1183,7 +1191,11 @@ pub(crate) fn config_tui_header(path: &std::path::Path) -> Vec { ), format!( "model {}", - session_model_label(profile.map(|p| p.default_model()).unwrap_or("auto")) + session_model_label( + profile + .map(|p| p.default_model()) + .unwrap_or(crate::config::DEFAULT_MODEL), + ) ), format!("file {}", path.display()), ] diff --git a/src/cmd/dispatch.rs b/src/cmd/dispatch.rs index d6fcfdd..6121e76 100644 --- a/src/cmd/dispatch.rs +++ b/src/cmd/dispatch.rs @@ -1,5 +1,5 @@ use std::collections::{BTreeMap, HashMap}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use crate::config::{resolve_config_path, write_config}; @@ -406,7 +406,7 @@ pub(crate) fn config_path(parsed: &ParsedArgs, env: &BTreeMap) - resolve_config_path(get_string_flag(&parsed.flags, "config").as_deref(), env) } -pub(crate) fn tool_command_for(path: &PathBuf, id: &str) -> String { +pub(crate) fn tool_command_for(path: &Path, id: &str) -> String { let cfg = load_config_if_present(path); resolve_tool(cfg.as_ref(), id) .map(|t| t.command) @@ -414,13 +414,13 @@ pub(crate) fn tool_command_for(path: &PathBuf, id: &str) -> String { } pub(crate) fn catalog_lookup_enabled(env: &BTreeMap) -> bool { - match env.get("ANYR_NO_CATALOG").map(|s| s.as_str()) { - Some("1" | "true" | "TRUE" | "yes") => false, - _ => true, - } + !matches!( + env.get("ANYR_NO_CATALOG").map(|s| s.as_str()), + Some("1" | "true" | "TRUE" | "yes") + ) } -pub(crate) fn persist_tool_command(path: &PathBuf, id: &str, command: &str) -> Result<(), String> { +pub(crate) fn persist_tool_command(path: &Path, id: &str, command: &str) -> Result<(), String> { let builtin = resolve_tool(None, id) .map(|t| t.command) .unwrap_or_else(|_| id.to_string()); @@ -435,7 +435,7 @@ pub(crate) fn persist_tool_command(path: &PathBuf, id: &str, command: &str) -> R } pub(crate) fn launcher_last_tool( - path: &PathBuf, + path: &Path, parsed: &ParsedArgs, env: &BTreeMap, ) -> String { diff --git a/src/cmd/keys.rs b/src/cmd/keys.rs index 7c33b71..826cc4a 100644 --- a/src/cmd/keys.rs +++ b/src/cmd/keys.rs @@ -1,5 +1,5 @@ use std::collections::BTreeMap; -use std::path::PathBuf; +use std::path::Path; use crate::config::write_config; use crate::http::{create_key, delete_key, fetch_keys, is_active_key_row, reveal_key}; @@ -276,7 +276,7 @@ pub(crate) fn key_pick_label(row: &crate::http::RemoteKey, current: bool) -> Str pub(crate) fn stored_api_key( parsed: &ParsedArgs, env: &BTreeMap, - path: &PathBuf, + path: &Path, ) -> Option { let existing = load_config_if_present(path); let profile = existing diff --git a/src/cmd/launch.rs b/src/cmd/launch.rs index fbd1430..eee3146 100644 --- a/src/cmd/launch.rs +++ b/src/cmd/launch.rs @@ -172,7 +172,7 @@ pub(crate) fn run_launch( let id = catalog_model_id(&flag_model); let stored = if is_auto_model(&id) { None } else { Some(id) }; if let Some(p) = cfg.profiles.get_mut(&cfg.active_profile) { - // Auto stays unset so the next launch re-picks the most-used model. + // Auto stays unset so the next launch keeps the anyrouter/auto preset. p.default_model = stored.clone(); } cfg.agent_binding_mut(tool_name).default_model = stored; diff --git a/src/cmd/menu.rs b/src/cmd/menu.rs index 42d0886..b7ae525 100644 --- a/src/cmd/menu.rs +++ b/src/cmd/menu.rs @@ -1,5 +1,5 @@ use std::collections::BTreeMap; -use std::path::PathBuf; +use std::path::Path; use std::sync::{Arc, Mutex}; use crate::http::{fetch_keys, fetch_models, is_active_key_row, reveal_key}; @@ -45,7 +45,11 @@ pub(crate) fn agent_binding_detail( .filter(|s| !s.is_empty()) .map(session_model_label) .unwrap_or_else(|| { - session_model_label(profile.map(|p| p.default_model()).unwrap_or("auto")) + session_model_label( + profile + .map(|p| p.default_model()) + .unwrap_or(crate::config::DEFAULT_MODEL), + ) }); let account = binding .and_then(|b| b.profile.as_deref()) @@ -82,7 +86,7 @@ pub(crate) fn agent_binding_detail( #[cfg(feature = "native")] pub(crate) fn launcher_palette( - path: &PathBuf, + path: &Path, parsed: &ParsedArgs, env: &BTreeMap, credits: &mut CreditsCache, @@ -158,7 +162,7 @@ pub(crate) fn launcher_palette( #[cfg(not(feature = "native"))] pub(crate) fn launcher_palette( - path: &PathBuf, + path: &Path, parsed: &ParsedArgs, env: &BTreeMap, _credits: &mut CreditsCache, @@ -375,7 +379,7 @@ pub(crate) fn run_menu(parsed: &ParsedArgs, env: &BTreeMap) -> R } pub(crate) fn launcher_hud( - path: &PathBuf, + path: &Path, parsed: &ParsedArgs, env: &BTreeMap, credits: &mut CreditsCache, @@ -389,7 +393,11 @@ pub(crate) fn launcher_hud( .peek_identity() .map(|me| me.display_label()) .unwrap_or_else(|| cfg.active_profile.clone()); - let model = session_model_label(profile.map(|p| p.default_model()).unwrap_or("auto")); + let model = session_model_label( + profile + .map(|p| p.default_model()) + .unwrap_or(crate::config::DEFAULT_MODEL), + ); let credits_s = credits.peek_credits(); let dot = if signed_in { term::ok("●") @@ -533,7 +541,7 @@ pub(crate) fn push_agent_configure_entries( } pub(crate) fn install_agent_dialog( - path: &PathBuf, + path: &Path, env: &BTreeMap, ) -> Result { let missing = missing_agents(env, |id| tool_command_for(path, id)); @@ -568,7 +576,7 @@ pub(crate) fn install_agent_dialog( } pub(crate) fn launcher_signed_in( - path: &PathBuf, + path: &Path, parsed: &ParsedArgs, env: &BTreeMap, ) -> bool { @@ -579,7 +587,7 @@ pub(crate) fn launcher_dispatch( action: &str, parsed: &ParsedArgs, env: &BTreeMap, - path: &PathBuf, + path: &Path, ) -> Result { if action == "Quit" || action.starts_with("Quit") { return Ok(LauncherNext::Exit(0)); @@ -707,7 +715,7 @@ pub(crate) fn launcher_dispatch( pub(crate) fn switch_agent_model( parsed: &ParsedArgs, env: &BTreeMap, - path: &PathBuf, + path: &Path, agent: &str, ) -> Result { if agent.is_empty() { @@ -728,7 +736,7 @@ pub(crate) fn switch_agent_model( pub(crate) fn bind_agent_model( parsed: &ParsedArgs, env: &BTreeMap, - path: &PathBuf, + path: &Path, agent: &str, ) -> Result { let existing = load_config_if_present(path); @@ -750,7 +758,7 @@ pub(crate) fn bind_agent_model( pub(crate) fn switch_agent_account( _parsed: &ParsedArgs, _env: &BTreeMap, - path: &PathBuf, + path: &Path, agent: &str, ) -> Result { if agent.is_empty() { @@ -764,7 +772,7 @@ pub(crate) fn switch_agent_account( Ok(LauncherNext::Continue) } -pub(crate) fn bind_agent_account(path: &PathBuf, agent: &str) -> Result { +pub(crate) fn bind_agent_account(path: &Path, agent: &str) -> Result { let cfg = load_config_if_present(path).unwrap_or_default(); let mut names: Vec = cfg.profiles.keys().cloned().collect(); names.sort(); @@ -799,7 +807,7 @@ pub(crate) fn bind_agent_account(path: &PathBuf, agent: &str) -> Result, - path: &PathBuf, + path: &Path, agent: &str, ) -> Result { if agent.is_empty() { @@ -820,7 +828,7 @@ pub(crate) fn switch_agent_key( pub(crate) fn bind_agent_key( parsed: &ParsedArgs, env: &BTreeMap, - path: &PathBuf, + path: &Path, agent: &str, ) -> Result { let (_keys_path, cfg, base, api_key) = keys_credential(parsed, env)?; @@ -863,7 +871,7 @@ pub(crate) fn bind_agent_key( pub(crate) fn launch_agent_picker( parsed: &ParsedArgs, env: &BTreeMap, - path: &PathBuf, + path: &Path, ) -> Result { if !launcher_signed_in(path, parsed, env) { eprintln!( diff --git a/src/cmd/models.rs b/src/cmd/models.rs index aced415..663c28b 100644 --- a/src/cmd/models.rs +++ b/src/cmd/models.rs @@ -1,5 +1,5 @@ use std::collections::BTreeMap; -use std::path::PathBuf; +use std::path::Path; use crate::config::{write_config, Profile}; use crate::http::{fetch_credits, fetch_models, format_models_list, CatalogModel}; @@ -214,7 +214,7 @@ pub(crate) fn known_model_id(models: &[CatalogModel], id: &str) -> bool { pub(crate) fn save_model_slot( existing: Option, - path: &PathBuf, + path: &Path, slot: &str, id: &str, ) -> Result { @@ -256,7 +256,7 @@ pub(crate) fn known_agent(name: &str) -> Result { } } -pub(crate) fn save_agent_model(path: &PathBuf, agent: &str, id: &str) -> Result { +pub(crate) fn save_agent_model(path: &Path, agent: &str, id: &str) -> Result { let agent = known_agent(agent)?; let mut cfg = load_config_if_present(path).ok_or_else(no_key_error)?; let id = catalog_model_id(id); @@ -276,11 +276,7 @@ pub(crate) fn save_agent_model(path: &PathBuf, agent: &str, id: &str) -> Result< Ok(0) } -pub(crate) fn save_agent_account( - path: &PathBuf, - agent: &str, - profile: &str, -) -> Result { +pub(crate) fn save_agent_account(path: &Path, agent: &str, profile: &str) -> Result { let agent = known_agent(agent)?; let mut cfg = load_config_if_present(path).ok_or_else(no_key_error)?; if !cfg.profiles.contains_key(profile) { @@ -296,7 +292,7 @@ pub(crate) fn save_agent_account( Ok(0) } -pub(crate) fn save_agent_key(path: &PathBuf, agent: &str, key: &str) -> Result { +pub(crate) fn save_agent_key(path: &Path, agent: &str, key: &str) -> Result { let agent = known_agent(agent)?; let mut cfg = load_config_if_present(path).ok_or_else(no_key_error)?; cfg.agent_binding_mut(&agent).api_key = Some(key.to_string()); diff --git a/src/config.rs b/src/config.rs index a886478..290616e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,6 +8,8 @@ pub const DEFAULT_BASE_URL: &str = "https://anyrouter.dev/api"; pub const DEFAULT_TIMEOUT_MS: i64 = 3_000_000; pub const DEFAULT_PROFILE: &str = "default"; pub const DEFAULT_PRESET: &str = "@preset/coding-stack"; +/// Documented gateway auto preset. Never invent a catalog SKU for this slot. +pub const DEFAULT_MODEL: &str = "anyrouter/auto"; pub const DEFAULT_CLAUDE_HAIKU: &str = "anthropic/claude-haiku-4.5"; pub const DEFAULT_CLAUDE_SONNET: &str = "anthropic/claude-sonnet-4.6"; pub const DEFAULT_CLAUDE_OPUS: &str = "anthropic/claude-opus-4.6"; @@ -78,7 +80,7 @@ impl Profile { } pub fn default_model(&self) -> &str { - self.default_model.as_deref().unwrap_or("auto") + self.default_model.as_deref().unwrap_or(DEFAULT_MODEL) } pub fn claude_haiku(&self) -> &str { @@ -328,7 +330,7 @@ pub fn create_default_profile(input: DefaultProfileInput) -> Profile { pinned_preset: Some(normalize_preset( input.preset.as_deref().unwrap_or(DEFAULT_PRESET), )), - default_model: Some(input.default_model.unwrap_or_else(|| "auto".into())), + default_model: Some(input.default_model.unwrap_or_else(|| DEFAULT_MODEL.into())), timeout_ms: Some(input.timeout_ms.unwrap_or(DEFAULT_TIMEOUT_MS)), ..Profile::default() } diff --git a/src/demo.rs b/src/demo.rs index c2df290..9a375f4 100644 --- a/src/demo.rs +++ b/src/demo.rs @@ -196,7 +196,7 @@ fn demo_usage(json: bool) -> String { fn demo_whoami() -> String { let _env: BTreeMap = BTreeMap::new(); format!( - "active account default\napi_key {}\ndefault_model auto\n{DEMO_HINT}", + "active account default\napi_key {}\ndefault_model anyrouter/auto\n{DEMO_HINT}", mask_api_key(Some("sk-ar-v1-demo-key-value")) ) } diff --git a/src/help.rs b/src/help.rs index c76bbfb..a00e738 100644 --- a/src/help.rs +++ b/src/help.rs @@ -4,7 +4,7 @@ use std::path::Path; use crate::VERSION; thread_local! { - static INVOKED_BIN: RefCell = RefCell::new(String::new()); + static INVOKED_BIN: RefCell = const { RefCell::new(String::new()) }; } const LAUNCH_HELP_BODY: &str = "\ @@ -12,10 +12,8 @@ Launches the coding agent through AnyRouter (signs in first if needed). Options: --yes, --ok Skip confirmation prompts (login / install) - --model auto| Session model. Default (no flag) = \"auto\" → \ -anyrouter/auto on a fresh install; \"auto\" resolves to the most-used \ -catalog model on an authed key, else anyrouter/auto. Omit to let failover \ -pick the best stable model per turn. + --model auto| Session model. Omit the flag (or pass auto) to use \ +the documented anyrouter/auto preset — gateway failover, not a catalog SKU. --haiku Claude /model haiku and subagents --sonnet Claude /model sonnet --opus Claude /model opus @@ -87,7 +85,7 @@ pub fn root_help() -> String { Start $ curl -fsSL https://anyrouter.dev/setup.sh | bash $ {bin} auth login - $ {bin} claude + $ {bin} claude # default model: anyrouter/auto Swap the model, keep the agent $ {bin} claude --model z-ai/glm-4.7-flash diff --git a/src/http.rs b/src/http.rs index 34fcb30..01f07fd 100644 --- a/src/http.rs +++ b/src/http.rs @@ -781,7 +781,7 @@ mod tests { Some("sk-ar-v1-DifferentTail00000000000000000000abcd") )); // A longer-head ASCII-dot mask matches on head AND literal tail. - let long = getKeyPrefixStyleMask("sk-ar-v1-abcd", "wxyz"); + let long = get_key_prefix_style_mask("sk-ar-v1-abcd", "wxyz"); assert!(is_active_key_row( &long, Some("sk-ar-v1-abcd-middle-secret-wxyz") @@ -790,7 +790,7 @@ mod tests { } /// Helper mirroring the server's getKeyPrefix() shape for tests. - fn getKeyPrefixStyleMask(head: &str, tail: &str) -> String { + fn get_key_prefix_style_mask(head: &str, tail: &str) -> String { format!("{head}...{tail}") } } diff --git a/src/key.rs b/src/key.rs index c9cb955..214c91e 100644 --- a/src/key.rs +++ b/src/key.rs @@ -107,7 +107,7 @@ pub fn resolve_launch_model( tool: &str, ) -> String { if let Some(m) = get_string_flag(flags, "model") { - return crate::spawn::catalog_model_id(&m); + return crate::spawn::display_model_id(&m); } let id = canonical_tool(tool); if let Some(m) = config @@ -116,9 +116,9 @@ pub fn resolve_launch_model( .map(str::trim) .filter(|s| !s.is_empty()) { - return crate::spawn::catalog_model_id(m); + return crate::spawn::display_model_id(m); } - profile.default_model().to_string() + crate::spawn::display_model_id(profile.default_model()) } pub fn resolve_base_url( @@ -316,5 +316,16 @@ agents: resolve_launch_model(&with_flag, Some(&cfg), claude_profile, "claude"), "stealth/ox-alpha" ); + let mut auto_flag = HashMap::new(); + auto_flag.insert("model".into(), FlagValue::Value("auto".into())); + assert_eq!( + resolve_launch_model(&auto_flag, Some(&cfg), claude_profile, "claude"), + "anyrouter/auto" + ); + let empty = Profile::default(); + assert_eq!( + resolve_launch_model(&flags, None, &empty, "claude"), + "anyrouter/auto" + ); } } diff --git a/src/spawn.rs b/src/spawn.rs index a0a3317..3eccb5d 100644 --- a/src/spawn.rs +++ b/src/spawn.rs @@ -3,7 +3,9 @@ use std::path::{Path, PathBuf}; #[cfg(feature = "native")] use std::process::{Command, Stdio}; -use crate::config::{Profile, YamlValue, DEFAULT_BASE_URL, DEFAULT_PRESET, DEFAULT_TIMEOUT_MS}; +use crate::config::{ + Profile, YamlValue, DEFAULT_BASE_URL, DEFAULT_MODEL, DEFAULT_PRESET, DEFAULT_TIMEOUT_MS, +}; pub const PI_DEFAULT_MODEL: &str = "anthropic/claude-sonnet-4.6"; /// Historical 1M-context suffix used by Claude Code. Claude Code used to strip @@ -25,7 +27,7 @@ const CLAUDE_EFFORT_TOKENS: &[(&str, i64)] = &[ ("max", 32000), ]; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct ToolConfig { pub command: String, pub base_url_env: String, @@ -37,21 +39,6 @@ pub struct ToolConfig { pub extra: BTreeMap, } -impl Default for ToolConfig { - fn default() -> Self { - Self { - command: String::new(), - base_url_env: String::new(), - auth_env: String::new(), - model_env: None, - base_suffix: String::new(), - enable_gateway_model_discovery: false, - shadow_env: None, - extra: BTreeMap::new(), - } - } -} - impl ToolConfig { pub fn merge(&mut self, over: &ToolConfig) { if !over.command.is_empty() { @@ -285,7 +272,7 @@ pub fn default_profile_for_env(base_url: Option<&str>, api_key: Option<&str>) -> api_key: api_key.map(str::to_string), base_url: Some(base_url.unwrap_or(DEFAULT_BASE_URL).to_string()), pinned_preset: Some(DEFAULT_PRESET.into()), - default_model: Some("auto".into()), + default_model: Some(DEFAULT_MODEL.into()), timeout_ms: Some(DEFAULT_TIMEOUT_MS), ..Profile::default() } @@ -872,7 +859,10 @@ mod tests { sanitize_model_id("\u{1b}[1mstealth/ox-alpha\u{1b}[0m"), "stealth/ox-alpha" ); - assert_eq!(sanitize_model_id("stealth/ox-alpha[2m]"), "stealth/ox-alpha"); + assert_eq!( + sanitize_model_id("stealth/ox-alpha[2m]"), + "stealth/ox-alpha" + ); assert_eq!( sanitize_model_id("stealth/ox-alpha[0;1m]"), "stealth/ox-alpha" @@ -1301,7 +1291,7 @@ mod tests { let mut env = BTreeMap::new(); let mut routing = crate::config::RoutingConstraints::default(); apply_routing_env(&mut env, &routing, "claude"); - assert!(env.get("CLAUDE_CODE_EXTRA_BODY").is_none()); + assert!(!env.contains_key("CLAUDE_CODE_EXTRA_BODY")); routing.set_exacto(true); routing.set_require_tools(true); routing.set_require_1m(true); @@ -1373,8 +1363,10 @@ mod tests { #[test] fn merge_command_only_overlay_keeps_codex_suffix() { let mut t = builtin("codex").unwrap(); - let mut over = ToolConfig::default(); - over.command = "/opt/codex".into(); + let over = ToolConfig { + command: "/opt/codex".into(), + ..Default::default() + }; t.merge(&over); assert_eq!(t.command, "/opt/codex"); assert_eq!(t.base_suffix, "/v1"); diff --git a/src/term.rs b/src/term.rs index 6c5bb4b..6aa159a 100644 --- a/src/term.rs +++ b/src/term.rs @@ -82,7 +82,7 @@ const MARK_PNG: &[u8] = include_bytes!("../assets/mark.png"); const B64: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; fn base64_encode(bytes: &[u8]) -> String { - let mut out = String::with_capacity((bytes.len() + 2) / 3 * 4); + let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4); for chunk in bytes.chunks(3) { let a = chunk[0] as u32; let b = chunk.get(1).copied().unwrap_or(0) as u32; @@ -507,7 +507,7 @@ fn pick_inline_raw( ) .map_err(|e| e.to_string())?; } - write!(out, "{frame}\n").map_err(|e| e.to_string())?; + writeln!(out, "{frame}").map_err(|e| e.to_string())?; out.flush().map_err(|e| e.to_string())?; last_lines = n; diff --git a/src/tui/view.rs b/src/tui/view.rs index f6faf5d..15eb8b2 100644 --- a/src/tui/view.rs +++ b/src/tui/view.rs @@ -69,7 +69,7 @@ pub fn render_picker(frame: &mut Frame, state: &PickerState) { ListItem::new(Line::from(vec![ Span::styled(if i == state.cursor { "❯ " } else { " " }, theme::accent()), Span::styled(item_icon(label), item_icon_style(label)), - Span::styled(format!("{label}"), style), + Span::styled(label.to_string(), style), ])) }) .collect(); diff --git a/src/upgrade.rs b/src/upgrade.rs index 15b92a1..74cc6da 100644 --- a/src/upgrade.rs +++ b/src/upgrade.rs @@ -18,7 +18,7 @@ use crate::channel::{ select_latest_release_with_asset, Channel, Release, GITHUB_EXPANDED_ASSETS_PREFIX, GITHUB_RELEASES_API, GITHUB_RELEASES_HTML, }; -use crate::config::{resolve_config_path, write_config, Config}; +use crate::config::{resolve_config_path, write_config}; use crate::http::{http_get_github, http_get_web}; use crate::key::load_config_if_present; use crate::parse::{get_string_flag, ParsedArgs}; @@ -183,7 +183,7 @@ fn resolve_channel(parsed: &ParsedArgs, env: &BTreeMap) -> Resul /// Persist `channel:` so future auto-updates follow the switched track. fn persist_channel(channel: Channel, env: &BTreeMap) -> Result { let path = resolve_config_path(None, env); - let mut cfg = load_config_if_present(&path).unwrap_or_else(Config::default); + let mut cfg = load_config_if_present(&path).unwrap_or_default(); let next = channel.as_str().to_string(); let changed = cfg.channel.as_deref() != Some(next.as_str()); if !changed && cfg.channel.is_some() { diff --git a/tests/cli.rs b/tests/cli.rs index 492ef96..17a1c82 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -395,6 +395,45 @@ fn pi_dry_run_uses_anyrouter_provider() { let _ = std::fs::remove_dir_all(&dir); } +#[test] +fn claude_dry_run_omitted_model_uses_anyrouter_auto() { + let key = "sk-ar-v1-fixture-key-0001"; + let (code, stdout, stderr) = { + let out = anyr() + .args(["claude", "--dry-run", "--yes", "--key", key]) + .env("ANYROUTER_HOME", temp_home()) + .env_remove("ANYROUTER_API_KEY") + .output() + .expect("dry-run"); + ( + out.status.code().unwrap_or(1), + String::from_utf8_lossy(&out.stdout).into_owned(), + String::from_utf8_lossy(&out.stderr).into_owned(), + ) + }; + assert_eq!(code, 0, "stderr={stderr}"); + assert!( + stdout.contains("ANTHROPIC_MODEL=anyrouter/auto"), + "omitted --model must send the documented auto preset:\n{stdout}" + ); + assert!(stdout.contains("ANYROUTER_MODEL_MODE=auto"), "{stdout}"); + assert!( + !stdout.to_ascii_lowercase().contains("most used"), + "{stdout}" + ); +} + +#[test] +fn claude_help_documents_anyrouter_auto_not_invented_skus() { + let (code, stdout, stderr) = run(&["claude", "--help"]); + assert_eq!(code, 0, "{stdout}{stderr}"); + assert!(stdout.contains("anyrouter/auto"), "{stdout}"); + assert!( + !stdout.to_ascii_lowercase().contains("most-used"), + "help must not claim auto invents a usage SKU:\n{stdout}" + ); +} + #[test] fn claude_dry_run_with_key_prints_base_and_redacts_secret() { let key = "sk-ar-v1-fixture-key-0001";