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.
Files changed (230) hide show
  1. {cade_cli-0.21.0 → cade_cli-0.22.0}/PKG-INFO +2 -2
  2. {cade_cli-0.21.0 → cade_cli-0.22.0}/README.md +1 -1
  3. {cade_cli-0.21.0 → cade_cli-0.22.0}/pyproject.toml +1 -1
  4. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/agents.py +1 -1
  5. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/agents.py +76 -3
  6. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/tools.py +106 -81
  7. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/agent.py +22 -3
  8. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/spawn.py +3 -0
  9. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/allowlist.py +34 -45
  10. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/config.py +5 -3
  11. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/message_pump.py +1 -0
  12. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/supervisor.py +3 -0
  13. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/execution/orchestrator.py +28 -2
  14. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/providers/__init__.py +82 -10
  15. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/providers/anthropic.py +26 -9
  16. cade_cli-0.22.0/src/cadecoder/providers/endpoint.py +57 -0
  17. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/providers/openai.py +3 -3
  18. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/manager/composite.py +539 -313
  19. cade_cli-0.22.0/src/cadecoder/tools/manager/gating.py +252 -0
  20. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/manager/mcp.py +1 -1
  21. cade_cli-0.22.0/src/cadecoder/tools/manager/profiles.py +237 -0
  22. cade_cli-0.22.0/src/cadecoder/tools/manager/surface.py +203 -0
  23. cade_cli-0.21.0/src/cadecoder/tools/manager/surface.py +0 -353
  24. cade_cli-0.21.0/src/cadecoder/tools/profiles.py +0 -74
  25. {cade_cli-0.21.0 → cade_cli-0.22.0}/.gitignore +0 -0
  26. {cade_cli-0.21.0 → cade_cli-0.22.0}/LICENSE +0 -0
  27. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/__init__.py +0 -0
  28. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/__main__.py +0 -0
  29. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/_metadata.py +0 -0
  30. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/config.py +0 -0
  31. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/errors.py +0 -0
  32. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/grants.py +0 -0
  33. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/resources.py +0 -0
  34. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/sandbox.py +0 -0
  35. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/server.py +0 -0
  36. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/__init__.py +0 -0
  37. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/_read_cache.py +0 -0
  38. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/access.py +0 -0
  39. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/filesystem.py +0 -0
  40. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/git.py +0 -0
  41. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/notifications.py +0 -0
  42. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/questions.py +0 -0
  43. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/results.py +0 -0
  44. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/schemas.py +0 -0
  45. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/search.py +0 -0
  46. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/shell.py +0 -0
  47. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/tasks.py +0 -0
  48. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/web_fetch.py +0 -0
  49. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/tools/web_search.py +0 -0
  50. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/utils.py +0 -0
  51. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/__init__.py +0 -0
  52. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/arcade_client.py +0 -0
  53. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/cache.py +0 -0
  54. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/extract.py +0 -0
  55. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/markdown.py +0 -0
  56. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/preapproved.py +0 -0
  57. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/prompts.py +0 -0
  58. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/scrape.py +0 -0
  59. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/search/__init__.py +0 -0
  60. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/search/arcade.py +0 -0
  61. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/search/base.py +0 -0
  62. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cade_mcp_local/web/search/http.py +0 -0
  63. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/__init__.py +0 -0
  64. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/ai/__init__.py +0 -0
  65. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/ai/prompts.py +0 -0
  66. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/__init__.py +0 -0
  67. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/app.py +0 -0
  68. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/auth.py +0 -0
  69. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/__init__.py +0 -0
  70. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/account.py +0 -0
  71. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/agent_group.py +0 -0
  72. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/auth.py +0 -0
  73. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/chat.py +0 -0
  74. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/cron.py +0 -0
  75. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/daemon.py +0 -0
  76. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/grant.py +0 -0
  77. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/hooks.py +0 -0
  78. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/mcp.py +0 -0
  79. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/mem.py +0 -0
  80. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/tasks.py +0 -0
  81. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/commands/thread.py +0 -0
  82. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/mcp_hosts.py +0 -0
  83. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/onboarding.py +0 -0
  84. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/cli/ui.py +0 -0
  85. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/__init__.py +0 -0
  86. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/binary.py +0 -0
  87. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/comms.py +0 -0
  88. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/config.py +0 -0
  89. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/constants.py +0 -0
  90. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/errors.py +0 -0
  91. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/frozen.py +0 -0
  92. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/git.py +0 -0
  93. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/inbox.py +0 -0
  94. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/logging.py +0 -0
  95. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/memory.py +0 -0
  96. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/model_specs.py +0 -0
  97. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/names.py +0 -0
  98. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/paths.py +0 -0
  99. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/runtime.py +0 -0
  100. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/secrets.py +0 -0
  101. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/text.py +0 -0
  102. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/core/types.py +0 -0
  103. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/__init__.py +0 -0
  104. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/adapters/__init__.py +0 -0
  105. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/adapters/base.py +0 -0
  106. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/adapters/factory.py +0 -0
  107. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/adapters/imessage.py +0 -0
  108. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/adapters/registry.py +0 -0
  109. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/adapters/stub.py +0 -0
  110. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/adapters/telegram.py +0 -0
  111. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/attach.py +0 -0
  112. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/chunker.py +0 -0
  113. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/client.py +0 -0
  114. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/commands.py +0 -0
  115. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/control.py +0 -0
  116. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/cron.py +0 -0
  117. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/elicitation.py +0 -0
  118. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/ensure.py +0 -0
  119. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/format.py +0 -0
  120. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/__init__.py +0 -0
  121. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/app.py +0 -0
  122. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/auth.py +0 -0
  123. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/constants.py +0 -0
  124. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/elicitation.py +0 -0
  125. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/exposure.py +0 -0
  126. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/host_agent.py +0 -0
  127. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/limits.py +0 -0
  128. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/middleware.py +0 -0
  129. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/principal.py +0 -0
  130. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/resources.py +0 -0
  131. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/subscriptions.py +0 -0
  132. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/gateway/turn_bridge.py +0 -0
  133. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/grants.py +0 -0
  134. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/inbox.py +0 -0
  135. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/inbox_cron.py +0 -0
  136. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/install.py +0 -0
  137. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/lock.py +0 -0
  138. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/mesh.py +0 -0
  139. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/observability.py +0 -0
  140. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/owner.py +0 -0
  141. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/progress_sink.py +0 -0
  142. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/registry.py +0 -0
  143. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/router.py +0 -0
  144. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/runner.py +0 -0
  145. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/runner_main.py +0 -0
  146. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/secrets.py +0 -0
  147. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/secrets_broker.py +0 -0
  148. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/server.py +0 -0
  149. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/services.py +0 -0
  150. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/sinks.py +0 -0
  151. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/tokens.py +0 -0
  152. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/traffic.py +0 -0
  153. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/turns.py +0 -0
  154. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/daemon/wake.py +0 -0
  155. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/execution/__init__.py +0 -0
  156. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/execution/context_window.py +0 -0
  157. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/execution/mcp_schema_index.py +0 -0
  158. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/execution/parallel.py +0 -0
  159. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/execution/tool_result_store.py +0 -0
  160. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/execution/tool_schema_cache.py +0 -0
  161. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/__init__.py +0 -0
  162. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/config.py +0 -0
  163. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/engine.py +0 -0
  164. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/executors/__init__.py +0 -0
  165. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/executors/callback.py +0 -0
  166. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/executors/command.py +0 -0
  167. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/executors/http.py +0 -0
  168. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/executors/prompt.py +0 -0
  169. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/executors/ssrf_guard.py +0 -0
  170. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/matchers.py +0 -0
  171. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/registry.py +0 -0
  172. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/hooks/types.py +0 -0
  173. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/__init__.py +0 -0
  174. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/.claude-plugin/marketplace.json +0 -0
  175. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/.claude-plugin/plugin.json +0 -0
  176. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/.cursor-plugin/marketplace.json +0 -0
  177. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/.cursor-plugin/plugin.json +0 -0
  178. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/agents/operator.md +0 -0
  179. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/commands/ask.md +0 -0
  180. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/commands/memory.md +0 -0
  181. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/commands/tasks.md +0 -0
  182. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/mcp.json +0 -0
  183. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/rules/gateway.mdc +0 -0
  184. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/skills/ask/SKILL.md +0 -0
  185. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/skills/memory/SKILL.md +0 -0
  186. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/assets/skills/tasks/SKILL.md +0 -0
  187. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/doctor.py +0 -0
  188. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/install.py +0 -0
  189. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/paths.py +0 -0
  190. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/host_plugin/state.py +0 -0
  191. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/providers/base.py +0 -0
  192. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/providers/http_client.py +0 -0
  193. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/providers/ollama.py +0 -0
  194. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/storage/__init__.py +0 -0
  195. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/storage/threads.py +0 -0
  196. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tasks/__init__.py +0 -0
  197. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tasks/scheduler.py +0 -0
  198. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tasks/store.py +0 -0
  199. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tasks/types.py +0 -0
  200. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/telemetry/__init__.py +0 -0
  201. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/telemetry/otel.py +0 -0
  202. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/templates/login_failed.html +0 -0
  203. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/templates/login_success.html +0 -0
  204. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/templates/serve/launchd.plist.tmpl +0 -0
  205. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/templates/serve/serve.toml.example +0 -0
  206. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/templates/serve/systemd.service.tmpl +0 -0
  207. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/templates/styles.css +0 -0
  208. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/__init__.py +0 -0
  209. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/manager/__init__.py +0 -0
  210. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/manager/_request_ctx.py +0 -0
  211. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/manager/base.py +0 -0
  212. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/manager/config.py +0 -0
  213. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/merged.py +0 -0
  214. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/search/__init__.py +0 -0
  215. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/search/discovered.py +0 -0
  216. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/search/scoring.py +0 -0
  217. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/tools/search/service.py +0 -0
  218. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/ui/__init__.py +0 -0
  219. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/ui/completion.py +0 -0
  220. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/ui/display.py +0 -0
  221. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/ui/elicitation.py +0 -0
  222. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/ui/input.py +0 -0
  223. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/ui/session.py +0 -0
  224. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/ui/slash.py +0 -0
  225. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/voice/__init__.py +0 -0
  226. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/voice/audio.py +0 -0
  227. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/voice/cleanup.py +0 -0
  228. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/voice/session.py +0 -0
  229. {cade_cli-0.21.0 → cade_cli-0.22.0}/src/cadecoder/voice/stt.py +0 -0
  230. {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.21.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 OpenAI-compatible endpoint |
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 OpenAI-compatible endpoint |
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 |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cade-cli"
3
- version = "0.21.0"
3
+ version = "0.22.0"
4
4
  description = "Cade - The CLI Agent from Arcade.dev (install: curl -fsSL https://arcadeagent.dev/install | sh)"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -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.surface``).
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, PROFILE_NAMES
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 = (model, prompt, prompt_file, description, workspace, allow_spawn, tools_profile)
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
- async def _load_tools() -> tuple[
39
- list[dict], dict[str, str], dict[str, str], list[tuple[str, str | None]]
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
- Returns:
44
- ``(tools, source_map, hidden, auth_warnings)`` where ``hidden`` maps each
45
- registered tool the session does not advertise (and each hidden action,
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
- for info in tool_manager.get_all_tool_info():
53
- source_map[info["name"]] = info["source"]
69
+ server_map: dict[str, str] = {}
54
70
 
55
- hidden = tool_manager.get_suppressed_tools()
56
- for name, actions in tool_manager.get_hidden_actions().items():
57
- for action, reason in actions.items():
58
- hidden[f"{name}(action={action})"] = reason
59
- auth_warnings = getattr(tool_manager, "_auth_warnings", [])
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 tools, source_map, hidden, auth_warnings
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
- return agent_name, list(cfg.tools.allow or []), list(cfg.tools.deny or [])
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
- tools, source_map, hidden, auth_warnings = asyncio.run(_load_tools())
357
+ loaded = asyncio.run(_load_tools())
328
358
  else:
329
359
  with console.status("[cyan]Loading tools...", spinner="dots"):
330
- tools, source_map, hidden, auth_warnings = asyncio.run(_load_tools())
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 just allowed by name: a merged tool permitted only via
340
- # a former name (allow = ["Local_TaskList"]) is listed with those actions.
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, hidden_reason
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, hidden_reason = asyncio.run(get_tool_details())
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, hidden_reason = asyncio.run(get_tool_details())
517
+ tool, source, auth_warnings = asyncio.run(get_tool_details())
490
518
 
491
519
  if not tool:
492
- if hidden_reason:
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
- """Allow/deny patterns gating which tools an agent may call.
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,