Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ cc-switch config validate # Validate config file
# Common snippet (shared settings across providers)
# Tries to refresh live config when applicable (`--apply` is kept only as a compatibility flag)
cc-switch --app claude config common show
# Warning: CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC disables Claude Code's Monitor tool (see note below)
cc-switch --app claude config common set --snippet '{"env":{"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC":1},"includeCoAuthoredBy":false}'
cc-switch --app claude config common clear

Expand Down Expand Up @@ -469,6 +470,8 @@ cc-switch config webdav migrate-v1-to-v2
cc-switch config reset # Reset to default configuration
```

> **Warning:** The `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` snippet above disables Claude Code's Monitor tool (and other "nonessential traffic"). CC Switch now warns when you set this, but it isn't a validation error — the snippet is still applied. Only set it if you don't need Monitor.

### 🌉 Proxy Management & Model Relay

Inspect and control daemon-managed per-app proxy routes for supported apps.
Expand Down
3 changes: 3 additions & 0 deletions README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@ cc-switch config validate # 验证配置文件
# 通用配置片段(跨所有供应商共享设置)
# 会在适用时尝试刷新 live config(`--apply` 仅保留为兼容参数)
cc-switch --app claude config common show
# 警告:CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC 会禁用 Claude Code 的 Monitor 工具(见下方说明)
cc-switch --app claude config common set --snippet '{"env":{"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC":1},"includeCoAuthoredBy":false}'
cc-switch --app claude config common clear

Expand Down Expand Up @@ -471,6 +472,8 @@ cc-switch config webdav migrate-v1-to-v2
cc-switch config reset # 重置为默认配置
```

> **警告:** 上面示例中的 `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` 片段会禁用 Claude Code 的 Monitor 工具(及其他“非必要流量”)。CC Switch 现在会在你设置该值时给出警告,但这不是校验错误——片段仍会被应用。仅在确实不需要 Monitor 时才设置它。

### 🌉 代理管理与模型接入

查看并控制由守护进程管理的按应用代理路由。
Expand Down
47 changes: 46 additions & 1 deletion src-tauri/src/cli/commands/config_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::path::Path;

use crate::app_config::AppType;
use crate::cli::i18n::texts;
use crate::cli::ui::{highlight, info, success};
use crate::cli::ui::{highlight, info, success, warning};
use crate::error::AppError;
use crate::services::ProviderService;
use crate::store::AppState;
Expand Down Expand Up @@ -207,6 +207,15 @@ fn canonical_common_snippet(app_type: AppType, raw: &str) -> Result<Option<Strin
}
}

fn print_monitor_traffic_warning_if_disabled(disables_monitor_traffic: bool) {
if disables_monitor_traffic {
println!(
"{}",
warning(texts::common_config_snippet_disables_monitor_traffic())
);
}
}

