1 line
104 KiB
JSON
1 line
104 KiB
JSON
{"repo": "cagedbird043/cxf", "files": {"tests/integration.rs": {"9d3d9c1e27607c93ffc66ec98b515830bc00122d": {"t": 1785746260.0422602, "n": 22733, "h": "416ef2c794d84510", "c": "use std::{error::Error, fs, path::Path, process::Command};\n\nfn bin() -> &'static str {\n env!(\"CARGO_BIN_EXE_cxf\")\n}\n\nfn run(home: &Path, args: &[&str]) -> Result<std::process::Output, Box<dyn Error>> {\n Ok(Command::new(bin())\n .env(\"HOME\", home)\n .env(\"XDG_CONFIG_HOME\", home.join(\".config\"))\n .env(\"XDG_STATE_HOME\", home.join(\".local/state\"))\n .args(args)\n .output()?)\n}\n\n#[test]\nfn codex_use_preserves_unrelated_config_and_status_is_clean_with_overrides()\n-> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n fs::write(\n home.join(\".codex/config.toml\"),\n r#\"model_provider = \"Other\"\n\n[model_providers.Other]\nbase_url = \"https://other.example\"\n\n[projects.\"/home/user/project\"]\ntrust_level = \"trusted\"\n\n[features]\nshell_tool = true\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.4\"\nmodel_reasoning_effort = \"high\"\nmodel_context_window = 272000\nmodel_auto_compact_token_limit = 240000\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = true\ncontext_window = 1000000\nauto_compact_token_limit = 900000\n\"#,\n )?;\n\n let out = run(home, &[\"use\", \"timi\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let config = fs::read_to_string(home.join(\".codex/config.toml\"))?;\n assert!(config.contains(\"# cxf: provider = timi\"));\n assert!(config.contains(\"base_url = \\\"https://timicc.com\\\"\"));\n assert!(config.contains(\"trust_level = \\\"trusted\\\"\"));\n assert!(config.contains(\"shell_tool = true\"));\n assert!(config.contains(\"model_context_window = 1000000\"));\n assert!(config.contains(\"model_auto_compact_token_limit = 900000\"));\n\n let status = run(home, &[\"status\"])?;\n assert!(\n status.status.success(),\n \"{}\",\n String::from_utf8_lossy(&status.stdout)\n );\n assert!(String::from_utf8_lossy(&status.stdout).contains(\"controlled: yes\"));\n Ok(())\n}\n\n#[test]\nfn claude_use_cleans_managed_keys_and_status_is_clean() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".claude\"))?;\n fs::create_dir_all(home.join(\".config/cxf/claude/providers\"))?;\n fs::write(\n home.join(\".claude/settings.json\"),\n r#\"{\n \"model\": \"old-model\",\n \"env\": {\n \"GITHUB_TOKEN\": \"keep\",\n \"ANTHROPIC_API_KEY\": \"old\",\n \"ANTHROPIC_MODEL\": \"old-model\"\n }\n}\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/claude/providers/deepseek.toml\"),\n r#\"[env]\nANTHROPIC_BASE_URL = \"https://api.deepseek.com/anthropic\"\nANTHROPIC_AUTH_TOKEN = \"sk-test\"\n\"#,\n )?;\n\n let out = run(home, &[\"claude\", \"use\", \"deepseek\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let settings = fs::read_to_string(home.join(\".claude/settings.json\"))?;\n assert!(settings.contains(\"\\\"GITHUB_TOKEN\\\": \\\"keep\\\"\"));\n assert!(settings.contains(\"\\\"CXF_CLAUDE_PROVIDER\\\": \\\"deepseek\\\"\"));\n assert!(!settings.contains(\"ANTHROPIC_API_KEY\"));\n assert!(!settings.contains(\"\\\"model\\\"\"));\n\n let status = run(home, &[\"claude\", \"status\"])?;\n assert!(\n status.status.success(),\n \"{}\",\n String::from_utf8_lossy(&status.stdout)\n );\n Ok(())\n}\n\n#[test]\nfn init_extracts_existing_codex_provider_and_current_reports_it() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::write(\n home.join(\".codex/config.toml\"),\n r#\"model = \"gpt-5.5\"\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nname = \"OpenAI\"\nbase_url = \"https://api.openai.example\"\nwire_api = \"responses\"\nsupports_websockets = true\nrequires_openai_auth = true\n\"#,\n )?;\n fs::write(\n home.join(\".codex/auth.json\"),\n r#\"{\"OPENAI_API_KEY\":\"sk-existing\"}\"#,\n )?;\n\n let out = run(home, &[\"init\", \"openai\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let provider = fs::read_to_string(home.join(\".config/cxf/providers/openai.toml\"))?;\n assert!(provider.contains(\"base_url = \\\"https://api.openai.example\\\"\"));\n assert!(provider.contains(\"api_key = \\\"sk-existing\\\"\"));\n let config = fs::read_to_string(home.join(\".codex/config.toml\"))?;\n assert!(config.contains(\"# cxf: provider = openai\"));\n\n assert_eq!(\n fs::read_to_string(home.join(\".config/cxf/auth/codex/openai.json\"))?,\n r#\"{\"OPENAI_API_KEY\":\"sk-existing\"}\"#,\n \"init should save the current live auth profile before any future switch\"\n );\n fs::write(\n home.join(\".codex/auth.json\"),\n r#\"{\"OPENAI_API_KEY\":\"sk-refreshed\"}\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-timi\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = false\n\"#,\n )?;\n let switched = run(home, &[\"use\", \"timi\"])?;\n assert!(\n switched.status.success(),\n \"{}\",\n String::from_utf8_lossy(&switched.stderr)\n );\n assert_eq!(\n fs::read_to_string(home.join(\".config/cxf/auth/codex/openai.json\"))?,\n r#\"{\"OPENAI_API_KEY\":\"sk-refreshed\"}\"#,\n \"first post-init switch should save refreshed live auth using the init probe\"\n );\n\n let list = run(home, &[\"list\"])?;\n assert!(\n list.status.success(),\n \"{}\",\n String::from_utf8_lossy(&list.stderr)\n );\n assert!(String::from_utf8_lossy(&list.stdout).contains(\"openai\"));\n Ok(())\n}\n\n#[test]\nfn add_remove_and_rename_provider_noninteractive() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n let add = run(\n home,\n &[\n \"add\",\n \"--provider-id\",\n \"one\",\n \"--base-url\",\n \"https://one.example\",\n \"--api-key\",\n \"sk-one\",\n \"--wire-api\",\n \"chat\",\n \"--no-websocket\",\n ],\n )?;\n assert!(\n add.status.success(),\n \"{}\",\n String::from_utf8_lossy(&add.stderr)\n );\n let provider = fs::read_to_string(home.join(\".config/cxf/providers/one.toml\"))?;\n assert!(provider.contains(\"wire_api = \\\"chat\\\"\"));\n assert!(provider.contains(\"websocket = false\"));\n\n let rename = run(home, &[\"rename\", \"one\", \"two\"])?;\n assert!(\n rename.status.success(),\n \"{}\",\n String::from_utf8_lossy(&rename.stderr)\n );\n assert!(!home.join(\".config/cxf/providers/one.toml\").exists());\n assert!(home.join(\".config/cxf/providers/two.toml\").exists());\n\n let remove = run(home, &[\"remove\", \"two\", \"-y\"])?;\n assert!(\n remove.status.success(),\n \"{}\",\n String::from_utf8_lossy(&remove.stderr)\n );\n assert!(!home.join(\".config/cxf/providers/two.toml\").exists());\n Ok(())\n}\n\n#[test]\nfn claude_current_falls_back_to_top_level_model() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".claude\"))?;\n fs::write(\n home.join(\".claude/settings.json\"),\n r#\"{\n \"model\": \"top-level-model\",\n \"env\": {\n \"CXF_CLAUDE_PROVIDER\": \"anthropic\",\n \"ANTHROPIC_BASE_URL\": \"https://api.anthropic.com\"\n }\n}\n\"#,\n )?;\n let current = run(home, &[\"claude\", \"current\"])?;\n assert!(\n current.status.success(),\n \"{}\",\n String::from_utf8_lossy(¤t.stderr)\n );\n assert!(String::from_utf8_lossy(¤t.stdout).contains(\"top-level-model\"));\n Ok(())\n}\n\n#[test]\nfn codex_status_with_drift() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n fs::write(\n home.join(\".codex/config.toml\"),\n r#\"model_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\nwire_api = \"responses\"\nsupports_websockets = false\nrequires_openai_auth = true\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = false\n\"#,\n )?;\n fs::write(\n home.join(\".codex/auth.json\"),\n r#\"{\"OPENAI_API_KEY\":\"sk-test\"}\"#,\n )?;\n\n // Apply provider first so config is fully populated by cxf\n let apply = run(home, &[\"use\", \"timi\"])?;\n assert!(\n apply.status.success(),\n \"{}\",\n String::from_utf8_lossy(&apply.stderr)\n );\n\n // Status should be clean after apply\n let status = run(home, &[\"status\"])?;\n assert!(\n status.status.success(),\n \"{}\",\n String::from_utf8_lossy(&status.stdout)\n );\n assert!(String::from_utf8_lossy(&status.stdout).contains(\"controlled: yes\"));\n\n // Introduce drift: change base_url\n let config_path = home.join(\".codex/config.toml\");\n let mut config = fs::read_to_string(&config_path)?;\n config = config.replace(\"https://timicc.com\", \"https://drifted.example\");\n fs::write(&config_path, config)?;\n\n let status2 = run(home, &[\"status\"])?;\n assert!(\n !status2.status.success(),\n \"status should exit non-zero with drift\"\n );\n let out = String::from_utf8_lossy(&status2.stdout);\n assert!(\n out.contains(\"controlled: partial\"),\n \"expected partial, got: {out}\"\n );\n Ok(())\n}\n\n#[test]\nfn codex_use_before_init_warns_when_base_toml_missing() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n // No base.toml\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = timi\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = false\n\"#,\n )?;\n fs::write(\n home.join(\".codex/auth.json\"),\n r#\"{\"OPENAI_API_KEY\":\"sk-test\"}\"#,\n )?;\n\n let out = run(home, &[\"use\", \"timi\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let stderr = String::from_utf8_lossy(&out.stderr);\n assert!(\n stderr.contains(\"warning\") && stderr.contains(\"base.toml\"),\n \"expected warning about base.toml, stderr: {stderr}\"\n );\n Ok(())\n}\n\n#[test]\nfn codex_use_rejects_malformed_auth_json() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = timi\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = false\n\"#,\n )?;\n // Broken JSON\n fs::write(home.join(\".codex/auth.json\"), r#\"{broken\"#)?;\n\n let out = run(home, &[\"use\", \"timi\"])?;\n assert!(\n !out.status.success(),\n \"should fail with malformed auth.json\"\n );\n let stderr = String::from_utf8_lossy(&out.stderr);\n assert!(\n stderr.contains(\"invalid JSON\"),\n \"expected JSON parse error, got: {stderr}\"\n );\n Ok(())\n}\n\n#[test]\nfn claude_add_and_rename_noninteractive() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n let add = run(\n home,\n &[\n \"claude\",\n \"add\",\n \"--provider-id\",\n \"my-provider\",\n \"--base-url\",\n \"https://my.example\",\n \"--api-key\",\n \"sk-my\",\n \"--model\",\n \"my-model\",\n ],\n )?;\n assert!(\n add.status.success(),\n \"{}\",\n String::from_utf8_lossy(&add.stderr)\n );\n\n let provider = fs::read_to_string(home.join(\".config/cxf/claude/providers/my-provider.toml\"))?;\n assert!(provider.contains(r#\"ANTHROPIC_BASE_URL = \"https://my.example\"\"#));\n assert!(provider.contains(r#\"ANTHROPIC_AUTH_TOKEN = \"sk-my\"\"#));\n assert!(provider.contains(r#\"ANTHROPIC_MODEL = \"my-model\"\"#));\n\n let rename = run(home, &[\"claude\", \"rename\", \"my-provider\", \"renamed\"])?;\n assert!(\n rename.status.success(),\n \"{}\",\n String::from_utf8_lossy(&rename.stderr)\n );\n assert!(\n !home\n .join(\".config/cxf/claude/providers/my-provider.toml\")\n .exists()\n );\n assert!(\n home.join(\".config/cxf/claude/providers/renamed.toml\")\n .exists()\n );\n Ok(())\n}\n\n#[test]\nfn completion_contains_provider_functions() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let out = run(tmp.path(), &[\"completion\", \"zsh\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let text = String::from_utf8_lossy(&out.stdout);\n assert!(text.contains(\"_cxf_provider_ids\"));\n assert!(text.contains(\"_cxf_claude_provider_ids\"));\n Ok(())\n}\n\n#[test]\nfn codex_use_oauth_without_profile_preserves_existing_auth_json() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = timi\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/oauth.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"\"\napi_key = \"\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = true\n\"#,\n )?;\n\n let existing_auth = \"{\\n \\\"OPENAI_API_KEY\\\": \\\"sk-test\\\"\\n}\\n\";\n fs::write(home.join(\".codex/auth.json\"), existing_auth)?;\n\n let out = run(home, &[\"use\", \"oauth\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let content = fs::read_to_string(home.join(\".codex/auth.json\"))?;\n assert_eq!(\n content, existing_auth,\n \"OAuth provider without auth profile should not destroy live auth\"\n );\n assert_eq!(\n fs::read_to_string(home.join(\".config/cxf/auth/codex/timi.json\"))?,\n existing_auth,\n \"previous provider auth should be saved as a profile\"\n );\n\n Ok(())\n}\n\n#[test]\nfn codex_use_api_key_saves_oauth_profile_and_bootstraps_target() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = official\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = false\n\"#,\n )?;\n let oauth_auth = r#\"{\n \"auth_mode\": \"chatgpt\",\n \"OPENAI_API_KEY\": null,\n \"tokens\": {\n \"id_token\": \"id\",\n \"access_token\": \"access\",\n \"refresh_token\": \"refresh\",\n \"account_id\": \"account\"\n },\n \"last_refresh\": \"2026-06-05T16:05:15.057134715Z\"\n}\n\"#;\n fs::write(home.join(\".codex/auth.json\"), oauth_auth)?;\n\n let out = run(home, &[\"use\", \"timi\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let api_auth = \"{\\n \\\"OPENAI_API_KEY\\\": \\\"sk-test\\\"\\n}\\n\";\n assert_eq!(fs::read_to_string(home.join(\".codex/auth.json\"))?, api_auth);\n assert_eq!(\n fs::read_to_string(home.join(\".config/cxf/auth/codex/official.json\"))?,\n oauth_auth,\n \"OAuth auth should be saved as an opaque provider profile\"\n );\n assert_eq!(\n fs::read_to_string(home.join(\".config/cxf/auth/codex/timi.json\"))?,\n api_auth,\n \"API-key auth should be bootstrapped as a provider profile\"\n );\n\n Ok(())\n}\n\n#[test]\nfn codex_use_restores_target_auth_profile_byte_for_byte() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n fs::create_dir_all(home.join(\".config/cxf/auth/codex\"))?;\n\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = timi\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/official.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"\"\napi_key = \"\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = true\n\"#,\n )?;\n\n let api_auth = \"{\\n \\\"OPENAI_API_KEY\\\": \\\"sk-test\\\"\\n}\\n\";\n fs::write(home.join(\".codex/auth.json\"), api_auth)?;\n let oauth_auth = r#\"{\n \"auth_mode\": \"chatgpt\",\n \"OPENAI_API_KEY\": null,\n \"tokens\": {\n \"refresh_token\": \"refresh\"\n },\n \"last_refresh\": \"2026-06-05T16:05:15.057134715Z\"\n}\n\"#;\n fs::write(\n home.join(\".config/cxf/auth/codex/official.json\"),\n oauth_auth,\n )?;\n\n let out = run(home, &[\"use\", \"official\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n assert_eq!(\n fs::read_to_string(home.join(\".codex/auth.json\"))?,\n oauth_auth,\n \"target auth profile should be restored byte-for-byte\"\n );\n assert_eq!(\n fs::read_to_string(home.join(\".config/cxf/auth/codex/timi.json\"))?,\n api_auth,\n \"previous live auth should be saved before restore\"\n );\n\n Ok(())\n}\n\n#[test]\nfn codex_use_imports_legacy_auth_snapshot_as_profile() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n fs::create_dir_all(home.join(\".local/state/cxf/snapshots\"))?;\n\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = timi\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/official.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"\"\napi_key = \"\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = true\n\"#,\n )?;\n\n fs::write(\n home.join(\".codex/auth.json\"),\n \"{\\n \\\"OPENAI_API_KEY\\\": \\\"sk-test\\\"\\n}\\n\",\n )?;\n let oauth_auth = r#\"{\n \"auth_mode\": \"chatgpt\",\n \"OPENAI_API_KEY\": null,\n \"tokens\": {\n \"refresh_token\": \"refresh\"\n }\n}\n\"#;\n fs::write(\n home.join(\".local/state/cxf/snapshots/codex-auth-official.json\"),\n oauth_auth,\n )?;\n\n let out = run(home, &[\"use\", \"official\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n assert_eq!(\n fs::read_to_string(home.join(\".codex/auth.json\"))?,\n oauth_auth\n );\n assert_eq!(\n fs::read_to_string(home.join(\".config/cxf/auth/codex/official.json\"))?,\n oauth_auth,\n \"legacy snapshot should be imported into normal auth profile storage\"\n );\n\n Ok(())\n}\n\n#[cfg(unix)]\n#[test]\nfn codex_use_restored_auth_profile_resets_live_auth_permissions() -> Result<(), Box<dyn Error>> {\n use std::os::unix::fs::PermissionsExt;\n\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n fs::create_dir_all(home.join(\".config/cxf/auth/codex\"))?;\n\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = timi\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/official.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"\"\napi_key = \"\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = true\n\"#,\n )?;\n\n let auth_path = home.join(\".codex/auth.json\");\n fs::write(&auth_path, \"{\\n \\\"OPENAI_API_KEY\\\": \\\"sk-test\\\"\\n}\\n\")?;\n let mut perms = fs::metadata(&auth_path)?.permissions();\n perms.set_mode(0o644);\n fs::set_permissions(&auth_path, perms)?;\n\n let oauth_auth = r#\"{\n \"auth_mode\": \"chatgpt\",\n \"OPENAI_API_KEY\": null,\n \"tokens\": {\n \"refresh_token\": \"refresh\"\n }\n}\n\"#;\n fs::write(\n home.join(\".config/cxf/auth/codex/official.json\"),\n oauth_auth,\n )?;\n\n let out = run(home, &[\"use\", \"official\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n assert_eq!(fs::metadata(auth_path)?.permissions().mode() & 0o777, 0o600);\n\n Ok(())\n}\n"}, "66cfe5486b5ed0b9330047e55a4b3db0f0e57161": {"t": 1785761191.7166321, "n": 22733, "h": "416ef2c794d84510", "c": "use std::{error::Error, fs, path::Path, process::Command};\n\nfn bin() -> &'static str {\n env!(\"CARGO_BIN_EXE_cxf\")\n}\n\nfn run(home: &Path, args: &[&str]) -> Result<std::process::Output, Box<dyn Error>> {\n Ok(Command::new(bin())\n .env(\"HOME\", home)\n .env(\"XDG_CONFIG_HOME\", home.join(\".config\"))\n .env(\"XDG_STATE_HOME\", home.join(\".local/state\"))\n .args(args)\n .output()?)\n}\n\n#[test]\nfn codex_use_preserves_unrelated_config_and_status_is_clean_with_overrides()\n-> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n fs::write(\n home.join(\".codex/config.toml\"),\n r#\"model_provider = \"Other\"\n\n[model_providers.Other]\nbase_url = \"https://other.example\"\n\n[projects.\"/home/user/project\"]\ntrust_level = \"trusted\"\n\n[features]\nshell_tool = true\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.4\"\nmodel_reasoning_effort = \"high\"\nmodel_context_window = 272000\nmodel_auto_compact_token_limit = 240000\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = true\ncontext_window = 1000000\nauto_compact_token_limit = 900000\n\"#,\n )?;\n\n let out = run(home, &[\"use\", \"timi\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let config = fs::read_to_string(home.join(\".codex/config.toml\"))?;\n assert!(config.contains(\"# cxf: provider = timi\"));\n assert!(config.contains(\"base_url = \\\"https://timicc.com\\\"\"));\n assert!(config.contains(\"trust_level = \\\"trusted\\\"\"));\n assert!(config.contains(\"shell_tool = true\"));\n assert!(config.contains(\"model_context_window = 1000000\"));\n assert!(config.contains(\"model_auto_compact_token_limit = 900000\"));\n\n let status = run(home, &[\"status\"])?;\n assert!(\n status.status.success(),\n \"{}\",\n String::from_utf8_lossy(&status.stdout)\n );\n assert!(String::from_utf8_lossy(&status.stdout).contains(\"controlled: yes\"));\n Ok(())\n}\n\n#[test]\nfn claude_use_cleans_managed_keys_and_status_is_clean() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".claude\"))?;\n fs::create_dir_all(home.join(\".config/cxf/claude/providers\"))?;\n fs::write(\n home.join(\".claude/settings.json\"),\n r#\"{\n \"model\": \"old-model\",\n \"env\": {\n \"GITHUB_TOKEN\": \"keep\",\n \"ANTHROPIC_API_KEY\": \"old\",\n \"ANTHROPIC_MODEL\": \"old-model\"\n }\n}\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/claude/providers/deepseek.toml\"),\n r#\"[env]\nANTHROPIC_BASE_URL = \"https://api.deepseek.com/anthropic\"\nANTHROPIC_AUTH_TOKEN = \"sk-test\"\n\"#,\n )?;\n\n let out = run(home, &[\"claude\", \"use\", \"deepseek\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let settings = fs::read_to_string(home.join(\".claude/settings.json\"))?;\n assert!(settings.contains(\"\\\"GITHUB_TOKEN\\\": \\\"keep\\\"\"));\n assert!(settings.contains(\"\\\"CXF_CLAUDE_PROVIDER\\\": \\\"deepseek\\\"\"));\n assert!(!settings.contains(\"ANTHROPIC_API_KEY\"));\n assert!(!settings.contains(\"\\\"model\\\"\"));\n\n let status = run(home, &[\"claude\", \"status\"])?;\n assert!(\n status.status.success(),\n \"{}\",\n String::from_utf8_lossy(&status.stdout)\n );\n Ok(())\n}\n\n#[test]\nfn init_extracts_existing_codex_provider_and_current_reports_it() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::write(\n home.join(\".codex/config.toml\"),\n r#\"model = \"gpt-5.5\"\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nname = \"OpenAI\"\nbase_url = \"https://api.openai.example\"\nwire_api = \"responses\"\nsupports_websockets = true\nrequires_openai_auth = true\n\"#,\n )?;\n fs::write(\n home.join(\".codex/auth.json\"),\n r#\"{\"OPENAI_API_KEY\":\"sk-existing\"}\"#,\n )?;\n\n let out = run(home, &[\"init\", \"openai\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let provider = fs::read_to_string(home.join(\".config/cxf/providers/openai.toml\"))?;\n assert!(provider.contains(\"base_url = \\\"https://api.openai.example\\\"\"));\n assert!(provider.contains(\"api_key = \\\"sk-existing\\\"\"));\n let config = fs::read_to_string(home.join(\".codex/config.toml\"))?;\n assert!(config.contains(\"# cxf: provider = openai\"));\n\n assert_eq!(\n fs::read_to_string(home.join(\".config/cxf/auth/codex/openai.json\"))?,\n r#\"{\"OPENAI_API_KEY\":\"sk-existing\"}\"#,\n \"init should save the current live auth profile before any future switch\"\n );\n fs::write(\n home.join(\".codex/auth.json\"),\n r#\"{\"OPENAI_API_KEY\":\"sk-refreshed\"}\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-timi\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = false\n\"#,\n )?;\n let switched = run(home, &[\"use\", \"timi\"])?;\n assert!(\n switched.status.success(),\n \"{}\",\n String::from_utf8_lossy(&switched.stderr)\n );\n assert_eq!(\n fs::read_to_string(home.join(\".config/cxf/auth/codex/openai.json\"))?,\n r#\"{\"OPENAI_API_KEY\":\"sk-refreshed\"}\"#,\n \"first post-init switch should save refreshed live auth using the init probe\"\n );\n\n let list = run(home, &[\"list\"])?;\n assert!(\n list.status.success(),\n \"{}\",\n String::from_utf8_lossy(&list.stderr)\n );\n assert!(String::from_utf8_lossy(&list.stdout).contains(\"openai\"));\n Ok(())\n}\n\n#[test]\nfn add_remove_and_rename_provider_noninteractive() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n let add = run(\n home,\n &[\n \"add\",\n \"--provider-id\",\n \"one\",\n \"--base-url\",\n \"https://one.example\",\n \"--api-key\",\n \"sk-one\",\n \"--wire-api\",\n \"chat\",\n \"--no-websocket\",\n ],\n )?;\n assert!(\n add.status.success(),\n \"{}\",\n String::from_utf8_lossy(&add.stderr)\n );\n let provider = fs::read_to_string(home.join(\".config/cxf/providers/one.toml\"))?;\n assert!(provider.contains(\"wire_api = \\\"chat\\\"\"));\n assert!(provider.contains(\"websocket = false\"));\n\n let rename = run(home, &[\"rename\", \"one\", \"two\"])?;\n assert!(\n rename.status.success(),\n \"{}\",\n String::from_utf8_lossy(&rename.stderr)\n );\n assert!(!home.join(\".config/cxf/providers/one.toml\").exists());\n assert!(home.join(\".config/cxf/providers/two.toml\").exists());\n\n let remove = run(home, &[\"remove\", \"two\", \"-y\"])?;\n assert!(\n remove.status.success(),\n \"{}\",\n String::from_utf8_lossy(&remove.stderr)\n );\n assert!(!home.join(\".config/cxf/providers/two.toml\").exists());\n Ok(())\n}\n\n#[test]\nfn claude_current_falls_back_to_top_level_model() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".claude\"))?;\n fs::write(\n home.join(\".claude/settings.json\"),\n r#\"{\n \"model\": \"top-level-model\",\n \"env\": {\n \"CXF_CLAUDE_PROVIDER\": \"anthropic\",\n \"ANTHROPIC_BASE_URL\": \"https://api.anthropic.com\"\n }\n}\n\"#,\n )?;\n let current = run(home, &[\"claude\", \"current\"])?;\n assert!(\n current.status.success(),\n \"{}\",\n String::from_utf8_lossy(¤t.stderr)\n );\n assert!(String::from_utf8_lossy(¤t.stdout).contains(\"top-level-model\"));\n Ok(())\n}\n\n#[test]\nfn codex_status_with_drift() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n fs::write(\n home.join(\".codex/config.toml\"),\n r#\"model_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\nwire_api = \"responses\"\nsupports_websockets = false\nrequires_openai_auth = true\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = false\n\"#,\n )?;\n fs::write(\n home.join(\".codex/auth.json\"),\n r#\"{\"OPENAI_API_KEY\":\"sk-test\"}\"#,\n )?;\n\n // Apply provider first so config is fully populated by cxf\n let apply = run(home, &[\"use\", \"timi\"])?;\n assert!(\n apply.status.success(),\n \"{}\",\n String::from_utf8_lossy(&apply.stderr)\n );\n\n // Status should be clean after apply\n let status = run(home, &[\"status\"])?;\n assert!(\n status.status.success(),\n \"{}\",\n String::from_utf8_lossy(&status.stdout)\n );\n assert!(String::from_utf8_lossy(&status.stdout).contains(\"controlled: yes\"));\n\n // Introduce drift: change base_url\n let config_path = home.join(\".codex/config.toml\");\n let mut config = fs::read_to_string(&config_path)?;\n config = config.replace(\"https://timicc.com\", \"https://drifted.example\");\n fs::write(&config_path, config)?;\n\n let status2 = run(home, &[\"status\"])?;\n assert!(\n !status2.status.success(),\n \"status should exit non-zero with drift\"\n );\n let out = String::from_utf8_lossy(&status2.stdout);\n assert!(\n out.contains(\"controlled: partial\"),\n \"expected partial, got: {out}\"\n );\n Ok(())\n}\n\n#[test]\nfn codex_use_before_init_warns_when_base_toml_missing() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n // No base.toml\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = timi\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = false\n\"#,\n )?;\n fs::write(\n home.join(\".codex/auth.json\"),\n r#\"{\"OPENAI_API_KEY\":\"sk-test\"}\"#,\n )?;\n\n let out = run(home, &[\"use\", \"timi\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let stderr = String::from_utf8_lossy(&out.stderr);\n assert!(\n stderr.contains(\"warning\") && stderr.contains(\"base.toml\"),\n \"expected warning about base.toml, stderr: {stderr}\"\n );\n Ok(())\n}\n\n#[test]\nfn codex_use_rejects_malformed_auth_json() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = timi\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = false\n\"#,\n )?;\n // Broken JSON\n fs::write(home.join(\".codex/auth.json\"), r#\"{broken\"#)?;\n\n let out = run(home, &[\"use\", \"timi\"])?;\n assert!(\n !out.status.success(),\n \"should fail with malformed auth.json\"\n );\n let stderr = String::from_utf8_lossy(&out.stderr);\n assert!(\n stderr.contains(\"invalid JSON\"),\n \"expected JSON parse error, got: {stderr}\"\n );\n Ok(())\n}\n\n#[test]\nfn claude_add_and_rename_noninteractive() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n let add = run(\n home,\n &[\n \"claude\",\n \"add\",\n \"--provider-id\",\n \"my-provider\",\n \"--base-url\",\n \"https://my.example\",\n \"--api-key\",\n \"sk-my\",\n \"--model\",\n \"my-model\",\n ],\n )?;\n assert!(\n add.status.success(),\n \"{}\",\n String::from_utf8_lossy(&add.stderr)\n );\n\n let provider = fs::read_to_string(home.join(\".config/cxf/claude/providers/my-provider.toml\"))?;\n assert!(provider.contains(r#\"ANTHROPIC_BASE_URL = \"https://my.example\"\"#));\n assert!(provider.contains(r#\"ANTHROPIC_AUTH_TOKEN = \"sk-my\"\"#));\n assert!(provider.contains(r#\"ANTHROPIC_MODEL = \"my-model\"\"#));\n\n let rename = run(home, &[\"claude\", \"rename\", \"my-provider\", \"renamed\"])?;\n assert!(\n rename.status.success(),\n \"{}\",\n String::from_utf8_lossy(&rename.stderr)\n );\n assert!(\n !home\n .join(\".config/cxf/claude/providers/my-provider.toml\")\n .exists()\n );\n assert!(\n home.join(\".config/cxf/claude/providers/renamed.toml\")\n .exists()\n );\n Ok(())\n}\n\n#[test]\nfn completion_contains_provider_functions() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let out = run(tmp.path(), &[\"completion\", \"zsh\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let text = String::from_utf8_lossy(&out.stdout);\n assert!(text.contains(\"_cxf_provider_ids\"));\n assert!(text.contains(\"_cxf_claude_provider_ids\"));\n Ok(())\n}\n\n#[test]\nfn codex_use_oauth_without_profile_preserves_existing_auth_json() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = timi\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/oauth.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"\"\napi_key = \"\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = true\n\"#,\n )?;\n\n let existing_auth = \"{\\n \\\"OPENAI_API_KEY\\\": \\\"sk-test\\\"\\n}\\n\";\n fs::write(home.join(\".codex/auth.json\"), existing_auth)?;\n\n let out = run(home, &[\"use\", \"oauth\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let content = fs::read_to_string(home.join(\".codex/auth.json\"))?;\n assert_eq!(\n content, existing_auth,\n \"OAuth provider without auth profile should not destroy live auth\"\n );\n assert_eq!(\n fs::read_to_string(home.join(\".config/cxf/auth/codex/timi.json\"))?,\n existing_auth,\n \"previous provider auth should be saved as a profile\"\n );\n\n Ok(())\n}\n\n#[test]\nfn codex_use_api_key_saves_oauth_profile_and_bootstraps_target() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = official\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = false\n\"#,\n )?;\n let oauth_auth = r#\"{\n \"auth_mode\": \"chatgpt\",\n \"OPENAI_API_KEY\": null,\n \"tokens\": {\n \"id_token\": \"id\",\n \"access_token\": \"access\",\n \"refresh_token\": \"refresh\",\n \"account_id\": \"account\"\n },\n \"last_refresh\": \"2026-06-05T16:05:15.057134715Z\"\n}\n\"#;\n fs::write(home.join(\".codex/auth.json\"), oauth_auth)?;\n\n let out = run(home, &[\"use\", \"timi\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let api_auth = \"{\\n \\\"OPENAI_API_KEY\\\": \\\"sk-test\\\"\\n}\\n\";\n assert_eq!(fs::read_to_string(home.join(\".codex/auth.json\"))?, api_auth);\n assert_eq!(\n fs::read_to_string(home.join(\".config/cxf/auth/codex/official.json\"))?,\n oauth_auth,\n \"OAuth auth should be saved as an opaque provider profile\"\n );\n assert_eq!(\n fs::read_to_string(home.join(\".config/cxf/auth/codex/timi.json\"))?,\n api_auth,\n \"API-key auth should be bootstrapped as a provider profile\"\n );\n\n Ok(())\n}\n\n#[test]\nfn codex_use_restores_target_auth_profile_byte_for_byte() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n fs::create_dir_all(home.join(\".config/cxf/auth/codex\"))?;\n\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = timi\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/official.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"\"\napi_key = \"\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = true\n\"#,\n )?;\n\n let api_auth = \"{\\n \\\"OPENAI_API_KEY\\\": \\\"sk-test\\\"\\n}\\n\";\n fs::write(home.join(\".codex/auth.json\"), api_auth)?;\n let oauth_auth = r#\"{\n \"auth_mode\": \"chatgpt\",\n \"OPENAI_API_KEY\": null,\n \"tokens\": {\n \"refresh_token\": \"refresh\"\n },\n \"last_refresh\": \"2026-06-05T16:05:15.057134715Z\"\n}\n\"#;\n fs::write(\n home.join(\".config/cxf/auth/codex/official.json\"),\n oauth_auth,\n )?;\n\n let out = run(home, &[\"use\", \"official\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n assert_eq!(\n fs::read_to_string(home.join(\".codex/auth.json\"))?,\n oauth_auth,\n \"target auth profile should be restored byte-for-byte\"\n );\n assert_eq!(\n fs::read_to_string(home.join(\".config/cxf/auth/codex/timi.json\"))?,\n api_auth,\n \"previous live auth should be saved before restore\"\n );\n\n Ok(())\n}\n\n#[test]\nfn codex_use_imports_legacy_auth_snapshot_as_profile() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n fs::create_dir_all(home.join(\".local/state/cxf/snapshots\"))?;\n\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = timi\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/official.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"\"\napi_key = \"\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = true\n\"#,\n )?;\n\n fs::write(\n home.join(\".codex/auth.json\"),\n \"{\\n \\\"OPENAI_API_KEY\\\": \\\"sk-test\\\"\\n}\\n\",\n )?;\n let oauth_auth = r#\"{\n \"auth_mode\": \"chatgpt\",\n \"OPENAI_API_KEY\": null,\n \"tokens\": {\n \"refresh_token\": \"refresh\"\n }\n}\n\"#;\n fs::write(\n home.join(\".local/state/cxf/snapshots/codex-auth-official.json\"),\n oauth_auth,\n )?;\n\n let out = run(home, &[\"use\", \"official\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n assert_eq!(\n fs::read_to_string(home.join(\".codex/auth.json\"))?,\n oauth_auth\n );\n assert_eq!(\n fs::read_to_string(home.join(\".config/cxf/auth/codex/official.json\"))?,\n oauth_auth,\n \"legacy snapshot should be imported into normal auth profile storage\"\n );\n\n Ok(())\n}\n\n#[cfg(unix)]\n#[test]\nfn codex_use_restored_auth_profile_resets_live_auth_permissions() -> Result<(), Box<dyn Error>> {\n use std::os::unix::fs::PermissionsExt;\n\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n fs::create_dir_all(home.join(\".config/cxf/auth/codex\"))?;\n\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = timi\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/official.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"\"\napi_key = \"\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = true\n\"#,\n )?;\n\n let auth_path = home.join(\".codex/auth.json\");\n fs::write(&auth_path, \"{\\n \\\"OPENAI_API_KEY\\\": \\\"sk-test\\\"\\n}\\n\")?;\n let mut perms = fs::metadata(&auth_path)?.permissions();\n perms.set_mode(0o644);\n fs::set_permissions(&auth_path, perms)?;\n\n let oauth_auth = r#\"{\n \"auth_mode\": \"chatgpt\",\n \"OPENAI_API_KEY\": null,\n \"tokens\": {\n \"refresh_token\": \"refresh\"\n }\n}\n\"#;\n fs::write(\n home.join(\".config/cxf/auth/codex/official.json\"),\n oauth_auth,\n )?;\n\n let out = run(home, &[\"use\", \"official\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n assert_eq!(fs::metadata(auth_path)?.permissions().mode() & 0o777, 0o600);\n\n Ok(())\n}\n"}, "88190b088ae9b01efc78921c846f94092a967d40": {"t": 1785761192.9957533, "n": 16307, "h": "3a8f2fe709193a6b", "c": "use std::{error::Error, fs, path::Path, process::Command};\n\nfn bin() -> &'static str {\n env!(\"CARGO_BIN_EXE_cxf\")\n}\n\nfn run(home: &Path, args: &[&str]) -> Result<std::process::Output, Box<dyn Error>> {\n Ok(Command::new(bin())\n .env(\"HOME\", home)\n .env(\"XDG_CONFIG_HOME\", home.join(\".config\"))\n .env(\"XDG_STATE_HOME\", home.join(\".local/state\"))\n .args(args)\n .output()?)\n}\n\n#[test]\nfn codex_use_preserves_unrelated_config_and_status_is_clean_with_overrides()\n-> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n fs::write(\n home.join(\".codex/config.toml\"),\n r#\"model_provider = \"Other\"\n\n[model_providers.Other]\nbase_url = \"https://other.example\"\n\n[projects.\"/home/user/project\"]\ntrust_level = \"trusted\"\n\n[features]\nshell_tool = true\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.4\"\nmodel_reasoning_effort = \"high\"\nmodel_context_window = 272000\nmodel_auto_compact_token_limit = 240000\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = true\ncontext_window = 1000000\nauto_compact_token_limit = 900000\n\"#,\n )?;\n\n let out = run(home, &[\"use\", \"timi\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let config = fs::read_to_string(home.join(\".codex/config.toml\"))?;\n assert!(config.contains(\"# cxf: provider = timi\"));\n assert!(config.contains(\"base_url = \\\"https://timicc.com\\\"\"));\n assert!(config.contains(\"trust_level = \\\"trusted\\\"\"));\n assert!(config.contains(\"shell_tool = true\"));\n assert!(config.contains(\"model_context_window = 1000000\"));\n assert!(config.contains(\"model_auto_compact_token_limit = 900000\"));\n\n let status = run(home, &[\"status\"])?;\n assert!(\n status.status.success(),\n \"{}\",\n String::from_utf8_lossy(&status.stdout)\n );\n assert!(String::from_utf8_lossy(&status.stdout).contains(\"controlled: yes\"));\n Ok(())\n}\n\n#[test]\nfn claude_use_cleans_managed_keys_and_status_is_clean() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".claude\"))?;\n fs::create_dir_all(home.join(\".config/cxf/claude/providers\"))?;\n fs::write(\n home.join(\".claude/settings.json\"),\n r#\"{\n \"model\": \"old-model\",\n \"env\": {\n \"GITHUB_TOKEN\": \"keep\",\n \"ANTHROPIC_API_KEY\": \"old\",\n \"ANTHROPIC_MODEL\": \"old-model\"\n }\n}\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/claude/providers/deepseek.toml\"),\n r#\"[env]\nANTHROPIC_BASE_URL = \"https://api.deepseek.com/anthropic\"\nANTHROPIC_AUTH_TOKEN = \"sk-test\"\n\"#,\n )?;\n\n let out = run(home, &[\"claude\", \"use\", \"deepseek\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let settings = fs::read_to_string(home.join(\".claude/settings.json\"))?;\n assert!(settings.contains(\"\\\"GITHUB_TOKEN\\\": \\\"keep\\\"\"));\n assert!(settings.contains(\"\\\"CXF_CLAUDE_PROVIDER\\\": \\\"deepseek\\\"\"));\n assert!(!settings.contains(\"ANTHROPIC_API_KEY\"));\n assert!(!settings.contains(\"\\\"model\\\"\"));\n\n let status = run(home, &[\"claude\", \"status\"])?;\n assert!(\n status.status.success(),\n \"{}\",\n String::from_utf8_lossy(&status.stdout)\n );\n Ok(())\n}\n\n#[test]\nfn init_extracts_existing_codex_provider_and_current_reports_it() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::write(\n home.join(\".codex/config.toml\"),\n r#\"model = \"gpt-5.5\"\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nname = \"OpenAI\"\nbase_url = \"https://api.openai.example\"\nwire_api = \"responses\"\nsupports_websockets = true\nrequires_openai_auth = true\n\"#,\n )?;\n fs::write(\n home.join(\".codex/auth.json\"),\n r#\"{\"OPENAI_API_KEY\":\"sk-existing\"}\"#,\n )?;\n\n let out = run(home, &[\"init\", \"openai\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let provider = fs::read_to_string(home.join(\".config/cxf/providers/openai.toml\"))?;\n assert!(provider.contains(\"base_url = \\\"https://api.openai.example\\\"\"));\n assert!(provider.contains(\"api_key = \\\"sk-existing\\\"\"));\n\n let list = run(home, &[\"list\"])?;\n assert!(\n list.status.success(),\n \"{}\",\n String::from_utf8_lossy(&list.stderr)\n );\n assert!(String::from_utf8_lossy(&list.stdout).contains(\"openai\"));\n Ok(())\n}\n\n#[test]\nfn add_remove_and_rename_provider_noninteractive() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n let add = run(\n home,\n &[\n \"add\",\n \"--provider-id\",\n \"one\",\n \"--base-url\",\n \"https://one.example\",\n \"--api-key\",\n \"sk-one\",\n \"--wire-api\",\n \"chat\",\n \"--no-websocket\",\n ],\n )?;\n assert!(\n add.status.success(),\n \"{}\",\n String::from_utf8_lossy(&add.stderr)\n );\n let provider = fs::read_to_string(home.join(\".config/cxf/providers/one.toml\"))?;\n assert!(provider.contains(\"wire_api = \\\"chat\\\"\"));\n assert!(provider.contains(\"websocket = false\"));\n\n let rename = run(home, &[\"rename\", \"one\", \"two\"])?;\n assert!(\n rename.status.success(),\n \"{}\",\n String::from_utf8_lossy(&rename.stderr)\n );\n assert!(!home.join(\".config/cxf/providers/one.toml\").exists());\n assert!(home.join(\".config/cxf/providers/two.toml\").exists());\n\n let remove = run(home, &[\"remove\", \"two\", \"-y\"])?;\n assert!(\n remove.status.success(),\n \"{}\",\n String::from_utf8_lossy(&remove.stderr)\n );\n assert!(!home.join(\".config/cxf/providers/two.toml\").exists());\n Ok(())\n}\n\n#[test]\nfn claude_current_falls_back_to_top_level_model() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".claude\"))?;\n fs::write(\n home.join(\".claude/settings.json\"),\n r#\"{\n \"model\": \"top-level-model\",\n \"env\": {\n \"CXF_CLAUDE_PROVIDER\": \"anthropic\",\n \"ANTHROPIC_BASE_URL\": \"https://api.anthropic.com\"\n }\n}\n\"#,\n )?;\n let current = run(home, &[\"claude\", \"current\"])?;\n assert!(\n current.status.success(),\n \"{}\",\n String::from_utf8_lossy(¤t.stderr)\n );\n assert!(String::from_utf8_lossy(¤t.stdout).contains(\"top-level-model\"));\n Ok(())\n}\n\n#[test]\nfn codex_status_with_drift() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n fs::write(\n home.join(\".codex/config.toml\"),\n r#\"model_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\nwire_api = \"responses\"\nsupports_websockets = false\nrequires_openai_auth = true\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = false\n\"#,\n )?;\n fs::write(\n home.join(\".codex/auth.json\"),\n r#\"{\"OPENAI_API_KEY\":\"sk-test\"}\"#,\n )?;\n\n // Apply provider first so config is fully populated by cxf\n let apply = run(home, &[\"use\", \"timi\"])?;\n assert!(\n apply.status.success(),\n \"{}\",\n String::from_utf8_lossy(&apply.stderr)\n );\n\n // Status should be clean after apply\n let status = run(home, &[\"status\"])?;\n assert!(\n status.status.success(),\n \"{}\",\n String::from_utf8_lossy(&status.stdout)\n );\n assert!(String::from_utf8_lossy(&status.stdout).contains(\"controlled: yes\"));\n\n // Introduce drift: change base_url\n let config_path = home.join(\".codex/config.toml\");\n let mut config = fs::read_to_string(&config_path)?;\n config = config.replace(\"https://timicc.com\", \"https://drifted.example\");\n fs::write(&config_path, config)?;\n\n let status2 = run(home, &[\"status\"])?;\n assert!(\n !status2.status.success(),\n \"status should exit non-zero with drift\"\n );\n let out = String::from_utf8_lossy(&status2.stdout);\n assert!(\n out.contains(\"controlled: partial\"),\n \"expected partial, got: {out}\"\n );\n Ok(())\n}\n\n#[test]\nfn codex_use_before_init_warns_when_base_toml_missing() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n // No base.toml\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = timi\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = false\n\"#,\n )?;\n fs::write(\n home.join(\".codex/auth.json\"),\n r#\"{\"OPENAI_API_KEY\":\"sk-test\"}\"#,\n )?;\n\n let out = run(home, &[\"use\", \"timi\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let stderr = String::from_utf8_lossy(&out.stderr);\n assert!(\n stderr.contains(\"warning\") && stderr.contains(\"base.toml\"),\n \"expected warning about base.toml, stderr: {stderr}\"\n );\n Ok(())\n}\n\n#[test]\nfn codex_use_rejects_malformed_auth_json() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = timi\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = false\n\"#,\n )?;\n // Broken JSON\n fs::write(home.join(\".codex/auth.json\"), r#\"{broken\"#)?;\n\n let out = run(home, &[\"use\", \"timi\"])?;\n assert!(\n !out.status.success(),\n \"should fail with malformed auth.json\"\n );\n let stderr = String::from_utf8_lossy(&out.stderr);\n assert!(\n stderr.contains(\"invalid JSON\"),\n \"expected JSON parse error, got: {stderr}\"\n );\n Ok(())\n}\n\n#[test]\nfn claude_add_and_rename_noninteractive() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n let add = run(\n home,\n &[\n \"claude\",\n \"add\",\n \"--provider-id\",\n \"my-provider\",\n \"--base-url\",\n \"https://my.example\",\n \"--api-key\",\n \"sk-my\",\n \"--model\",\n \"my-model\",\n ],\n )?;\n assert!(\n add.status.success(),\n \"{}\",\n String::from_utf8_lossy(&add.stderr)\n );\n\n let provider = fs::read_to_string(home.join(\".config/cxf/claude/providers/my-provider.toml\"))?;\n assert!(provider.contains(r#\"ANTHROPIC_BASE_URL = \"https://my.example\"\"#));\n assert!(provider.contains(r#\"ANTHROPIC_AUTH_TOKEN = \"sk-my\"\"#));\n assert!(provider.contains(r#\"ANTHROPIC_MODEL = \"my-model\"\"#));\n\n let rename = run(home, &[\"claude\", \"rename\", \"my-provider\", \"renamed\"])?;\n assert!(\n rename.status.success(),\n \"{}\",\n String::from_utf8_lossy(&rename.stderr)\n );\n assert!(\n !home\n .join(\".config/cxf/claude/providers/my-provider.toml\")\n .exists()\n );\n assert!(\n home.join(\".config/cxf/claude/providers/renamed.toml\")\n .exists()\n );\n Ok(())\n}\n\n#[test]\nfn completion_contains_provider_functions() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let out = run(tmp.path(), &[\"completion\", \"zsh\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let text = String::from_utf8_lossy(&out.stdout);\n assert!(text.contains(\"_cxf_provider_ids\"));\n assert!(text.contains(\"_cxf_claude_provider_ids\"));\n Ok(())\n}\n\n#[test]\nfn codex_use_oauth_preserves_existing_auth_json() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = timi\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/oauth.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"\"\napi_key = \"\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = true\n\"#,\n )?;\n\n let oauth_auth = r#\"{\n \"auth_mode\": \"chatgpt\",\n \"OPENAI_API_KEY\": null,\n \"tokens\": {\n \"id_token\": \"id\",\n \"access_token\": \"access\",\n \"refresh_token\": \"refresh\",\n \"account_id\": \"account\"\n },\n \"last_refresh\": \"2026-06-05T16:05:15.057134715Z\"\n}\n\"#;\n fs::write(home.join(\".codex/auth.json\"), oauth_auth)?;\n\n let out = run(home, &[\"use\", \"oauth\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let content = fs::read_to_string(home.join(\".codex/auth.json\"))?;\n assert_eq!(content, oauth_auth, \"OAuth auth.json should be untouched\");\n\n Ok(())\n}\n\n#[test]\nfn codex_use_api_key_replaces_oauth_auth_json_with_snapshot() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = official\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = false\n\"#,\n )?;\n let oauth_auth = r#\"{\n \"auth_mode\": \"chatgpt\",\n \"OPENAI_API_KEY\": null,\n \"tokens\": {\n \"id_token\": \"id\",\n \"access_token\": \"access\",\n \"refresh_token\": \"refresh\",\n \"account_id\": \"account\"\n },\n \"last_refresh\": \"2026-06-05T16:05:15.057134715Z\"\n}\n\"#;\n fs::write(home.join(\".codex/auth.json\"), oauth_auth)?;\n\n let out = run(home, &[\"use\", \"timi\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let content = fs::read_to_string(home.join(\".codex/auth.json\"))?;\n assert_eq!(\n content, \"{\\n \\\"OPENAI_API_KEY\\\": \\\"sk-test\\\"\\n}\\n\",\n \"API-key auth.json should not retain OAuth fields\"\n );\n let snapshot = home.join(\".local/state/cxf/snapshots/codex-auth-official.json\");\n assert!(snapshot.exists(), \"OAuth auth.json should be snapshotted\");\n assert_eq!(fs::read_to_string(snapshot)?, oauth_auth);\n\n Ok(())\n}\n"}, "91b73e1baf7a7324077275f5782c37cc403bf57b": {"t": 1785761194.2779481, "n": 15804, "h": "eba121308ad0b5df", "c": "use std::{error::Error, fs, path::Path, process::Command};\n\nfn bin() -> &'static str {\n env!(\"CARGO_BIN_EXE_cxf\")\n}\n\nfn run(home: &Path, args: &[&str]) -> Result<std::process::Output, Box<dyn Error>> {\n Ok(Command::new(bin())\n .env(\"HOME\", home)\n .env(\"XDG_CONFIG_HOME\", home.join(\".config\"))\n .env(\"XDG_STATE_HOME\", home.join(\".local/state\"))\n .args(args)\n .output()?)\n}\n\n#[test]\nfn codex_use_preserves_unrelated_config_and_status_is_clean_with_overrides()\n-> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n fs::write(\n home.join(\".codex/config.toml\"),\n r#\"model_provider = \"Other\"\n\n[model_providers.Other]\nbase_url = \"https://other.example\"\n\n[projects.\"/home/user/project\"]\ntrust_level = \"trusted\"\n\n[features]\nshell_tool = true\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.4\"\nmodel_reasoning_effort = \"high\"\nmodel_context_window = 272000\nmodel_auto_compact_token_limit = 240000\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = true\ncontext_window = 1000000\nauto_compact_token_limit = 900000\n\"#,\n )?;\n\n let out = run(home, &[\"use\", \"timi\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let config = fs::read_to_string(home.join(\".codex/config.toml\"))?;\n assert!(config.contains(\"# cxf: provider = timi\"));\n assert!(config.contains(\"base_url = \\\"https://timicc.com\\\"\"));\n assert!(config.contains(\"trust_level = \\\"trusted\\\"\"));\n assert!(config.contains(\"shell_tool = true\"));\n assert!(config.contains(\"model_context_window = 1000000\"));\n assert!(config.contains(\"model_auto_compact_token_limit = 900000\"));\n\n let status = run(home, &[\"status\"])?;\n assert!(\n status.status.success(),\n \"{}\",\n String::from_utf8_lossy(&status.stdout)\n );\n assert!(String::from_utf8_lossy(&status.stdout).contains(\"controlled: yes\"));\n Ok(())\n}\n\n#[test]\nfn claude_use_cleans_managed_keys_and_status_is_clean() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".claude\"))?;\n fs::create_dir_all(home.join(\".config/cxf/claude/providers\"))?;\n fs::write(\n home.join(\".claude/settings.json\"),\n r#\"{\n \"model\": \"old-model\",\n \"env\": {\n \"GITHUB_TOKEN\": \"keep\",\n \"ANTHROPIC_API_KEY\": \"old\",\n \"ANTHROPIC_MODEL\": \"old-model\"\n }\n}\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/claude/providers/deepseek.toml\"),\n r#\"[env]\nANTHROPIC_BASE_URL = \"https://api.deepseek.com/anthropic\"\nANTHROPIC_AUTH_TOKEN = \"sk-test\"\n\"#,\n )?;\n\n let out = run(home, &[\"claude\", \"use\", \"deepseek\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let settings = fs::read_to_string(home.join(\".claude/settings.json\"))?;\n assert!(settings.contains(\"\\\"GITHUB_TOKEN\\\": \\\"keep\\\"\"));\n assert!(settings.contains(\"\\\"CXF_CLAUDE_PROVIDER\\\": \\\"deepseek\\\"\"));\n assert!(!settings.contains(\"ANTHROPIC_API_KEY\"));\n assert!(!settings.contains(\"\\\"model\\\"\"));\n\n let status = run(home, &[\"claude\", \"status\"])?;\n assert!(\n status.status.success(),\n \"{}\",\n String::from_utf8_lossy(&status.stdout)\n );\n Ok(())\n}\n\n#[test]\nfn init_extracts_existing_codex_provider_and_current_reports_it() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::write(\n home.join(\".codex/config.toml\"),\n r#\"model = \"gpt-5.5\"\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nname = \"OpenAI\"\nbase_url = \"https://api.openai.example\"\nwire_api = \"responses\"\nsupports_websockets = true\nrequires_openai_auth = true\n\"#,\n )?;\n fs::write(\n home.join(\".codex/auth.json\"),\n r#\"{\"OPENAI_API_KEY\":\"sk-existing\"}\"#,\n )?;\n\n let out = run(home, &[\"init\", \"openai\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let provider = fs::read_to_string(home.join(\".config/cxf/providers/openai.toml\"))?;\n assert!(provider.contains(\"base_url = \\\"https://api.openai.example\\\"\"));\n assert!(provider.contains(\"api_key = \\\"sk-existing\\\"\"));\n\n let list = run(home, &[\"list\"])?;\n assert!(\n list.status.success(),\n \"{}\",\n String::from_utf8_lossy(&list.stderr)\n );\n assert!(String::from_utf8_lossy(&list.stdout).contains(\"openai\"));\n Ok(())\n}\n\n#[test]\nfn add_remove_and_rename_provider_noninteractive() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n let add = run(\n home,\n &[\n \"add\",\n \"--provider-id\",\n \"one\",\n \"--base-url\",\n \"https://one.example\",\n \"--api-key\",\n \"sk-one\",\n \"--wire-api\",\n \"chat\",\n \"--no-websocket\",\n ],\n )?;\n assert!(\n add.status.success(),\n \"{}\",\n String::from_utf8_lossy(&add.stderr)\n );\n let provider = fs::read_to_string(home.join(\".config/cxf/providers/one.toml\"))?;\n assert!(provider.contains(\"wire_api = \\\"chat\\\"\"));\n assert!(provider.contains(\"websocket = false\"));\n\n let rename = run(home, &[\"rename\", \"one\", \"two\"])?;\n assert!(\n rename.status.success(),\n \"{}\",\n String::from_utf8_lossy(&rename.stderr)\n );\n assert!(!home.join(\".config/cxf/providers/one.toml\").exists());\n assert!(home.join(\".config/cxf/providers/two.toml\").exists());\n\n let remove = run(home, &[\"remove\", \"two\", \"-y\"])?;\n assert!(\n remove.status.success(),\n \"{}\",\n String::from_utf8_lossy(&remove.stderr)\n );\n assert!(!home.join(\".config/cxf/providers/two.toml\").exists());\n Ok(())\n}\n\n#[test]\nfn claude_current_falls_back_to_top_level_model() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".claude\"))?;\n fs::write(\n home.join(\".claude/settings.json\"),\n r#\"{\n \"model\": \"top-level-model\",\n \"env\": {\n \"CXF_CLAUDE_PROVIDER\": \"anthropic\",\n \"ANTHROPIC_BASE_URL\": \"https://api.anthropic.com\"\n }\n}\n\"#,\n )?;\n let current = run(home, &[\"claude\", \"current\"])?;\n assert!(\n current.status.success(),\n \"{}\",\n String::from_utf8_lossy(¤t.stderr)\n );\n assert!(String::from_utf8_lossy(¤t.stdout).contains(\"top-level-model\"));\n Ok(())\n}\n\n#[test]\nfn codex_status_with_drift() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n fs::write(\n home.join(\".codex/config.toml\"),\n r#\"model_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\nwire_api = \"responses\"\nsupports_websockets = false\nrequires_openai_auth = true\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = false\n\"#,\n )?;\n fs::write(\n home.join(\".codex/auth.json\"),\n r#\"{\"OPENAI_API_KEY\":\"sk-test\"}\"#,\n )?;\n\n // Apply provider first so config is fully populated by cxf\n let apply = run(home, &[\"use\", \"timi\"])?;\n assert!(\n apply.status.success(),\n \"{}\",\n String::from_utf8_lossy(&apply.stderr)\n );\n\n // Status should be clean after apply\n let status = run(home, &[\"status\"])?;\n assert!(\n status.status.success(),\n \"{}\",\n String::from_utf8_lossy(&status.stdout)\n );\n assert!(String::from_utf8_lossy(&status.stdout).contains(\"controlled: yes\"));\n\n // Introduce drift: change base_url\n let config_path = home.join(\".codex/config.toml\");\n let mut config = fs::read_to_string(&config_path)?;\n config = config.replace(\"https://timicc.com\", \"https://drifted.example\");\n fs::write(&config_path, config)?;\n\n let status2 = run(home, &[\"status\"])?;\n assert!(\n !status2.status.success(),\n \"status should exit non-zero with drift\"\n );\n let out = String::from_utf8_lossy(&status2.stdout);\n assert!(\n out.contains(\"controlled: partial\"),\n \"expected partial, got: {out}\"\n );\n Ok(())\n}\n\n#[test]\nfn codex_use_before_init_warns_when_base_toml_missing() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n // No base.toml\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = timi\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = false\n\"#,\n )?;\n fs::write(\n home.join(\".codex/auth.json\"),\n r#\"{\"OPENAI_API_KEY\":\"sk-test\"}\"#,\n )?;\n\n let out = run(home, &[\"use\", \"timi\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let stderr = String::from_utf8_lossy(&out.stderr);\n assert!(\n stderr.contains(\"warning\") && stderr.contains(\"base.toml\"),\n \"expected warning about base.toml, stderr: {stderr}\"\n );\n Ok(())\n}\n\n#[test]\nfn codex_use_rejects_malformed_auth_json() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = timi\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = false\n\"#,\n )?;\n // Broken JSON\n fs::write(home.join(\".codex/auth.json\"), r#\"{broken\"#)?;\n\n let out = run(home, &[\"use\", \"timi\"])?;\n assert!(\n !out.status.success(),\n \"should fail with malformed auth.json\"\n );\n let stderr = String::from_utf8_lossy(&out.stderr);\n assert!(\n stderr.contains(\"invalid JSON\"),\n \"expected JSON parse error, got: {stderr}\"\n );\n Ok(())\n}\n\n#[test]\nfn claude_add_and_rename_noninteractive() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n let add = run(\n home,\n &[\n \"claude\",\n \"add\",\n \"--provider-id\",\n \"my-provider\",\n \"--base-url\",\n \"https://my.example\",\n \"--api-key\",\n \"sk-my\",\n \"--model\",\n \"my-model\",\n ],\n )?;\n assert!(\n add.status.success(),\n \"{}\",\n String::from_utf8_lossy(&add.stderr)\n );\n\n let provider = fs::read_to_string(home.join(\".config/cxf/claude/providers/my-provider.toml\"))?;\n assert!(provider.contains(r#\"ANTHROPIC_BASE_URL = \"https://my.example\"\"#));\n assert!(provider.contains(r#\"ANTHROPIC_AUTH_TOKEN = \"sk-my\"\"#));\n assert!(provider.contains(r#\"ANTHROPIC_MODEL = \"my-model\"\"#));\n\n let rename = run(home, &[\"claude\", \"rename\", \"my-provider\", \"renamed\"])?;\n assert!(\n rename.status.success(),\n \"{}\",\n String::from_utf8_lossy(&rename.stderr)\n );\n assert!(\n !home\n .join(\".config/cxf/claude/providers/my-provider.toml\")\n .exists()\n );\n assert!(\n home.join(\".config/cxf/claude/providers/renamed.toml\")\n .exists()\n );\n Ok(())\n}\n\n#[test]\nfn completion_contains_provider_functions() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let out = run(tmp.path(), &[\"completion\", \"zsh\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let text = String::from_utf8_lossy(&out.stdout);\n assert!(text.contains(\"_cxf_provider_ids\"));\n assert!(text.contains(\"_cxf_claude_provider_ids\"));\n Ok(())\n}\n\n#[test]\nfn codex_use_oauth_deletes_empty_auth_json_and_preserves_tokens() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = timi\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n // Create an OAuth provider (api_key is empty)\n fs::write(\n home.join(\".config/cxf/providers/oauth.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"\"\napi_key = \"\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = true\n\"#,\n )?;\n\n // Scenario 1: auth.json contains only OPENAI_API_KEY.\n // Switching to OAuth should delete auth.json.\n fs::write(\n home.join(\".codex/auth.json\"),\n r#\"{\"OPENAI_API_KEY\":\"sk-test\"}\"#,\n )?;\n let out1 = run(home, &[\"use\", \"oauth\"])?;\n assert!(\n out1.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out1.stderr)\n );\n assert!(\n !home.join(\".codex/auth.json\").exists(),\n \"auth.json should be deleted when empty\"\n );\n\n // Scenario 2: auth.json contains OPENAI_API_KEY and other tokens.\n // Switching to OAuth should remove OPENAI_API_KEY but preserve the other fields.\n fs::write(\n home.join(\".codex/auth.json\"),\n r#\"{\"OPENAI_API_KEY\":\"sk-test\",\"tokens\":{\"id_token\":\"abc\"}}\"#,\n )?;\n let out2 = run(home, &[\"use\", \"oauth\"])?;\n assert!(\n out2.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out2.stderr)\n );\n assert!(\n home.join(\".codex/auth.json\").exists(),\n \"auth.json should exist when preserving other keys\"\n );\n let content = fs::read_to_string(home.join(\".codex/auth.json\"))?;\n assert!(\n !content.contains(\"OPENAI_API_KEY\"),\n \"OPENAI_API_KEY should be removed\"\n );\n assert!(content.contains(\"tokens\"), \"tokens should be preserved\");\n\n // Scenario 3: auth.json does not exist.\n // Switching to OAuth should not fail and should not create auth.json.\n fs::remove_file(home.join(\".codex/auth.json\"))?;\n let out3 = run(home, &[\"use\", \"oauth\"])?;\n assert!(\n out3.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out3.stderr)\n );\n assert!(\n !home.join(\".codex/auth.json\").exists(),\n \"auth.json should not exist when starting from missing\"\n );\n\n Ok(())\n}\n"}, "97531f7209d713dbf7686d6d529170fe165ea4c9": {"t": 1785761195.6045318, "n": 13153, "h": "588b5afb701602ba", "c": "use std::{error::Error, fs, path::Path, process::Command};\n\nfn bin() -> &'static str {\n env!(\"CARGO_BIN_EXE_cxf\")\n}\n\nfn run(home: &Path, args: &[&str]) -> Result<std::process::Output, Box<dyn Error>> {\n Ok(Command::new(bin())\n .env(\"HOME\", home)\n .env(\"XDG_CONFIG_HOME\", home.join(\".config\"))\n .env(\"XDG_STATE_HOME\", home.join(\".local/state\"))\n .args(args)\n .output()?)\n}\n\n#[test]\nfn codex_use_preserves_unrelated_config_and_status_is_clean_with_overrides()\n-> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n fs::write(\n home.join(\".codex/config.toml\"),\n r#\"model_provider = \"Other\"\n\n[model_providers.Other]\nbase_url = \"https://other.example\"\n\n[projects.\"/home/user/project\"]\ntrust_level = \"trusted\"\n\n[features]\nshell_tool = true\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.4\"\nmodel_reasoning_effort = \"high\"\nmodel_context_window = 272000\nmodel_auto_compact_token_limit = 240000\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = true\ncontext_window = 1000000\nauto_compact_token_limit = 900000\n\"#,\n )?;\n\n let out = run(home, &[\"use\", \"timi\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let config = fs::read_to_string(home.join(\".codex/config.toml\"))?;\n assert!(config.contains(\"# cxf: provider = timi\"));\n assert!(config.contains(\"base_url = \\\"https://timicc.com\\\"\"));\n assert!(config.contains(\"trust_level = \\\"trusted\\\"\"));\n assert!(config.contains(\"shell_tool = true\"));\n assert!(config.contains(\"model_context_window = 1000000\"));\n assert!(config.contains(\"model_auto_compact_token_limit = 900000\"));\n\n let status = run(home, &[\"status\"])?;\n assert!(\n status.status.success(),\n \"{}\",\n String::from_utf8_lossy(&status.stdout)\n );\n assert!(String::from_utf8_lossy(&status.stdout).contains(\"controlled: yes\"));\n Ok(())\n}\n\n#[test]\nfn claude_use_cleans_managed_keys_and_status_is_clean() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".claude\"))?;\n fs::create_dir_all(home.join(\".config/cxf/claude/providers\"))?;\n fs::write(\n home.join(\".claude/settings.json\"),\n r#\"{\n \"model\": \"old-model\",\n \"env\": {\n \"GITHUB_TOKEN\": \"keep\",\n \"ANTHROPIC_API_KEY\": \"old\",\n \"ANTHROPIC_MODEL\": \"old-model\"\n }\n}\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/claude/providers/deepseek.toml\"),\n r#\"[env]\nANTHROPIC_BASE_URL = \"https://api.deepseek.com/anthropic\"\nANTHROPIC_AUTH_TOKEN = \"sk-test\"\n\"#,\n )?;\n\n let out = run(home, &[\"claude\", \"use\", \"deepseek\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let settings = fs::read_to_string(home.join(\".claude/settings.json\"))?;\n assert!(settings.contains(\"\\\"GITHUB_TOKEN\\\": \\\"keep\\\"\"));\n assert!(settings.contains(\"\\\"CXF_CLAUDE_PROVIDER\\\": \\\"deepseek\\\"\"));\n assert!(!settings.contains(\"ANTHROPIC_API_KEY\"));\n assert!(!settings.contains(\"\\\"model\\\"\"));\n\n let status = run(home, &[\"claude\", \"status\"])?;\n assert!(\n status.status.success(),\n \"{}\",\n String::from_utf8_lossy(&status.stdout)\n );\n Ok(())\n}\n\n#[test]\nfn init_extracts_existing_codex_provider_and_current_reports_it() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::write(\n home.join(\".codex/config.toml\"),\n r#\"model = \"gpt-5.5\"\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nname = \"OpenAI\"\nbase_url = \"https://api.openai.example\"\nwire_api = \"responses\"\nsupports_websockets = true\nrequires_openai_auth = true\n\"#,\n )?;\n fs::write(\n home.join(\".codex/auth.json\"),\n r#\"{\"OPENAI_API_KEY\":\"sk-existing\"}\"#,\n )?;\n\n let out = run(home, &[\"init\", \"openai\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let provider = fs::read_to_string(home.join(\".config/cxf/providers/openai.toml\"))?;\n assert!(provider.contains(\"base_url = \\\"https://api.openai.example\\\"\"));\n assert!(provider.contains(\"api_key = \\\"sk-existing\\\"\"));\n\n let list = run(home, &[\"list\"])?;\n assert!(\n list.status.success(),\n \"{}\",\n String::from_utf8_lossy(&list.stderr)\n );\n assert!(String::from_utf8_lossy(&list.stdout).contains(\"openai\"));\n Ok(())\n}\n\n#[test]\nfn add_remove_and_rename_provider_noninteractive() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n let add = run(\n home,\n &[\n \"add\",\n \"--provider-id\",\n \"one\",\n \"--base-url\",\n \"https://one.example\",\n \"--api-key\",\n \"sk-one\",\n \"--wire-api\",\n \"chat\",\n \"--no-websocket\",\n ],\n )?;\n assert!(\n add.status.success(),\n \"{}\",\n String::from_utf8_lossy(&add.stderr)\n );\n let provider = fs::read_to_string(home.join(\".config/cxf/providers/one.toml\"))?;\n assert!(provider.contains(\"wire_api = \\\"chat\\\"\"));\n assert!(provider.contains(\"websocket = false\"));\n\n let rename = run(home, &[\"rename\", \"one\", \"two\"])?;\n assert!(\n rename.status.success(),\n \"{}\",\n String::from_utf8_lossy(&rename.stderr)\n );\n assert!(!home.join(\".config/cxf/providers/one.toml\").exists());\n assert!(home.join(\".config/cxf/providers/two.toml\").exists());\n\n let remove = run(home, &[\"remove\", \"two\", \"-y\"])?;\n assert!(\n remove.status.success(),\n \"{}\",\n String::from_utf8_lossy(&remove.stderr)\n );\n assert!(!home.join(\".config/cxf/providers/two.toml\").exists());\n Ok(())\n}\n\n#[test]\nfn claude_current_falls_back_to_top_level_model() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".claude\"))?;\n fs::write(\n home.join(\".claude/settings.json\"),\n r#\"{\n \"model\": \"top-level-model\",\n \"env\": {\n \"CXF_CLAUDE_PROVIDER\": \"anthropic\",\n \"ANTHROPIC_BASE_URL\": \"https://api.anthropic.com\"\n }\n}\n\"#,\n )?;\n let current = run(home, &[\"claude\", \"current\"])?;\n assert!(\n current.status.success(),\n \"{}\",\n String::from_utf8_lossy(¤t.stderr)\n );\n assert!(String::from_utf8_lossy(¤t.stdout).contains(\"top-level-model\"));\n Ok(())\n}\n\n#[test]\nfn codex_status_with_drift() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n fs::write(\n home.join(\".codex/config.toml\"),\n r#\"model_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\nwire_api = \"responses\"\nsupports_websockets = false\nrequires_openai_auth = true\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = false\n\"#,\n )?;\n fs::write(\n home.join(\".codex/auth.json\"),\n r#\"{\"OPENAI_API_KEY\":\"sk-test\"}\"#,\n )?;\n\n // Apply provider first so config is fully populated by cxf\n let apply = run(home, &[\"use\", \"timi\"])?;\n assert!(\n apply.status.success(),\n \"{}\",\n String::from_utf8_lossy(&apply.stderr)\n );\n\n // Status should be clean after apply\n let status = run(home, &[\"status\"])?;\n assert!(\n status.status.success(),\n \"{}\",\n String::from_utf8_lossy(&status.stdout)\n );\n assert!(String::from_utf8_lossy(&status.stdout).contains(\"controlled: yes\"));\n\n // Introduce drift: change base_url\n let config_path = home.join(\".codex/config.toml\");\n let mut config = fs::read_to_string(&config_path)?;\n config = config.replace(\"https://timicc.com\", \"https://drifted.example\");\n fs::write(&config_path, config)?;\n\n let status2 = run(home, &[\"status\"])?;\n assert!(\n !status2.status.success(),\n \"status should exit non-zero with drift\"\n );\n let out = String::from_utf8_lossy(&status2.stdout);\n assert!(\n out.contains(\"controlled: partial\"),\n \"expected partial, got: {out}\"\n );\n Ok(())\n}\n\n#[test]\nfn codex_use_before_init_warns_when_base_toml_missing() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n // No base.toml\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = timi\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = false\n\"#,\n )?;\n fs::write(\n home.join(\".codex/auth.json\"),\n r#\"{\"OPENAI_API_KEY\":\"sk-test\"}\"#,\n )?;\n\n let out = run(home, &[\"use\", \"timi\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let stderr = String::from_utf8_lossy(&out.stderr);\n assert!(\n stderr.contains(\"warning\") && stderr.contains(\"base.toml\"),\n \"expected warning about base.toml, stderr: {stderr}\"\n );\n Ok(())\n}\n\n#[test]\nfn codex_use_rejects_malformed_auth_json() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n fs::write(\n home.join(\".codex/config.toml\"),\n r##\"# cxf: provider = timi\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nbase_url = \"https://timicc.com\"\n\"##,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.5\"\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = false\n\"#,\n )?;\n // Broken JSON\n fs::write(home.join(\".codex/auth.json\"), r#\"{broken\"#)?;\n\n let out = run(home, &[\"use\", \"timi\"])?;\n assert!(\n !out.status.success(),\n \"should fail with malformed auth.json\"\n );\n let stderr = String::from_utf8_lossy(&out.stderr);\n assert!(\n stderr.contains(\"invalid JSON\"),\n \"expected JSON parse error, got: {stderr}\"\n );\n Ok(())\n}\n\n#[test]\nfn claude_add_and_rename_noninteractive() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n let add = run(\n home,\n &[\n \"claude\",\n \"add\",\n \"--provider-id\",\n \"my-provider\",\n \"--base-url\",\n \"https://my.example\",\n \"--api-key\",\n \"sk-my\",\n \"--model\",\n \"my-model\",\n ],\n )?;\n assert!(\n add.status.success(),\n \"{}\",\n String::from_utf8_lossy(&add.stderr)\n );\n\n let provider = fs::read_to_string(home.join(\".config/cxf/claude/providers/my-provider.toml\"))?;\n assert!(provider.contains(r#\"ANTHROPIC_BASE_URL = \"https://my.example\"\"#));\n assert!(provider.contains(r#\"ANTHROPIC_AUTH_TOKEN = \"sk-my\"\"#));\n assert!(provider.contains(r#\"ANTHROPIC_MODEL = \"my-model\"\"#));\n\n let rename = run(home, &[\"claude\", \"rename\", \"my-provider\", \"renamed\"])?;\n assert!(\n rename.status.success(),\n \"{}\",\n String::from_utf8_lossy(&rename.stderr)\n );\n assert!(\n !home\n .join(\".config/cxf/claude/providers/my-provider.toml\")\n .exists()\n );\n assert!(\n home.join(\".config/cxf/claude/providers/renamed.toml\")\n .exists()\n );\n Ok(())\n}\n\n#[test]\nfn completion_contains_provider_functions() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let out = run(tmp.path(), &[\"completion\", \"zsh\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let text = String::from_utf8_lossy(&out.stdout);\n assert!(text.contains(\"_cxf_provider_ids\"));\n assert!(text.contains(\"_cxf_claude_provider_ids\"));\n Ok(())\n}\n"}, "7c496082b306763ad9b6ac528c044081c037533b": {"t": 1785761197.0267668, "n": 7385, "h": "621a3f930a68903d", "c": "use std::{error::Error, fs, path::Path, process::Command};\n\nfn bin() -> &'static str {\n env!(\"CARGO_BIN_EXE_cxf\")\n}\n\nfn run(home: &Path, args: &[&str]) -> Result<std::process::Output, Box<dyn Error>> {\n Ok(Command::new(bin())\n .env(\"HOME\", home)\n .env(\"XDG_CONFIG_HOME\", home.join(\".config\"))\n .env(\"XDG_STATE_HOME\", home.join(\".local/state\"))\n .args(args)\n .output()?)\n}\n\n#[test]\nfn codex_use_preserves_unrelated_config_and_status_is_clean_with_overrides()\n-> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::create_dir_all(home.join(\".config/cxf/providers\"))?;\n fs::write(\n home.join(\".codex/config.toml\"),\n r#\"model_provider = \"Other\"\n\n[model_providers.Other]\nbase_url = \"https://other.example\"\n\n[projects.\"/home/user/project\"]\ntrust_level = \"trusted\"\n\n[features]\nshell_tool = true\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/base.toml\"),\n r#\"model = \"gpt-5.5\"\nreview_model = \"gpt-5.4\"\nmodel_reasoning_effort = \"high\"\nmodel_context_window = 272000\nmodel_auto_compact_token_limit = 240000\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/providers/timi.toml\"),\n r#\"model_providers = \"OpenAI\"\nbase_url = \"https://timicc.com\"\napi_key = \"sk-test\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nwebsocket = true\ncontext_window = 1000000\nauto_compact_token_limit = 900000\n\"#,\n )?;\n\n let out = run(home, &[\"use\", \"timi\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let config = fs::read_to_string(home.join(\".codex/config.toml\"))?;\n assert!(config.contains(\"# cxf: provider = timi\"));\n assert!(config.contains(\"base_url = \\\"https://timicc.com\\\"\"));\n assert!(config.contains(\"trust_level = \\\"trusted\\\"\"));\n assert!(config.contains(\"shell_tool = true\"));\n assert!(config.contains(\"model_context_window = 1000000\"));\n assert!(config.contains(\"model_auto_compact_token_limit = 900000\"));\n\n let status = run(home, &[\"status\"])?;\n assert!(\n status.status.success(),\n \"{}\",\n String::from_utf8_lossy(&status.stdout)\n );\n assert!(String::from_utf8_lossy(&status.stdout).contains(\"controlled: yes\"));\n Ok(())\n}\n\n#[test]\nfn claude_use_cleans_managed_keys_and_status_is_clean() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".claude\"))?;\n fs::create_dir_all(home.join(\".config/cxf/claude/providers\"))?;\n fs::write(\n home.join(\".claude/settings.json\"),\n r#\"{\n \"model\": \"old-model\",\n \"env\": {\n \"GITHUB_TOKEN\": \"keep\",\n \"ANTHROPIC_API_KEY\": \"old\",\n \"ANTHROPIC_MODEL\": \"old-model\"\n }\n}\n\"#,\n )?;\n fs::write(\n home.join(\".config/cxf/claude/providers/deepseek.toml\"),\n r#\"[env]\nANTHROPIC_BASE_URL = \"https://api.deepseek.com/anthropic\"\nANTHROPIC_AUTH_TOKEN = \"sk-test\"\n\"#,\n )?;\n\n let out = run(home, &[\"claude\", \"use\", \"deepseek\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let settings = fs::read_to_string(home.join(\".claude/settings.json\"))?;\n assert!(settings.contains(\"\\\"GITHUB_TOKEN\\\": \\\"keep\\\"\"));\n assert!(settings.contains(\"\\\"CXF_CLAUDE_PROVIDER\\\": \\\"deepseek\\\"\"));\n assert!(!settings.contains(\"ANTHROPIC_API_KEY\"));\n assert!(!settings.contains(\"\\\"model\\\"\"));\n\n let status = run(home, &[\"claude\", \"status\"])?;\n assert!(\n status.status.success(),\n \"{}\",\n String::from_utf8_lossy(&status.stdout)\n );\n Ok(())\n}\n\n#[test]\nfn init_extracts_existing_codex_provider_and_current_reports_it() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".codex\"))?;\n fs::write(\n home.join(\".codex/config.toml\"),\n r#\"model = \"gpt-5.5\"\nmodel_provider = \"OpenAI\"\n\n[model_providers.OpenAI]\nname = \"OpenAI\"\nbase_url = \"https://api.openai.example\"\nwire_api = \"responses\"\nsupports_websockets = true\nrequires_openai_auth = true\n\"#,\n )?;\n fs::write(\n home.join(\".codex/auth.json\"),\n r#\"{\"OPENAI_API_KEY\":\"sk-existing\"}\"#,\n )?;\n\n let out = run(home, &[\"init\", \"openai\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let provider = fs::read_to_string(home.join(\".config/cxf/providers/openai.toml\"))?;\n assert!(provider.contains(\"base_url = \\\"https://api.openai.example\\\"\"));\n assert!(provider.contains(\"api_key = \\\"sk-existing\\\"\"));\n\n let list = run(home, &[\"list\"])?;\n assert!(\n list.status.success(),\n \"{}\",\n String::from_utf8_lossy(&list.stderr)\n );\n assert!(String::from_utf8_lossy(&list.stdout).contains(\"openai\"));\n Ok(())\n}\n\n#[test]\nfn add_remove_and_rename_provider_noninteractive() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n let add = run(\n home,\n &[\n \"add\",\n \"--provider-id\",\n \"one\",\n \"--base-url\",\n \"https://one.example\",\n \"--api-key\",\n \"sk-one\",\n \"--wire-api\",\n \"chat\",\n \"--no-websocket\",\n ],\n )?;\n assert!(\n add.status.success(),\n \"{}\",\n String::from_utf8_lossy(&add.stderr)\n );\n let provider = fs::read_to_string(home.join(\".config/cxf/providers/one.toml\"))?;\n assert!(provider.contains(\"wire_api = \\\"chat\\\"\"));\n assert!(provider.contains(\"websocket = false\"));\n\n let rename = run(home, &[\"rename\", \"one\", \"two\"])?;\n assert!(\n rename.status.success(),\n \"{}\",\n String::from_utf8_lossy(&rename.stderr)\n );\n assert!(!home.join(\".config/cxf/providers/one.toml\").exists());\n assert!(home.join(\".config/cxf/providers/two.toml\").exists());\n\n let remove = run(home, &[\"remove\", \"two\", \"-y\"])?;\n assert!(\n remove.status.success(),\n \"{}\",\n String::from_utf8_lossy(&remove.stderr)\n );\n assert!(!home.join(\".config/cxf/providers/two.toml\").exists());\n Ok(())\n}\n\n#[test]\nfn claude_current_falls_back_to_top_level_model() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let home = tmp.path();\n fs::create_dir_all(home.join(\".claude\"))?;\n fs::write(\n home.join(\".claude/settings.json\"),\n r#\"{\n \"model\": \"top-level-model\",\n \"env\": {\n \"CXF_CLAUDE_PROVIDER\": \"anthropic\",\n \"ANTHROPIC_BASE_URL\": \"https://api.anthropic.com\"\n }\n}\n\"#,\n )?;\n let current = run(home, &[\"claude\", \"current\"])?;\n assert!(\n current.status.success(),\n \"{}\",\n String::from_utf8_lossy(¤t.stderr)\n );\n assert!(String::from_utf8_lossy(¤t.stdout).contains(\"top-level-model\"));\n Ok(())\n}\n\n#[test]\nfn completion_contains_provider_functions() -> Result<(), Box<dyn Error>> {\n let tmp = tempfile::tempdir()?;\n let out = run(tmp.path(), &[\"completion\", \"zsh\"])?;\n assert!(\n out.status.success(),\n \"{}\",\n String::from_utf8_lossy(&out.stderr)\n );\n let text = String::from_utf8_lossy(&out.stdout);\n assert!(text.contains(\"_cxf_provider_ids\"));\n assert!(text.contains(\"_cxf_claude_provider_ids\"));\n Ok(())\n}\n"}}}} |