cade-cli 0.16.2__tar.gz → 0.16.4__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.
- {cade_cli-0.16.2 → cade_cli-0.16.4}/PKG-INFO +2 -2
- {cade_cli-0.16.2 → cade_cli-0.16.4}/README.md +1 -1
- {cade_cli-0.16.2 → cade_cli-0.16.4}/pyproject.toml +1 -1
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/cli/commands/mcp.py +64 -0
- cade_cli-0.16.4/src/cadecoder/cli/mcp_hosts.py +351 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/cli/onboarding.py +16 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/providers/anthropic.py +20 -2
- cade_cli-0.16.4/src/cadecoder/providers/http_client.py +52 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/providers/openai.py +25 -2
- {cade_cli-0.16.2 → cade_cli-0.16.4}/.gitignore +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/LICENSE +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/__main__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/_metadata.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/config.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/errors.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/grants.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/resources.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/sandbox.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/server.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/tools/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/tools/_read_cache.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/tools/access.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/tools/agents.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/tools/context.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/tools/filesystem.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/tools/git.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/tools/notifications.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/tools/questions.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/tools/results.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/tools/schemas.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/tools/search.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/tools/shell.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/tools/snippets.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/tools/tasks.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/tools/web_fetch.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/tools/web_search.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/utils.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/web/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/web/arcade_client.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/web/cache.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/web/extract.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/web/markdown.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/web/preapproved.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/web/prompts.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/web/scrape.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/web/search/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/web/search/arcade.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/web/search/base.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cade_mcp_local/web/search/http.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/ai/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/ai/prompts.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/cli/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/cli/app.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/cli/auth.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/cli/commands/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/cli/commands/account.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/cli/commands/agents.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/cli/commands/auth.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/cli/commands/chat.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/cli/commands/cron.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/cli/commands/daemon.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/cli/commands/grant.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/cli/commands/hooks.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/cli/commands/mem.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/cli/commands/tasks.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/cli/commands/thread.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/cli/commands/tools.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/core/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/core/agent.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/core/comms.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/core/config.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/core/constants.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/core/errors.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/core/frozen.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/core/git.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/core/inbox.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/core/logging.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/core/memory.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/core/model_specs.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/core/names.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/core/paths.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/core/runtime.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/core/secrets.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/core/spawn.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/core/types.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/adapters/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/adapters/base.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/adapters/factory.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/adapters/imessage.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/adapters/registry.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/adapters/stub.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/adapters/telegram.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/allowlist.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/attach.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/chunker.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/client.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/commands.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/config.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/control.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/cron.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/elicitation.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/ensure.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/format.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/gateway/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/gateway/app.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/gateway/arcade_export.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/gateway/auth.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/gateway/constants.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/gateway/elicitation.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/gateway/exposure.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/gateway/limits.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/gateway/middleware.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/gateway/resources.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/gateway/subscriptions.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/gateway/turn_bridge.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/grants.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/inbox_cron.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/install.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/live.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/lock.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/mesh.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/message_pump.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/observability.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/owner.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/progress_sink.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/registry.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/router.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/runner.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/runner_main.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/secrets.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/secrets_broker.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/server.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/services.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/shim.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/sinks.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/supervisor.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/tokens.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/traffic.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/turns.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/daemon/wake.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/execution/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/execution/context_window.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/execution/mcp_schema_index.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/execution/orchestrator.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/execution/parallel.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/execution/tool_result_store.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/execution/tool_schema_cache.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/hooks/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/hooks/config.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/hooks/engine.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/hooks/executors/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/hooks/executors/callback.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/hooks/executors/command.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/hooks/executors/http.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/hooks/executors/prompt.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/hooks/executors/ssrf_guard.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/hooks/matchers.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/hooks/registry.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/hooks/types.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/providers/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/providers/base.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/providers/ollama.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/serve/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/storage/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/storage/threads.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/tasks/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/tasks/scheduler.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/tasks/store.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/tasks/types.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/templates/login_failed.html +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/templates/login_success.html +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/templates/serve/launchd.plist.tmpl +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/templates/serve/serve.toml.example +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/templates/serve/systemd.service.tmpl +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/templates/styles.css +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/tools/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/tools/manager/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/tools/manager/_request_ctx.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/tools/manager/base.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/tools/manager/composite.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/tools/manager/config.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/tools/manager/mcp.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/tools/search/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/tools/search/discovered.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/tools/search/scoring.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/tools/search/service.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/ui/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/ui/completion.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/ui/display.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/ui/elicitation.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/ui/input.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/ui/session.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/ui/slash.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/voice/__init__.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/voice/audio.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/voice/cleanup.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/voice/session.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/voice/stt.py +0 -0
- {cade_cli-0.16.2 → cade_cli-0.16.4}/src/cadecoder/voice/tts.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cade-cli
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.4
|
|
4
4
|
Summary: Cade - The CLI Agent from Arcade.dev (install: curl -fsSL https://arcadeagent.dev/install | sh)
|
|
5
5
|
Project-URL: Homepage, https://arcade.dev
|
|
6
6
|
Project-URL: Documentation, https://arcadeagent.dev/docs
|
|
@@ -129,7 +129,7 @@ The script detects your OS/arch, downloads the matching prebuilt binary
|
|
|
129
129
|
`~/.local/bin`. Pin a version or install location:
|
|
130
130
|
|
|
131
131
|
```bash
|
|
132
|
-
CADE_VERSION=0.16.
|
|
132
|
+
CADE_VERSION=0.16.4 CADE_INSTALL_DIR=/usr/local/bin \
|
|
133
133
|
sh -c "$(curl -fsSL https://arcadeagent.dev/install)"
|
|
134
134
|
```
|
|
135
135
|
|
|
@@ -38,7 +38,7 @@ The script detects your OS/arch, downloads the matching prebuilt binary
|
|
|
38
38
|
`~/.local/bin`. Pin a version or install location:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
CADE_VERSION=0.16.
|
|
41
|
+
CADE_VERSION=0.16.4 CADE_INSTALL_DIR=/usr/local/bin \
|
|
42
42
|
sh -c "$(curl -fsSL https://arcadeagent.dev/install)"
|
|
43
43
|
```
|
|
44
44
|
|
|
@@ -617,6 +617,70 @@ def authorize_server(
|
|
|
617
617
|
raise typer.Exit(1)
|
|
618
618
|
|
|
619
619
|
|
|
620
|
+
@mcp_app.command("install-hosts")
|
|
621
|
+
def install_hosts_cmd(
|
|
622
|
+
agent: Annotated[
|
|
623
|
+
str,
|
|
624
|
+
typer.Option("--agent", "-a", help="Agent the stdio shim targets."),
|
|
625
|
+
] = "default",
|
|
626
|
+
force: Annotated[
|
|
627
|
+
bool,
|
|
628
|
+
typer.Option("--force", "-f", help="Overwrite an existing cade MCP entry."),
|
|
629
|
+
] = False,
|
|
630
|
+
dry_run: Annotated[
|
|
631
|
+
bool,
|
|
632
|
+
typer.Option("--dry-run", help="Show what would change without writing."),
|
|
633
|
+
] = False,
|
|
634
|
+
) -> None:
|
|
635
|
+
"""Register Cade as an MCP server in Claude Desktop, Cursor, Claude Code, etc.
|
|
636
|
+
|
|
637
|
+
Only touches hosts that already look installed (their config dir/file exists
|
|
638
|
+
under your home directory). Safe to re-run — updates the command path after
|
|
639
|
+
upgrades. The curl installer runs this automatically unless
|
|
640
|
+
``CADE_SKIP_MCP_HOSTS=1``.
|
|
641
|
+
"""
|
|
642
|
+
from cadecoder.cli.mcp_hosts import install_into_hosts, resolve_cade_command
|
|
643
|
+
|
|
644
|
+
command = resolve_cade_command()
|
|
645
|
+
updates = install_into_hosts(command=command, agent=agent, force=force, dry_run=dry_run)
|
|
646
|
+
if not updates:
|
|
647
|
+
console.print(
|
|
648
|
+
"[dim]No MCP hosts detected (looked for Cursor, Claude Desktop, "
|
|
649
|
+
"Claude Code, OpenCode, Windsurf).[/dim]"
|
|
650
|
+
)
|
|
651
|
+
console.print(
|
|
652
|
+
"[dim]After installing a host, re-run [bold]cade mcp install-hosts[/bold].[/dim]"
|
|
653
|
+
)
|
|
654
|
+
return
|
|
655
|
+
|
|
656
|
+
console.print(f"[dim]Using binary:[/dim] {command}")
|
|
657
|
+
changed = 0
|
|
658
|
+
for u in updates:
|
|
659
|
+
style = {
|
|
660
|
+
"added": "green",
|
|
661
|
+
"updated": "cyan",
|
|
662
|
+
"unchanged": "dim",
|
|
663
|
+
"would-added": "yellow",
|
|
664
|
+
"would-updated": "yellow",
|
|
665
|
+
"error": "red",
|
|
666
|
+
"skipped": "dim",
|
|
667
|
+
}.get(u.status, "white")
|
|
668
|
+
msg = f"[{style}]{u.status}[/{style}] {u.name} [dim]{u.path}[/dim]"
|
|
669
|
+
if u.detail:
|
|
670
|
+
msg += f" ({u.detail})"
|
|
671
|
+
console.print(msg)
|
|
672
|
+
if u.status in ("added", "updated", "would-added", "would-updated"):
|
|
673
|
+
changed += 1
|
|
674
|
+
|
|
675
|
+
if changed and not dry_run:
|
|
676
|
+
console.print(
|
|
677
|
+
"\n[green]Done.[/green] Restart those apps, then run "
|
|
678
|
+
"[bold]cade daemon start[/bold] (or just [bold]cade[/bold] — it autostarts)."
|
|
679
|
+
)
|
|
680
|
+
elif dry_run:
|
|
681
|
+
console.print(f"\n[dim]Dry run — {changed} host(s) would change.[/dim]")
|
|
682
|
+
|
|
683
|
+
|
|
620
684
|
@mcp_app.command("token-info")
|
|
621
685
|
def token_info(
|
|
622
686
|
name: Annotated[str, typer.Argument(help="Name of the server")],
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"""Register Cade as an MCP server in detected AI host apps.
|
|
2
|
+
|
|
3
|
+
Scans typical home-directory config locations for Claude Desktop, Cursor,
|
|
4
|
+
Claude Code, OpenCode, and Windsurf. When a host's config dir (or file)
|
|
5
|
+
already exists, merges a ``cade`` stdio entry that points at
|
|
6
|
+
``cade __mcp-adapter`` — using an absolute path so GUI apps find the binary
|
|
7
|
+
even when ``PATH`` is incomplete.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import platform
|
|
15
|
+
import re
|
|
16
|
+
import shutil
|
|
17
|
+
import sys
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from enum import Enum
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
SERVER_KEY = "cade"
|
|
25
|
+
DEFAULT_AGENT = "default"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class HostKind(str, Enum):
|
|
29
|
+
"""How a host stores MCP server entries."""
|
|
30
|
+
|
|
31
|
+
MCP_SERVERS = "mcpServers" # Claude Desktop / Cursor / Claude Code
|
|
32
|
+
OPENCODE = "opencode" # OpenCode ``mcp`` map with type/command
|
|
33
|
+
WINDSURF = "windsurf" # Windsurf mcp_config.json
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True)
|
|
37
|
+
class HostTarget:
|
|
38
|
+
"""One MCP host config file we may update."""
|
|
39
|
+
|
|
40
|
+
name: str
|
|
41
|
+
path: Path
|
|
42
|
+
kind: HostKind
|
|
43
|
+
# Parent path that must exist (app installed / used before).
|
|
44
|
+
marker: Path
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass(frozen=True)
|
|
48
|
+
class HostUpdate:
|
|
49
|
+
"""Result of attempting to register Cade with one host."""
|
|
50
|
+
|
|
51
|
+
name: str
|
|
52
|
+
path: Path
|
|
53
|
+
status: str # added | updated | unchanged | skipped | error
|
|
54
|
+
detail: str = ""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def resolve_cade_command() -> str:
|
|
58
|
+
"""Absolute path to the ``cade`` binary when possible."""
|
|
59
|
+
if getattr(sys, "frozen", False):
|
|
60
|
+
return str(Path(sys.executable).resolve())
|
|
61
|
+
which = shutil.which("cade")
|
|
62
|
+
if which:
|
|
63
|
+
return str(Path(which).resolve())
|
|
64
|
+
# Dev / module invocation fallback — hosts need a real executable.
|
|
65
|
+
return sys.executable
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def stdio_entry(*, command: str | None = None, agent: str = DEFAULT_AGENT) -> dict[str, Any]:
|
|
69
|
+
"""Claude/Cursor/Claude Code mcpServers entry."""
|
|
70
|
+
cmd = command or resolve_cade_command()
|
|
71
|
+
return {
|
|
72
|
+
"command": cmd,
|
|
73
|
+
"args": ["__mcp-adapter", "--agent", agent],
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def opencode_entry(*, command: str | None = None, agent: str = DEFAULT_AGENT) -> dict[str, Any]:
|
|
78
|
+
"""OpenCode ``mcp.<name>`` local server entry."""
|
|
79
|
+
cmd = command or resolve_cade_command()
|
|
80
|
+
return {
|
|
81
|
+
"type": "local",
|
|
82
|
+
"command": [cmd, "__mcp-adapter", "--agent", agent],
|
|
83
|
+
"enabled": True,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def discover_hosts(home: Path | None = None) -> list[HostTarget]:
|
|
88
|
+
"""Return host configs that look installed (marker path exists)."""
|
|
89
|
+
home = home or Path.home()
|
|
90
|
+
system = platform.system()
|
|
91
|
+
targets: list[HostTarget] = []
|
|
92
|
+
|
|
93
|
+
# Cursor (all platforms)
|
|
94
|
+
cursor_dir = home / ".cursor"
|
|
95
|
+
targets.append(
|
|
96
|
+
HostTarget(
|
|
97
|
+
name="Cursor",
|
|
98
|
+
path=cursor_dir / "mcp.json",
|
|
99
|
+
kind=HostKind.MCP_SERVERS,
|
|
100
|
+
marker=cursor_dir,
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Claude Code
|
|
105
|
+
claude_json = home / ".claude.json"
|
|
106
|
+
claude_dir = home / ".claude"
|
|
107
|
+
targets.append(
|
|
108
|
+
HostTarget(
|
|
109
|
+
name="Claude Code",
|
|
110
|
+
path=claude_json,
|
|
111
|
+
kind=HostKind.MCP_SERVERS,
|
|
112
|
+
marker=claude_json if claude_json.exists() else claude_dir,
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Claude Desktop
|
|
117
|
+
if system == "Darwin":
|
|
118
|
+
desktop_dir = home / "Library" / "Application Support" / "Claude"
|
|
119
|
+
elif system == "Windows":
|
|
120
|
+
appdata = os.environ.get("APPDATA", str(home / "AppData" / "Roaming"))
|
|
121
|
+
desktop_dir = Path(appdata) / "Claude"
|
|
122
|
+
else:
|
|
123
|
+
desktop_dir = home / ".config" / "Claude"
|
|
124
|
+
targets.append(
|
|
125
|
+
HostTarget(
|
|
126
|
+
name="Claude Desktop",
|
|
127
|
+
path=desktop_dir / "claude_desktop_config.json",
|
|
128
|
+
kind=HostKind.MCP_SERVERS,
|
|
129
|
+
marker=desktop_dir,
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# OpenCode — prefer XDG config; fall back to ~/.opencode
|
|
134
|
+
opencode_xdg = home / ".config" / "opencode"
|
|
135
|
+
opencode_legacy = home / ".opencode"
|
|
136
|
+
if opencode_xdg.exists():
|
|
137
|
+
# Prefer .json over .jsonc (we write strict JSON).
|
|
138
|
+
oc_path = opencode_xdg / "opencode.json"
|
|
139
|
+
if not oc_path.exists() and (opencode_xdg / "opencode.jsonc").exists():
|
|
140
|
+
oc_path = opencode_xdg / "opencode.jsonc"
|
|
141
|
+
targets.append(
|
|
142
|
+
HostTarget(
|
|
143
|
+
name="OpenCode",
|
|
144
|
+
path=oc_path,
|
|
145
|
+
kind=HostKind.OPENCODE,
|
|
146
|
+
marker=opencode_xdg,
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
elif opencode_legacy.exists():
|
|
150
|
+
oc_path = opencode_legacy / "opencode.json"
|
|
151
|
+
targets.append(
|
|
152
|
+
HostTarget(
|
|
153
|
+
name="OpenCode",
|
|
154
|
+
path=oc_path,
|
|
155
|
+
kind=HostKind.OPENCODE,
|
|
156
|
+
marker=opencode_legacy,
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Windsurf
|
|
161
|
+
windsurf = home / ".codeium" / "windsurf" / "mcp_config.json"
|
|
162
|
+
targets.append(
|
|
163
|
+
HostTarget(
|
|
164
|
+
name="Windsurf",
|
|
165
|
+
path=windsurf,
|
|
166
|
+
kind=HostKind.WINDSURF,
|
|
167
|
+
marker=home / ".codeium" / "windsurf",
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
return [t for t in targets if t.marker.exists()]
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _strip_jsonc(text: str) -> str:
|
|
175
|
+
"""Best-effort strip of // and /* */ comments for JSONC files."""
|
|
176
|
+
# Remove /* ... */ blocks
|
|
177
|
+
text = re.sub(r"/\*.*?\*/", "", text, flags=re.DOTALL)
|
|
178
|
+
# Remove // line comments (not inside strings — good enough for config)
|
|
179
|
+
out_lines: list[str] = []
|
|
180
|
+
for line in text.splitlines():
|
|
181
|
+
in_str = False
|
|
182
|
+
escaped = False
|
|
183
|
+
cut = len(line)
|
|
184
|
+
for i, ch in enumerate(line):
|
|
185
|
+
if escaped:
|
|
186
|
+
escaped = False
|
|
187
|
+
continue
|
|
188
|
+
if ch == "\\":
|
|
189
|
+
escaped = True
|
|
190
|
+
continue
|
|
191
|
+
if ch == '"':
|
|
192
|
+
in_str = not in_str
|
|
193
|
+
continue
|
|
194
|
+
if not in_str and ch == "/" and i + 1 < len(line) and line[i + 1] == "/":
|
|
195
|
+
cut = i
|
|
196
|
+
break
|
|
197
|
+
out_lines.append(line[:cut])
|
|
198
|
+
return "\n".join(out_lines)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _load_config(path: Path) -> dict[str, Any]:
|
|
202
|
+
if not path.exists():
|
|
203
|
+
return {}
|
|
204
|
+
raw = path.read_text(encoding="utf-8")
|
|
205
|
+
if path.suffix == ".jsonc":
|
|
206
|
+
raw = _strip_jsonc(raw)
|
|
207
|
+
data = json.loads(raw) if raw.strip() else {}
|
|
208
|
+
if not isinstance(data, dict):
|
|
209
|
+
raise ValueError(f"{path} root must be a JSON object")
|
|
210
|
+
return data
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _write_config(path: Path, data: dict[str, Any], *, dry_run: bool) -> None:
|
|
214
|
+
if dry_run:
|
|
215
|
+
return
|
|
216
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
217
|
+
if path.exists():
|
|
218
|
+
backup = path.with_suffix(path.suffix + ".bak")
|
|
219
|
+
shutil.copy2(path, backup)
|
|
220
|
+
# Always write .json when source was .jsonc so we don't re-emit comments.
|
|
221
|
+
out = path if path.suffix != ".jsonc" else path.with_suffix(".json")
|
|
222
|
+
out.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _merge_mcp_servers(
|
|
226
|
+
data: dict[str, Any],
|
|
227
|
+
entry: dict[str, Any],
|
|
228
|
+
*,
|
|
229
|
+
force: bool,
|
|
230
|
+
) -> tuple[dict[str, Any], str]:
|
|
231
|
+
servers = data.get("mcpServers")
|
|
232
|
+
if servers is None:
|
|
233
|
+
servers = {}
|
|
234
|
+
data["mcpServers"] = servers
|
|
235
|
+
if not isinstance(servers, dict):
|
|
236
|
+
raise ValueError("mcpServers must be an object")
|
|
237
|
+
|
|
238
|
+
existing = servers.get(SERVER_KEY)
|
|
239
|
+
if existing == entry:
|
|
240
|
+
return data, "unchanged"
|
|
241
|
+
if existing is not None and not force:
|
|
242
|
+
# Update in place when only the command path drifted (common after upgrade).
|
|
243
|
+
if isinstance(existing, dict) and existing.get("args") == entry.get("args"):
|
|
244
|
+
servers[SERVER_KEY] = entry
|
|
245
|
+
return data, "updated"
|
|
246
|
+
if isinstance(existing, dict):
|
|
247
|
+
servers[SERVER_KEY] = entry
|
|
248
|
+
return data, "updated"
|
|
249
|
+
servers[SERVER_KEY] = entry
|
|
250
|
+
return data, "added" if existing is None else "updated"
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _merge_opencode(
|
|
254
|
+
data: dict[str, Any],
|
|
255
|
+
entry: dict[str, Any],
|
|
256
|
+
*,
|
|
257
|
+
force: bool,
|
|
258
|
+
) -> tuple[dict[str, Any], str]:
|
|
259
|
+
mcp = data.get("mcp")
|
|
260
|
+
if mcp is None:
|
|
261
|
+
mcp = {}
|
|
262
|
+
data["mcp"] = mcp
|
|
263
|
+
if not isinstance(mcp, dict):
|
|
264
|
+
raise ValueError("mcp must be an object")
|
|
265
|
+
existing = mcp.get(SERVER_KEY)
|
|
266
|
+
if existing == entry:
|
|
267
|
+
return data, "unchanged"
|
|
268
|
+
if existing is not None and not force:
|
|
269
|
+
mcp[SERVER_KEY] = entry
|
|
270
|
+
return data, "updated"
|
|
271
|
+
mcp[SERVER_KEY] = entry
|
|
272
|
+
return data, "added" if existing is None else "updated"
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _merge_windsurf(
|
|
276
|
+
data: dict[str, Any],
|
|
277
|
+
entry: dict[str, Any],
|
|
278
|
+
*,
|
|
279
|
+
force: bool,
|
|
280
|
+
) -> tuple[dict[str, Any], str]:
|
|
281
|
+
# Windsurf uses the same mcpServers shape as Cursor in current builds.
|
|
282
|
+
return _merge_mcp_servers(data, entry, force=force)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def register_host(
|
|
286
|
+
target: HostTarget,
|
|
287
|
+
*,
|
|
288
|
+
command: str | None = None,
|
|
289
|
+
agent: str = DEFAULT_AGENT,
|
|
290
|
+
force: bool = False,
|
|
291
|
+
dry_run: bool = False,
|
|
292
|
+
) -> HostUpdate:
|
|
293
|
+
"""Merge the Cade MCP entry into one host config."""
|
|
294
|
+
cmd = command or resolve_cade_command()
|
|
295
|
+
try:
|
|
296
|
+
data = _load_config(target.path)
|
|
297
|
+
if target.kind == HostKind.OPENCODE:
|
|
298
|
+
entry = opencode_entry(command=cmd, agent=agent)
|
|
299
|
+
data, status = _merge_opencode(data, entry, force=force)
|
|
300
|
+
elif target.kind in (HostKind.MCP_SERVERS, HostKind.WINDSURF):
|
|
301
|
+
entry = stdio_entry(command=cmd, agent=agent)
|
|
302
|
+
merge = _merge_windsurf if target.kind == HostKind.WINDSURF else _merge_mcp_servers
|
|
303
|
+
data, status = merge(data, entry, force=force)
|
|
304
|
+
else:
|
|
305
|
+
return HostUpdate(target.name, target.path, "skipped", "unknown host kind")
|
|
306
|
+
|
|
307
|
+
if status != "unchanged":
|
|
308
|
+
_write_config(target.path, data, dry_run=dry_run)
|
|
309
|
+
if dry_run:
|
|
310
|
+
status = f"would-{status}"
|
|
311
|
+
return HostUpdate(target.name, target.path, status)
|
|
312
|
+
except Exception as e: # noqa: BLE001
|
|
313
|
+
return HostUpdate(target.name, target.path, "error", str(e))
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def install_into_hosts(
|
|
317
|
+
*,
|
|
318
|
+
home: Path | None = None,
|
|
319
|
+
command: str | None = None,
|
|
320
|
+
agent: str = DEFAULT_AGENT,
|
|
321
|
+
force: bool = False,
|
|
322
|
+
dry_run: bool = False,
|
|
323
|
+
) -> list[HostUpdate]:
|
|
324
|
+
"""Discover installed hosts and register Cade with each."""
|
|
325
|
+
updates: list[HostUpdate] = []
|
|
326
|
+
for target in discover_hosts(home):
|
|
327
|
+
updates.append(
|
|
328
|
+
register_host(
|
|
329
|
+
target,
|
|
330
|
+
command=command,
|
|
331
|
+
agent=agent,
|
|
332
|
+
force=force,
|
|
333
|
+
dry_run=dry_run,
|
|
334
|
+
)
|
|
335
|
+
)
|
|
336
|
+
return updates
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
__all__ = [
|
|
340
|
+
"HostKind",
|
|
341
|
+
"HostTarget",
|
|
342
|
+
"HostUpdate",
|
|
343
|
+
"DEFAULT_AGENT",
|
|
344
|
+
"SERVER_KEY",
|
|
345
|
+
"discover_hosts",
|
|
346
|
+
"install_into_hosts",
|
|
347
|
+
"opencode_entry",
|
|
348
|
+
"register_host",
|
|
349
|
+
"resolve_cade_command",
|
|
350
|
+
"stdio_entry",
|
|
351
|
+
]
|
|
@@ -312,6 +312,22 @@ def finish_first_run_cloud(console: Console, *, prefer_skip_login: bool = False)
|
|
|
312
312
|
"[bold]cade mcp authorize hub[/bold].[/dim]"
|
|
313
313
|
)
|
|
314
314
|
|
|
315
|
+
# Register Cade in Claude Desktop / Cursor / Claude Code / … when present.
|
|
316
|
+
try:
|
|
317
|
+
from cadecoder.cli.mcp_hosts import install_into_hosts
|
|
318
|
+
|
|
319
|
+
updates = install_into_hosts()
|
|
320
|
+
touched = [u for u in updates if u.status in ("added", "updated")]
|
|
321
|
+
if touched:
|
|
322
|
+
names = ", ".join(u.name for u in touched)
|
|
323
|
+
console.print(
|
|
324
|
+
f"\n[green]✓[/green] Registered Cade as an MCP server in: {names}\n"
|
|
325
|
+
"[dim]Restart those apps, and keep [bold]cade daemon[/bold] running "
|
|
326
|
+
"(or just use [bold]cade[/bold] — it autostarts).[/dim]"
|
|
327
|
+
)
|
|
328
|
+
except Exception: # noqa: BLE001
|
|
329
|
+
pass
|
|
330
|
+
|
|
315
331
|
|
|
316
332
|
def run_onboarding(console: Console, *, force: bool = False) -> OnboardingResult:
|
|
317
333
|
"""Run the interactive model-setup wizard (step 1 of first-run).
|
|
@@ -31,6 +31,11 @@ from cadecoder.providers.base import (
|
|
|
31
31
|
StreamEvent,
|
|
32
32
|
stream_with_retry,
|
|
33
33
|
)
|
|
34
|
+
from cadecoder.providers.http_client import (
|
|
35
|
+
is_decompress_error,
|
|
36
|
+
make_async_http_client,
|
|
37
|
+
make_sync_http_client,
|
|
38
|
+
)
|
|
34
39
|
|
|
35
40
|
# HTTP statuses from Anthropic that indicate a transient server-side issue
|
|
36
41
|
# and are safe to retry. 529 is Anthropic's "Overloaded" code; 408/429/5xx
|
|
@@ -87,13 +92,18 @@ class AnthropicProvider(Provider):
|
|
|
87
92
|
if not self.api_key:
|
|
88
93
|
raise ProviderError("Anthropic API key not configured", ProviderType.ANTHROPIC)
|
|
89
94
|
|
|
90
|
-
|
|
95
|
+
# Own the httpx client so we can pin Accept-Encoding (avoids brotli /
|
|
96
|
+
# zlib "incorrect header check" failures in frozen PyInstaller builds).
|
|
97
|
+
self._http_client = make_async_http_client()
|
|
98
|
+
self.client = AsyncAnthropic(api_key=self.api_key, http_client=self._http_client)
|
|
99
|
+
self._sync_http_client = None
|
|
91
100
|
self._sync_client: Anthropic | None = None
|
|
92
101
|
|
|
93
102
|
def _get_sync_client(self) -> Anthropic:
|
|
94
103
|
"""Get or create synchronous client for model fetching."""
|
|
95
104
|
if self._sync_client is None:
|
|
96
|
-
self.
|
|
105
|
+
self._sync_http_client = make_sync_http_client()
|
|
106
|
+
self._sync_client = Anthropic(api_key=self.api_key, http_client=self._sync_http_client)
|
|
97
107
|
return self._sync_client
|
|
98
108
|
|
|
99
109
|
@property
|
|
@@ -220,6 +230,9 @@ class AnthropicProvider(Provider):
|
|
|
220
230
|
return ErrorClass.TRANSIENT
|
|
221
231
|
if isinstance(exc, APIStatusError) and exc.status_code in _RETRIABLE_STATUSES:
|
|
222
232
|
return ErrorClass.TRANSIENT
|
|
233
|
+
# Broken Content-Encoding on SSE streams (seen in frozen builds).
|
|
234
|
+
if is_decompress_error(exc):
|
|
235
|
+
return ErrorClass.TRANSIENT
|
|
223
236
|
return ErrorClass.PERMANENT
|
|
224
237
|
|
|
225
238
|
def format_error_for_user(self, exc: BaseException) -> str:
|
|
@@ -233,6 +246,11 @@ class AnthropicProvider(Provider):
|
|
|
233
246
|
return f"Anthropic service error ({exc.status_code}) — please retry."
|
|
234
247
|
if isinstance(exc, APITimeoutError | APIConnectionError):
|
|
235
248
|
return "Network error talking to Anthropic — please retry."
|
|
249
|
+
if is_decompress_error(exc):
|
|
250
|
+
return (
|
|
251
|
+
"Network/encoding error talking to Anthropic (response decompress failed) — "
|
|
252
|
+
"please retry."
|
|
253
|
+
)
|
|
236
254
|
return str(exc)
|
|
237
255
|
|
|
238
256
|
def stream(self, request: ProviderRequest) -> AsyncIterator[StreamEvent]:
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Shared httpx client helpers for LLM providers.
|
|
2
|
+
|
|
3
|
+
Frozen/PyInstaller builds have hit ``zlib.error: Error -3 while decompressing
|
|
4
|
+
data: incorrect header check`` on Anthropic SSE streams when httpx negotiates
|
|
5
|
+
brotli (``br``) or otherwise mishandles ``Content-Encoding``. Pinning
|
|
6
|
+
``Accept-Encoding`` to gzip/deflate avoids that path while keeping compression.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
# Do not advertise ``br`` / ``zstd`` — those decoders are fragile in frozen
|
|
14
|
+
# binaries and have produced "incorrect header check" failures on streams.
|
|
15
|
+
_SAFE_ACCEPT_ENCODING = "gzip, deflate"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def make_async_http_client(*, timeout: float = 600.0) -> httpx.AsyncClient:
|
|
19
|
+
"""Return an AsyncClient safe for Anthropic/OpenAI streaming in frozen apps."""
|
|
20
|
+
return httpx.AsyncClient(
|
|
21
|
+
headers={"Accept-Encoding": _SAFE_ACCEPT_ENCODING},
|
|
22
|
+
timeout=httpx.Timeout(timeout, connect=10.0),
|
|
23
|
+
follow_redirects=True,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def make_sync_http_client(*, timeout: float = 60.0) -> httpx.Client:
|
|
28
|
+
"""Return a sync Client with the same encoding constraints."""
|
|
29
|
+
return httpx.Client(
|
|
30
|
+
headers={"Accept-Encoding": _SAFE_ACCEPT_ENCODING},
|
|
31
|
+
timeout=httpx.Timeout(timeout, connect=10.0),
|
|
32
|
+
follow_redirects=True,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def is_decompress_error(exc: BaseException) -> bool:
|
|
37
|
+
"""True when ``exc`` looks like a broken Content-Encoding decode."""
|
|
38
|
+
import zlib
|
|
39
|
+
|
|
40
|
+
if isinstance(exc, zlib.error):
|
|
41
|
+
return True
|
|
42
|
+
if isinstance(exc, httpx.DecodingError):
|
|
43
|
+
return True
|
|
44
|
+
msg = str(exc).lower()
|
|
45
|
+
return "incorrect header check" in msg or "while decompressing" in msg
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
__all__ = [
|
|
49
|
+
"is_decompress_error",
|
|
50
|
+
"make_async_http_client",
|
|
51
|
+
"make_sync_http_client",
|
|
52
|
+
]
|
|
@@ -30,6 +30,11 @@ from cadecoder.providers.base import (
|
|
|
30
30
|
StreamEvent,
|
|
31
31
|
stream_with_retry,
|
|
32
32
|
)
|
|
33
|
+
from cadecoder.providers.http_client import (
|
|
34
|
+
is_decompress_error,
|
|
35
|
+
make_async_http_client,
|
|
36
|
+
make_sync_http_client,
|
|
37
|
+
)
|
|
33
38
|
|
|
34
39
|
# Retriable HTTP statuses — 408 timeout, 429 rate limit, 500/502/503/504 5xx,
|
|
35
40
|
# 529 seen occasionally behind OpenAI-compatible proxies (Anthropic's code).
|
|
@@ -57,7 +62,13 @@ class OpenAIProvider(Provider):
|
|
|
57
62
|
if not self.api_key:
|
|
58
63
|
raise ProviderError("OpenAI API key not configured", ProviderType.OPENAI)
|
|
59
64
|
|
|
60
|
-
self.
|
|
65
|
+
self._http_client = make_async_http_client()
|
|
66
|
+
self.client = AsyncOpenAI(
|
|
67
|
+
api_key=self.api_key,
|
|
68
|
+
base_url=self.base_url,
|
|
69
|
+
http_client=self._http_client,
|
|
70
|
+
)
|
|
71
|
+
self._sync_http_client = None
|
|
61
72
|
self._sync_client: OpenAI | None = None
|
|
62
73
|
|
|
63
74
|
# Curated fallback list of OpenAI cloud chat models, used when the live API
|
|
@@ -83,7 +94,12 @@ class OpenAIProvider(Provider):
|
|
|
83
94
|
def _get_sync_client(self) -> OpenAI:
|
|
84
95
|
"""Get or create a synchronous client for model listing."""
|
|
85
96
|
if self._sync_client is None:
|
|
86
|
-
self.
|
|
97
|
+
self._sync_http_client = make_sync_http_client()
|
|
98
|
+
self._sync_client = OpenAI(
|
|
99
|
+
api_key=self.api_key,
|
|
100
|
+
base_url=self.base_url,
|
|
101
|
+
http_client=self._sync_http_client,
|
|
102
|
+
)
|
|
87
103
|
return self._sync_client
|
|
88
104
|
|
|
89
105
|
@property
|
|
@@ -173,6 +189,8 @@ class OpenAIProvider(Provider):
|
|
|
173
189
|
return ErrorClass.TRANSIENT
|
|
174
190
|
if isinstance(exc, APIStatusError) and exc.status_code in _RETRIABLE_STATUSES:
|
|
175
191
|
return ErrorClass.TRANSIENT
|
|
192
|
+
if is_decompress_error(exc):
|
|
193
|
+
return ErrorClass.TRANSIENT
|
|
176
194
|
return ErrorClass.PERMANENT
|
|
177
195
|
|
|
178
196
|
def format_error_for_user(self, exc: BaseException) -> str:
|
|
@@ -184,6 +202,11 @@ class OpenAIProvider(Provider):
|
|
|
184
202
|
return f"OpenAI service error ({exc.status_code}) — please retry."
|
|
185
203
|
if isinstance(exc, APITimeoutError | APIConnectionError):
|
|
186
204
|
return "Network error talking to OpenAI — please retry."
|
|
205
|
+
if is_decompress_error(exc):
|
|
206
|
+
return (
|
|
207
|
+
"Network/encoding error talking to OpenAI (response decompress failed) — "
|
|
208
|
+
"please retry."
|
|
209
|
+
)
|
|
187
210
|
return str(exc)
|
|
188
211
|
|
|
189
212
|
def stream(self, request: ProviderRequest) -> AsyncIterator[StreamEvent]:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|