fn format(
app_type: AppType,
snippet_text: Option<&str>,
Expand Down Expand Up @@ -235,6 +244,8 @@ fn set(
texts::config_common_snippet_require_json_or_file(),
)?;
let snippet = canonical_common_snippet(app_type.clone(), &raw)?.unwrap_or_default();
let disables_monitor_traffic =
ProviderService::common_config_snippet_disables_monitor_traffic(&app_type, &snippet);

let state = get_state()?;
ProviderService::set_common_config_snippet(&state, app_type.clone(), Some(snippet))?;
Expand All @@ -243,6 +254,7 @@ fn set(
"{}",
success(&texts::config_common_snippet_set_for_app(app_type.as_str()))
);
print_monitor_traffic_warning_if_disabled(disables_monitor_traffic);

let current_id = if app_type.is_additive_mode() {
String::new()
Expand Down Expand Up @@ -296,12 +308,15 @@ fn extract(

if save {
let snippet = canonical_common_snippet(app_type.clone(), &extracted)?.unwrap_or_default();
let disables_monitor_traffic =
ProviderService::common_config_snippet_disables_monitor_traffic(&app_type, &snippet);
ProviderService::set_common_config_snippet(
&state,
app_type.clone(),
Some(snippet.clone()),
)?;
println!("{}", success(texts::common_config_snippet_extracted()));
print_monitor_traffic_warning_if_disabled(disables_monitor_traffic);
if !snippet.trim().is_empty() {
println!();
println!("{}", snippet);
Expand Down Expand Up @@ -465,6 +480,36 @@ mod tests {
);
}

#[test]
#[serial]
fn set_succeeds_when_snippet_disables_monitor_traffic() {
let (_temp_home, _env) = seed_current_claude_provider();

set(
AppType::Claude,
Some(r#"{"env":{"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC":1}}"#),
None,
false,
)
.expect("set should still succeed for a snippet that disables Monitor traffic");

let state = AppState::try_new().expect("reload state");
let stored = state
.config
.read()
.expect("read config")
.common_config_snippets
.claude
.clone()
.expect("stored claude snippet");
let stored_json: serde_json::Value =
serde_json::from_str(&stored).expect("stored snippet should be valid JSON");
assert_eq!(
stored_json["env"]["CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC"],
1
);
}

#[test]
#[serial]
fn set_updates_live_config_for_common_config_enabled_claude_provider() {
Expand Down
8 changes: 8 additions & 0 deletions src-tauri/src/cli/i18n.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12743,6 +12743,14 @@ pub mod texts {
}
}

pub fn common_config_snippet_disables_monitor_traffic() -> &'static str {
if is_chinese() {
"警告:CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC 会禁用 Claude Code 的 Monitor 工具(及其他“非必要流量”)。"
} else {
"Warning: CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC disables Claude Code's Monitor tool (and other \"nonessential traffic\")."
}
}

pub fn common_config_snippet_extracted() -> &'static str {
if is_chinese() {
"已从当前编辑内容提取通用配置片段"
Expand Down
204 changes: 202 additions & 2 deletions src-tauri/src/cli/tui/runtime_actions/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1259,7 +1259,15 @@ fn submit_config_common_snippet(
}

ctx.app.editor = None;
ctx.app.push_toast(toast, ToastKind::Success);
let disables_monitor_traffic = next_snippet.as_deref().is_some_and(|snippet| {
ProviderService::common_config_snippet_disables_monitor_traffic(&app_type, snippet)
});
// Decide the final toast text/kind after reconciliation below runs, then
// push exactly once. `push_toast` replaces the app's single current toast
// (there is no toast queue), so pushing here first and potentially again
// in the reconciliation branch would let a later reconciliation-error
// toast silently swallow the Monitor-traffic warning.
let mut final_toast = (toast.to_string(), ToastKind::Success);
*ctx.data = UiData::load(&ctx.app.app_type)?;
if matches!(source, CommonSnippetViewSource::ProviderForm) {
if let Some(next_form) = reconciled_codex_form {
Expand All @@ -1279,10 +1287,21 @@ fn submit_config_common_snippet(
_ => Ok(()),
};
if let Err(err) = refresh_result {
ctx.app.push_toast(err, ToastKind::Warning);
final_toast = (err, ToastKind::Warning);
}
}
}
if disables_monitor_traffic {
final_toast = (
format!(
"{} {}",
final_toast.0,
texts::common_config_snippet_disables_monitor_traffic()
),
ToastKind::Warning,
);
}
ctx.app.push_toast(final_toast.0, final_toast.1);
if matches!(source, CommonSnippetViewSource::Global) {
ctx.app.overlay = crate::cli::tui::app::Overlay::None;
}
Expand Down Expand Up @@ -2094,6 +2113,187 @@ mod tests {
assert!(!form.claude_tool_search);
}

#[test]
#[serial(home_settings)]
fn submit_config_common_snippet_warns_when_disabling_monitor_traffic() {
let mut fixture = runtime_ctx(AppType::Claude);
let mut ctx = RuntimeActionContext {
terminal: &mut fixture.terminal,
app: &mut fixture.app,
data: &mut fixture.data,
speedtest_req_tx: None,
stream_check_req_tx: None,
skills_req_tx: None,
proxy_req_tx: None,
proxy_loading: &mut fixture.proxy_loading,
local_env_req_tx: None,
session_req_tx: None,
webdav_req_tx: None,
webdav_loading: &mut fixture.webdav_loading,
update_req_tx: None,
update_check: &mut fixture.update_check,
model_fetch_req_tx: None,
managed_auth_req_tx: None,
};

super::submit(
&mut ctx,
EditorSubmit::ConfigCommonSnippet {
app_type: AppType::Claude,
source: crate::cli::tui::app::CommonSnippetViewSource::Global,
},
r#"{"env":{"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC":1}}"#.to_string(),
)
.expect("common snippet submit should succeed even when it disables Monitor traffic");

let toast = ctx.app.toast.as_ref().expect("expected a toast");
assert_eq!(toast.kind, ToastKind::Warning);
assert!(
toast
.message
.contains(texts::common_config_snippet_disables_monitor_traffic()),
"toast should surface the Monitor-traffic warning: {}",
toast.message
);
}

#[test]
#[serial(home_settings)]
fn submit_config_common_snippet_warns_when_disabling_monitor_traffic_via_provider_form() {
// Regression coverage for the ProviderForm reconciliation path
// (`replace_common_config_snippet`), which pushes its own toast on
// failure and previously could silently overwrite the Monitor
// warning. This exercises the successful-reconciliation case; the
// final-toast merge (see the fix at the `submit_config_common_snippet`
// call site) ensures the warning survives regardless of which branch
// sets `final_toast` first.
let mut fixture = runtime_ctx(AppType::Claude);
fixture.app.form = Some(FormState::ProviderAdd(
crate::cli::tui::form::ProviderAddFormState::new_with_common_snippet(
AppType::Claude,
r#"{"env":{"ENABLE_TOOL_SEARCH":"true"}}"#,
),
));
let mut ctx = RuntimeActionContext {
terminal: &mut fixture.terminal,
app: &mut fixture.app,
data: &mut fixture.data,
speedtest_req_tx: None,
stream_check_req_tx: None,
skills_req_tx: None,
proxy_req_tx: None,
proxy_loading: &mut fixture.proxy_loading,
local_env_req_tx: None,
session_req_tx: None,
webdav_req_tx: None,
webdav_loading: &mut fixture.webdav_loading,
update_req_tx: None,
update_check: &mut fixture.update_check,
model_fetch_req_tx: None,
managed_auth_req_tx: None,
};

super::submit(
&mut ctx,
EditorSubmit::ConfigCommonSnippet {
app_type: AppType::Claude,
source: crate::cli::tui::app::CommonSnippetViewSource::ProviderForm,
},
r#"{"env":{"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC":1}}"#.to_string(),
)
.expect("common snippet submit should succeed via the ProviderForm source too");

let toast = ctx.app.toast.as_ref().expect("expected a toast");
assert_eq!(toast.kind, ToastKind::Warning);
assert!(
toast
.message
.contains(texts::common_config_snippet_disables_monitor_traffic()),
"warning must survive the ProviderForm reconciliation branch: {}",
toast.message
);
}

#[test]
#[serial(home_settings)]
fn submit_config_common_snippet_no_warning_when_key_absent() {
let mut fixture = runtime_ctx(AppType::Claude);
let mut ctx = RuntimeActionContext {
terminal: &mut fixture.terminal,
app: &mut fixture.app,
data: &mut fixture.data,
speedtest_req_tx: None,
stream_check_req_tx: None,
skills_req_tx: None,
proxy_req_tx: None,
proxy_loading: &mut fixture.proxy_loading,
local_env_req_tx: None,
session_req_tx: None,
webdav_req_tx: None,
webdav_loading: &mut fixture.webdav_loading,
update_req_tx: None,
update_check: &mut fixture.update_check,
model_fetch_req_tx: None,
managed_auth_req_tx: None,
};

super::submit(
&mut ctx,
EditorSubmit::ConfigCommonSnippet {
app_type: AppType::Claude,
source: crate::cli::tui::app::CommonSnippetViewSource::Global,
},
r#"{"env":{"ENABLE_TOOL_SEARCH":"true"}}"#.to_string(),
)
.expect("common snippet submit should succeed");

let toast = ctx.app.toast.as_ref().expect("expected a toast");
assert_eq!(toast.kind, ToastKind::Success);
assert!(!toast
.message
.contains(texts::common_config_snippet_disables_monitor_traffic()));
}

#[test]
#[serial(home_settings)]
fn submit_config_common_snippet_no_warning_for_non_claude_app() {
let mut fixture = runtime_ctx(AppType::Codex);
let mut ctx = RuntimeActionContext {
terminal: &mut fixture.terminal,
app: &mut fixture.app,
data: &mut fixture.data,
speedtest_req_tx: None,
stream_check_req_tx: None,
skills_req_tx: None,
proxy_req_tx: None,
proxy_loading: &mut fixture.proxy_loading,
local_env_req_tx: None,
session_req_tx: None,
webdav_req_tx: None,
webdav_loading: &mut fixture.webdav_loading,
update_req_tx: None,
update_check: &mut fixture.update_check,
model_fetch_req_tx: None,
managed_auth_req_tx: None,
};

super::submit(
&mut ctx,
EditorSubmit::ConfigCommonSnippet {
app_type: AppType::Codex,
source: crate::cli::tui::app::CommonSnippetViewSource::Global,
},
"model_reasoning_effort = \"high\"".to_string(),
)
.expect("common snippet submit should succeed for Codex");

let toast = ctx.app.toast.as_ref().expect("expected a toast");
assert_eq!(toast.kind, ToastKind::Success);
assert!(!toast
.message
.contains(texts::common_config_snippet_disables_monitor_traffic()));
}

#[test]
#[serial(home_settings)]
fn submit_common_snippet_reconciles_malformed_old_codex_form_to_saved_replacement() {
Expand Down
Loading