coderouter-cli 2.11.1__tar.gz → 2.11.2__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.11.1 → coderouter_cli-2.11.2}/.gitignore +5 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/CHANGELOG.md +89 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/PKG-INFO +1 -1
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/vscode_init.py +172 -16
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/guides/vscode.md +26 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/pyproject.toml +1 -1
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_launcher_gui_devices.py +4 -2
- coderouter_cli-2.11.2/tests/test_launcher_gui_events.py +498 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_launcher_gui_readiness.py +17 -6
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_launcher_variant_gui.py +4 -2
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_vscode_init.py +180 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/LICENSE +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/README.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/README.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/__init__.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/__main__.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/adapters/__init__.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/adapters/anthropic_native.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/adapters/base.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/adapters/openai_compat.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/adapters/registry.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/cli.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/cli_stats.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/config/__init__.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/config/capability_registry.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/config/env_file.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/config/loader.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/config/schemas.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/cost.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/data/__init__.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/data/model-capabilities.yaml +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/doctor.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/doctor_apply.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/env_security.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/errors.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/gguf_introspect.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/guards/__init__.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/guards/_fingerprint.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/guards/backend_health.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/guards/context_budget.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/guards/continuous_probe.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/guards/drift_actions.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/guards/drift_detection.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/guards/memory_budget.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/guards/memory_pressure.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/guards/self_healing.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/guards/tool_loop.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/hardware.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/ingress/__init__.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/ingress/anthropic_routes.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/ingress/app.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/ingress/dashboard_routes.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/ingress/launcher_routes.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/ingress/metrics_routes.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/ingress/openai_routes.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/language_tax.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/launcher_devices.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/launcher_speculative.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/launcher_swap.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/logging.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/metrics/__init__.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/metrics/collector.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/metrics/prometheus.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/output_filters.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/plugins/__init__.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/plugins/base.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/plugins/loader.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/plugins/registry.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/routing/__init__.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/routing/adaptive.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/routing/auto_router.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/routing/budget.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/routing/capability.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/routing/fallback.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/state/__init__.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/state/audit_log.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/state/replay.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/state/request_log.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/state/store.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/state/suggest_rules.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/token_estimation.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/token_estimation_accurate.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/translation/__init__.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/translation/anthropic.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/translation/convert.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/coderouter/translation/tool_repair.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/README.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/README.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/assets/dashboard-demo.png +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/backends/claude-code-llamacpp-vllm.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/backends/claude-code-llamacpp-vllm.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/backends/external-agents.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/backends/external-agents.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/backends/gguf_dl.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/backends/gguf_dl.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/backends/hf-ollama-models.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/backends/hf-ollama-models.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/backends/install-backends.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/backends/install-backends.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/backends/launcher-quickstart.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/backends/launcher-quickstart.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/backends/launcher.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/backends/launcher.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/backends/llamacpp-direct.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/backends/llamacpp-direct.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/backends/lmstudio-direct.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/backends/lmstudio-direct.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/backends/verify-ollama-0.23.1.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/concepts/architecture.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/concepts/architecture.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/concepts/context-budget.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/concepts/context-budget.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/concepts/continuous-probing.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/concepts/continuous-probing.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/concepts/drift-detection.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/concepts/drift-detection.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/concepts/partial-stitch.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/concepts/partial-stitch.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/designs/agent-cli-plugin-extraction.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/designs/external-agents-adapter.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/designs/launcher-model-swap.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/designs/launcher-multi-build.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/designs/orchestration-companion.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/designs/v1.5-dashboard-mockup.html +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/designs/v1.6-auto-router-verification.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/designs/v1.6-auto-router.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/guides/free-tier-guide.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/guides/free-tier-guide.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/guides/language-tax.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/guides/language-tax.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/guides/remote-access.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/guides/remote-access.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/guides/security.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/guides/security.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/guides/subagent-routing.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/guides/subagent-routing.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/guides/troubleshooting.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/guides/troubleshooting.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/guides/usage-guide.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/guides/usage-guide.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/low-memory-integration.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/low-memory-integration.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/openrouter-roster/CHANGES.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/openrouter-roster/README.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/openrouter-roster/latest.json +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/retrospectives/v0.4.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/retrospectives/v0.5-verify.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/retrospectives/v0.5.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/retrospectives/v0.6.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/retrospectives/v0.7.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/retrospectives/v1.0-verify.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/retrospectives/v1.0.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/start/quickstart.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/start/quickstart.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/start/when-do-i-need-coderouter.en.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/docs/start/when-do-i-need-coderouter.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/examples/.env.example +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/examples/README.md +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/examples/providers-agent-cli.yaml +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/examples/providers-multiagent.yaml +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/examples/providers.context-budget-test.yaml +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/examples/providers.llamacpp-vllm.yaml +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/examples/providers.nvidia-nim.yaml +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/examples/providers.ollama-auto-custom.yaml +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/examples/providers.ollama-auto.yaml +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/examples/providers.ollama-free-chain.yaml +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/examples/providers.raspberrypi.yaml +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/examples/providers.swap.yaml +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/examples/providers.yaml +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/scripts/demo_traffic.sh +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/scripts/openrouter_roster_diff.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/scripts/smoke_v2_2.sh +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/scripts/verify-providers.yaml +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/scripts/verify_ollama_0_23.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/scripts/verify_v0_5.sh +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/scripts/verify_v1_0.sh +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/__init__.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/conftest.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_adapter_anthropic.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_agent_cli.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_audit_log.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_auto_router.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_auto_router_cjk.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_backend_health.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_budget.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_capability.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_capability_degraded_payload.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_capability_registry.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_capability_registry_cache_control.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_capability_tool_choice.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_claude_code_suitability.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_cli.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_cli_bind_warning.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_cli_stats.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_config.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_config_cwd_warning.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_config_shim_actions.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_context_budget.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_continuous_probe.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_dashboard_endpoint.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_doctor.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_doctor_apply.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_doctor_cache_probe.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_drift_actions.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_drift_detection.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_drift_detection_integration.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_env_file.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_env_security.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_errors.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_examples_yaml.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_examples_yaml_roundtrip.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_fallback.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_fallback_anthropic.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_fallback_cache_control.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_fallback_cache_observed.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_fallback_cache_strip.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_fallback_empty_response.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_fallback_misconfig_warn.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_fallback_paid_gate.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_fallback_thinking.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_fallback_tool_choice_emulate.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_fix_h1_prometheus_drift.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_fix_h2_h6_translation.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_fix_h3_client_reuse.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_fix_h4_h5_guards.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_fix_h7_probe_restore.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_fix_h8_launcher_auth.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_fix_mA_routing.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_fix_mB_adapters.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_fix_mC_convert.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_fix_mD_logging_metrics.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_fix_mE_config_validation.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_fix_mF_ingress.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_gguf_introspect.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_guards_tool_loop.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_hardware.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_import_hygiene.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_ingress_anthropic.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_ingress_count_tokens.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_ingress_profile.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_language_tax.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_language_tax_integration.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_launcher_backend_variants.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_launcher_config_bench.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_launcher_devices.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_launcher_devices_detect.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_launcher_devices_routes.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_launcher_mtp.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_launcher_mtp_fallback.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_launcher_provider_sync.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_launcher_readiness_restart.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_launcher_swap.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_launcher_swap_review.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_launcher_variant_config.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_launcher_variant_routes.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_launcher_variant_sweep.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_memory_budget.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_memory_pressure.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_metrics_cache.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_metrics_collector.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_metrics_cost.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_metrics_endpoint.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_metrics_jsonl.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_metrics_prometheus.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_metrics_prometheus_cache.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_models_passthrough.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_openai_compat.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_openrouter_roster_diff.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_output_filters.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_output_filters_adapters.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_partial_stitch.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_plugin_adapter.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_plugins_integration.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_plugins_loader.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_plugins_registry.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_reasoning_strip.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_repair_byte_fallback.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_request_log.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_role_normalization.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_routing_adaptive.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_self_healing.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_setup_sh.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_state_store.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_stop_reason_forward_compat.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_token_estimation.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_token_estimation_accurate.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_tokens_saved_metric.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_tool_repair.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_tool_repair_differential.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_tool_repair_lenient.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_tool_repair_r4.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_tool_repair_scanners.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_toolrepair_bench.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_translation_anthropic.py +0 -0
- {coderouter_cli-2.11.1 → coderouter_cli-2.11.2}/tests/test_translation_reverse.py +0 -0
|
@@ -9,6 +9,11 @@ secrets.env
|
|
|
9
9
|
~/.coderouter/
|
|
10
10
|
CLAUDE.md
|
|
11
11
|
|
|
12
|
+
# *.bak — pre-overwrite backups of the files above (e.g. `vscode-init --force`
|
|
13
|
+
# saves `.envrc.bak` before clobbering `.envrc`; `doctor --apply` saves
|
|
14
|
+
# `providers.yaml.bak`). Same secret-risk as the originals, so same rule.
|
|
15
|
+
*.bak
|
|
16
|
+
|
|
12
17
|
# ============================================================
|
|
13
18
|
# Python
|
|
14
19
|
# ============================================================
|
|
@@ -9,6 +9,95 @@ are kept verbatim where the Japanese text itself is the subject).
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## [v2.11.2] — 2026-08-04 (file-write safety and launcher event hygiene)
|
|
13
|
+
|
|
14
|
+
Two fixes from the same review round as v2.11.1, split out because they
|
|
15
|
+
rewrite `_atomic_write` and the launcher GUI's event plumbing and deserved
|
|
16
|
+
their own rollback unit. **No breaking changes**: no config-schema field
|
|
17
|
+
changed, no CLI flag changed meaning, `--force` still replaces `.envrc`.
|
|
18
|
+
Existing `.vscode/settings.json` merge semantics are unchanged.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **`vscode-init --force` destroyed `.envrc` with no way back.** The
|
|
23
|
+
command replaced the file outright — no merge, no backup — so a
|
|
24
|
+
hand-added `source_env_if_exists .envrc.local`, the very pattern
|
|
25
|
+
`docs/guides/vscode.md` recommends, vanished silently. direnv then
|
|
26
|
+
looked healthy while exporting nothing, which is a hard failure to
|
|
27
|
+
trace back to its cause. The conflict message said only "Re-run with
|
|
28
|
+
`--force` to overwrite" and never mentioned that the whole file goes.
|
|
29
|
+
The previous contents are now copied to `.envrc.bak` before the write,
|
|
30
|
+
the path is reported in the command's output, and the conflict message
|
|
31
|
+
spells out that this is a whole-file replacement rather than a merge.
|
|
32
|
+
`--dry-run` makes no backup. Only one generation is kept, so a second
|
|
33
|
+
`--force` overwrites the first `.bak` (`vscode_init.py`).
|
|
34
|
+
- **The launcher GUI could lose track of a running backend because of
|
|
35
|
+
that backend's own log output.** `_log_queue` carried control events
|
|
36
|
+
and raw child stdout in the same channel, with the drain loop
|
|
37
|
+
dispatching on `line.startswith("_ERR_:")` and friends. A child line
|
|
38
|
+
beginning `_ERR_:` ran `del self.processes[proc_id]`, so a live
|
|
39
|
+
llama-server dropped out of the process table — unstoppable from the
|
|
40
|
+
UI, missed by the shutdown handler, holding its port and VRAM until
|
|
41
|
+
killed by hand. A line beginning `_SPAWNED_:` with too few colons
|
|
42
|
+
raised `ValueError` inside the drain loop and discarded that tick's
|
|
43
|
+
log processing entirely. This is reachable in ordinary use: llama.cpp
|
|
44
|
+
echoes GGUF metadata such as the chat template verbatim at startup,
|
|
45
|
+
and `extra_args` wrappers put arbitrary text on the same stream.
|
|
46
|
+
Events are now `(kind, proc_id, payload)` with `kind` in
|
|
47
|
+
`log / spawned / ready / error / devices`; raw child output is
|
|
48
|
+
enqueued unconditionally as `log` and never inspected for markers.
|
|
49
|
+
`_cr_log_queue` got the same treatment, the `"_DEVICES_"` proc_id
|
|
50
|
+
sentinel is gone, and a malformed control payload now skips its own
|
|
51
|
+
event instead of killing the loop. While here, `_SPAWNED_` parsing
|
|
52
|
+
moved from `split(":", 2)` to `rpartition`, so model names containing
|
|
53
|
+
a colon no longer mangle the port (`launcher_gui.py`).
|
|
54
|
+
|
|
55
|
+
### Security
|
|
56
|
+
|
|
57
|
+
- **`vscode-init` widened the permissions of the files it rewrote.**
|
|
58
|
+
`_atomic_write` created its temp file under the process umask and
|
|
59
|
+
`os.replace`d it over the target, so an `.envrc` hardened to `0600` —
|
|
60
|
+
a file that holds `ANTHROPIC_AUTH_TOKEN` — came back `0644`. The temp
|
|
61
|
+
path was also fully predictable (`.envrc.tmp`, `settings.json.tmp`),
|
|
62
|
+
so a pre-planted symlink at that path was followed and its target
|
|
63
|
+
overwritten (CWE-377), and two concurrent runs in one workspace raced
|
|
64
|
+
on the same name. Writes now go through `tempfile.mkstemp` with
|
|
65
|
+
`O_EXCL`, are `fsync`ed, restore the original mode before the rename,
|
|
66
|
+
and clean up the temp file on failure. A newly created `.envrc` is
|
|
67
|
+
written `0600`, matching the standard `coderouter doctor --check-env`
|
|
68
|
+
already enforces for credential-bearing files; an existing file keeps
|
|
69
|
+
whatever mode the operator set. Mode handling is POSIX-only —
|
|
70
|
+
`os.chmod` on Windows only toggles the read-only bit and is skipped.
|
|
71
|
+
`.vscode/settings.json` is backed up before any rewrite too, not just
|
|
72
|
+
under `--force`, since the merge path also edits it in place
|
|
73
|
+
(`vscode_init.py`).
|
|
74
|
+
|
|
75
|
+
### Changed
|
|
76
|
+
|
|
77
|
+
- `.gitignore` now excludes `*.bak`, so the backups above and
|
|
78
|
+
`providers.yaml.bak` from `doctor --apply` stay out of commits. They
|
|
79
|
+
carry the same secrets as the files they shadow.
|
|
80
|
+
- Three GUI test modules documented their `pytest.importorskip("tkinter")`
|
|
81
|
+
as a CI workaround, stating that the uv-managed Python on CI has no
|
|
82
|
+
tkinter and that these tests are therefore skipped there. Measured on
|
|
83
|
+
2026-08-04: `uv python install` ships Tk 8.6 on both 3.12.11 and
|
|
84
|
+
3.13.7, and all 93 cases in those files run and pass on CI. The
|
|
85
|
+
docstrings now say so, and record that the guards stay for local
|
|
86
|
+
system pythons built without Tk — which do exist.
|
|
87
|
+
|
|
88
|
+
### Tests
|
|
89
|
+
|
|
90
|
+
- 1 new file, 38 new cases (`test_launcher_gui_events.py`, plus
|
|
91
|
+
additions to `test_vscode_init.py`). Suite: 2345 passed, 1 skipped,
|
|
92
|
+
ruff clean.
|
|
93
|
+
- Each fix has a regression test confirmed to fail against the pre-fix
|
|
94
|
+
tree — 10 for the write path, 7 for the event path. The headline one
|
|
95
|
+
asserts a live process survives its own `_ERR_:` stdout line; on the
|
|
96
|
+
old code `app.processes` comes back empty. Another plants a symlink at
|
|
97
|
+
the predictable temp path and asserts its target is untouched.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
12
101
|
## [v2.11.1] — 2026-08-04 (second-round review: correctness & hardening)
|
|
13
102
|
|
|
14
103
|
Seven fixes from a full-source review of the 36k-line package, plus two
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coderouter-cli
|
|
3
|
-
Version: 2.11.
|
|
3
|
+
Version: 2.11.2
|
|
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
|
|
@@ -16,11 +16,21 @@ The design contract:
|
|
|
16
16
|
without ``--force`` and print a unified diff so the operator decides.
|
|
17
17
|
* ``--dry-run`` is byte-identical to the write path minus the actual
|
|
18
18
|
``os.replace`` at the end.
|
|
19
|
-
* All writes are atomic (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
* All writes are atomic (``tempfile.mkstemp`` + ``fsync`` +
|
|
20
|
+
``os.replace``) so a partial write cannot corrupt an existing
|
|
21
|
+
``settings.json``. This matters more here than in most CodeRouter
|
|
22
|
+
modules because the file we're editing is workspace-critical: a
|
|
23
|
+
broken JSON blob would silently break every VSCode terminal env
|
|
24
|
+
until a human debugged it.
|
|
25
|
+
* Nothing an operator wrote is destroyed without a copy: before any
|
|
26
|
+
existing file is rewritten, its previous bytes (and mode) land in
|
|
27
|
+
``<name>.bak``. ``--force`` on ``.envrc`` is a whole-file replace,
|
|
28
|
+
not a merge, so this is the only thing standing between the user and
|
|
29
|
+
a lost ``source_env_if_exists .envrc.local`` line.
|
|
30
|
+
* A freshly generated ``.envrc`` is created 0600 — it carries
|
|
31
|
+
``ANTHROPIC_AUTH_TOKEN`` and sits next to the operator's real
|
|
32
|
+
secrets. An existing file's mode is preserved across rewrites
|
|
33
|
+
instead of being reset to the umask default by ``os.replace``.
|
|
24
34
|
|
|
25
35
|
Files touched (opt-in per flag):
|
|
26
36
|
|
|
@@ -40,8 +50,12 @@ directly without argparse in the way.
|
|
|
40
50
|
|
|
41
51
|
from __future__ import annotations
|
|
42
52
|
|
|
53
|
+
import contextlib
|
|
43
54
|
import json
|
|
44
55
|
import os
|
|
56
|
+
import shutil
|
|
57
|
+
import stat
|
|
58
|
+
import tempfile
|
|
45
59
|
from dataclasses import dataclass
|
|
46
60
|
from difflib import unified_diff
|
|
47
61
|
from pathlib import Path
|
|
@@ -62,6 +76,15 @@ DEFAULT_PORT = 8088
|
|
|
62
76
|
# then flag the unused directive.)
|
|
63
77
|
DEFAULT_TOKEN = "dummy"
|
|
64
78
|
|
|
79
|
+
# v2.11 (H-11): a generated .envrc carries ANTHROPIC_AUTH_TOKEN, and
|
|
80
|
+
# users routinely add their real key next to it (or point
|
|
81
|
+
# ``source_env_if_exists .envrc.local`` at one). ``env_security``
|
|
82
|
+
# already WARNs on any ``.env`` whose mode has 0o077 bits set and tells
|
|
83
|
+
# the operator to ``chmod 0600``; a file we generate ourselves should
|
|
84
|
+
# not be born failing that check. Applies to newly created files only —
|
|
85
|
+
# an existing file keeps whatever mode its owner chose.
|
|
86
|
+
_ENVRC_MODE = 0o600
|
|
87
|
+
|
|
65
88
|
# VSCode reads terminal env from a separate key per host OS. Writing all
|
|
66
89
|
# three means the same ``.vscode/`` folder works whether the developer
|
|
67
90
|
# opens it on macOS, Linux, or WSL/Windows without a second scaffolder
|
|
@@ -261,7 +284,9 @@ def _apply_settings_json(
|
|
|
261
284
|
diff=diff,
|
|
262
285
|
reason=(
|
|
263
286
|
"existing settings.json has different value(s) for "
|
|
264
|
-
f"{', '.join(sorted(conflicts))}. Re-run with --force to
|
|
287
|
+
f"{', '.join(sorted(conflicts))}. Re-run with --force to "
|
|
288
|
+
"overwrite those keys (unrelated keys are preserved; the "
|
|
289
|
+
f"previous file is saved to {_backup_path(path).name})."
|
|
265
290
|
),
|
|
266
291
|
)
|
|
267
292
|
|
|
@@ -270,7 +295,24 @@ def _apply_settings_json(
|
|
|
270
295
|
return FileOutcome(path=path, action="unchanged")
|
|
271
296
|
|
|
272
297
|
diff = _text_diff(old_text, new_text, path)
|
|
298
|
+
reason = ""
|
|
273
299
|
if not dry_run:
|
|
300
|
+
# H-11: same deal as .envrc. The merge preserves unrelated keys
|
|
301
|
+
# by design, but a bug in _merge_terminal_env would rewrite a
|
|
302
|
+
# hand-tuned settings.json with no way back — so keep the old
|
|
303
|
+
# bytes. Unlike .envrc this is not gated on --force: an additive
|
|
304
|
+
# merge rewrites the file without --force too, and that is
|
|
305
|
+
# exactly the path a merge bug would take.
|
|
306
|
+
if old_text:
|
|
307
|
+
backup = _make_backup(path)
|
|
308
|
+
reason = (
|
|
309
|
+
f"previous contents backed up to {backup}"
|
|
310
|
+
if backup is not None
|
|
311
|
+
else (
|
|
312
|
+
f"could not write {_backup_path(path).name} — "
|
|
313
|
+
"previous contents were NOT preserved"
|
|
314
|
+
)
|
|
315
|
+
)
|
|
274
316
|
try:
|
|
275
317
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
276
318
|
_atomic_write(path, new_text)
|
|
@@ -281,11 +323,14 @@ def _apply_settings_json(
|
|
|
281
323
|
reason=f"write failed: {exc}",
|
|
282
324
|
diff=diff,
|
|
283
325
|
)
|
|
326
|
+
elif old_text:
|
|
327
|
+
reason = f"would back up previous contents to {_backup_path(path)}"
|
|
284
328
|
|
|
285
329
|
return FileOutcome(
|
|
286
330
|
path=path,
|
|
287
331
|
action="updated" if old_text else "created",
|
|
288
332
|
diff=diff,
|
|
333
|
+
reason=reason,
|
|
289
334
|
)
|
|
290
335
|
|
|
291
336
|
|
|
@@ -374,15 +419,32 @@ def _apply_envrc(
|
|
|
374
419
|
diff=diff,
|
|
375
420
|
reason=(
|
|
376
421
|
".envrc already exists and its contents differ. "
|
|
377
|
-
"Re-run with --force to
|
|
422
|
+
"Re-run with --force to replace the WHOLE file with the "
|
|
423
|
+
"generated contents (this is not a merge — custom lines "
|
|
424
|
+
"such as `source_env_if_exists .envrc.local` are dropped); "
|
|
425
|
+
f"the previous contents are saved to {_backup_path(path).name}. "
|
|
378
426
|
"(Tip: keep secrets in a separate .envrc.local and "
|
|
379
427
|
"``source_env_if_exists`` from .envrc.)"
|
|
380
428
|
),
|
|
381
429
|
)
|
|
382
430
|
|
|
431
|
+
reason = ""
|
|
383
432
|
if not dry_run:
|
|
433
|
+
# H-11: --force replaces the whole file, so stash the old bytes
|
|
434
|
+
# (mode included) before they are gone. Inside the dry_run guard
|
|
435
|
+
# on purpose — --dry-run must not touch the filesystem at all.
|
|
436
|
+
if old_text:
|
|
437
|
+
backup = _make_backup(path)
|
|
438
|
+
reason = (
|
|
439
|
+
f"previous contents backed up to {backup}"
|
|
440
|
+
if backup is not None
|
|
441
|
+
else (
|
|
442
|
+
f"could not write {_backup_path(path).name} — "
|
|
443
|
+
"previous contents were NOT preserved"
|
|
444
|
+
)
|
|
445
|
+
)
|
|
384
446
|
try:
|
|
385
|
-
_atomic_write(path, new_text)
|
|
447
|
+
_atomic_write(path, new_text, mode=_ENVRC_MODE)
|
|
386
448
|
except OSError as exc:
|
|
387
449
|
return FileOutcome(
|
|
388
450
|
path=path,
|
|
@@ -390,11 +452,14 @@ def _apply_envrc(
|
|
|
390
452
|
reason=f"write failed: {exc}",
|
|
391
453
|
diff=diff,
|
|
392
454
|
)
|
|
455
|
+
elif old_text:
|
|
456
|
+
reason = f"would back up previous contents to {_backup_path(path)}"
|
|
393
457
|
|
|
394
458
|
return FileOutcome(
|
|
395
459
|
path=path,
|
|
396
460
|
action="updated" if old_text else "created",
|
|
397
461
|
diff=diff,
|
|
462
|
+
reason=reason,
|
|
398
463
|
)
|
|
399
464
|
|
|
400
465
|
|
|
@@ -538,21 +603,112 @@ def _dump_json(obj: object) -> str:
|
|
|
538
603
|
return json.dumps(obj, indent=2, ensure_ascii=False) + "\n"
|
|
539
604
|
|
|
540
605
|
|
|
541
|
-
def
|
|
542
|
-
"""
|
|
606
|
+
def _default_file_mode() -> int:
|
|
607
|
+
"""Return the mode a plain ``open(..., "w")`` would produce.
|
|
608
|
+
|
|
609
|
+
``tempfile.mkstemp`` deliberately creates its file 0600. That is the
|
|
610
|
+
right default for a secret-bearing ``.envrc``, but it would silently
|
|
611
|
+
tighten ``settings.json`` (previously umask-derived, typically
|
|
612
|
+
0644). Reading the umask back keeps the pre-existing behaviour for
|
|
613
|
+
files that have no explicit mode requirement.
|
|
614
|
+
"""
|
|
615
|
+
current = os.umask(0)
|
|
616
|
+
os.umask(current)
|
|
617
|
+
return 0o666 & ~current
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def _atomic_write(path: Path, text: str, *, mode: int | None = None) -> None:
|
|
621
|
+
"""Write ``text`` to ``path`` atomically (tmp + ``os.replace``).
|
|
543
622
|
|
|
544
623
|
A partial write cannot corrupt the target file: the tmp file lives
|
|
545
624
|
on the same filesystem (same parent directory) so ``os.replace`` is
|
|
546
625
|
an atomic inode swap on POSIX. On Windows ``os.replace`` is also
|
|
547
626
|
atomic since Python 3.3.
|
|
548
627
|
|
|
549
|
-
The tmp file
|
|
550
|
-
``
|
|
551
|
-
|
|
628
|
+
v2.11 hardening (H-11). The tmp file used to be the predictable
|
|
629
|
+
``<name>.tmp`` next to the target, written with ``Path.write_text``:
|
|
630
|
+
|
|
631
|
+
* a pre-planted symlink at that exact path was *followed*, so the
|
|
632
|
+
write landed on the link's target (CWE-377);
|
|
633
|
+
* two concurrent ``vscode-init`` runs on the same workspace
|
|
634
|
+
clobbered each other's tmp file;
|
|
635
|
+
* ``os.replace`` swaps inodes, so the target's mode was reset to the
|
|
636
|
+
umask default — a 0600 ``.envrc`` holding ``ANTHROPIC_AUTH_TOKEN``
|
|
637
|
+
silently became 0644;
|
|
638
|
+
* without ``fsync`` a crash right after the rename could leave a
|
|
639
|
+
zero-length file.
|
|
640
|
+
|
|
641
|
+
So: ``tempfile.mkstemp`` (random name, ``O_EXCL``, no symlink
|
|
642
|
+
following), ``fsync`` before the rename, mode restored from the
|
|
643
|
+
file being replaced (or from ``mode`` for a fresh file), and the tmp
|
|
644
|
+
file unlinked in ``finally`` so a failed write leaves no debris.
|
|
645
|
+
|
|
646
|
+
Parameters
|
|
647
|
+
----------
|
|
648
|
+
mode:
|
|
649
|
+
POSIX mode for a *newly created* file. Ignored when ``path``
|
|
650
|
+
already exists — an existing file's own mode wins, so we never
|
|
651
|
+
loosen (or tighten) what the operator chose. ``None`` means
|
|
652
|
+
"whatever the umask would have given us".
|
|
653
|
+
"""
|
|
654
|
+
existing_mode: int | None = None
|
|
655
|
+
try:
|
|
656
|
+
existing_mode = stat.S_IMODE(path.stat().st_mode)
|
|
657
|
+
except OSError:
|
|
658
|
+
existing_mode = None
|
|
659
|
+
|
|
660
|
+
target_mode = existing_mode if existing_mode is not None else mode
|
|
661
|
+
if target_mode is None:
|
|
662
|
+
target_mode = _default_file_mode()
|
|
663
|
+
|
|
664
|
+
fd, tmp_name = tempfile.mkstemp(
|
|
665
|
+
dir=str(path.parent),
|
|
666
|
+
prefix=f".{path.name}.",
|
|
667
|
+
suffix=".tmp",
|
|
668
|
+
)
|
|
669
|
+
tmp = Path(tmp_name)
|
|
670
|
+
try:
|
|
671
|
+
with os.fdopen(fd, "w", encoding="utf-8") as handle:
|
|
672
|
+
handle.write(text)
|
|
673
|
+
handle.flush()
|
|
674
|
+
os.fsync(handle.fileno())
|
|
675
|
+
# Windows has no POSIX mode bits worth honouring; os.chmod there
|
|
676
|
+
# only toggles the read-only flag, which would be a surprising
|
|
677
|
+
# side effect of "preserve the mode".
|
|
678
|
+
if os.name != "nt":
|
|
679
|
+
os.chmod(tmp, target_mode)
|
|
680
|
+
os.replace(tmp, path)
|
|
681
|
+
finally:
|
|
682
|
+
# Best effort: a leftover tmp file is noise, not corruption, and
|
|
683
|
+
# cleaning it up must never mask the original exception.
|
|
684
|
+
with contextlib.suppress(OSError):
|
|
685
|
+
tmp.unlink(missing_ok=True)
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
def _backup_path(path: Path) -> Path:
|
|
689
|
+
"""Return the ``.bak`` sibling used to preserve ``path``'s old bytes."""
|
|
690
|
+
return path.with_name(path.name + ".bak")
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
def _make_backup(path: Path) -> Path | None:
|
|
694
|
+
"""Copy ``path`` to ``<name>.bak``, preserving mode and mtime.
|
|
695
|
+
|
|
696
|
+
Returns the backup path, or ``None`` when the copy failed (the
|
|
697
|
+
caller then proceeds without a backup rather than refusing to write
|
|
698
|
+
— but the failure is not silent, see the ``reason`` plumbing).
|
|
699
|
+
|
|
700
|
+
The destination is unlinked first: ``shutil.copy2`` opens the
|
|
701
|
+
destination for writing and would happily follow a symlink planted
|
|
702
|
+
at ``<name>.bak``. ``Path.unlink`` removes the link itself, so the
|
|
703
|
+
subsequent copy always creates a fresh regular file.
|
|
552
704
|
"""
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
705
|
+
backup = _backup_path(path)
|
|
706
|
+
try:
|
|
707
|
+
backup.unlink(missing_ok=True)
|
|
708
|
+
shutil.copy2(path, backup)
|
|
709
|
+
except OSError:
|
|
710
|
+
return None
|
|
711
|
+
return backup
|
|
556
712
|
|
|
557
713
|
|
|
558
714
|
def _text_diff(old: str, new: str, path: Path) -> str:
|
|
@@ -64,6 +64,25 @@ coderouter vscode-init [--target PATH]
|
|
|
64
64
|
- `--dry-run`: `.vscode/settings.json` に何を書くかの unified diff だけ表示
|
|
65
65
|
- `--force`: 既存の `ANTHROPIC_BASE_URL` などが違う値だったとき上書き(既定はコンフリクト報告のみで書かない)
|
|
66
66
|
|
|
67
|
+
#### `--force` の挙動(v2.11 以降)
|
|
68
|
+
|
|
69
|
+
`--force` の効き方は 2 つのファイルで異なります。
|
|
70
|
+
|
|
71
|
+
- `.vscode/settings.json`: **マージ**です。CodeRouter が管理する 3 キー(`ANTHROPIC_BASE_URL` / `ANTHROPIC_AUTH_TOKEN` / `CODEROUTER_MODE`)だけを上書きし、`editor.fontSize` などの無関係なキーはそのまま残ります
|
|
72
|
+
- `.envrc`: **ファイル全体の置き換え**です。マージはしません。手で足した行(例: `source_env_if_exists .envrc.local`)は生成内容には含まれないため消えます
|
|
73
|
+
|
|
74
|
+
そのため、既存ファイルを書き換える前に**必ず退避コピーを作ります**。
|
|
75
|
+
|
|
76
|
+
| 元のファイル | 退避先 |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `.envrc` | `.envrc.bak` |
|
|
79
|
+
| `.vscode/settings.json` | `.vscode/settings.json.bak` |
|
|
80
|
+
|
|
81
|
+
- 退避先のパスは実行結果の `→` 行に表示されます
|
|
82
|
+
- `--dry-run` では退避も含めて**一切ファイルを作りません**
|
|
83
|
+
- `.bak` は毎回上書きされます。2 世代前は残らないので、重要な `.envrc` は `--force` の前に自分でも控えを取るか、素直に `.envrc.local` へ分離してください
|
|
84
|
+
- `*.bak` はプロジェクトの `.gitignore` に入れておくことを推奨します
|
|
85
|
+
|
|
67
86
|
### 再実行しても壊れない
|
|
68
87
|
|
|
69
88
|
`vscode-init` は冪等です。同じ引数で再実行すれば `unchanged` を報告して終了。異なる値と衝突した場合は `conflict` を出して**ファイルに触りません**(`--force` 必要)。オンボーディングスクリプトに含めて安全です。
|
|
@@ -76,6 +95,13 @@ coderouter vscode-init --with-envrc
|
|
|
76
95
|
|
|
77
96
|
これで `.envrc` も生成されます。生成後に **`direnv allow`** を 1 回実行してください。シークレットは `.envrc.local` に分けて `source_env_if_exists .envrc.local` の形が安全です(`.envrc` はプロジェクトに commit することが多いので)。
|
|
78
97
|
|
|
98
|
+
パーミッションについて(v2.11 以降):
|
|
99
|
+
|
|
100
|
+
- **新規生成した `.envrc` は `0600`(所有者のみ読み書き)**で作られます。`ANTHROPIC_AUTH_TOKEN` を含むファイルなので、`coderouter --check-env` が `.env` に課しているのと同じ基準に揃えてあります
|
|
101
|
+
- **既存ファイルを上書きするときは、そのファイルの現在の mode をそのまま維持**します(以前は `os.replace` で umask 既定=多くの環境で `0644` に戻っていました)
|
|
102
|
+
- Windows では POSIX の mode ビットに意味がないため、mode の設定・維持は行いません
|
|
103
|
+
- なお `--force` で `source_env_if_exists .envrc.local` の行は消えます。消える前の内容は `.envrc.bak` に残るので、必要な行はそこから戻してください
|
|
104
|
+
|
|
79
105
|
### 注意点
|
|
80
106
|
|
|
81
107
|
- `.vscode/` は CodeRouter リポジトリの `.gitignore` に既に入っているため、`.vscode/settings.json` は既定で **git に含まれません**。あなたのプロジェクトの `.gitignore` は個別に確認してください
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# in plan.md §11.B; once granted, this name will become an alias and
|
|
12
12
|
# `coderouter` will become the canonical distribution name.
|
|
13
13
|
name = "coderouter-cli"
|
|
14
|
-
version = "2.11.
|
|
14
|
+
version = "2.11.2"
|
|
15
15
|
description = "Local-first, free-first, fallback-built-in LLM router. Claude Code / OpenAI compatible."
|
|
16
16
|
readme = "README.md"
|
|
17
17
|
requires-python = ">=3.12"
|
|
@@ -5,8 +5,10 @@ bench-sweep pure logic was deliberately factored into Tk-independent
|
|
|
5
5
|
module-level functions / dataclasses (mirroring the readiness/auto-restart
|
|
6
6
|
split covered by tests/test_launcher_gui_readiness.py) so every case below
|
|
7
7
|
exercises them without ever instantiating ``tk.Tk()`` / ``LauncherApp`` /
|
|
8
|
-
``SweepWindow`` — no display server
|
|
9
|
-
|
|
8
|
+
``SweepWindow`` — no display server needed. The importorskip below is not
|
|
9
|
+
for CI: uv-managed CPython does ship tkinter, so these tests really do run
|
|
10
|
+
there. It is for local system pythons built without Tk, which would
|
|
11
|
+
otherwise fail at collection time.
|
|
10
12
|
|
|
11
13
|
Subprocess spawning is fully mocked: no real llama-server / llmbench ever
|
|
12
14
|
runs. The shared, frozen core logic lives in coderouter.launcher_devices and
|