diff --git a/README.md b/README.md index acb6e741..f473c5ce 100644 --- a/README.md +++ b/README.md @@ -58,13 +58,12 @@ This project is a **CLI fork** of [CC-Switch](https://github.com/farion1231/cc-s - - RightCode + + Cubence - Thanks to RightCode for sponsoring this project! RightCode reliably provides routing services for models such as Claude Code, Codex, and Gemini. It features a highly cost-effective Codex monthly subscription plan and supports quota rollovers—unused quota from one day can be carried over and used the next day.
- RightCode offers a special deal for CC-Switch CLI users: register via this link and get 5% bonus pay-as-you-go credits on every top-up! + Thanks to Cubence for sponsoring this project! Cubence is an API relay service provider dedicated to offering stable and efficient services to its customers. Operating since September 2025, it has provided support for various models such as Claude code, Codex, and Gemini. Register via this link and use the CCSCLI discount code when topping up to enjoy a 10% discount. diff --git a/README_ZH.md b/README_ZH.md index f84f4810..3939f33c 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -58,14 +58,12 @@ - - RightCode + + Cubence - 感谢 RightCode 赞助本项目!
- RightCode 为 Claude Code、Codex、Gemini 等模型提供稳定的路由服务,拥有高性价比的 Codex 月付方案,且支持额度滚存——当天未用完的额度可顺延至次日使用。
- RightCode 为 CC-Switch CLI 用户提供了特别优惠:通过此链接注册,每次充值均可获得实付金额 5% 的按量额度! + 感谢 Cubence 赞助本项目!Cubence 是一家致力为客户提供稳定、高效的API中转服务商。从25年9月运营至今,提供了Claude code、Codex、Gemini等多种模型支持。通过此链接注册,并在充值时使用 CCSCLI 优惠码享受9折优惠。 diff --git a/assets/partners/logos/cubence.png b/assets/partners/logos/cubence.png new file mode 100644 index 00000000..30ba163a Binary files /dev/null and b/assets/partners/logos/cubence.png differ diff --git a/assets/partners/logos/rightcode.jpg b/assets/partners/logos/rightcode.jpg deleted file mode 100644 index 06ac3a63..00000000 Binary files a/assets/partners/logos/rightcode.jpg and /dev/null differ diff --git a/src-tauri/src/cli/tui/form/provider_templates.rs b/src-tauri/src/cli/tui/form/provider_templates.rs index 32a5e421..c82de4e7 100644 --- a/src-tauri/src/cli/tui/form/provider_templates.rs +++ b/src-tauri/src/cli/tui/form/provider_templates.rs @@ -1,7 +1,10 @@ use crate::app_config::AppType; use serde_json::json; -use super::{ClaudeApiFormat, CodexWireApi, FormMode, GeminiAuthType, ProviderAddFormState}; +use super::{ + ClaudeApiFormat, CodexWireApi, FormMode, GeminiAuthType, ProviderAddFormState, + HERMES_DEFAULT_API_MODE, OPENCLAW_DEFAULT_API_PROTOCOL, +}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum ProviderTemplateId { @@ -30,6 +33,9 @@ pub(super) struct SponsorProviderPreset { claude_base_url: &'static str, codex_base_url: &'static str, gemini_base_url: &'static str, + opencode_base_url: &'static str, + openclaw_base_url: &'static str, + hermes_base_url: &'static str, } #[cfg(test)] @@ -55,6 +61,9 @@ static SPONSOR_PROVIDER_PRESETS: [SponsorProviderPreset; 4] = [ claude_base_url: "https://www.packyapi.com", codex_base_url: "https://www.packyapi.com/v1", gemini_base_url: "https://www.packyapi.com", + opencode_base_url: "https://www.packyapi.com/v1", + openclaw_base_url: "https://www.packyapi.com", + hermes_base_url: "https://www.packyapi.com", }, SponsorProviderPreset { id: "aicodemirror", @@ -67,18 +76,24 @@ static SPONSOR_PROVIDER_PRESETS: [SponsorProviderPreset; 4] = [ claude_base_url: "https://api.aicodemirror.com/api/claudecode", codex_base_url: "https://api.aicodemirror.com/api/codex/backend-api/codex", gemini_base_url: "https://api.aicodemirror.com/api/gemini", + opencode_base_url: "https://api.aicodemirror.com/api/claudecode", + openclaw_base_url: "https://api.aicodemirror.com/api/claudecode", + hermes_base_url: "", }, SponsorProviderPreset { - id: "rightcode", - provider_name: "RightCode", - chip_label: "* RightCode", - website_url: "https://right.codes", - register_url: "https://www.right.codes/register?aff=ccswitch-cli", - promo_code: "", - partner_promotion_key: "rightcode", - claude_base_url: "https://www.right.codes/claude", - codex_base_url: "https://right.codes/codex/v1", - gemini_base_url: "https://www.right.codes", + id: "cubence", + provider_name: "Cubence", + chip_label: "* Cubence", + website_url: "https://cubence.com", + register_url: "https://cubence.com/signup?code=SC3M1CAH&source=ccscli", + promo_code: "CCSCLI", + partner_promotion_key: "cubence", + claude_base_url: "https://api.cubence.com", + codex_base_url: "https://api.cubence.com/v1", + gemini_base_url: "https://api.cubence.com", + opencode_base_url: "https://api.cubence.com/v1", + openclaw_base_url: "https://api.cubence.com", + hermes_base_url: "https://api.cubence.com", }, SponsorProviderPreset { id: "dds", @@ -91,6 +106,9 @@ static SPONSOR_PROVIDER_PRESETS: [SponsorProviderPreset; 4] = [ claude_base_url: "https://www.ddshub.cc", codex_base_url: "https://www.ddshub.cc", gemini_base_url: "", + opencode_base_url: "", + openclaw_base_url: "", + hermes_base_url: "", }, ]; @@ -114,11 +132,13 @@ static SPONSOR_PROVIDER_PRESETS_GEMINI: [SponsorProviderPreset; 3] = [ SPONSOR_PROVIDER_PRESETS[2], ]; -static SPONSOR_PROVIDER_PRESETS_OPENCODE: [SponsorProviderPreset; 1] = - [SPONSOR_PROVIDER_PRESETS[1]]; +static SPONSOR_PROVIDER_PRESETS_OPENCODE: [SponsorProviderPreset; 2] = + [SPONSOR_PROVIDER_PRESETS[1], SPONSOR_PROVIDER_PRESETS[2]]; + +static SPONSOR_PROVIDER_PRESETS_HERMES: [SponsorProviderPreset; 1] = [SPONSOR_PROVIDER_PRESETS[2]]; -static SPONSOR_PROVIDER_PRESETS_OPENCLAW: [SponsorProviderPreset; 1] = - [SPONSOR_PROVIDER_PRESETS[1]]; +static SPONSOR_PROVIDER_PRESETS_OPENCLAW: [SponsorProviderPreset; 2] = + [SPONSOR_PROVIDER_PRESETS[1], SPONSOR_PROVIDER_PRESETS[2]]; static PROVIDER_TEMPLATE_DEFS_CLAUDE: [ProviderTemplateDef; 3] = [ ProviderTemplateDef { @@ -189,7 +209,7 @@ pub(super) fn provider_sponsor_presets(app_type: &AppType) -> &'static [SponsorP AppType::Codex => &SPONSOR_PROVIDER_PRESETS_CODEX, AppType::Gemini => &SPONSOR_PROVIDER_PRESETS_GEMINI, AppType::OpenCode => &SPONSOR_PROVIDER_PRESETS_OPENCODE, - AppType::Hermes => &[], + AppType::Hermes => &SPONSOR_PROVIDER_PRESETS_HERMES, AppType::OpenClaw => &SPONSOR_PROVIDER_PRESETS_OPENCLAW, } } @@ -430,9 +450,24 @@ impl ProviderAddFormState { self.opencode_model_context_limit.set(""); self.opencode_model_output_limit.set(""); self.opencode_model_original_id = Some("claude-opus-4.6".to_string()); + } else { + self.opencode_npm_package.set("@ai-sdk/openai-compatible"); + self.opencode_api_key.set(""); + self.opencode_base_url.set(preset.opencode_base_url); + self.opencode_model_id.set(""); + self.opencode_model_name.set(""); + self.opencode_model_context_limit.set(""); + self.opencode_model_output_limit.set(""); + self.opencode_model_original_id = None; } } - AppType::Hermes => {} + AppType::Hermes => { + self.hermes_api_mode = HERMES_DEFAULT_API_MODE.to_string(); + self.hermes_api_key.set(""); + self.hermes_base_url.set(preset.hermes_base_url); + self.hermes_models = Vec::new(); + self.hermes_rate_limit_delay.set(""); + } AppType::OpenClaw => { if preset.id == "aicodemirror" { self.opencode_api_key.set(""); @@ -463,6 +498,17 @@ impl ProviderAddFormState { self.opencode_model_name.set("Claude Opus 4.6"); self.opencode_model_context_limit.set("200000"); self.opencode_model_original_id = Some("claude-opus-4-6".to_string()); + } else { + self.opencode_api_key.set(""); + self.opencode_base_url.set(preset.openclaw_base_url); + self.opencode_npm_package.set(OPENCLAW_DEFAULT_API_PROTOCOL); + self.openclaw_user_agent = false; + self.openclaw_models = Vec::new(); + self.opencode_model_id.set(""); + self.opencode_model_name.set(""); + self.opencode_model_context_limit.set(""); + self.opencode_model_output_limit.set(""); + self.opencode_model_original_id = None; } } } diff --git a/src-tauri/src/cli/tui/form/tests.rs b/src-tauri/src/cli/tui/form/tests.rs index 55dac9d0..68de44d6 100644 --- a/src-tauri/src/cli/tui/form/tests.rs +++ b/src-tauri/src/cli/tui/form/tests.rs @@ -18,8 +18,8 @@ fn aicodemirror_template_index(app_type: AppType) -> usize { template_index_by_label(app_type, "* AICodeMirror") } -fn rightcode_template_index(app_type: AppType) -> usize { - template_index_by_label(app_type, "* RightCode") +fn cubence_template_index(app_type: AppType) -> usize { + template_index_by_label(app_type, "* Cubence") } fn dds_template_index(app_type: AppType) -> usize { @@ -48,7 +48,7 @@ fn provider_add_form_template_labels_follow_explicit_support_matrix() { "Codex", "* PackyCode", "* AICodeMirror", - "* RightCode", + "* Cubence", "* DDS", ] ); @@ -61,7 +61,7 @@ fn provider_add_form_template_labels_follow_explicit_support_matrix() { "OpenAI Official", "* PackyCode", "* AICodeMirror", - "* RightCode", + "* Cubence", "* DDS", ] ); @@ -74,22 +74,31 @@ fn provider_add_form_template_labels_follow_explicit_support_matrix() { "Google OAuth", "* PackyCode", "* AICodeMirror", - "* RightCode", + "* Cubence", ] ); let opencode_labels = ProviderAddFormState::new(AppType::OpenCode).template_labels(); - assert_eq!(opencode_labels, vec!["Custom", "* AICodeMirror"]); + assert_eq!( + opencode_labels, + vec!["Custom", "* AICodeMirror", "* Cubence"] + ); assert!( - !opencode_labels.contains(&"* PackyCode") && !opencode_labels.contains(&"* RightCode"), - "OpenCode should only expose the AICodeMirror sponsor preset" + !opencode_labels.contains(&"* PackyCode"), + "OpenCode should expose only explicitly supported sponsor presets" ); + let hermes_labels = ProviderAddFormState::new(AppType::Hermes).template_labels(); + assert_eq!(hermes_labels, vec!["Custom", "* Cubence"]); + let openclaw_labels = ProviderAddFormState::new(AppType::OpenClaw).template_labels(); - assert_eq!(openclaw_labels, vec!["Custom", "* AICodeMirror"]); + assert_eq!( + openclaw_labels, + vec!["Custom", "* AICodeMirror", "* Cubence"] + ); assert!( - !openclaw_labels.contains(&"* PackyCode") && !openclaw_labels.contains(&"* RightCode"), - "OpenClaw should only expose the AICodeMirror sponsor preset" + !openclaw_labels.contains(&"* PackyCode"), + "OpenClaw should expose only explicitly supported sponsor presets" ); } @@ -228,6 +237,20 @@ fn provider_add_form_dds_preset_keeps_affiliate_register_url_in_metadata() { assert_eq!(dds.register_url(), "https://ddshub.short.gy/ccscli"); } +#[test] +fn provider_add_form_cubence_preset_keeps_affiliate_register_url_in_metadata() { + let claude_presets = super::provider_templates::provider_sponsor_presets(&AppType::Claude); + let cubence = claude_presets + .iter() + .find(|preset| preset.id() == "cubence") + .expect("expected Cubence sponsor preset for Claude"); + + assert_eq!( + cubence.register_url(), + "https://cubence.com/signup?code=SC3M1CAH&source=ccscli" + ); +} + #[test] fn provider_add_form_dds_template_claude_sets_base_url_and_partner_meta() { let mut form = ProviderAddFormState::new(AppType::Claude); @@ -294,19 +317,19 @@ fn provider_add_form_dds_template_codex_sets_base_url_and_partner_meta() { } #[test] -fn provider_add_form_rightcode_template_claude_sets_base_url_and_partner_meta() { +fn provider_add_form_cubence_template_claude_sets_base_url_and_partner_meta() { let mut form = ProviderAddFormState::new(AppType::Claude); let existing_ids = Vec::::new(); - let idx = rightcode_template_index(AppType::Claude); + let idx = cubence_template_index(AppType::Claude); form.apply_template(idx, &existing_ids); let provider = form.to_provider_json_value(); - assert_eq!(provider["name"], "RightCode"); - assert_eq!(provider["websiteUrl"], "https://right.codes"); + assert_eq!(provider["name"], "Cubence"); + assert_eq!(provider["websiteUrl"], "https://cubence.com"); assert_eq!( provider["settingsConfig"]["env"]["ANTHROPIC_BASE_URL"], - "https://www.right.codes/claude" + "https://api.cubence.com" ); let meta = provider["meta"] .as_object() @@ -314,47 +337,122 @@ fn provider_add_form_rightcode_template_claude_sets_base_url_and_partner_meta() assert_eq!( meta.get("isPartner").and_then(|value| value.as_bool()), Some(true), - "expected RightCode sponsor to set meta.isPartner" + "expected Cubence sponsor to set meta.isPartner" ); assert_eq!( meta.get("partnerPromotionKey") .and_then(|value| value.as_str()), - Some("rightcode"), - "expected RightCode sponsor to set meta.partnerPromotionKey" + Some("cubence"), + "expected Cubence sponsor to set meta.partnerPromotionKey" ); } #[test] -fn provider_add_form_rightcode_template_codex_sets_base_url_and_partner_meta() { +fn provider_add_form_cubence_template_codex_sets_base_url_and_partner_meta() { let mut form = ProviderAddFormState::new(AppType::Codex); let existing_ids = Vec::::new(); - let idx = rightcode_template_index(AppType::Codex); + let idx = cubence_template_index(AppType::Codex); form.apply_template(idx, &existing_ids); let provider = form.to_provider_json_value(); - assert_eq!(provider["name"], "RightCode"); - assert_eq!(provider["websiteUrl"], "https://right.codes"); + assert_eq!(provider["name"], "Cubence"); + assert_eq!(provider["websiteUrl"], "https://cubence.com"); let cfg = provider["settingsConfig"]["config"] .as_str() .expect("settingsConfig.config should be string"); - assert!(cfg.contains("base_url = \"https://right.codes/codex/v1\"")); + assert!(cfg.contains("base_url = \"https://api.cubence.com/v1\"")); let meta = provider["meta"] .as_object() .expect("meta should be an object"); assert_eq!( meta.get("isPartner").and_then(|value| value.as_bool()), Some(true), - "expected RightCode sponsor to set meta.isPartner" + "expected Cubence sponsor to set meta.isPartner" ); assert_eq!( meta.get("partnerPromotionKey") .and_then(|value| value.as_str()), - Some("rightcode"), - "expected RightCode sponsor to set meta.partnerPromotionKey" + Some("cubence"), + "expected Cubence sponsor to set meta.partnerPromotionKey" ); } +#[test] +fn provider_add_form_cubence_template_gemini_sets_base_url_and_partner_meta() { + let mut form = ProviderAddFormState::new(AppType::Gemini); + + form.apply_template(cubence_template_index(AppType::Gemini), &[]); + + let provider = form.to_provider_json_value(); + assert_eq!(provider["name"], "Cubence"); + assert_eq!(provider["websiteUrl"], "https://cubence.com"); + assert_eq!( + provider["settingsConfig"]["env"]["GOOGLE_GEMINI_BASE_URL"], + "https://api.cubence.com" + ); + assert_eq!(provider["meta"]["isPartner"], true); + assert_eq!(provider["meta"]["partnerPromotionKey"], "cubence"); +} + +#[test] +fn provider_add_form_cubence_template_opencode_sets_base_url_and_partner_meta() { + let mut form = ProviderAddFormState::new(AppType::OpenCode); + + form.apply_template(cubence_template_index(AppType::OpenCode), &[]); + + let provider = form.to_provider_json_value(); + assert_eq!(provider["name"], "Cubence"); + assert_eq!(provider["websiteUrl"], "https://cubence.com"); + assert_eq!( + provider["settingsConfig"]["npm"], + "@ai-sdk/openai-compatible" + ); + assert_eq!( + provider["settingsConfig"]["options"]["baseURL"], + "https://api.cubence.com/v1" + ); + assert_eq!(provider["meta"]["isPartner"], true); + assert_eq!(provider["meta"]["partnerPromotionKey"], "cubence"); +} + +#[test] +fn provider_add_form_cubence_template_hermes_sets_base_url_and_partner_meta() { + let mut form = ProviderAddFormState::new(AppType::Hermes); + + form.apply_template(cubence_template_index(AppType::Hermes), &[]); + + let provider = form.to_provider_json_value(); + let settings = provider["settingsConfig"].as_object().unwrap(); + assert_eq!(provider["name"], "Cubence"); + assert_eq!(provider["websiteUrl"], "https://cubence.com"); + assert_eq!(settings.get("api_mode"), Some(&json!("chat_completions"))); + assert_eq!( + settings.get("base_url"), + Some(&json!("https://api.cubence.com")) + ); + assert_eq!(provider["meta"]["isPartner"], true); + assert_eq!(provider["meta"]["partnerPromotionKey"], "cubence"); +} + +#[test] +fn provider_add_form_cubence_template_openclaw_sets_base_url_and_partner_meta() { + let mut form = ProviderAddFormState::new(AppType::OpenClaw); + + form.apply_template(cubence_template_index(AppType::OpenClaw), &[]); + + let provider = form.to_provider_json_value(); + assert_eq!(provider["name"], "Cubence"); + assert_eq!(provider["websiteUrl"], "https://cubence.com"); + assert_eq!( + provider["settingsConfig"]["baseUrl"], + "https://api.cubence.com" + ); + assert_eq!(provider["settingsConfig"]["api"], "openai-completions"); + assert_eq!(provider["meta"]["isPartner"], true); + assert_eq!(provider["meta"]["partnerPromotionKey"], "cubence"); +} + #[test] fn provider_add_form_fields_include_notes() { for app_type in AppType::all() {