cade-cli 0.21.0__tar.gz → 0.22.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.
- {cade_cli-0.21.0 → cade_cli-0.22.0}/PKG-INFO +2 -2
- {cade_cli-0.21.0 → cade_cli-0.22.0}/README.md +1 -1
- {cade_cli-0.21.0 → cade_cli-0.22.0}/pyproject.toml +1 -1
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/agents.py +1 -1
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/agents.py +76 -3
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/tools.py +106 -81
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/agent.py +22 -3
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/spawn.py +3 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/allowlist.py +34 -45
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/config.py +5 -3
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/message_pump.py +1 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/supervisor.py +3 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/execution/orchestrator.py +28 -2
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/providers/__init__.py +82 -10
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/providers/anthropic.py +26 -9
- cade_cli-0.22.0/src/cadecoder/providers/endpoint.py +57 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/providers/openai.py +3 -3
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/manager/composite.py +539 -313
- cade_cli-0.22.0/src/cadecoder/tools/manager/gating.py +252 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/manager/mcp.py +1 -1
- cade_cli-0.22.0/src/cadecoder/tools/manager/profiles.py +237 -0
- cade_cli-0.22.0/src/cadecoder/tools/manager/surface.py +203 -0
- cade_cli-0.21.0/src/cadecoder/tools/manager/surface.py +0 -353
- cade_cli-0.21.0/src/cadecoder/tools/profiles.py +0 -74
- {cade_cli-0.21.0 → cade_cli-0.22.0}/.gitignore +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/LICENSE +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/__main__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/_metadata.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/config.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/errors.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/grants.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/resources.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/sandbox.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/server.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/_read_cache.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/access.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/filesystem.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/git.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/notifications.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/questions.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/results.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/schemas.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/search.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/shell.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/tasks.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/web_fetch.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/web_search.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/utils.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/arcade_client.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/cache.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/extract.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/markdown.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/preapproved.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/prompts.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/scrape.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/search/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/search/arcade.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/search/base.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/search/http.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/ai/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/ai/prompts.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/app.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/auth.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/account.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/agent_group.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/auth.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/chat.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/cron.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/daemon.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/grant.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/hooks.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/mcp.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/mem.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/tasks.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/thread.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/mcp_hosts.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/onboarding.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/ui.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/binary.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/comms.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/config.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/constants.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/errors.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/frozen.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/git.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/inbox.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/logging.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/memory.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/model_specs.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/names.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/paths.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/runtime.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/secrets.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/text.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/types.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/adapters/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/adapters/base.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/adapters/factory.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/adapters/imessage.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/adapters/registry.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/adapters/stub.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/adapters/telegram.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/attach.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/chunker.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/client.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/commands.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/control.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/cron.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/elicitation.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/ensure.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/format.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/app.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/auth.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/constants.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/elicitation.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/exposure.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/host_agent.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/limits.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/middleware.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/principal.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/resources.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/subscriptions.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/turn_bridge.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/grants.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/inbox.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/inbox_cron.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/install.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/lock.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/mesh.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/observability.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/owner.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/progress_sink.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/registry.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/router.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/runner.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/runner_main.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/secrets.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/secrets_broker.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/server.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/services.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/sinks.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/tokens.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/traffic.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/turns.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/wake.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/execution/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/execution/context_window.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/execution/mcp_schema_index.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/execution/parallel.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/execution/tool_result_store.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/execution/tool_schema_cache.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/config.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/engine.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/executors/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/executors/callback.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/executors/command.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/executors/http.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/executors/prompt.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/executors/ssrf_guard.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/matchers.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/registry.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/types.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/.claude-plugin/marketplace.json +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/.claude-plugin/plugin.json +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/.cursor-plugin/marketplace.json +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/.cursor-plugin/plugin.json +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/agents/operator.md +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/commands/ask.md +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/commands/memory.md +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/commands/tasks.md +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/mcp.json +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/rules/gateway.mdc +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/skills/ask/SKILL.md +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/skills/memory/SKILL.md +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/skills/tasks/SKILL.md +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/doctor.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/install.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/paths.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/state.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/providers/base.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/providers/http_client.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/providers/ollama.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/storage/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/storage/threads.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tasks/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tasks/scheduler.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tasks/store.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tasks/types.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/telemetry/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/telemetry/otel.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/templates/login_failed.html +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/templates/login_success.html +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/templates/serve/launchd.plist.tmpl +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/templates/serve/serve.toml.example +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/templates/serve/systemd.service.tmpl +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/templates/styles.css +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/manager/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/manager/_request_ctx.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/manager/base.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/manager/config.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/merged.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/search/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/search/discovered.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/search/scoring.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/search/service.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/ui/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/ui/completion.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/ui/display.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/ui/elicitation.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/ui/input.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/ui/session.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/ui/slash.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/voice/__init__.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/voice/audio.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/voice/cleanup.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/voice/session.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/voice/stt.py +0 -0
- {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/voice/tts.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cade-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.22.0
|
|
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
|
|
@@ -352,7 +352,7 @@ Full layout, env vars, and TOML schema in [`docs/configuration.md`](./docs/confi
|
|
|
352
352
|
| Variable | Purpose |
|
|
353
353
|
|---|---|
|
|
354
354
|
| `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` | LLM provider keys |
|
|
355
|
-
| `OPENAI_BASE_URL` | Custom
|
|
355
|
+
| `OPENAI_BASE_URL` / `ANTHROPIC_BASE_URL` | Custom provider endpoint (gateway, proxy, compatible server); per agent: `cade agent edit <agent> --base-url <url>` |
|
|
356
356
|
| `ARCADE_API_KEY`, `ARCADE_BASE_URL` | Arcade Cloud (alternative to OAuth) |
|
|
357
357
|
| `CADE_LOCAL_ONLY=1` | Skip remote tools entirely |
|
|
358
358
|
| `CADE_HOME` | Override config directory |
|
|
@@ -256,7 +256,7 @@ Full layout, env vars, and TOML schema in [`docs/configuration.md`](./docs/confi
|
|
|
256
256
|
| Variable | Purpose |
|
|
257
257
|
|---|---|
|
|
258
258
|
| `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` | LLM provider keys |
|
|
259
|
-
| `OPENAI_BASE_URL` | Custom
|
|
259
|
+
| `OPENAI_BASE_URL` / `ANTHROPIC_BASE_URL` | Custom provider endpoint (gateway, proxy, compatible server); per agent: `cade agent edit <agent> --base-url <url>` |
|
|
260
260
|
| `ARCADE_API_KEY`, `ARCADE_BASE_URL` | Arcade Cloud (alternative to OAuth) |
|
|
261
261
|
| `CADE_LOCAL_ONLY=1` | Skip remote tools entirely |
|
|
262
262
|
| `CADE_HOME` | Override config directory |
|
|
@@ -4,7 +4,7 @@ One ``Agent`` tool with an ``action`` (send | list | spawn), replacing the
|
|
|
4
4
|
former AgentSend / AgentList / AgentSpawn tools; the host's composite tool
|
|
5
5
|
manager rewrites calls to those legacy names, and trims the ``action`` enum
|
|
6
6
|
to the actions this agent can actually use (see
|
|
7
|
-
``cadecoder.tools.manager.
|
|
7
|
+
``cadecoder.tools.manager.gating``).
|
|
8
8
|
|
|
9
9
|
Lets the model address other agents the user has explicitly linked. Delivery
|
|
10
10
|
rides the durable per-agent inbox (so the message survives until the target
|
|
@@ -17,7 +17,9 @@ from cadecoder.core import agent as agent_mod
|
|
|
17
17
|
from cadecoder.core import comms as comms_mod
|
|
18
18
|
from cadecoder.core.agent import AgentConfig
|
|
19
19
|
from cadecoder.core.errors import ConfigError
|
|
20
|
-
from cadecoder.tools.profiles import DEFAULT_PROFILE,
|
|
20
|
+
from cadecoder.tools.manager.profiles import DEFAULT_PROFILE, PROFILES
|
|
21
|
+
|
|
22
|
+
PROFILE_NAMES = tuple(PROFILES)
|
|
21
23
|
|
|
22
24
|
channel_app = typer.Typer(
|
|
23
25
|
name="channel",
|
|
@@ -29,6 +31,39 @@ channel_app = typer.Typer(
|
|
|
29
31
|
help_on_empty(channel_app)
|
|
30
32
|
|
|
31
33
|
|
|
34
|
+
_BASE_URL_HELP = (
|
|
35
|
+
"Model-provider endpoint for this agent (gateway, proxy, or compatible server). "
|
|
36
|
+
'Pass "" to clear and use the default endpoint.'
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _checked_base_url(value: str | None) -> str | None:
|
|
41
|
+
"""Validate a ``--base-url`` value; ``""`` clears it (returns ``None``).
|
|
42
|
+
|
|
43
|
+
Fails the command on a malformed URL so a bad value never reaches the
|
|
44
|
+
agent file, and warns when the API key would cross the network in clear.
|
|
45
|
+
"""
|
|
46
|
+
from cadecoder.providers.endpoint import normalize_base_url, sends_key_in_cleartext
|
|
47
|
+
|
|
48
|
+
if value is None or not value.strip():
|
|
49
|
+
return None
|
|
50
|
+
try:
|
|
51
|
+
url = normalize_base_url(value)
|
|
52
|
+
except ValueError as e:
|
|
53
|
+
fail(f"Invalid --base-url: {e}")
|
|
54
|
+
if sends_key_in_cleartext(url):
|
|
55
|
+
console.print(
|
|
56
|
+
f"[yellow]Warning: {url} is plain http to a non-local host — the API key "
|
|
57
|
+
"will be sent unencrypted.[/yellow]"
|
|
58
|
+
)
|
|
59
|
+
return url
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _endpoint_label(agent: agent_mod.AgentConfig) -> str:
|
|
63
|
+
"""How to show an agent's endpoint: its own URL, or that it uses the default."""
|
|
64
|
+
return agent.base_url or "[dim](default)[/dim]"
|
|
65
|
+
|
|
66
|
+
|
|
32
67
|
def new_agent(
|
|
33
68
|
name: Annotated[str, typer.Argument(help="Unique name for the new agent")],
|
|
34
69
|
model: Annotated[str | None, typer.Option("--model", help="Model id")] = None,
|
|
@@ -45,10 +80,12 @@ def new_agent(
|
|
|
45
80
|
allow_spawn: Annotated[
|
|
46
81
|
bool, typer.Option("--allow-spawn", help="Permit this agent to spawn task-agents")
|
|
47
82
|
] = False,
|
|
83
|
+
base_url: Annotated[str | None, typer.Option("--base-url", help=_BASE_URL_HELP)] = None,
|
|
48
84
|
) -> None:
|
|
49
85
|
"""Create a new agent and switch to it."""
|
|
50
86
|
if prompt and prompt_file:
|
|
51
87
|
fail("Use either --prompt or --prompt-file, not both.")
|
|
88
|
+
endpoint = _checked_base_url(base_url)
|
|
52
89
|
try:
|
|
53
90
|
agent = agent_mod.new_agent(
|
|
54
91
|
name,
|
|
@@ -58,6 +95,7 @@ def new_agent(
|
|
|
58
95
|
description=description,
|
|
59
96
|
workspace=workspace,
|
|
60
97
|
allow_spawn=allow_spawn,
|
|
98
|
+
base_url=endpoint,
|
|
61
99
|
)
|
|
62
100
|
except ConfigError as e:
|
|
63
101
|
fail(str(e))
|
|
@@ -65,6 +103,8 @@ def new_agent(
|
|
|
65
103
|
agent_mod.set_active_agent(agent.name)
|
|
66
104
|
console.print(f"[green]Created agent '{agent.name}'[/green] [dim]({agent.id})[/dim]")
|
|
67
105
|
console.print(f"[dim]Model: {agent.model} · now the current agent[/dim]")
|
|
106
|
+
if agent.base_url:
|
|
107
|
+
console.print(f"[dim]Endpoint: {agent.base_url}[/dim]")
|
|
68
108
|
# Provider keys and the Arcade login are global, but a loadout is not: a new
|
|
69
109
|
# agent starts with no tool servers even when the first one is fully set up.
|
|
70
110
|
if not agent.mcp:
|
|
@@ -157,6 +197,7 @@ def agent_info(
|
|
|
157
197
|
return
|
|
158
198
|
console.print(f"[bold cyan]Agent:[/bold cyan] {agent.name} [dim]{agent.id}[/dim]")
|
|
159
199
|
console.print(f" [bold]Model:[/bold] {agent.model}")
|
|
200
|
+
console.print(f" [bold]Endpoint:[/bold] {_endpoint_label(agent)}")
|
|
160
201
|
if agent.description:
|
|
161
202
|
console.print(f" [bold]Description:[/bold] {agent.description}")
|
|
162
203
|
prompt_src = agent.prompt_file or ("(inline)" if agent.system_prompt else "(base prompt)")
|
|
@@ -198,6 +239,7 @@ def agent_status() -> None:
|
|
|
198
239
|
|
|
199
240
|
console.print(f"[bold cyan]Agent:[/bold cyan] {agent.name} [dim]{agent.id}[/dim]")
|
|
200
241
|
console.print(f" [bold]Model:[/bold] {agent.model}")
|
|
242
|
+
console.print(f" [bold]Endpoint:[/bold] {_endpoint_label(agent)}")
|
|
201
243
|
|
|
202
244
|
pid = read_running_pid(get_paths().daemon_pidfile)
|
|
203
245
|
if pid is not None:
|
|
@@ -250,6 +292,7 @@ def edit_agent(
|
|
|
250
292
|
allow_spawn: Annotated[
|
|
251
293
|
bool | None, typer.Option("--allow-spawn/--no-allow-spawn", help="Toggle spawn")
|
|
252
294
|
] = None,
|
|
295
|
+
base_url: Annotated[str | None, typer.Option("--base-url", help=_BASE_URL_HELP)] = None,
|
|
253
296
|
tools_profile: Annotated[
|
|
254
297
|
str | None,
|
|
255
298
|
typer.Option(
|
|
@@ -268,7 +311,16 @@ def edit_agent(
|
|
|
268
311
|
return
|
|
269
312
|
if tools_profile is not None and tools_profile not in PROFILE_NAMES:
|
|
270
313
|
fail(f"Unknown tools profile '{tools_profile}'. Choose one of: {', '.join(PROFILE_NAMES)}.")
|
|
271
|
-
edits = (
|
|
314
|
+
edits = (
|
|
315
|
+
model,
|
|
316
|
+
prompt,
|
|
317
|
+
prompt_file,
|
|
318
|
+
description,
|
|
319
|
+
workspace,
|
|
320
|
+
allow_spawn,
|
|
321
|
+
tools_profile,
|
|
322
|
+
base_url,
|
|
323
|
+
)
|
|
272
324
|
if all(v is None for v in edits):
|
|
273
325
|
console.print("[yellow]No changes specified.[/yellow]")
|
|
274
326
|
raise typer.Exit(0)
|
|
@@ -286,11 +338,16 @@ def edit_agent(
|
|
|
286
338
|
agent.workspace = workspace
|
|
287
339
|
if allow_spawn is not None:
|
|
288
340
|
agent.allow_spawn = allow_spawn
|
|
341
|
+
if base_url is not None:
|
|
342
|
+
# "" clears the override; anything else must be a usable URL.
|
|
343
|
+
agent.base_url = _checked_base_url(base_url)
|
|
289
344
|
if tools_profile is not None:
|
|
290
345
|
# "full" is the default; store it as unset so the file stays minimal.
|
|
291
346
|
agent.tools.profile = None if tools_profile == DEFAULT_PROFILE else tools_profile
|
|
292
347
|
agent_mod.save_agent(agent)
|
|
293
348
|
console.print(f"[green]Updated agent '{agent.name}'[/green]")
|
|
349
|
+
if base_url is not None:
|
|
350
|
+
console.print(f"[dim]Endpoint: {_endpoint_label(agent)}[/dim]")
|
|
294
351
|
|
|
295
352
|
|
|
296
353
|
def remove_agent(
|
|
@@ -413,7 +470,7 @@ def list_models(
|
|
|
413
470
|
] = None,
|
|
414
471
|
) -> None:
|
|
415
472
|
"""List models you can assign to an agent (catalog of configured providers)."""
|
|
416
|
-
from cadecoder.providers import provider_registry
|
|
473
|
+
from cadecoder.providers import provider_for_model, provider_registry
|
|
417
474
|
from cadecoder.providers.base import ProviderType
|
|
418
475
|
|
|
419
476
|
current_model: str | None = None
|
|
@@ -421,6 +478,15 @@ def list_models(
|
|
|
421
478
|
if current is not None:
|
|
422
479
|
current_model = current.model
|
|
423
480
|
|
|
481
|
+
# An agent with its own endpoint should see what that endpoint serves —
|
|
482
|
+
# a gateway's catalogue can differ from the provider's own.
|
|
483
|
+
endpoint_provider = None
|
|
484
|
+
if current is not None and current.base_url:
|
|
485
|
+
try:
|
|
486
|
+
endpoint_provider = provider_for_model(current.model, current.base_url)
|
|
487
|
+
except ValueError as e:
|
|
488
|
+
console.print(f"[yellow]Agent '{current.name}' has an unusable base_url: {e}[/yellow]")
|
|
489
|
+
|
|
424
490
|
wanted = provider or "all"
|
|
425
491
|
table = Table(title="Available models", show_header=True, header_style="bold cyan")
|
|
426
492
|
table.add_column("Model", style="cyan")
|
|
@@ -432,6 +498,8 @@ def list_models(
|
|
|
432
498
|
if wanted not in ("all", ptype.value):
|
|
433
499
|
continue
|
|
434
500
|
prov = provider_registry.get(ptype)
|
|
501
|
+
if endpoint_provider is not None and endpoint_provider.provider_type == ptype:
|
|
502
|
+
prov = endpoint_provider
|
|
435
503
|
if prov is None:
|
|
436
504
|
continue
|
|
437
505
|
try:
|
|
@@ -453,6 +521,11 @@ def list_models(
|
|
|
453
521
|
console.print(table)
|
|
454
522
|
if current_model:
|
|
455
523
|
console.print(f"\n[dim]Current agent model: {current_model}[/dim]")
|
|
524
|
+
if endpoint_provider is not None:
|
|
525
|
+
console.print(
|
|
526
|
+
f"[dim]{endpoint_provider.provider_type.value} models listed from "
|
|
527
|
+
f"{endpoint_provider.base_url} (this agent's endpoint)[/dim]"
|
|
528
|
+
)
|
|
456
529
|
console.print(
|
|
457
530
|
"[dim]Assign with: cade agent new <name> --model <model> · "
|
|
458
531
|
"cade agent edit <name> --model[/dim]"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""CLI commands for viewing and managing tools."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
from dataclasses import dataclass, field
|
|
4
5
|
from typing import Annotated
|
|
5
6
|
|
|
6
7
|
import typer
|
|
@@ -35,31 +36,57 @@ def _get_tool_description(tool: dict) -> str:
|
|
|
35
36
|
return desc
|
|
36
37
|
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"""Load tools from the composite manager.
|
|
39
|
+
@dataclass
|
|
40
|
+
class _LoadedTools:
|
|
41
|
+
"""One load of the tool surface, plus everything left out of it.
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
keyed ``Tool(action=x)``) to the reason.
|
|
43
|
+
Three different filters can remove a tool from the advertised list, and a
|
|
44
|
+
user asking "where did my tool go?" needs to know which one did it — so
|
|
45
|
+
each is reported separately rather than folded into one bucket.
|
|
47
46
|
"""
|
|
47
|
+
|
|
48
|
+
tools: list[dict] = field(default_factory=list)
|
|
49
|
+
source_map: dict[str, str] = field(default_factory=dict)
|
|
50
|
+
server_map: dict[str, str] = field(default_factory=dict)
|
|
51
|
+
auth_warnings: list[tuple[str, str | None]] = field(default_factory=list)
|
|
52
|
+
#: Capability gate — the tool could not work here. Still callable.
|
|
53
|
+
suppressed: dict[str, str] = field(default_factory=dict)
|
|
54
|
+
#: Tool policy (profile + allow/deny) — the user excluded it. Not callable.
|
|
55
|
+
policy_filtered: list[str] = field(default_factory=list)
|
|
56
|
+
#: CADE_TOOL_SURFACE=minimal — reachable via Local_ToolSchema.
|
|
57
|
+
withheld_by_surface: list[str] = field(default_factory=list)
|
|
58
|
+
surface_mode: str = "full"
|
|
59
|
+
#: Merged tools advertised with some actions removed: tool -> {action: why}.
|
|
60
|
+
trimmed_actions: dict[str, dict[str, str]] = field(default_factory=dict)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
async def _load_tools() -> _LoadedTools:
|
|
64
|
+
"""Load tools from the composite manager."""
|
|
48
65
|
tool_manager = CompositeToolManager(enable_mcp=True, local_only=False)
|
|
49
66
|
tools = await tool_manager.get_tools()
|
|
50
67
|
|
|
51
68
|
source_map: dict[str, str] = {}
|
|
52
|
-
|
|
53
|
-
source_map[info["name"]] = info["source"]
|
|
69
|
+
server_map: dict[str, str] = {}
|
|
54
70
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
71
|
+
for info in tool_manager.get_all_tool_info():
|
|
72
|
+
tool_name = info["name"]
|
|
73
|
+
source_map[tool_name] = info["source"]
|
|
74
|
+
server_map[tool_name] = info.get("server", "")
|
|
75
|
+
|
|
76
|
+
loaded = _LoadedTools(
|
|
77
|
+
tools=tools,
|
|
78
|
+
source_map=source_map,
|
|
79
|
+
server_map=server_map,
|
|
80
|
+
auth_warnings=getattr(tool_manager, "_auth_warnings", []),
|
|
81
|
+
suppressed=tool_manager.suppressed_tools(),
|
|
82
|
+
policy_filtered=tool_manager.policy_filtered_tools(),
|
|
83
|
+
withheld_by_surface=tool_manager.withheld_by_surface(),
|
|
84
|
+
trimmed_actions=tool_manager.trimmed_actions(),
|
|
85
|
+
surface_mode=tool_manager.surface_mode(),
|
|
86
|
+
)
|
|
60
87
|
|
|
61
88
|
await tool_manager.close()
|
|
62
|
-
return
|
|
89
|
+
return loaded
|
|
63
90
|
|
|
64
91
|
|
|
65
92
|
def _serve_status(name: str, policy: dict[str, bool] | None) -> str:
|
|
@@ -170,48 +197,6 @@ def _display_tools(
|
|
|
170
197
|
console.print(f"\n[dim]Total: {total_count} tools[/dim]")
|
|
171
198
|
|
|
172
199
|
|
|
173
|
-
def _display_hidden_tools(
|
|
174
|
-
hidden: dict[str, str],
|
|
175
|
-
source_map: dict[str, str],
|
|
176
|
-
source_filter: str | None,
|
|
177
|
-
server: str | None,
|
|
178
|
-
search: str | None,
|
|
179
|
-
) -> None:
|
|
180
|
-
"""List registered tools this session does not advertise, with the reason.
|
|
181
|
-
|
|
182
|
-
A tool can be hidden by a capability gate (e.g. ``Local_Git`` outside a git
|
|
183
|
-
work tree) or by the agent's ``[tools]`` policy; a single action can be
|
|
184
|
-
hidden too (``Local_Agent(action=spawn)`` for an agent without
|
|
185
|
-
``allow_spawn``). Shown so an absence is explainable rather than mysterious.
|
|
186
|
-
"""
|
|
187
|
-
rows = sorted(hidden.items())
|
|
188
|
-
if source_filter is not None:
|
|
189
|
-
# Hidden actions are keyed "Tool(action=x)"; their source is the tool's.
|
|
190
|
-
rows = [
|
|
191
|
-
(n, r)
|
|
192
|
-
for n, r in rows
|
|
193
|
-
if source_map.get(n.split("(", 1)[0], "").lower() == source_filter
|
|
194
|
-
]
|
|
195
|
-
if server:
|
|
196
|
-
rows = [(n, r) for n, r in rows if n.startswith(f"{server}_")]
|
|
197
|
-
if search:
|
|
198
|
-
query = search.lower()
|
|
199
|
-
rows = [(n, r) for n, r in rows if query in n.lower()]
|
|
200
|
-
if not rows:
|
|
201
|
-
return
|
|
202
|
-
|
|
203
|
-
table = Table(title="Hidden this session", show_lines=False, box=None)
|
|
204
|
-
table.add_column("Name", style="dim", no_wrap=True, width=45)
|
|
205
|
-
table.add_column("Why", style="dim", max_width=70)
|
|
206
|
-
for name, reason in rows:
|
|
207
|
-
table.add_row(name, reason)
|
|
208
|
-
console.print()
|
|
209
|
-
console.print(table)
|
|
210
|
-
console.print(
|
|
211
|
-
"[dim]Capability gates can be lifted for debugging with CADE_DISABLE_TOOL_GATING=1.[/dim]"
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
|
|
215
200
|
def _display_discovered_tools(
|
|
216
201
|
show_all: bool, search: str | None = None, server: str | None = None
|
|
217
202
|
) -> None:
|
|
@@ -275,6 +260,45 @@ def _display_discovered_tools(
|
|
|
275
260
|
)
|
|
276
261
|
|
|
277
262
|
|
|
263
|
+
def _display_omissions(loaded: _LoadedTools) -> None:
|
|
264
|
+
"""Report every tool left out of the advertised list, and which filter did it.
|
|
265
|
+
|
|
266
|
+
A tool that silently vanished is harder to debug than one that fails, so
|
|
267
|
+
each omission is printed with its reason and the way to undo it.
|
|
268
|
+
"""
|
|
269
|
+
if loaded.suppressed:
|
|
270
|
+
console.print(
|
|
271
|
+
f"\n[dim]{len(loaded.suppressed)} built-in tool(s) not advertised "
|
|
272
|
+
"(capability unavailable; still callable):[/dim]"
|
|
273
|
+
)
|
|
274
|
+
for name, reason in sorted(loaded.suppressed.items()):
|
|
275
|
+
console.print(f" [dim]{name} — {reason}[/dim]")
|
|
276
|
+
console.print(" [dim]Set CADE_DISABLE_TOOL_GATING=1 to advertise them anyway.[/dim]")
|
|
277
|
+
|
|
278
|
+
if loaded.policy_filtered:
|
|
279
|
+
console.print(
|
|
280
|
+
f"\n[dim]{len(loaded.policy_filtered)} tool(s) excluded by this agent's "
|
|
281
|
+
"tool policy (not callable):[/dim]"
|
|
282
|
+
)
|
|
283
|
+
console.print(f" [dim]{', '.join(sorted(loaded.policy_filtered))}[/dim]")
|
|
284
|
+
# Escape the literal table name: Rich reads a bare [tools] as markup.
|
|
285
|
+
console.print(r" [dim]Edit the \[tools] profile/allow/deny in the agent's config.[/dim]")
|
|
286
|
+
|
|
287
|
+
if loaded.trimmed_actions:
|
|
288
|
+
console.print("\n[dim]Actions not offered on merged tools (the rest remain):[/dim]")
|
|
289
|
+
for tool, actions in sorted(loaded.trimmed_actions.items()):
|
|
290
|
+
detail = ", ".join(f"{a} — {why}" for a, why in sorted(actions.items()))
|
|
291
|
+
console.print(f" [dim]{tool}: {detail}[/dim]")
|
|
292
|
+
|
|
293
|
+
if loaded.withheld_by_surface:
|
|
294
|
+
console.print(
|
|
295
|
+
f"\n[dim]{len(loaded.withheld_by_surface)} built-in(s) withheld by "
|
|
296
|
+
f"CADE_TOOL_SURFACE={loaded.surface_mode} "
|
|
297
|
+
"(the model loads them on demand via Local_ToolSchema):[/dim]"
|
|
298
|
+
)
|
|
299
|
+
console.print(f" [dim]{', '.join(sorted(loaded.withheld_by_surface))}[/dim]")
|
|
300
|
+
|
|
301
|
+
|
|
278
302
|
def _display_auth_warnings(auth_warnings: list[tuple[str, str | None]]) -> None:
|
|
279
303
|
"""Display authorization warnings for servers that need OAuth."""
|
|
280
304
|
for server_name, auth_url in auth_warnings:
|
|
@@ -286,13 +310,19 @@ def _display_auth_warnings(auth_warnings: list[tuple[str, str | None]]) -> None:
|
|
|
286
310
|
|
|
287
311
|
|
|
288
312
|
def _resolve_serve_policy() -> tuple[str, list[str], list[str]]:
|
|
289
|
-
"""Return (agent_name, allow, deny) for the current agent's serve tool policy.
|
|
313
|
+
"""Return (agent_name, allow, deny) for the current agent's serve tool policy.
|
|
314
|
+
|
|
315
|
+
Resolves the profile the same way the hook and the composite manager do,
|
|
316
|
+
so the annotation shown here matches what actually runs.
|
|
317
|
+
"""
|
|
290
318
|
from cadecoder.core import agent as agent_mod
|
|
319
|
+
from cadecoder.daemon.allowlist import resolve_settings
|
|
291
320
|
from cadecoder.daemon.config import load_config
|
|
292
321
|
|
|
293
322
|
cfg = load_config()
|
|
294
323
|
agent_name = agent_mod.get_active_agent().name
|
|
295
|
-
|
|
324
|
+
allow, deny = resolve_settings(cfg.tools)
|
|
325
|
+
return agent_name, allow, deny
|
|
296
326
|
|
|
297
327
|
|
|
298
328
|
def _list_tools_impl(
|
|
@@ -324,10 +354,14 @@ def _list_tools_impl(
|
|
|
324
354
|
pass
|
|
325
355
|
if is_verbose():
|
|
326
356
|
console.print("[cyan]Loading tools (verbose)...[/cyan]")
|
|
327
|
-
|
|
357
|
+
loaded = asyncio.run(_load_tools())
|
|
328
358
|
else:
|
|
329
359
|
with console.status("[cyan]Loading tools...", spinner="dots"):
|
|
330
|
-
|
|
360
|
+
loaded = asyncio.run(_load_tools())
|
|
361
|
+
|
|
362
|
+
tools = loaded.tools
|
|
363
|
+
source_map = loaded.source_map
|
|
364
|
+
auth_warnings = loaded.auth_warnings
|
|
331
365
|
|
|
332
366
|
# Resolve the serve allow/deny policy when requested.
|
|
333
367
|
policy: dict[str, bool] | None = None
|
|
@@ -336,8 +370,8 @@ def _list_tools_impl(
|
|
|
336
370
|
from cadecoder.daemon.allowlist import is_tool_reachable
|
|
337
371
|
|
|
338
372
|
agent_name, allow, deny = _resolve_serve_policy()
|
|
339
|
-
# Reachable, not
|
|
340
|
-
#
|
|
373
|
+
# Reachable, not allowed-by-name: Local_Task under allow = ["Local_TaskList"]
|
|
374
|
+
# is usable (list/get) even though its own name matches nothing.
|
|
341
375
|
policy = {
|
|
342
376
|
_get_tool_name(t): is_tool_reachable(_get_tool_name(t), allow, deny) for t in tools
|
|
343
377
|
}
|
|
@@ -382,15 +416,15 @@ def _list_tools_impl(
|
|
|
382
416
|
effective_show_all = show_all or source_filter is not None
|
|
383
417
|
_display_tools(tools, source_map, server, effective_show_all, policy=policy, search=search)
|
|
384
418
|
|
|
385
|
-
if show_all or source_filter is not None or apply_policy:
|
|
386
|
-
_display_hidden_tools(hidden, source_map, source_filter, server, search)
|
|
387
|
-
|
|
388
419
|
# Append discovered tools (cached SelectTools schemas), filtered by the same
|
|
389
420
|
# search/server query so a search reaches the whole Arcade catalog cache.
|
|
390
421
|
# Skip when filtering to local-only — discovered Arcade tools are MCP.
|
|
391
422
|
if not server and source_filter != "local":
|
|
392
423
|
_display_discovered_tools(show_all=show_all, search=search)
|
|
393
424
|
|
|
425
|
+
if show_all or source_filter == "local":
|
|
426
|
+
_display_omissions(loaded)
|
|
427
|
+
|
|
394
428
|
if auth_warnings:
|
|
395
429
|
_display_auth_warnings(auth_warnings)
|
|
396
430
|
|
|
@@ -458,9 +492,7 @@ def tool_info(
|
|
|
458
492
|
) -> None:
|
|
459
493
|
"""Show detailed information about a tool."""
|
|
460
494
|
|
|
461
|
-
async def get_tool_details() -> tuple[
|
|
462
|
-
dict | None, str | None, list[tuple[str, str | None]], str | None
|
|
463
|
-
]:
|
|
495
|
+
async def get_tool_details() -> tuple[dict | None, str | None, list[tuple[str, str | None]]]:
|
|
464
496
|
tool_manager = CompositeToolManager(enable_mcp=True, local_only=False)
|
|
465
497
|
tools = await tool_manager.get_tools()
|
|
466
498
|
|
|
@@ -473,26 +505,19 @@ def tool_info(
|
|
|
473
505
|
actual_name = tool.get("function", {}).get("name", name) if tool else name
|
|
474
506
|
source = tool_manager.get_tool_source(actual_name)
|
|
475
507
|
auth_warnings = getattr(tool_manager, "_auth_warnings", [])
|
|
476
|
-
hidden_reason = next(
|
|
477
|
-
(r for n, r in tool_manager.get_suppressed_tools().items() if n.lower() == name_lower),
|
|
478
|
-
None,
|
|
479
|
-
)
|
|
480
508
|
|
|
481
509
|
await tool_manager.close()
|
|
482
|
-
return tool, source, auth_warnings
|
|
510
|
+
return tool, source, auth_warnings
|
|
483
511
|
|
|
484
512
|
if is_verbose():
|
|
485
513
|
console.print("[cyan]Loading tool info (verbose)...[/cyan]")
|
|
486
|
-
tool, source, auth_warnings
|
|
514
|
+
tool, source, auth_warnings = asyncio.run(get_tool_details())
|
|
487
515
|
else:
|
|
488
516
|
with console.status("[cyan]Loading tool info...", spinner="dots"):
|
|
489
|
-
tool, source, auth_warnings
|
|
517
|
+
tool, source, auth_warnings = asyncio.run(get_tool_details())
|
|
490
518
|
|
|
491
519
|
if not tool:
|
|
492
|
-
|
|
493
|
-
console.print(f"[yellow]Tool '{name}' is hidden this session: {hidden_reason}[/yellow]")
|
|
494
|
-
else:
|
|
495
|
-
console.print(f"[red]Tool '{name}' not found.[/red]")
|
|
520
|
+
console.print(f"[red]Tool '{name}' not found.[/red]")
|
|
496
521
|
raise typer.Exit(1)
|
|
497
522
|
|
|
498
523
|
func = tool.get("function", {})
|
|
@@ -38,20 +38,28 @@ class AgentKind(str, Enum):
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
class ToolPolicy(BaseModel):
|
|
41
|
-
"""
|
|
41
|
+
"""Which tools an agent may see and call.
|
|
42
42
|
|
|
43
43
|
``None`` means "no policy was ever written for this agent", which is not the
|
|
44
44
|
same as an explicit empty list. An empty list is a deliberate choice to
|
|
45
45
|
impose no restriction on that side, and seeding code must leave it alone.
|
|
46
46
|
Agent files written before this distinction existed simply have no
|
|
47
47
|
``[tools]`` table, which loads as unset — the seeding behaviour they had.
|
|
48
|
+
|
|
49
|
+
``profile`` names a preset bundle of allow patterns (``coding``,
|
|
50
|
+
``research``, ``agentic``, ``full``; see
|
|
51
|
+
:mod:`cadecoder.tools.manager.profiles`). It is a floor rather than a
|
|
52
|
+
ceiling: ``allow`` is unioned on top of it and ``deny`` still wins over
|
|
53
|
+
both. Unset, or ``full``, imposes no restriction.
|
|
54
|
+
|
|
55
|
+
The policy governs the advertised tool list as well as execution — a tool
|
|
56
|
+
the agent may not run is not shown to the model in the first place.
|
|
48
57
|
"""
|
|
49
58
|
|
|
50
59
|
model_config = ConfigDict(extra="ignore")
|
|
60
|
+
profile: str | None = None
|
|
51
61
|
allow: list[str] | None = None
|
|
52
62
|
deny: list[str] | None = None
|
|
53
|
-
# Named built-in tool preset (see cadecoder.tools.profiles); None = "full".
|
|
54
|
-
profile: str | None = None
|
|
55
63
|
|
|
56
64
|
def is_configured(self) -> bool:
|
|
57
65
|
"""Return whether a tool policy has ever been set on this agent."""
|
|
@@ -121,6 +129,13 @@ class AgentConfig(BaseModel):
|
|
|
121
129
|
# Brain
|
|
122
130
|
model: str = constants.DEFAULT_AI_MODEL
|
|
123
131
|
provider: str | None = None
|
|
132
|
+
# Endpoint for this agent's model provider — a gateway, proxy, or
|
|
133
|
+
# compatible server. Unset uses the process-wide endpoint (env or
|
|
134
|
+
# cade.toml [model_settings] host), which for most agents is the provider's
|
|
135
|
+
# own API. Validated when the provider is built, not here: a typo in a
|
|
136
|
+
# hand-edited agent file should fail that agent's turns with a clear
|
|
137
|
+
# message, not make the file unloadable.
|
|
138
|
+
base_url: str | None = None
|
|
124
139
|
temperature: float | None = None
|
|
125
140
|
# Overrides the model registry's context window (tokens) for this agent.
|
|
126
141
|
context_window: int | None = None
|
|
@@ -375,6 +390,7 @@ def new_agent(
|
|
|
375
390
|
workspace: str | None = None,
|
|
376
391
|
allow_spawn: bool = False,
|
|
377
392
|
tools: ToolPolicy | None = None,
|
|
393
|
+
base_url: str | None = None,
|
|
378
394
|
kind: AgentKind = AgentKind.TOP_LEVEL,
|
|
379
395
|
parent_id: str | None = None,
|
|
380
396
|
root_id: str | None = None,
|
|
@@ -391,6 +407,8 @@ def new_agent(
|
|
|
391
407
|
replace_base: Replace the base prompt entirely vs. prepend.
|
|
392
408
|
workspace: File-tool sandbox root.
|
|
393
409
|
allow_spawn: Whether this agent may spawn task-agents.
|
|
410
|
+
base_url: Model-provider endpoint for this agent; ``None`` uses the
|
|
411
|
+
process-wide default.
|
|
394
412
|
tools: Tool allow/deny policy.
|
|
395
413
|
kind: Top-level (user) or task-agent (spawned).
|
|
396
414
|
parent_id: Parent agent id (task-agents).
|
|
@@ -419,6 +437,7 @@ def new_agent(
|
|
|
419
437
|
replace_base=replace_base,
|
|
420
438
|
workspace=workspace,
|
|
421
439
|
allow_spawn=allow_spawn,
|
|
440
|
+
base_url=base_url,
|
|
422
441
|
tools=tools or ToolPolicy(),
|
|
423
442
|
kind=kind,
|
|
424
443
|
parent_id=parent_id,
|
|
@@ -195,6 +195,9 @@ async def spawn_task_agent(
|
|
|
195
195
|
child = agent_mod.new_agent(
|
|
196
196
|
name or _auto_name(),
|
|
197
197
|
model=parent.model,
|
|
198
|
+
# Same endpoint as the parent: a child silently falling back to the
|
|
199
|
+
# default endpoint would send the parent's task somewhere it chose not to.
|
|
200
|
+
base_url=parent.base_url,
|
|
198
201
|
description=f"Task-agent spawned by {parent.name}",
|
|
199
202
|
tools=parent.tools,
|
|
200
203
|
kind=AgentKind.TASK_AGENT,
|