coderouter-cli 2.12.0__tar.gz → 2.14.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/CHANGELOG.md +215 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/PKG-INFO +1 -1
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/adapters/anthropic_native.py +4 -2
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/adapters/openai_compat.py +5 -2
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/cli.py +159 -22
- coderouter_cli-2.14.0/coderouter/config/loader.py +287 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/config/schemas.py +237 -47
- coderouter_cli-2.14.0/coderouter/credentials.py +332 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/doctor_apply.py +54 -9
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/guards/self_healing.py +58 -3
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/ingress/app.py +72 -29
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/ingress/dashboard_routes.py +24 -4
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/ingress/launcher_routes.py +237 -61
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/ingress/metrics_routes.py +13 -2
- coderouter_cli-2.14.0/coderouter/ingress/ui_auth.py +94 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/launcher_devices.py +11 -4
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/logging.py +7 -0
- coderouter_cli-2.14.0/coderouter/rollback.py +217 -0
- coderouter_cli-2.14.0/coderouter/secret_redaction.py +606 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/state/audit_log.py +6 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/state/request_log.py +6 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/vscode_init.py +156 -20
- coderouter_cli-2.14.0/examples/providers.cli-session.yaml +121 -0
- coderouter_cli-2.14.0/examples/providers.llamacpp-vllm.yaml +544 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/pyproject.toml +1 -1
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/conftest.py +1 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_cli.py +68 -0
- coderouter_cli-2.14.0/tests/test_config_cwd_warning.py +477 -0
- coderouter_cli-2.14.0/tests/test_credentials.py +348 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_doctor_apply.py +65 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_fix_h8_launcher_auth.py +5 -4
- coderouter_cli-2.14.0/tests/test_fix_v213_launcher_hardening.py +604 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_launcher_devices.py +14 -0
- coderouter_cli-2.14.0/tests/test_launcher_gui_sweep_close.py +444 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_launcher_variant_sweep.py +8 -2
- coderouter_cli-2.14.0/tests/test_rollback.py +195 -0
- coderouter_cli-2.14.0/tests/test_secret_redaction.py +340 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_self_healing.py +157 -2
- coderouter_cli-2.14.0/tests/test_ui_auth.py +142 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_vscode_init.py +145 -3
- coderouter_cli-2.12.0/coderouter/config/loader.py +0 -165
- coderouter_cli-2.12.0/examples/providers.llamacpp-vllm.yaml +0 -332
- coderouter_cli-2.12.0/tests/test_config_cwd_warning.py +0 -268
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/.gitignore +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/LICENSE +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/README.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/README.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/__init__.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/__main__.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/adapters/__init__.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/adapters/base.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/adapters/registry.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/cli_stats.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/config/__init__.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/config/capability_registry.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/config/env_file.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/cost.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/data/__init__.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/data/model-capabilities.yaml +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/doctor.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/env_security.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/errors.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/gguf_introspect.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/guards/__init__.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/guards/_fingerprint.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/guards/backend_health.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/guards/context_budget.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/guards/continuous_probe.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/guards/drift_actions.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/guards/drift_detection.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/guards/memory_budget.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/guards/memory_pressure.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/guards/tool_loop.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/hardware.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/ingress/__init__.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/ingress/anthropic_routes.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/ingress/openai_routes.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/language_tax.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/launcher_speculative.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/launcher_swap.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/metrics/__init__.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/metrics/collector.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/metrics/prometheus.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/output_filters.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/plugins/__init__.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/plugins/base.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/plugins/loader.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/plugins/registry.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/py.typed +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/routing/__init__.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/routing/adaptive.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/routing/auto_router.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/routing/budget.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/routing/capability.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/routing/fallback.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/state/__init__.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/state/replay.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/state/store.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/state/suggest_rules.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/token_estimation.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/token_estimation_accurate.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/translation/__init__.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/translation/anthropic.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/translation/convert.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/coderouter/translation/tool_repair.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/README.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/README.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/assets/dashboard-demo.png +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/backends/claude-code-llamacpp-vllm.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/backends/claude-code-llamacpp-vllm.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/backends/external-agents.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/backends/external-agents.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/backends/gguf_dl.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/backends/gguf_dl.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/backends/hf-ollama-models.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/backends/hf-ollama-models.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/backends/install-backends.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/backends/install-backends.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/backends/launcher-quickstart.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/backends/launcher-quickstart.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/backends/launcher.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/backends/launcher.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/backends/llamacpp-direct.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/backends/llamacpp-direct.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/backends/lmstudio-direct.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/backends/lmstudio-direct.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/backends/verify-ollama-0.23.1.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/concepts/architecture.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/concepts/architecture.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/concepts/context-budget.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/concepts/context-budget.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/concepts/continuous-probing.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/concepts/continuous-probing.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/concepts/drift-detection.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/concepts/drift-detection.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/concepts/partial-stitch.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/concepts/partial-stitch.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/designs/agent-cli-plugin-extraction.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/designs/external-agents-adapter.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/designs/launcher-model-swap.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/designs/launcher-multi-build.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/designs/orchestration-companion.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/designs/v1.5-dashboard-mockup.html +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/designs/v1.6-auto-router-verification.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/designs/v1.6-auto-router.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/guides/free-tier-guide.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/guides/free-tier-guide.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/guides/language-tax.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/guides/language-tax.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/guides/remote-access.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/guides/remote-access.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/guides/security.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/guides/security.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/guides/subagent-routing.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/guides/subagent-routing.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/guides/troubleshooting.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/guides/troubleshooting.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/guides/usage-guide.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/guides/usage-guide.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/guides/vscode.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/low-memory-integration.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/low-memory-integration.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/openrouter-roster/CHANGES.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/openrouter-roster/README.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/openrouter-roster/latest.json +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/retrospectives/v0.4.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/retrospectives/v0.5-verify.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/retrospectives/v0.5.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/retrospectives/v0.6.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/retrospectives/v0.7.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/retrospectives/v1.0-verify.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/retrospectives/v1.0.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/start/quickstart.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/start/quickstart.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/start/when-do-i-need-coderouter.en.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/docs/start/when-do-i-need-coderouter.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/examples/.env.example +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/examples/README.md +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/examples/providers-agent-cli.yaml +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/examples/providers-multiagent.yaml +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/examples/providers.context-budget-test.yaml +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/examples/providers.nvidia-nim.yaml +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/examples/providers.ollama-auto-custom.yaml +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/examples/providers.ollama-auto.yaml +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/examples/providers.ollama-free-chain.yaml +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/examples/providers.raspberrypi.yaml +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/examples/providers.swap.yaml +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/examples/providers.yaml +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/scripts/demo_traffic.sh +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/scripts/openrouter_roster_diff.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/scripts/smoke_v2_2.sh +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/scripts/verify-providers.yaml +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/scripts/verify_ollama_0_23.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/scripts/verify_v0_5.sh +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/scripts/verify_v1_0.sh +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/__init__.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_adapter_anthropic.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_agent_cli.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_audit_log.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_auto_router.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_auto_router_cjk.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_backend_health.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_budget.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_capability.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_capability_degraded_payload.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_capability_registry.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_capability_registry_cache_control.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_capability_tool_choice.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_claude_code_suitability.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_cli_bind_warning.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_cli_stats.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_config.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_config_shim_actions.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_context_budget.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_continuous_probe.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_dashboard_endpoint.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_doctor.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_doctor_cache_probe.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_drift_actions.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_drift_detection.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_drift_detection_integration.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_env_file.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_env_security.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_errors.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_examples_yaml.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_examples_yaml_roundtrip.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_fallback.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_fallback_anthropic.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_fallback_cache_control.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_fallback_cache_observed.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_fallback_cache_strip.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_fallback_empty_response.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_fallback_misconfig_warn.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_fallback_paid_gate.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_fallback_thinking.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_fallback_tool_choice_emulate.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_fix_h1_prometheus_drift.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_fix_h2_h6_translation.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_fix_h3_client_reuse.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_fix_h4_h5_guards.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_fix_h7_probe_restore.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_fix_mA_routing.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_fix_mB_adapters.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_fix_mC_convert.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_fix_mD_logging_metrics.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_fix_mE_config_validation.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_fix_mF_ingress.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_gguf_introspect.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_guards_tool_loop.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_hardware.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_import_hygiene.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_ingress_anthropic.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_ingress_count_tokens.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_ingress_profile.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_language_tax.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_language_tax_integration.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_launcher_backend_variants.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_launcher_config_bench.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_launcher_devices_detect.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_launcher_devices_routes.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_launcher_gui_devices.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_launcher_gui_events.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_launcher_gui_readiness.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_launcher_mtp.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_launcher_mtp_fallback.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_launcher_provider_sync.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_launcher_readiness_restart.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_launcher_swap.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_launcher_swap_review.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_launcher_variant_config.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_launcher_variant_gui.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_launcher_variant_routes.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_memory_budget.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_memory_pressure.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_metrics_cache.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_metrics_collector.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_metrics_cost.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_metrics_endpoint.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_metrics_jsonl.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_metrics_prometheus.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_metrics_prometheus_cache.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_models_passthrough.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_openai_compat.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_openrouter_roster_diff.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_output_filters.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_output_filters_adapters.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_packaging.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_partial_stitch.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_plugin_adapter.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_plugins_integration.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_plugins_loader.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_plugins_registry.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_reasoning_strip.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_repair_byte_fallback.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_request_log.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_role_normalization.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_routing_adaptive.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_setup_sh.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_state_store.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_stop_reason_forward_compat.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_token_estimation.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_token_estimation_accurate.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_tokens_saved_metric.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_tool_repair.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_tool_repair_differential.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_tool_repair_lenient.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_tool_repair_r4.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_tool_repair_scanners.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_toolrepair_bench.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_translation_anthropic.py +0 -0
- {coderouter_cli-2.12.0 → coderouter_cli-2.14.0}/tests/test_translation_reverse.py +0 -0
|
@@ -9,6 +9,221 @@ are kept verbatim where the Japanese text itself is the subject).
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## [v2.14.0] — 2026-08-09 (credential hygiene + undo)
|
|
13
|
+
|
|
14
|
+
Five changes that came out of reading `duolahypercho/codex-router` and
|
|
15
|
+
comparing it with what CodeRouter actually does. Four of them close holes
|
|
16
|
+
this project had; one imports a capability it was missing. Everything is
|
|
17
|
+
opt-in except the `.envrc` behaviour change called out under **Changed
|
|
18
|
+
(BREAKING)**.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **`credential.source: cli_session` — borrow the token a vendor CLI
|
|
23
|
+
already wrote.** A subscription-authenticated provider (Kimi Code CLI,
|
|
24
|
+
Grok CLI, …) can now be an ordinary `openai_compat` / `anthropic`
|
|
25
|
+
entry: CodeRouter reads the OAuth token out of the JSON file the
|
|
26
|
+
vendor's CLI maintains and makes the HTTP call itself. Until now the
|
|
27
|
+
only answer to those was `kind: agent_cli`, which spawns the CLI once
|
|
28
|
+
per request and therefore has no streaming, no tool-call repair, no
|
|
29
|
+
context-budget guard, and does not sit in a fallback chain — the
|
|
30
|
+
subscription ended up an island. It is now a chain link:
|
|
31
|
+
`providers: [kimi-sub, free-cloud, local-llama]` just works.
|
|
32
|
+
Refresh delegates to the vendor's own CLI (`refresh.command`, an argv
|
|
33
|
+
list run with `shell=False`) rather than reimplementing OAuth, because
|
|
34
|
+
endpoints, client ids and rotation policies all change and a bespoke
|
|
35
|
+
implementation rots. Refresh is single-flighted in-process and
|
|
36
|
+
serialised across processes with an advisory `flock`. `credential` and
|
|
37
|
+
`api_key_env` are mutually exclusive at load time, `credential.path`
|
|
38
|
+
must live under `$HOME`, and a missing or malformed session file
|
|
39
|
+
resolves to `None` (unauthenticated request → upstream 401 → the chain
|
|
40
|
+
moves on) instead of raising. See `examples/providers.cli-session.yaml`.
|
|
41
|
+
- **`coderouter rollback`** — restores `providers.yaml`,
|
|
42
|
+
`~/.coderouter/model-capabilities.yaml` and (with `--workspace`)
|
|
43
|
+
`.vscode/settings.json` / `.envrc` from their `.bak` siblings.
|
|
44
|
+
`doctor --apply` and `vscode-init` have always written a backup and
|
|
45
|
+
never offered a way back. Restore is a **swap**: the current contents
|
|
46
|
+
become the new `.bak`, so a second run returns you to where you
|
|
47
|
+
started. Exit 0 restored / 2 nothing to restore / 1 a restore failed.
|
|
48
|
+
- **`coderouter doctor --check-secrets`** — proves the log-redaction
|
|
49
|
+
filter scrubs by pushing a canary through it, reports which declared
|
|
50
|
+
`api_key_env` vars are actually set, flags credentials pasted into a
|
|
51
|
+
`base_url`, and scans the already-written `requests.jsonl` /
|
|
52
|
+
`audit.jsonl` (plus rotated `.1`) for live key values. Same 0/2/1 exit
|
|
53
|
+
contract as `--check-env`. The log scan is the part that produces
|
|
54
|
+
evidence rather than reassurance.
|
|
55
|
+
- **`CODEROUTER_METRICS_TOKEN`** — opt-in token auth for `/dashboard`,
|
|
56
|
+
`/metrics.json` and `/metrics`, mirroring `CODEROUTER_LAUNCHER_TOKEN`.
|
|
57
|
+
|
|
58
|
+
### Changed (BREAKING)
|
|
59
|
+
|
|
60
|
+
- **`vscode-init --with-envrc --force` no longer replaces `.envrc`.** The
|
|
61
|
+
block CodeRouter owns is now fenced between
|
|
62
|
+
`# BEGIN coderouter-managed` / `# END coderouter-managed`, and only
|
|
63
|
+
that block is rewritten. Consequences:
|
|
64
|
+
- A re-run against a fenced file needs **no `--force`** — rewriting our
|
|
65
|
+
own fence is not destructive, so a port change is an ordinary update
|
|
66
|
+
instead of a conflict to override.
|
|
67
|
+
- `--force` now means "adopt a file I did not write": the block is
|
|
68
|
+
appended and every existing line survives. Previously it deleted
|
|
69
|
+
them, which is how the H-11 `.envrc` incident happened — including
|
|
70
|
+
the `source_env_if_exists .envrc.local` line the docs tell users to
|
|
71
|
+
add.
|
|
72
|
+
- A managed variable exported *outside* the fence is refused, because
|
|
73
|
+
direnv applies exports in order and a silent duplicate leaves nobody
|
|
74
|
+
able to say which value is live. Commented-out lines are not
|
|
75
|
+
conflicts.
|
|
76
|
+
- A file byte-identical to a pre-v2.14.0 generation is adopted into the
|
|
77
|
+
fence silently, so upgrading does not demand `--force` for our own
|
|
78
|
+
previous output.
|
|
79
|
+
`settings.json` handling is unchanged (it already refused to overwrite
|
|
80
|
+
a user-owned value without `--force`).
|
|
81
|
+
- **A failed `doctor --apply` is now a no-op instead of a partial one.**
|
|
82
|
+
The write loop had no error handling: an apply spanning `providers.yaml`
|
|
83
|
+
and `model-capabilities.yaml` left the first file rewritten when the
|
|
84
|
+
second failed. It now restores what it already wrote (and deletes what
|
|
85
|
+
it created) before raising.
|
|
86
|
+
|
|
87
|
+
### Fixed (security)
|
|
88
|
+
|
|
89
|
+
- **Credentials are scrubbed from every log sink.** CodeRouter had no
|
|
90
|
+
notion of "this string is a secret": keys were read by
|
|
91
|
+
`resolve_api_key`, dropped into `x-api-key` / `Authorization`, and
|
|
92
|
+
nothing masked them because nothing knew which strings needed masking.
|
|
93
|
+
No call site logged a header dict, so this was not a live leak — the
|
|
94
|
+
problem was that the safety of every future log line depended on
|
|
95
|
+
whoever wrote it remembering what was in their hand. There is now a
|
|
96
|
+
process-global registry keyed on the exact credential value, armed by
|
|
97
|
+
`resolve_api_key` (the choke point every key passes through, plugins
|
|
98
|
+
included) and pre-armed by `load_config`, plus a
|
|
99
|
+
`SecretRedactingFilter` on the stderr handler, `RequestLogHandler` and
|
|
100
|
+
`AuditLogHandler`. It scrubs the message, printf args, `exc_text` and
|
|
101
|
+
every `extra={...}` field, recursing into nested dicts and lists.
|
|
102
|
+
Exact-match registry first (zero false positives); a small set of
|
|
103
|
+
anchored backstop patterns (`sk-*`, `gh*_`, `AIza*`, `Bearer`,
|
|
104
|
+
`?api_key=`, URL userinfo) covers credentials that never passed through
|
|
105
|
+
the resolver. Values under 8 characters are refused — redacting those
|
|
106
|
+
would corrupt ordinary log text.
|
|
107
|
+
- **`/dashboard`, `/metrics.json` and `/metrics` were unauthenticated
|
|
108
|
+
with no way to close them.** They change nothing, so they were never
|
|
109
|
+
part of the H-8 work, but `/metrics.json` returns every provider's
|
|
110
|
+
name, kind, paid flag and `base_url` plus the profile graph — the full
|
|
111
|
+
topology of which models an operator runs and which vendors they pay.
|
|
112
|
+
With `CODEROUTER_METRICS_TOKEN` unset they stay open exactly as before
|
|
113
|
+
(no running scrape breaks on upgrade) and log a one-time
|
|
114
|
+
`metrics-auth-disabled` warning. The check runs **before** the payload
|
|
115
|
+
is assembled, so a 401 leaks nothing, and the page receives only a
|
|
116
|
+
boolean — never the token. `base_url` now also goes through the
|
|
117
|
+
scrubber on its way into the JSON, since it is the one config field an
|
|
118
|
+
operator can paste a credential into and this endpoint hands it to a
|
|
119
|
+
browser.
|
|
120
|
+
|
|
121
|
+
### Tests
|
|
122
|
+
|
|
123
|
+
- 2572 passed / 1 skipped (was 2464 on `main`), ruff clean, and 51 mypy
|
|
124
|
+
errors against 53 on `main` (net −2; strict mypy is not CI-enforced).
|
|
125
|
+
- New: `tests/test_secret_redaction.py` (36),
|
|
126
|
+
`tests/test_credentials.py` (25), `tests/test_rollback.py` (17),
|
|
127
|
+
`tests/test_ui_auth.py` (17), plus 9 in `tests/test_vscode_init.py` and
|
|
128
|
+
1 in `tests/test_doctor_apply.py`.
|
|
129
|
+
- `test_existing_envrc_force_overwrites` asserted the old destructive
|
|
130
|
+
`--force` contract and is rewritten as
|
|
131
|
+
`test_existing_envrc_force_adopts_without_destroying`, pinning the
|
|
132
|
+
opposite.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## [v2.13.0] — 2026-08-07 (remaining-high security hardening)
|
|
137
|
+
|
|
138
|
+
### Changed (BREAKING)
|
|
139
|
+
|
|
140
|
+
- **Implicit `./providers.yaml` discovery is now opt-in, gated behind
|
|
141
|
+
`CODEROUTER_ALLOW_CWD_CONFIG`.** Previously, starting CodeRouter (or the
|
|
142
|
+
GUI launcher) from a directory containing a `providers.yaml` loaded that
|
|
143
|
+
file automatically. That was a code-execution vector: a hostile config
|
|
144
|
+
dropped into a repo could steer `restart_command`,
|
|
145
|
+
`launcher.backends[*].binary`, and `launcher.bench.command_template` —
|
|
146
|
+
all of which name executables — simply because of the working directory.
|
|
147
|
+
The current-working-directory step now runs only when
|
|
148
|
+
`CODEROUTER_ALLOW_CWD_CONFIG` is truthy (`1`/`true`/`yes`/`on`). When the
|
|
149
|
+
opt-in is off but a `./providers.yaml` exists and was not explicitly
|
|
150
|
+
named, a one-time `cwd-config-skipped` warning explains why it was
|
|
151
|
+
ignored and how to enable it (`--config`, `CODEROUTER_CONFIG`, or the
|
|
152
|
+
opt-in). To keep the old behaviour, set `CODEROUTER_ALLOW_CWD_CONFIG=1`
|
|
153
|
+
in directories you trust. (The v2.12.0 heads-up mis-stated the landing
|
|
154
|
+
release as "v2.12.0"; it actually lands here in v2.13.0.)
|
|
155
|
+
- **`restart_command` is now run as `subprocess.run(shlex.split(command),
|
|
156
|
+
shell=False)` — argv dispatch, no shell.** Self-healing previously ran
|
|
157
|
+
it through `shell=True`, so a `providers.yaml` from an untrusted source
|
|
158
|
+
meant arbitrary shell execution (directly, and unconditionally at
|
|
159
|
+
startup when excluded providers were restored from `state_dir`).
|
|
160
|
+
Pipelines, redirects, `~` expansion, and `FOO=bar cmd` env prefixes are
|
|
161
|
+
no longer supported. A value containing shell metacharacters (`&&`,
|
|
162
|
+
`||`, `|`, `;`, `>`, `<`, `$(...)`, `` ` ``) is now **refused (not run)**
|
|
163
|
+
rather than half-executed — under `shell=False`, `touch a; b` would
|
|
164
|
+
otherwise create a file literally named `a;` and report a "successful"
|
|
165
|
+
restart. Wrap shell-dependent commands in a script, or write them as
|
|
166
|
+
`/bin/sh -c '...'` explicitly.
|
|
167
|
+
- **`doctor --apply` now writes back to exactly the file the loader read.**
|
|
168
|
+
The apply path's config resolution was a hand-maintained copy of the
|
|
169
|
+
loader's search order; it now delegates to the loader's single
|
|
170
|
+
`resolve_config_path`, so the v2.13.0 CWD change can never make `--apply`
|
|
171
|
+
rewrite a file that `load_config` did not actually load.
|
|
172
|
+
- **The GUI launcher (`launcher_gui.py`) applies the same
|
|
173
|
+
`CODEROUTER_ALLOW_CWD_CONFIG` gate** to its own `providers.yaml`
|
|
174
|
+
discovery.
|
|
175
|
+
- **The launcher sweep API no longer accepts a `bench_command` (or
|
|
176
|
+
`results_dir`) from the request body.** `POST /api/launcher/sweep/start`
|
|
177
|
+
previously took an arbitrary command string, `shlex.split` it, and ran
|
|
178
|
+
it — so anyone who could reach the (loopback, token-optional) launcher
|
|
179
|
+
port could run any program under the CodeRouter process. The request now
|
|
180
|
+
carries only a `bench_preset` key that must name an entry under
|
|
181
|
+
`launcher.bench.presets` in `providers.yaml`; the executable template
|
|
182
|
+
lives solely in the operator-owned config, matching the trust boundary
|
|
183
|
+
already used for `launcher.backends[*].binary`. `bench_command` /
|
|
184
|
+
`results_dir` in a request are rejected with `400`. To keep a custom
|
|
185
|
+
sweep command, declare it under `launcher.bench.presets`; a single
|
|
186
|
+
`launcher.bench.command_template` still works unchanged as the implicit
|
|
187
|
+
`default` preset. Placeholder substitution in bench templates now splits
|
|
188
|
+
the template into argv *before* substituting, so a config label can no
|
|
189
|
+
longer inject extra arguments.
|
|
190
|
+
|
|
191
|
+
### Fixed (security)
|
|
192
|
+
|
|
193
|
+
- **The launcher HTML no longer embeds `CODEROUTER_LAUNCHER_TOKEN`.**
|
|
194
|
+
`GET /launcher` used to substitute the shared secret straight into the
|
|
195
|
+
page, so `curl /launcher | grep` recovered it and defeated
|
|
196
|
+
`_require_launcher_token`. The page now receives only a boolean telling
|
|
197
|
+
the UI whether auth is enabled; the operator enters the token once and it
|
|
198
|
+
is held in `sessionStorage` (per-tab). Token-free deployments are
|
|
199
|
+
unaffected.
|
|
200
|
+
- **`POST /api/launcher/start` and `/sweep/start` now validate
|
|
201
|
+
`model_path` / `draft_model_path` against `launcher.model_dirs`** (the
|
|
202
|
+
same check previously applied only to `/suggest`). When `model_dirs` is
|
|
203
|
+
unset the endpoints stay open as before, with a one-time warning;
|
|
204
|
+
configure `model_dirs` when the launcher is reachable beyond loopback.
|
|
205
|
+
- **Stored XSS in the launcher process table is fixed.** Process-row
|
|
206
|
+
buttons no longer build `onclick` attributes from the process name
|
|
207
|
+
(which an HTML parser decoded back into executable JS); they use
|
|
208
|
+
`data-*` attributes with a single delegated listener, so a name like
|
|
209
|
+
`x');alert(1);//` renders as inert text.
|
|
210
|
+
- **The request body-size limit is now enforced for
|
|
211
|
+
`Transfer-Encoding: chunked` requests.** The middleware previously only
|
|
212
|
+
read `Content-Length`, so a chunked body bypassed the cap entirely (and
|
|
213
|
+
a malformed length header fell through as unlimited). It now also counts
|
|
214
|
+
bytes as they are received and aborts with `413` the moment the running
|
|
215
|
+
total exceeds the cap, without buffering the body.
|
|
216
|
+
- **The GUI bench-sweep window no longer orphans `llama-server`
|
|
217
|
+
processes.** Closing the sweep window (the `×` button) did not signal the
|
|
218
|
+
worker, and sweep child processes were never registered for the app's
|
|
219
|
+
bulk shutdown, so backends kept running and leaked VRAM and ports. The
|
|
220
|
+
window now stops the worker and its children on close (non-blocking, via
|
|
221
|
+
an `after()` poll with a grace period then `SIGKILL`), sweep children are
|
|
222
|
+
drained when the app closes, and the app confirms before quitting while a
|
|
223
|
+
sweep is running.
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
12
227
|
## [v2.12.0] — 2026-08-04 (the context-budget guard actually fires now)
|
|
13
228
|
|
|
14
229
|
**This release changes behaviour you can observe.** The token estimator
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coderouter-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.14.0
|
|
4
4
|
Summary: Local-first, free-first, fallback-built-in LLM router. Claude Code / OpenAI compatible.
|
|
5
5
|
Project-URL: Homepage, https://github.com/zephel01/CodeRouter
|
|
6
6
|
Project-URL: Repository, https://github.com/zephel01/CodeRouter
|
|
@@ -45,7 +45,7 @@ from coderouter.adapters.base import (
|
|
|
45
45
|
ProviderCallOverrides,
|
|
46
46
|
StreamChunk,
|
|
47
47
|
)
|
|
48
|
-
from coderouter.
|
|
48
|
+
from coderouter.credentials import resolve_provider_credential
|
|
49
49
|
from coderouter.logging import get_logger, log_output_filter_applied
|
|
50
50
|
from coderouter.output_filters import OutputFilterChain
|
|
51
51
|
from coderouter.translation.anthropic import (
|
|
@@ -134,7 +134,9 @@ class AnthropicAdapter(BaseAdapter):
|
|
|
134
134
|
self.config.extra_body.get("anthropic_version", _DEFAULT_ANTHROPIC_VERSION)
|
|
135
135
|
),
|
|
136
136
|
}
|
|
137
|
-
|
|
137
|
+
# v2.14.0: see openai_compat._headers — same resolver, so an
|
|
138
|
+
# Anthropic-protocol provider can also ride a CLI session token.
|
|
139
|
+
api_key = resolve_provider_credential(self.config)
|
|
138
140
|
if api_key:
|
|
139
141
|
headers["x-api-key"] = api_key
|
|
140
142
|
# v0.4-D: forward the client's anthropic-beta header verbatim.
|
|
@@ -29,7 +29,7 @@ from coderouter.adapters.base import (
|
|
|
29
29
|
ProviderCallOverrides,
|
|
30
30
|
StreamChunk,
|
|
31
31
|
)
|
|
32
|
-
from coderouter.
|
|
32
|
+
from coderouter.credentials import resolve_provider_credential
|
|
33
33
|
from coderouter.logging import (
|
|
34
34
|
get_logger,
|
|
35
35
|
log_capability_degraded,
|
|
@@ -105,7 +105,10 @@ class OpenAICompatAdapter(BaseAdapter):
|
|
|
105
105
|
"Content-Type": "application/json",
|
|
106
106
|
"User-Agent": "CodeRouter/0.1",
|
|
107
107
|
}
|
|
108
|
-
|
|
108
|
+
# v2.14.0: resolve_provider_credential covers api_key_env (the
|
|
109
|
+
# historical path, unchanged) and credential.source=cli_session,
|
|
110
|
+
# which borrows the token a vendor CLI already wrote to disk.
|
|
111
|
+
api_key = resolve_provider_credential(self.config)
|
|
109
112
|
if api_key:
|
|
110
113
|
headers["Authorization"] = f"Bearer {api_key}"
|
|
111
114
|
return headers
|
|
@@ -149,6 +149,22 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
149
149
|
"See docs/guides/troubleshooting.md §5 for the threat model."
|
|
150
150
|
),
|
|
151
151
|
)
|
|
152
|
+
# v2.14.0: --check-secrets is the runtime counterpart to --check-env.
|
|
153
|
+
# --check-env asks "is the file holding my keys protected?"; this asks
|
|
154
|
+
# "does the running process know its own secrets, and did any of them
|
|
155
|
+
# already reach a log file?". The second question is the one that used
|
|
156
|
+
# to have no answer at all.
|
|
157
|
+
doctor.add_argument(
|
|
158
|
+
"--check-secrets",
|
|
159
|
+
action="store_true",
|
|
160
|
+
help=(
|
|
161
|
+
"Audit credential hygiene: prove the log-redaction filter "
|
|
162
|
+
"actually scrubs, report which declared api_key_env vars are "
|
|
163
|
+
"set, flag credentials pasted into a base_url, and scan the "
|
|
164
|
+
"already-written requests.jsonl / audit.jsonl under state_dir "
|
|
165
|
+
"for live key values. Exit 0 clean / 2 needs attention / 1 leak."
|
|
166
|
+
),
|
|
167
|
+
)
|
|
152
168
|
doctor.add_argument(
|
|
153
169
|
"--config",
|
|
154
170
|
default=None,
|
|
@@ -192,6 +208,55 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
192
208
|
),
|
|
193
209
|
)
|
|
194
210
|
|
|
211
|
+
# v2.14.0: `coderouter rollback` — the missing half of --apply.
|
|
212
|
+
# Both writers (doctor --apply, vscode-init) already dropped a `.bak`
|
|
213
|
+
# next to every file they rewrote; nothing could put it back. Restore
|
|
214
|
+
# is a swap, not an overwrite, so running it twice returns you to
|
|
215
|
+
# where you started rather than destroying the newer version.
|
|
216
|
+
rollback = sub.add_parser(
|
|
217
|
+
"rollback",
|
|
218
|
+
help="Restore files a previous --apply / vscode-init rewrote (v2.14.0).",
|
|
219
|
+
description=(
|
|
220
|
+
"Swap each managed file with its .bak sibling: providers.yaml, "
|
|
221
|
+
"~/.coderouter/model-capabilities.yaml, and (with --workspace) "
|
|
222
|
+
".vscode/settings.json and .envrc. The current contents become "
|
|
223
|
+
"the new .bak, so a second run toggles back. "
|
|
224
|
+
"Exit codes: 0 restored, 2 nothing to restore, 1 a restore failed."
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
rollback.add_argument(
|
|
228
|
+
"--config",
|
|
229
|
+
default=None,
|
|
230
|
+
help=(
|
|
231
|
+
"Path to providers.yaml. Defaults to the same file the loader "
|
|
232
|
+
"would read ($CODEROUTER_CONFIG, then ~/.coderouter/providers.yaml)."
|
|
233
|
+
),
|
|
234
|
+
)
|
|
235
|
+
rollback.add_argument(
|
|
236
|
+
"--workspace",
|
|
237
|
+
metavar="DIR",
|
|
238
|
+
default=None,
|
|
239
|
+
help=(
|
|
240
|
+
"Also restore the vscode-init outputs under DIR: "
|
|
241
|
+
"DIR/.vscode/settings.json and DIR/.envrc."
|
|
242
|
+
),
|
|
243
|
+
)
|
|
244
|
+
rollback.add_argument(
|
|
245
|
+
"--path",
|
|
246
|
+
action="append",
|
|
247
|
+
metavar="FILE",
|
|
248
|
+
default=None,
|
|
249
|
+
help=(
|
|
250
|
+
"Restore exactly this file from its .bak sibling. Repeatable. "
|
|
251
|
+
"When given, the default discovery set is not used."
|
|
252
|
+
),
|
|
253
|
+
)
|
|
254
|
+
rollback.add_argument(
|
|
255
|
+
"--dry-run",
|
|
256
|
+
action="store_true",
|
|
257
|
+
help="Report what would be restored without touching any file.",
|
|
258
|
+
)
|
|
259
|
+
|
|
195
260
|
# v1.5-C: `coderouter stats` — live TUI over GET /metrics.json.
|
|
196
261
|
# Lazy-imports ``curses`` inside the runner so the CLI boot stays
|
|
197
262
|
# snappy and environments without curses (rare, but e.g. minimal
|
|
@@ -490,6 +555,9 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
490
555
|
if args.command == "doctor":
|
|
491
556
|
return _run_doctor(args)
|
|
492
557
|
|
|
558
|
+
if args.command == "rollback":
|
|
559
|
+
return _run_rollback(args)
|
|
560
|
+
|
|
493
561
|
if args.command == "stats":
|
|
494
562
|
# v1.5-C: stats is intentionally a thin wrapper — all logic
|
|
495
563
|
# (fetch, render, curses loop) lives in coderouter.cli_stats so
|
|
@@ -526,9 +594,11 @@ def _run_doctor(args: argparse.Namespace) -> int:
|
|
|
526
594
|
worst-case of the two reports so CI guarding against both leak
|
|
527
595
|
risks AND broken providers can use a single command.
|
|
528
596
|
"""
|
|
529
|
-
|
|
597
|
+
check_secrets = bool(getattr(args, "check_secrets", False))
|
|
598
|
+
if args.check_model is None and args.check_env is None and not check_secrets:
|
|
530
599
|
print(
|
|
531
|
-
"doctor: provide --check-model PROVIDER
|
|
600
|
+
"doctor: provide --check-model PROVIDER, --check-env [PATH], "
|
|
601
|
+
"and/or --check-secrets",
|
|
532
602
|
file=sys.stderr,
|
|
533
603
|
)
|
|
534
604
|
return 1
|
|
@@ -541,6 +611,9 @@ def _run_doctor(args: argparse.Namespace) -> int:
|
|
|
541
611
|
if args.check_env is not None:
|
|
542
612
|
worst_exit = max(worst_exit, _run_check_env(args.check_env))
|
|
543
613
|
|
|
614
|
+
if check_secrets:
|
|
615
|
+
worst_exit = max(worst_exit, _run_check_secrets(args.config))
|
|
616
|
+
|
|
544
617
|
if args.check_model is not None:
|
|
545
618
|
worst_exit = max(worst_exit, _run_check_model(args))
|
|
546
619
|
|
|
@@ -600,28 +673,27 @@ def _run_check_model(args: argparse.Namespace) -> int:
|
|
|
600
673
|
|
|
601
674
|
|
|
602
675
|
def _resolve_config_path(explicit: str | None) -> Path:
|
|
603
|
-
"""
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
676
|
+
"""Return the file the loader actually read, for ``--apply`` write-back.
|
|
677
|
+
|
|
678
|
+
Delegates the search order to
|
|
679
|
+
:func:`coderouter.config.loader.resolve_config_path` rather than
|
|
680
|
+
re-implementing it. Re-implementing it here is exactly how the
|
|
681
|
+
v2.13.0 CWD opt-in change would introduce a silent bug: if the loader
|
|
682
|
+
gates ``./providers.yaml`` behind ``CODEROUTER_ALLOW_CWD_CONFIG`` but
|
|
683
|
+
this copy still searched CWD unconditionally, ``doctor --apply`` would
|
|
684
|
+
write patches back into a file that ``load_config`` never read. Keeping
|
|
685
|
+
one source of truth makes that impossible.
|
|
686
|
+
|
|
687
|
+
When nothing exists, falls back to ``~/.coderouter/providers.yaml``
|
|
688
|
+
(the loader's last candidate) — the apply step surfaces a clearer
|
|
689
|
+
error against that path than this resolver would.
|
|
609
690
|
"""
|
|
610
|
-
import
|
|
691
|
+
from coderouter.config.loader import resolve_config_path
|
|
611
692
|
|
|
612
|
-
|
|
613
|
-
if
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
candidates.append(Path(env_path))
|
|
617
|
-
candidates.append(Path.cwd() / "providers.yaml")
|
|
618
|
-
candidates.append(Path.home() / ".coderouter" / "providers.yaml")
|
|
619
|
-
for p in candidates:
|
|
620
|
-
if p.is_file():
|
|
621
|
-
return p
|
|
622
|
-
# Fall back to the last candidate even if absent — the apply step
|
|
623
|
-
# will surface a clearer error than this resolver would.
|
|
624
|
-
return candidates[-1]
|
|
693
|
+
resolved = resolve_config_path(explicit)
|
|
694
|
+
if resolved is not None:
|
|
695
|
+
return resolved
|
|
696
|
+
return Path.home() / ".coderouter" / "providers.yaml"
|
|
625
697
|
|
|
626
698
|
|
|
627
699
|
def _write_verdict_line(*, write: bool, files_written: int) -> None:
|
|
@@ -970,5 +1042,70 @@ def _run_check_env(arg_value: str) -> int:
|
|
|
970
1042
|
return exit_code_for_env_security(report)
|
|
971
1043
|
|
|
972
1044
|
|
|
1045
|
+
def _run_rollback(args: argparse.Namespace) -> int:
|
|
1046
|
+
"""v2.14.0: put back what ``--apply`` / ``vscode-init`` overwrote.
|
|
1047
|
+
|
|
1048
|
+
Explicit ``--path`` wins over discovery so an operator can restore one
|
|
1049
|
+
file without also reverting an unrelated ``model-capabilities.yaml``
|
|
1050
|
+
edit from last week. Discovery resolves providers.yaml through the
|
|
1051
|
+
loader's own search order rather than a second copy of it.
|
|
1052
|
+
"""
|
|
1053
|
+
from coderouter.rollback import (
|
|
1054
|
+
discover_managed_files,
|
|
1055
|
+
exit_code_for_rollback,
|
|
1056
|
+
format_rollback_report,
|
|
1057
|
+
restore_many,
|
|
1058
|
+
)
|
|
1059
|
+
|
|
1060
|
+
explicit = getattr(args, "path", None)
|
|
1061
|
+
if explicit:
|
|
1062
|
+
targets: list[Path] = [Path(p).expanduser() for p in explicit]
|
|
1063
|
+
else:
|
|
1064
|
+
try:
|
|
1065
|
+
config_path: Path | None = _resolve_config_path(args.config)
|
|
1066
|
+
except Exception:
|
|
1067
|
+
# No config anywhere is not fatal here — the workspace files
|
|
1068
|
+
# and the user-layer capabilities file may still have backups.
|
|
1069
|
+
config_path = None
|
|
1070
|
+
targets = discover_managed_files(
|
|
1071
|
+
config_path=config_path,
|
|
1072
|
+
workspace=getattr(args, "workspace", None),
|
|
1073
|
+
)
|
|
1074
|
+
|
|
1075
|
+
outcomes = restore_many(targets, dry_run=bool(args.dry_run))
|
|
1076
|
+
print(format_rollback_report(outcomes, dry_run=bool(args.dry_run)))
|
|
1077
|
+
return exit_code_for_rollback(outcomes)
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
def _run_check_secrets(config_path: str | None) -> int:
|
|
1081
|
+
"""v2.14.0: credential-hygiene audit for the running configuration.
|
|
1082
|
+
|
|
1083
|
+
Loads the config through the normal loader — which is also what arms
|
|
1084
|
+
the redaction registry — then runs the suite from
|
|
1085
|
+
:mod:`coderouter.secret_redaction`. A config that fails to load is a
|
|
1086
|
+
blocker (exit 1) rather than a skip: we cannot claim anything about
|
|
1087
|
+
secret hygiene for a file we could not read.
|
|
1088
|
+
"""
|
|
1089
|
+
from coderouter.config.loader import load_config
|
|
1090
|
+
from coderouter.secret_redaction import (
|
|
1091
|
+
check_secret_hygiene,
|
|
1092
|
+
exit_code_for_secret_report,
|
|
1093
|
+
format_secret_report,
|
|
1094
|
+
)
|
|
1095
|
+
|
|
1096
|
+
try:
|
|
1097
|
+
config = load_config(config_path)
|
|
1098
|
+
except FileNotFoundError as exc:
|
|
1099
|
+
print(f"doctor: {exc}", file=sys.stderr)
|
|
1100
|
+
return 1
|
|
1101
|
+
except Exception as exc:
|
|
1102
|
+
print(f"doctor: failed to load config: {exc}", file=sys.stderr)
|
|
1103
|
+
return 1
|
|
1104
|
+
|
|
1105
|
+
report = check_secret_hygiene(config)
|
|
1106
|
+
print(format_secret_report(report))
|
|
1107
|
+
return exit_code_for_secret_report(report)
|
|
1108
|
+
|
|
1109
|
+
|
|
973
1110
|
if __name__ == "__main__":
|
|
974
1111
|
sys.exit(main())
|