cade-cli 0.15.10__tar.gz → 0.16.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 (206) hide show
  1. {cade_cli-0.15.10 → cade_cli-0.16.0}/.gitignore +3 -0
  2. {cade_cli-0.15.10 → cade_cli-0.16.0}/PKG-INFO +19 -18
  3. {cade_cli-0.15.10 → cade_cli-0.16.0}/README.md +15 -16
  4. {cade_cli-0.15.10 → cade_cli-0.16.0}/pyproject.toml +5 -2
  5. cade_cli-0.16.0/src/cade_mcp_local/grants.py +279 -0
  6. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/resources.py +52 -6
  7. cade_cli-0.16.0/src/cade_mcp_local/sandbox.py +273 -0
  8. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/server.py +9 -1
  9. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/__init__.py +4 -1
  10. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/_read_cache.py +2 -2
  11. cade_cli-0.16.0/src/cade_mcp_local/tools/access.py +190 -0
  12. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/agents.py +27 -1
  13. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/shell.py +183 -27
  14. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/utils.py +11 -5
  15. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/ai/prompts.py +1 -1
  16. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/app.py +47 -5
  17. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/agents.py +7 -6
  18. cade_cli-0.16.0/src/cadecoder/cli/commands/daemon.py +413 -0
  19. cade_cli-0.16.0/src/cadecoder/cli/commands/grant.py +121 -0
  20. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/tools.py +13 -2
  21. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/agent.py +16 -1
  22. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/config.py +1 -1
  23. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/paths.py +20 -0
  24. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/runtime.py +5 -0
  25. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/secrets.py +35 -1
  26. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/spawn.py +17 -0
  27. cade_cli-0.16.0/src/cadecoder/daemon/__init__.py +9 -0
  28. {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/adapters/__init__.py +2 -3
  29. cade_cli-0.16.0/src/cadecoder/daemon/adapters/factory.py +34 -0
  30. {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/adapters/imessage.py +7 -7
  31. {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/adapters/registry.py +2 -2
  32. {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/adapters/stub.py +1 -1
  33. {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/adapters/telegram.py +6 -6
  34. {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/allowlist.py +32 -3
  35. cade_cli-0.16.0/src/cadecoder/daemon/attach.py +115 -0
  36. cade_cli-0.16.0/src/cadecoder/daemon/client.py +206 -0
  37. {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/config.py +16 -12
  38. cade_cli-0.16.0/src/cadecoder/daemon/control.py +224 -0
  39. cade_cli-0.16.0/src/cadecoder/daemon/cron.py +94 -0
  40. {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/elicitation.py +2 -2
  41. cade_cli-0.16.0/src/cadecoder/daemon/ensure.py +123 -0
  42. cade_cli-0.16.0/src/cadecoder/daemon/gateway/__init__.py +10 -0
  43. cade_cli-0.16.0/src/cadecoder/daemon/gateway/app.py +180 -0
  44. cade_cli-0.16.0/src/cadecoder/daemon/gateway/arcade_export.py +31 -0
  45. cade_cli-0.16.0/src/cadecoder/daemon/gateway/auth.py +106 -0
  46. cade_cli-0.16.0/src/cadecoder/daemon/gateway/constants.py +24 -0
  47. cade_cli-0.16.0/src/cadecoder/daemon/gateway/elicitation.py +104 -0
  48. cade_cli-0.16.0/src/cadecoder/daemon/gateway/exposure.py +98 -0
  49. cade_cli-0.16.0/src/cadecoder/daemon/gateway/limits.py +61 -0
  50. cade_cli-0.16.0/src/cadecoder/daemon/gateway/middleware.py +204 -0
  51. cade_cli-0.16.0/src/cadecoder/daemon/gateway/resources.py +129 -0
  52. cade_cli-0.16.0/src/cadecoder/daemon/gateway/subscriptions.py +224 -0
  53. cade_cli-0.16.0/src/cadecoder/daemon/gateway/turn_bridge.py +166 -0
  54. cade_cli-0.16.0/src/cadecoder/daemon/grants.py +122 -0
  55. cade_cli-0.15.10/src/cadecoder/serve/cron.py → cade_cli-0.16.0/src/cadecoder/daemon/inbox_cron.py +1 -2
  56. {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/install.py +36 -55
  57. cade_cli-0.16.0/src/cadecoder/daemon/live.py +23 -0
  58. {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/lock.py +2 -2
  59. cade_cli-0.16.0/src/cadecoder/daemon/mesh.py +105 -0
  60. cade_cli-0.16.0/src/cadecoder/daemon/message_pump.py +254 -0
  61. {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/observability.py +6 -6
  62. {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/owner.py +3 -6
  63. {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/progress_sink.py +2 -2
  64. cade_cli-0.16.0/src/cadecoder/daemon/registry.py +43 -0
  65. {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/router.py +10 -3
  66. cade_cli-0.16.0/src/cadecoder/daemon/runner.py +243 -0
  67. cade_cli-0.16.0/src/cadecoder/daemon/runner_main.py +65 -0
  68. cade_cli-0.16.0/src/cadecoder/daemon/secrets_broker.py +120 -0
  69. cade_cli-0.16.0/src/cadecoder/daemon/server.py +374 -0
  70. {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/services.py +1 -1
  71. cade_cli-0.16.0/src/cadecoder/daemon/shim.py +186 -0
  72. {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/sinks.py +3 -3
  73. cade_cli-0.16.0/src/cadecoder/daemon/supervisor.py +220 -0
  74. cade_cli-0.16.0/src/cadecoder/daemon/tokens.py +170 -0
  75. {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/traffic.py +3 -3
  76. cade_cli-0.16.0/src/cadecoder/daemon/turns.py +285 -0
  77. cade_cli-0.16.0/src/cadecoder/daemon/wake.py +166 -0
  78. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/execution/orchestrator.py +1 -1
  79. cade_cli-0.16.0/src/cadecoder/serve/__init__.py +23 -0
  80. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/storage/threads.py +95 -17
  81. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tasks/__init__.py +0 -2
  82. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tasks/scheduler.py +5 -21
  83. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/templates/serve/launchd.plist.tmpl +3 -2
  84. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/templates/serve/serve.toml.example +21 -28
  85. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/templates/serve/systemd.service.tmpl +2 -2
  86. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tools/manager/_request_ctx.py +1 -1
  87. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tools/manager/composite.py +7 -1
  88. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/ui/elicitation.py +1 -1
  89. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/ui/session.py +104 -18
  90. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/voice/session.py +17 -0
  91. cade_cli-0.15.10/src/cadecoder/cli/commands/serve.py +0 -437
  92. cade_cli-0.15.10/src/cadecoder/serve/__init__.py +0 -11
  93. cade_cli-0.15.10/src/cadecoder/serve/adapters/desktop.py +0 -408
  94. cade_cli-0.15.10/src/cadecoder/serve/daemon.py +0 -682
  95. cade_cli-0.15.10/src/cadecoder/serve/worker.py +0 -682
  96. cade_cli-0.15.10/src/cadecoder/tasks/lock.py +0 -124
  97. {cade_cli-0.15.10 → cade_cli-0.16.0}/LICENSE +0 -0
  98. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/__init__.py +0 -0
  99. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/__main__.py +0 -0
  100. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/_metadata.py +0 -0
  101. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/config.py +0 -0
  102. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/errors.py +0 -0
  103. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/context.py +0 -0
  104. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/filesystem.py +0 -0
  105. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/git.py +0 -0
  106. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/notifications.py +0 -0
  107. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/questions.py +0 -0
  108. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/results.py +0 -0
  109. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/schemas.py +0 -0
  110. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/search.py +0 -0
  111. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/snippets.py +0 -0
  112. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/tasks.py +0 -0
  113. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/web_fetch.py +0 -0
  114. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/web_search.py +0 -0
  115. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/__init__.py +0 -0
  116. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/arcade_client.py +0 -0
  117. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/cache.py +0 -0
  118. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/extract.py +0 -0
  119. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/markdown.py +0 -0
  120. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/preapproved.py +0 -0
  121. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/prompts.py +0 -0
  122. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/scrape.py +0 -0
  123. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/search/__init__.py +0 -0
  124. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/search/arcade.py +0 -0
  125. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/search/base.py +0 -0
  126. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/search/http.py +0 -0
  127. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/__init__.py +0 -0
  128. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/ai/__init__.py +0 -0
  129. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/__init__.py +0 -0
  130. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/auth.py +0 -0
  131. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/__init__.py +0 -0
  132. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/account.py +0 -0
  133. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/auth.py +0 -0
  134. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/chat.py +0 -0
  135. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/cron.py +0 -0
  136. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/hooks.py +0 -0
  137. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/mcp.py +0 -0
  138. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/mem.py +0 -0
  139. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/tasks.py +0 -0
  140. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/thread.py +0 -0
  141. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/onboarding.py +0 -0
  142. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/__init__.py +0 -0
  143. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/comms.py +0 -0
  144. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/constants.py +0 -0
  145. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/errors.py +0 -0
  146. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/frozen.py +0 -0
  147. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/git.py +0 -0
  148. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/inbox.py +0 -0
  149. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/logging.py +0 -0
  150. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/memory.py +0 -0
  151. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/model_specs.py +0 -0
  152. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/names.py +0 -0
  153. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/types.py +0 -0
  154. {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/adapters/base.py +0 -0
  155. {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/chunker.py +0 -0
  156. {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/commands.py +0 -0
  157. {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/format.py +0 -0
  158. {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/secrets.py +0 -0
  159. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/execution/__init__.py +0 -0
  160. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/execution/context_window.py +0 -0
  161. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/execution/mcp_schema_index.py +0 -0
  162. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/execution/parallel.py +0 -0
  163. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/execution/tool_result_store.py +0 -0
  164. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/execution/tool_schema_cache.py +0 -0
  165. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/__init__.py +0 -0
  166. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/config.py +0 -0
  167. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/engine.py +0 -0
  168. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/executors/__init__.py +0 -0
  169. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/executors/callback.py +0 -0
  170. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/executors/command.py +0 -0
  171. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/executors/http.py +0 -0
  172. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/executors/prompt.py +0 -0
  173. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/executors/ssrf_guard.py +0 -0
  174. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/matchers.py +0 -0
  175. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/registry.py +0 -0
  176. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/types.py +0 -0
  177. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/providers/__init__.py +0 -0
  178. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/providers/anthropic.py +0 -0
  179. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/providers/base.py +0 -0
  180. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/providers/ollama.py +0 -0
  181. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/providers/openai.py +0 -0
  182. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/storage/__init__.py +0 -0
  183. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tasks/store.py +0 -0
  184. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tasks/types.py +0 -0
  185. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/templates/login_failed.html +0 -0
  186. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/templates/login_success.html +0 -0
  187. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/templates/styles.css +0 -0
  188. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tools/__init__.py +0 -0
  189. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tools/manager/__init__.py +0 -0
  190. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tools/manager/base.py +0 -0
  191. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tools/manager/config.py +0 -0
  192. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tools/manager/mcp.py +0 -0
  193. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tools/search/__init__.py +0 -0
  194. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tools/search/discovered.py +0 -0
  195. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tools/search/scoring.py +0 -0
  196. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tools/search/service.py +0 -0
  197. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/ui/__init__.py +0 -0
  198. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/ui/completion.py +0 -0
  199. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/ui/display.py +0 -0
  200. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/ui/input.py +0 -0
  201. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/ui/slash.py +0 -0
  202. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/voice/__init__.py +0 -0
  203. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/voice/audio.py +0 -0
  204. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/voice/cleanup.py +0 -0
  205. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/voice/stt.py +0 -0
  206. {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/voice/tts.py +0 -0
@@ -32,6 +32,9 @@ htmlcov/
32
32
  dist/
33
33
  build/
34
34
 
35
+ # MkDocs build output
36
+ site/
37
+
35
38
  # Logs
36
39
  *.log
37
40
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cade-cli
3
- Version: 0.15.10
3
+ Version: 0.16.0
4
4
  Summary: Cade - The CLI Agent from Arcade.dev
5
5
  Project-URL: Homepage, https://arcade.dev
6
6
  Project-URL: Documentation, https://docs.arcade.dev
@@ -63,7 +63,6 @@ Requires-Dist: rich<14.0.0,>=13.0.0
63
63
  Requires-Dist: toml<1.0.0,>=0.10.0
64
64
  Requires-Dist: typer>0.10.0
65
65
  Requires-Dist: ulid==1.1
66
- Requires-Dist: websockets<16.0,>=14.0
67
66
  Provides-Extra: build
68
67
  Requires-Dist: pyinstaller<7.0.0,>=6.16.0; extra == 'build'
69
68
  Provides-Extra: dev
@@ -73,6 +72,9 @@ Requires-Dist: pytest-cov<5.0.0,>=4.0.0; extra == 'dev'
73
72
  Requires-Dist: pytest-mock<4.0.0,>=3.11.0; extra == 'dev'
74
73
  Requires-Dist: pytest<9.0.0,>=8.0.0; extra == 'dev'
75
74
  Requires-Dist: ruff<1.0.0,>=0.5.0; extra == 'dev'
75
+ Provides-Extra: docs
76
+ Requires-Dist: mkdocs-material<10.0.0,>=9.5.0; extra == 'docs'
77
+ Requires-Dist: mkdocs<2.0.0,>=1.6.0; extra == 'docs'
76
78
  Provides-Extra: training
77
79
  Requires-Dist: accelerate>=0.27.0; extra == 'training'
78
80
  Requires-Dist: safetensors>=0.4.2; extra == 'training'
@@ -100,7 +102,7 @@ cade
100
102
 
101
103
  ## Highlights
102
104
 
103
- - **Three deployment modes from the same binary.** Interactive chat (`cade`), one-shot or pipe (`cade -m "…"`), or a headless daemon that replies over messaging (`cade serve`).
105
+ - **Three deployment modes from the same binary.** Interactive chat (`cade`), one-shot or pipe (`cade -m "…"`), or a headless daemon (`cade daemon`) that serves MCP hosts and messaging.
104
106
  - **Tools from anywhere.** Built-in local tools (filesystem / shell / git / search / tasks / memory), [Arcade Cloud](https://arcade.dev) tools (Slack, GitHub, Gmail, calendars, …), and any [MCP](https://modelcontextprotocol.io/) server you register — all unified under one tool surface.
105
107
  - **Bidirectional MCP.** Tools can call `context.ui.elicit(…)` for structured input, `context.log.*` for live operator logs, and `context.progress.report(…)` for editable progress UI — routed end-to-end through whichever adapter is active (Telegram inline keyboard, terminal prompt).
106
108
  - **Agents + per-agent memory.** Multiple agents, each with their own model, system prompt, tools, and an indexed memory store backed by [agent-library](https://pypi.org/project/agent-library/).
@@ -202,7 +204,7 @@ cade tools search "send slack" # keyword search
202
204
  cade tools info Local_ReadFile # full schema + description
203
205
  ```
204
206
 
205
- The full local tool surface (including the bidirectional MCP capabilities and `cade serve` integration) is documented in [`docs/mcp.md`](./docs/mcp.md).
207
+ The full local tool surface (including the bidirectional MCP capabilities and `cade daemon` integration) is documented in [`docs/mcp.md`](./docs/mcp.md).
206
208
 
207
209
  ### Custom MCP servers
208
210
 
@@ -243,24 +245,22 @@ warmup before a conversation.
243
245
 
244
246
  ---
245
247
 
246
- ## Headless daemon: `cade serve`
248
+ ## Headless daemon: `cade daemon`
247
249
 
248
- Run Cade as a long-lived daemon that replies through adapters. Telegram is supported for personal bot workflows, and Desktop exposes a local WebSocket for native clients. Each conversation gets its own persistent thread — model, memory, and tools carry across messages.
250
+ One long-lived supervisor serves **all** your agents to MCP hosts (Claude Desktop, Cursor) and messaging adapters (Telegram, iMessage). Each conversation gets its own persistent thread — model, memory, and tools carry across messages. See [`docs/daemon.md`](./docs/daemon.md).
249
251
 
250
252
  ```bash
251
- cade serve init # wizard: bot token, allowed senders
252
- cade serve # foreground (current agent)
253
- cade serve --agent ops # serve a specific agent (one daemon per agent)
254
- cade serve list # agents with a running daemon
255
- cade serve --install launchd # macOS LaunchAgent (or --install systemd)
256
- cade serve status / stop # health, uptime; SIGTERM running daemon
253
+ cade daemon init # mint a token + optional Telegram/iMessage wizard
254
+ cade daemon start # foreground (gateway + messaging)
255
+ cade daemon start -v # also print message traffic
256
+ cade daemon install launchd # macOS LaunchAgent (or: systemd)
257
+ cade daemon status / stop # health; SIGTERM
258
+ cade daemon token new cursor -e delegate -e resources
257
259
  ```
258
260
 
259
- Each agent's serve config lives in `~/.cade/config/agents/<name>.toml [serve]`. Edit and `kill -HUP $(cat ~/.cade/run/agents/<name>/serve.pid)` to reload without restart.
261
+ Adapter + tool config lives in `~/.cade/config/agents/<name>.toml`. Edit and `kill -HUP $(cat ~/.cade/run/daemon.pid)` to reload without restart.
260
262
 
261
- **Security defaults are fail-closed:** `allowed_senders = []` blocks everyone; tools are default-deny against `[serve.tools].allow` (globs / regex / pipe lists supported); `deny` always wins.
262
-
263
- The MCP transport is fully bidirectional, so tools running inside the daemon can elicit structured input, stream live progress, and emit logs through whichever adapter is active. See [`docs/adapters/`](./docs/adapters/) for adapter details, [`docs/mcp.md`](./docs/mcp.md) for protocol details, and [`docs/configuration.md`](./docs/configuration.md) for the full daemon config reference.
263
+ **Security defaults are fail-closed:** `allowed_senders = []` blocks everyone on messengers; MCP hosts authenticate with bearer tokens; tools are default-deny against `[tools].allow`.
264
264
 
265
265
  ---
266
266
 
@@ -313,7 +313,8 @@ All state lives under `~/.cade/`. Override the location with `CADE_HOME=/path/to
313
313
  | `config/cade.toml` | Global settings + `[agent_defaults]` (model, provider, UI, …) |
314
314
  | `config/agents/<name>.toml` | Per-agent config (model, prompt, `[[mcp]]`, `[serve]`, hooks) |
315
315
  | `data/agents/<id>/` | Per-agent state: `threads/ memory/ tasks/ inbox/` |
316
- | `run/agents/<name>/` | Per-agent daemon runtime: `serve.pid`, mcp-services, cron lease |
316
+ | `run/daemon.pid` | Single gateway-supervisor pidfile |
317
+ | `run/agents/<name>/` | Per-agent runtime: mcp-services, grants |
317
318
  | `logs/cade.log` | Rotating log file |
318
319
 
319
320
  Full layout, env vars, and TOML schema in [`docs/configuration.md`](./docs/configuration.md).
@@ -352,7 +353,7 @@ welcome — open an issue first for anything substantial.
352
353
 
353
354
  - [arcade.dev](https://arcade.dev) — product
354
355
  - [docs.arcade.dev](https://docs.arcade.dev) — API and tool catalog
355
- - [`docs/`](./docs/) — quickstart, examples, install, config, MCP, channels, voice, development, and adapters
356
+ - [`docs/`](./docs/) — quickstart, examples, install, config, MCP, agents, automation, channels, voice, development, and adapters. Browse as a site with `make docs` (MkDocs Material)
356
357
  - [Issues](https://github.com/arcadeai-labs/cade/issues) · [Releases](https://github.com/arcadeai-labs/cade/releases)
357
358
 
358
359
  ## License
@@ -12,7 +12,7 @@ cade
12
12
 
13
13
  ## Highlights
14
14
 
15
- - **Three deployment modes from the same binary.** Interactive chat (`cade`), one-shot or pipe (`cade -m "…"`), or a headless daemon that replies over messaging (`cade serve`).
15
+ - **Three deployment modes from the same binary.** Interactive chat (`cade`), one-shot or pipe (`cade -m "…"`), or a headless daemon (`cade daemon`) that serves MCP hosts and messaging.
16
16
  - **Tools from anywhere.** Built-in local tools (filesystem / shell / git / search / tasks / memory), [Arcade Cloud](https://arcade.dev) tools (Slack, GitHub, Gmail, calendars, …), and any [MCP](https://modelcontextprotocol.io/) server you register — all unified under one tool surface.
17
17
  - **Bidirectional MCP.** Tools can call `context.ui.elicit(…)` for structured input, `context.log.*` for live operator logs, and `context.progress.report(…)` for editable progress UI — routed end-to-end through whichever adapter is active (Telegram inline keyboard, terminal prompt).
18
18
  - **Agents + per-agent memory.** Multiple agents, each with their own model, system prompt, tools, and an indexed memory store backed by [agent-library](https://pypi.org/project/agent-library/).
@@ -114,7 +114,7 @@ cade tools search "send slack" # keyword search
114
114
  cade tools info Local_ReadFile # full schema + description
115
115
  ```
116
116
 
117
- The full local tool surface (including the bidirectional MCP capabilities and `cade serve` integration) is documented in [`docs/mcp.md`](./docs/mcp.md).
117
+ The full local tool surface (including the bidirectional MCP capabilities and `cade daemon` integration) is documented in [`docs/mcp.md`](./docs/mcp.md).
118
118
 
119
119
  ### Custom MCP servers
120
120
 
@@ -155,24 +155,22 @@ warmup before a conversation.
155
155
 
156
156
  ---
157
157
 
158
- ## Headless daemon: `cade serve`
158
+ ## Headless daemon: `cade daemon`
159
159
 
160
- Run Cade as a long-lived daemon that replies through adapters. Telegram is supported for personal bot workflows, and Desktop exposes a local WebSocket for native clients. Each conversation gets its own persistent thread — model, memory, and tools carry across messages.
160
+ One long-lived supervisor serves **all** your agents to MCP hosts (Claude Desktop, Cursor) and messaging adapters (Telegram, iMessage). Each conversation gets its own persistent thread — model, memory, and tools carry across messages. See [`docs/daemon.md`](./docs/daemon.md).
161
161
 
162
162
  ```bash
163
- cade serve init # wizard: bot token, allowed senders
164
- cade serve # foreground (current agent)
165
- cade serve --agent ops # serve a specific agent (one daemon per agent)
166
- cade serve list # agents with a running daemon
167
- cade serve --install launchd # macOS LaunchAgent (or --install systemd)
168
- cade serve status / stop # health, uptime; SIGTERM running daemon
163
+ cade daemon init # mint a token + optional Telegram/iMessage wizard
164
+ cade daemon start # foreground (gateway + messaging)
165
+ cade daemon start -v # also print message traffic
166
+ cade daemon install launchd # macOS LaunchAgent (or: systemd)
167
+ cade daemon status / stop # health; SIGTERM
168
+ cade daemon token new cursor -e delegate -e resources
169
169
  ```
170
170
 
171
- Each agent's serve config lives in `~/.cade/config/agents/<name>.toml [serve]`. Edit and `kill -HUP $(cat ~/.cade/run/agents/<name>/serve.pid)` to reload without restart.
171
+ Adapter + tool config lives in `~/.cade/config/agents/<name>.toml`. Edit and `kill -HUP $(cat ~/.cade/run/daemon.pid)` to reload without restart.
172
172
 
173
- **Security defaults are fail-closed:** `allowed_senders = []` blocks everyone; tools are default-deny against `[serve.tools].allow` (globs / regex / pipe lists supported); `deny` always wins.
174
-
175
- The MCP transport is fully bidirectional, so tools running inside the daemon can elicit structured input, stream live progress, and emit logs through whichever adapter is active. See [`docs/adapters/`](./docs/adapters/) for adapter details, [`docs/mcp.md`](./docs/mcp.md) for protocol details, and [`docs/configuration.md`](./docs/configuration.md) for the full daemon config reference.
173
+ **Security defaults are fail-closed:** `allowed_senders = []` blocks everyone on messengers; MCP hosts authenticate with bearer tokens; tools are default-deny against `[tools].allow`.
176
174
 
177
175
  ---
178
176
 
@@ -225,7 +223,8 @@ All state lives under `~/.cade/`. Override the location with `CADE_HOME=/path/to
225
223
  | `config/cade.toml` | Global settings + `[agent_defaults]` (model, provider, UI, …) |
226
224
  | `config/agents/<name>.toml` | Per-agent config (model, prompt, `[[mcp]]`, `[serve]`, hooks) |
227
225
  | `data/agents/<id>/` | Per-agent state: `threads/ memory/ tasks/ inbox/` |
228
- | `run/agents/<name>/` | Per-agent daemon runtime: `serve.pid`, mcp-services, cron lease |
226
+ | `run/daemon.pid` | Single gateway-supervisor pidfile |
227
+ | `run/agents/<name>/` | Per-agent runtime: mcp-services, grants |
229
228
  | `logs/cade.log` | Rotating log file |
230
229
 
231
230
  Full layout, env vars, and TOML schema in [`docs/configuration.md`](./docs/configuration.md).
@@ -264,7 +263,7 @@ welcome — open an issue first for anything substantial.
264
263
 
265
264
  - [arcade.dev](https://arcade.dev) — product
266
265
  - [docs.arcade.dev](https://docs.arcade.dev) — API and tool catalog
267
- - [`docs/`](./docs/) — quickstart, examples, install, config, MCP, channels, voice, development, and adapters
266
+ - [`docs/`](./docs/) — quickstart, examples, install, config, MCP, agents, automation, channels, voice, development, and adapters. Browse as a site with `make docs` (MkDocs Material)
268
267
  - [Issues](https://github.com/arcadeai-labs/cade/issues) · [Releases](https://github.com/arcadeai-labs/cade/releases)
269
268
 
270
269
  ## License
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cade-cli"
3
- version = "0.15.10"
3
+ version = "0.16.0"
4
4
  description = "Cade - The CLI Agent from Arcade.dev"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -43,7 +43,6 @@ dependencies = [
43
43
  "python-telegram-bot>=21.0,<22.0",
44
44
  "keyring>=24.0,<26.0",
45
45
  "html2text>=2024.2.26",
46
- "websockets>=14.0,<16.0",
47
46
  ]
48
47
 
49
48
  [project.urls]
@@ -69,6 +68,10 @@ dev = [
69
68
  build = [
70
69
  "pyinstaller>=6.16.0,<7.0.0",
71
70
  ]
71
+ docs = [
72
+ "mkdocs>=1.6.0,<2.0.0",
73
+ "mkdocs-material>=9.5.0,<10.0.0",
74
+ ]
72
75
  voice = [
73
76
  "mlx-audio>=0.2.0,<0.4.0",
74
77
  "mlx-whisper>=0.1.0",
@@ -0,0 +1,279 @@
1
+ """Live filesystem GrantSet shared across processes.
2
+
3
+ The GrantSet is the single source of truth for which directories the agent's
4
+ tools may touch, split into three scopes:
5
+
6
+ * **scratch** — per-agent scratch directory, always granted, writable, and
7
+ identical in every scope (a script written there under ``ScratchBash`` runs
8
+ unchanged under workspace ``Bash``);
9
+ * **workspace** — the agent's working directory (``CADE_PROJECT_ROOT``
10
+ today);
11
+ * **grants** — additional directories, each read-only or read-write, added at
12
+ startup from config or at runtime with operator consent.
13
+
14
+ Because the local-tools server may run in a separate process from the daemon
15
+ or CLI that mutates grants, the GrantSet is published as a JSON file whose
16
+ path travels in the ``CADE_GRANTS_FILE`` environment variable. Readers cache
17
+ by mtime, so a runtime grant is visible to the next tool call with no
18
+ restarts. When no grants file exists the GrantSet degrades to exactly the
19
+ pre-GrantSet behavior: workspace = project root, no extra grants.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import json
25
+ import logging
26
+ import os
27
+ import pathlib
28
+ import tempfile
29
+ from dataclasses import dataclass, field
30
+
31
+ from cade_mcp_local.config import get_project_root
32
+
33
+ log = logging.getLogger(__name__)
34
+
35
+ GRANTS_FILE_ENV = "CADE_GRANTS_FILE"
36
+ SCRATCH_DIR_ENV = "CADE_SCRATCH_DIR"
37
+
38
+ MODE_RO = "ro"
39
+ MODE_RW = "rw"
40
+
41
+
42
+ @dataclass(frozen=True)
43
+ class Grant:
44
+ """One granted directory and its access mode."""
45
+
46
+ path: pathlib.Path
47
+ mode: str = MODE_RW # "ro" | "rw"
48
+
49
+ def to_json(self) -> dict[str, str]:
50
+ return {"path": str(self.path), "mode": self.mode}
51
+
52
+ @classmethod
53
+ def from_json(cls, data: dict[str, object]) -> Grant | None:
54
+ raw_path = data.get("path")
55
+ mode = data.get("mode", MODE_RW)
56
+ if not isinstance(raw_path, str) or mode not in (MODE_RO, MODE_RW):
57
+ return None
58
+ try:
59
+ return cls(path=pathlib.Path(raw_path).expanduser().resolve(), mode=str(mode))
60
+ except OSError:
61
+ return None
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class GrantSet:
66
+ """The agent's live filesystem scopes."""
67
+
68
+ scratch: pathlib.Path
69
+ workspace: pathlib.Path
70
+ grants: tuple[Grant, ...] = field(default_factory=tuple)
71
+
72
+ # ------------------------------------------------------------- queries
73
+
74
+ def roots(self, *, scope: str = "workspace") -> list[Grant]:
75
+ """Return the accessible roots for a scope, scratch always included.
76
+
77
+ ``scope="scratch"`` → scratch only. ``scope="workspace"`` → scratch +
78
+ workspace + grants.
79
+ """
80
+ scratch_grant = Grant(path=self.scratch, mode=MODE_RW)
81
+ if scope == "scratch":
82
+ return [scratch_grant]
83
+ out = [scratch_grant, Grant(path=self.workspace, mode=MODE_RW)]
84
+ out.extend(self.grants)
85
+ return out
86
+
87
+ def contains(self, path: pathlib.Path, *, write: bool = False) -> bool:
88
+ """True when ``path`` falls inside an accessible root (rw if writing)."""
89
+ try:
90
+ resolved = path.resolve()
91
+ except OSError:
92
+ return False
93
+ for grant in self.roots():
94
+ if write and grant.mode != MODE_RW:
95
+ continue
96
+ try:
97
+ resolved.relative_to(grant.path)
98
+ return True
99
+ except ValueError:
100
+ continue
101
+ return False
102
+
103
+ def describe(self) -> str:
104
+ """Human-readable scope summary for tool descriptions and prompts."""
105
+ parts = [f"{self.workspace} (rw)"]
106
+ parts.extend(f"{g.path} ({g.mode})" for g in self.grants)
107
+ parts.append(f"scratch: {self.scratch}")
108
+ return ", ".join(parts)
109
+
110
+ # -------------------------------------------------------------- persist
111
+
112
+ def to_json(self) -> dict[str, object]:
113
+ return {
114
+ "scratch": str(self.scratch),
115
+ "workspace": str(self.workspace),
116
+ "grants": [g.to_json() for g in self.grants],
117
+ }
118
+
119
+ def save(self, path: pathlib.Path) -> None:
120
+ """Atomically write the grants file (rename over temp)."""
121
+ path.parent.mkdir(parents=True, exist_ok=True)
122
+ handle, tmp_name = tempfile.mkstemp(dir=path.parent, prefix=".grants-")
123
+ try:
124
+ with os.fdopen(handle, "w", encoding="utf-8") as fh:
125
+ json.dump(self.to_json(), fh)
126
+ os.replace(tmp_name, path)
127
+ except OSError:
128
+ try:
129
+ os.unlink(tmp_name)
130
+ except OSError:
131
+ pass
132
+ raise
133
+ _load_grants_file.cache_bust()
134
+
135
+
136
+ def default_scratch_dir() -> pathlib.Path:
137
+ """Resolve the scratch directory: env override → per-user temp fallback."""
138
+ override = os.environ.get(SCRATCH_DIR_ENV, "").strip()
139
+ if override:
140
+ return pathlib.Path(override).expanduser().resolve()
141
+ base = pathlib.Path(tempfile.gettempdir()) / f"cade-scratch-{os.getuid()}"
142
+ return base.resolve()
143
+
144
+
145
+ def default_grant_set() -> GrantSet:
146
+ """Pre-GrantSet behavior: workspace = project root, no extra grants."""
147
+ return GrantSet(scratch=default_scratch_dir(), workspace=get_project_root())
148
+
149
+
150
+ class _GrantsFileLoader:
151
+ """mtime-cached loader so runtime grants are cheap to read per call."""
152
+
153
+ def __init__(self) -> None:
154
+ self._cached: GrantSet | None = None
155
+ self._cached_key: tuple[str, float] | None = None
156
+
157
+ def __call__(self) -> GrantSet:
158
+ raw_path = os.environ.get(GRANTS_FILE_ENV, "").strip()
159
+ if not raw_path:
160
+ return default_grant_set()
161
+ path = pathlib.Path(raw_path)
162
+ try:
163
+ mtime = path.stat().st_mtime
164
+ except OSError:
165
+ return default_grant_set()
166
+ key = (raw_path, mtime)
167
+ if self._cached is not None and self._cached_key == key:
168
+ return self._cached
169
+ grant_set = self._parse(path)
170
+ self._cached, self._cached_key = grant_set, key
171
+ return grant_set
172
+
173
+ def cache_bust(self) -> None:
174
+ self._cached = None
175
+ self._cached_key = None
176
+
177
+ @staticmethod
178
+ def _parse(path: pathlib.Path) -> GrantSet:
179
+ try:
180
+ data = json.loads(path.read_text(encoding="utf-8"))
181
+ except (OSError, json.JSONDecodeError) as e:
182
+ log.warning("unreadable grants file at %s: %s; using defaults", path, e)
183
+ return default_grant_set()
184
+ if not isinstance(data, dict):
185
+ return default_grant_set()
186
+ try:
187
+ scratch = pathlib.Path(str(data.get("scratch") or default_scratch_dir()))
188
+ workspace = pathlib.Path(str(data.get("workspace") or get_project_root()))
189
+ except OSError:
190
+ return default_grant_set()
191
+ grants: list[Grant] = []
192
+ raw_grants = data.get("grants")
193
+ if isinstance(raw_grants, list):
194
+ for entry in raw_grants:
195
+ if isinstance(entry, dict):
196
+ grant = Grant.from_json(entry)
197
+ if grant is not None:
198
+ grants.append(grant)
199
+ return GrantSet(
200
+ scratch=scratch.expanduser().resolve(),
201
+ workspace=workspace.expanduser().resolve(),
202
+ grants=tuple(grants),
203
+ )
204
+
205
+
206
+ _load_grants_file = _GrantsFileLoader()
207
+
208
+
209
+ def current_grant_set() -> GrantSet:
210
+ """Return the live GrantSet (grants file when published, defaults otherwise)."""
211
+ return _load_grants_file()
212
+
213
+
214
+ def ensure_scratch_dir(grant_set: GrantSet | None = None) -> pathlib.Path:
215
+ """Create the scratch directory if missing; return its path."""
216
+ scratch = (grant_set or current_grant_set()).scratch
217
+ scratch.mkdir(parents=True, exist_ok=True)
218
+ return scratch
219
+
220
+
221
+ def grants_file_path() -> pathlib.Path | None:
222
+ """The published grants-file path, or None when no grants file is in use."""
223
+ raw = os.environ.get(GRANTS_FILE_ENV, "").strip()
224
+ return pathlib.Path(raw) if raw else None
225
+
226
+
227
+ def add_grant(path: str | pathlib.Path, mode: str = MODE_RW) -> GrantSet:
228
+ """Add (or upgrade the mode of) a runtime grant; persist and return the set.
229
+
230
+ Requires a published grants file (``CADE_GRANTS_FILE``); raises otherwise
231
+ so a caller never believes a grant took effect when it silently didn't.
232
+ Adding an ``rw`` grant for a path already granted ``ro`` upgrades it.
233
+ """
234
+ target = grants_file_path()
235
+ if target is None:
236
+ raise RuntimeError("no grants file published (CADE_GRANTS_FILE unset)")
237
+ if mode not in (MODE_RO, MODE_RW):
238
+ raise ValueError(f"invalid grant mode: {mode!r}")
239
+ resolved = pathlib.Path(path).expanduser().resolve()
240
+
241
+ current = current_grant_set()
242
+ kept = [g for g in current.grants if g.path != resolved]
243
+ kept.append(Grant(path=resolved, mode=mode))
244
+ updated = GrantSet(scratch=current.scratch, workspace=current.workspace, grants=tuple(kept))
245
+ updated.save(target)
246
+ return updated
247
+
248
+
249
+ def remove_grant(path: str | pathlib.Path) -> GrantSet:
250
+ """Remove a runtime grant; persist and return the updated set.
251
+
252
+ Removing a path that isn't granted is a no-op (idempotent revoke).
253
+ """
254
+ target = grants_file_path()
255
+ if target is None:
256
+ raise RuntimeError("no grants file published (CADE_GRANTS_FILE unset)")
257
+ resolved = pathlib.Path(path).expanduser().resolve()
258
+ current = current_grant_set()
259
+ kept = tuple(g for g in current.grants if g.path != resolved)
260
+ updated = GrantSet(scratch=current.scratch, workspace=current.workspace, grants=kept)
261
+ updated.save(target)
262
+ return updated
263
+
264
+
265
+ __all__ = [
266
+ "GRANTS_FILE_ENV",
267
+ "MODE_RO",
268
+ "MODE_RW",
269
+ "SCRATCH_DIR_ENV",
270
+ "Grant",
271
+ "GrantSet",
272
+ "add_grant",
273
+ "current_grant_set",
274
+ "default_grant_set",
275
+ "default_scratch_dir",
276
+ "ensure_scratch_dir",
277
+ "grants_file_path",
278
+ "remove_grant",
279
+ ]
@@ -8,9 +8,9 @@ Two resources today:
8
8
  - ``cade://memory/sources`` — a JSON listing of the active persona's
9
9
  ``cade mem`` sources, read from the librarian sources registry.
10
10
 
11
- Persona resolution mirrors ``cade mem``: ``CADE_PERSONA`` env var
12
- ``"default"``. Context data is supplied by the parent process through
13
- ``CADE_CONTEXT_DATA_DIR``.
11
+ Persona / agent identity prefers ``CADE_AGENT`` (daemon runners) then
12
+ ``CADE_PERSONA``, falling back to ``"default"``. Memory paths prefer the
13
+ agent data dir (``CADE_AGENT_DATA_DIR``) then ``CADE_CONTEXT_DATA_DIR``.
14
14
  """
15
15
 
16
16
  from __future__ import annotations
@@ -28,6 +28,7 @@ log = logging.getLogger(__name__)
28
28
 
29
29
  INSTRUCTIONS_URI = "cade://server/instructions"
30
30
  MEMORY_SOURCES_URI = "cade://memory/sources"
31
+ FILESYSTEM_URI = "cade://filesystem"
31
32
 
32
33
 
33
34
  def _cade_home() -> Path:
@@ -39,15 +40,23 @@ def _cade_home() -> Path:
39
40
 
40
41
 
41
42
  def _resolve_persona() -> str:
42
- """Return the active persona name. Env var → ``"default"``."""
43
- name = (os.environ.get("CADE_PERSONA") or "").strip()
44
- return name or "default"
43
+ """Return the active agent/persona name for memory paths."""
44
+ for key in ("CADE_AGENT", "CADE_PERSONA"):
45
+ name = (os.environ.get(key) or "").strip()
46
+ if name:
47
+ return name
48
+ return "default"
45
49
 
46
50
 
47
51
  def _sources_path(persona: str) -> Path:
52
+ """Resolve ``sources.json`` under the agent (preferred) or legacy context tree."""
53
+ agent_data = os.environ.get("CADE_AGENT_DATA_DIR")
54
+ if agent_data:
55
+ return Path(agent_data) / "memory" / ".librarian" / "sources.json"
48
56
  context_data = os.environ.get("CADE_CONTEXT_DATA_DIR")
49
57
  if context_data:
50
58
  return Path(context_data) / "memory" / persona / ".librarian" / "sources.json"
59
+ # Legacy contexts/ layout — kept for older installs only.
51
60
  context = os.environ.get("CADE_CONTEXT") or "default"
52
61
  return (
53
62
  _cade_home()
@@ -95,6 +104,28 @@ def _memory_sources_payload() -> str:
95
104
  return json.dumps(payload, indent=2, default=str)
96
105
 
97
106
 
107
+ def _filesystem_payload() -> str:
108
+ """Build the JSON body describing the agent's current filesystem scopes."""
109
+ from cade_mcp_local.grants import current_grant_set
110
+ from cade_mcp_local.sandbox import describe_backend, sandbox_available
111
+
112
+ grant_set = current_grant_set()
113
+ payload = {
114
+ "scratch": str(grant_set.scratch),
115
+ "workspace": str(grant_set.workspace),
116
+ "grants": [{"path": str(g.path), "mode": g.mode} for g in grant_set.grants],
117
+ "sandbox_backend": describe_backend(),
118
+ "sandbox_available": sandbox_available(),
119
+ "summary": grant_set.describe(),
120
+ "note": (
121
+ "Local_Bash sees scratch + workspace + grants; Local_ScratchBash "
122
+ "sees only scratch (network off). Request more with "
123
+ "Local_RequestAccess(path, mode, reason)."
124
+ ),
125
+ }
126
+ return json.dumps(payload, indent=2, default=str)
127
+
128
+
98
129
  def register_resources(app: MCPApp, instructions: str) -> None:
99
130
  """Register the local server's static + dynamic MCP resources."""
100
131
  app.add_text_resource(
@@ -123,8 +154,23 @@ def register_resources(app: MCPApp, instructions: str) -> None:
123
154
  handler=lambda _uri: _memory_sources_payload(),
124
155
  )
125
156
 
157
+ app.add_resource(
158
+ FILESYSTEM_URI,
159
+ name="filesystem-scopes",
160
+ title="Filesystem scopes (workspace, grants, scratch)",
161
+ description=(
162
+ "JSON description of the directories the agent's tools may touch: "
163
+ "workspace, runtime grants (rw/ro), and the isolated scratch dir, "
164
+ "plus the OS sandbox backend. Read each call so it reflects grants "
165
+ "added at runtime via Local_RequestAccess or the operator."
166
+ ),
167
+ mime_type="application/json",
168
+ handler=lambda _uri: _filesystem_payload(),
169
+ )
170
+
126
171
 
127
172
  __all__ = (
173
+ "FILESYSTEM_URI",
128
174
  "INSTRUCTIONS_URI",
129
175
  "MEMORY_SOURCES_URI",
130
176
  "register_resources",