cade-cli 0.11.0__tar.gz → 0.13.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. {cade_cli-0.11.0 → cade_cli-0.13.2}/PKG-INFO +34 -28
  2. {cade_cli-0.11.0 → cade_cli-0.13.2}/README.md +27 -20
  3. {cade_cli-0.11.0 → cade_cli-0.13.2}/pyproject.toml +11 -8
  4. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/server.py +7 -3
  5. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/cli/app.py +48 -13
  6. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/cli/commands/context.py +8 -1
  7. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/core/config.py +12 -0
  8. cade_cli-0.13.2/src/cadecoder/core/frozen.py +37 -0
  9. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/execution/tool_result_store.py +4 -2
  10. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/execution/tool_schema_cache.py +34 -2
  11. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/providers/ollama.py +36 -6
  12. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/serve/adapters/__init__.py +4 -1
  13. cade_cli-0.13.2/src/cadecoder/serve/adapters/desktop.py +408 -0
  14. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/serve/config.py +9 -0
  15. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/serve/daemon.py +56 -26
  16. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/serve/observability.py +4 -1
  17. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/serve/worker.py +91 -11
  18. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/templates/serve/serve.toml.example +9 -0
  19. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/tools/manager/composite.py +41 -31
  20. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/tools/search/service.py +61 -12
  21. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/ui/session.py +75 -0
  22. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/ui/slash.py +1 -0
  23. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/voice/__init__.py +7 -1
  24. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/voice/session.py +66 -7
  25. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/voice/tts.py +15 -0
  26. {cade_cli-0.11.0 → cade_cli-0.13.2}/.gitignore +0 -0
  27. {cade_cli-0.11.0 → cade_cli-0.13.2}/LICENSE +0 -0
  28. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/__init__.py +0 -0
  29. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/__main__.py +0 -0
  30. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/_metadata.py +0 -0
  31. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/config.py +0 -0
  32. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/errors.py +0 -0
  33. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/resources.py +0 -0
  34. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/tools/__init__.py +0 -0
  35. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/tools/_read_cache.py +0 -0
  36. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/tools/context.py +0 -0
  37. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/tools/filesystem.py +0 -0
  38. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/tools/git.py +0 -0
  39. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/tools/notifications.py +0 -0
  40. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/tools/questions.py +0 -0
  41. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/tools/results.py +0 -0
  42. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/tools/schemas.py +0 -0
  43. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/tools/search.py +0 -0
  44. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/tools/shell.py +0 -0
  45. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/tools/snippets.py +0 -0
  46. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/tools/tasks.py +0 -0
  47. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/tools/web_fetch.py +0 -0
  48. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/tools/web_search.py +0 -0
  49. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/utils.py +0 -0
  50. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/web/__init__.py +0 -0
  51. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/web/arcade_client.py +0 -0
  52. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/web/cache.py +0 -0
  53. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/web/extract.py +0 -0
  54. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/web/markdown.py +0 -0
  55. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/web/preapproved.py +0 -0
  56. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/web/prompts.py +0 -0
  57. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/web/scrape.py +0 -0
  58. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/web/search/__init__.py +0 -0
  59. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/web/search/arcade.py +0 -0
  60. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/web/search/base.py +0 -0
  61. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cade_mcp_local/web/search/http.py +0 -0
  62. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/__init__.py +0 -0
  63. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/ai/__init__.py +0 -0
  64. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/ai/prompts.py +0 -0
  65. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/cli/__init__.py +0 -0
  66. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/cli/auth.py +0 -0
  67. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/cli/commands/__init__.py +0 -0
  68. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/cli/commands/account.py +0 -0
  69. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/cli/commands/auth.py +0 -0
  70. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/cli/commands/channels.py +0 -0
  71. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/cli/commands/chat.py +0 -0
  72. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/cli/commands/cron.py +0 -0
  73. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/cli/commands/hooks.py +0 -0
  74. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/cli/commands/mcp.py +0 -0
  75. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/cli/commands/mem.py +0 -0
  76. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/cli/commands/model.py +0 -0
  77. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/cli/commands/persona.py +0 -0
  78. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/cli/commands/serve.py +0 -0
  79. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/cli/commands/tasks.py +0 -0
  80. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/cli/commands/thread.py +0 -0
  81. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/cli/commands/tools.py +0 -0
  82. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/core/__init__.py +0 -0
  83. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/core/constants.py +0 -0
  84. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/core/errors.py +0 -0
  85. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/core/git.py +0 -0
  86. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/core/logging.py +0 -0
  87. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/core/names.py +0 -0
  88. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/core/paths.py +0 -0
  89. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/core/runtime.py +0 -0
  90. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/core/types.py +0 -0
  91. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/execution/__init__.py +0 -0
  92. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/execution/context_window.py +0 -0
  93. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/execution/mcp_schema_index.py +0 -0
  94. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/execution/orchestrator.py +0 -0
  95. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/execution/parallel.py +0 -0
  96. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/hooks/__init__.py +0 -0
  97. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/hooks/config.py +0 -0
  98. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/hooks/engine.py +0 -0
  99. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/hooks/executors/__init__.py +0 -0
  100. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/hooks/executors/callback.py +0 -0
  101. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/hooks/executors/command.py +0 -0
  102. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/hooks/executors/http.py +0 -0
  103. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/hooks/executors/prompt.py +0 -0
  104. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/hooks/executors/ssrf_guard.py +0 -0
  105. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/hooks/matchers.py +0 -0
  106. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/hooks/registry.py +0 -0
  107. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/hooks/types.py +0 -0
  108. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/providers/__init__.py +0 -0
  109. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/providers/anthropic.py +0 -0
  110. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/providers/base.py +0 -0
  111. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/providers/openai.py +0 -0
  112. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/serve/__init__.py +0 -0
  113. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/serve/adapters/base.py +0 -0
  114. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/serve/adapters/registry.py +0 -0
  115. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/serve/adapters/stub.py +0 -0
  116. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/serve/adapters/telegram.py +0 -0
  117. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/serve/allowlist.py +0 -0
  118. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/serve/chunker.py +0 -0
  119. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/serve/commands.py +0 -0
  120. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/serve/elicitation.py +0 -0
  121. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/serve/format.py +0 -0
  122. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/serve/install.py +0 -0
  123. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/serve/lock.py +0 -0
  124. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/serve/progress_sink.py +0 -0
  125. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/serve/router.py +0 -0
  126. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/serve/secrets.py +0 -0
  127. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/serve/sinks.py +0 -0
  128. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/storage/__init__.py +0 -0
  129. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/storage/personas.py +0 -0
  130. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/storage/threads.py +0 -0
  131. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/tasks/__init__.py +0 -0
  132. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/tasks/channels.py +0 -0
  133. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/tasks/lock.py +0 -0
  134. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/tasks/notifications.py +0 -0
  135. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/tasks/scheduler.py +0 -0
  136. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/tasks/store.py +0 -0
  137. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/tasks/types.py +0 -0
  138. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/templates/login_failed.html +0 -0
  139. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/templates/login_success.html +0 -0
  140. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/templates/serve/launchd.plist.tmpl +0 -0
  141. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/templates/serve/systemd.service.tmpl +0 -0
  142. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/templates/styles.css +0 -0
  143. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/tools/__init__.py +0 -0
  144. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/tools/manager/__init__.py +0 -0
  145. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/tools/manager/_request_ctx.py +0 -0
  146. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/tools/manager/base.py +0 -0
  147. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/tools/manager/config.py +0 -0
  148. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/tools/manager/mcp.py +0 -0
  149. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/tools/search/__init__.py +0 -0
  150. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/tools/search/discovered.py +0 -0
  151. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/tools/search/scoring.py +0 -0
  152. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/ui/__init__.py +0 -0
  153. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/ui/display.py +0 -0
  154. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/ui/elicitation.py +0 -0
  155. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/ui/input.py +0 -0
  156. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/voice/audio.py +0 -0
  157. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/voice/cleanup.py +0 -0
  158. {cade_cli-0.11.0 → cade_cli-0.13.2}/src/cadecoder/voice/stt.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cade-cli
3
- Version: 0.11.0
3
+ Version: 0.13.2
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
@@ -43,10 +43,10 @@ Classifier: Topic :: Software Development
43
43
  Classifier: Topic :: Software Development :: Code Generators
44
44
  Classifier: Typing :: Typed
45
45
  Requires-Python: >=3.11
46
- Requires-Dist: agent-library<1.0.0,>=0.12.0
46
+ Requires-Dist: agent-library<1.0.0,>=0.13.1
47
47
  Requires-Dist: anthropic<1.0.0,>=0.34.0
48
48
  Requires-Dist: arcade-core<5.0.0,>=4.7.0
49
- Requires-Dist: arcade-mcp-server<2.0.0,>=1.21.0
49
+ Requires-Dist: arcade-mcp-server<2.0.0,>=1.22.0
50
50
  Requires-Dist: arcade-tdk<4.0.0,>=3.8.0
51
51
  Requires-Dist: arcadepy<2.0.0,>=1.10.0
52
52
  Requires-Dist: authlib<2.0.0,>=1.6.0
@@ -58,15 +58,14 @@ Requires-Dist: keyring<26.0,>=24.0
58
58
  Requires-Dist: openai<2.0.0,>=1.0.0
59
59
  Requires-Dist: prompt-toolkit<4.0.0,>=3.0.52
60
60
  Requires-Dist: pydantic[email]<3.0.0,>=2.0.0
61
- Requires-Dist: pyperclip<2.0.0,>=1.8.0
62
61
  Requires-Dist: python-telegram-bot<22.0,>=21.0
63
- Requires-Dist: pyyaml<7.0.0,>=6.0
64
62
  Requires-Dist: rich<14.0.0,>=13.0.0
65
- Requires-Dist: sounddevice>=0.5.5
66
- Requires-Dist: tiktoken<1.0.0,>=0.11.0
67
63
  Requires-Dist: toml<1.0.0,>=0.10.0
68
64
  Requires-Dist: typer>0.10.0
69
65
  Requires-Dist: ulid==1.1
66
+ Requires-Dist: websockets<16.0,>=14.0
67
+ Provides-Extra: build
68
+ Requires-Dist: pyinstaller<7.0.0,>=6.16.0; extra == 'build'
70
69
  Provides-Extra: dev
71
70
  Requires-Dist: mypy<2.0.0,>=1.10.0; extra == 'dev'
72
71
  Requires-Dist: pytest-asyncio<1.0.0,>=0.24.0; extra == 'dev'
@@ -83,7 +82,7 @@ Provides-Extra: voice
83
82
  Requires-Dist: mlx-audio<0.4.0,>=0.2.0; extra == 'voice'
84
83
  Requires-Dist: mlx-whisper>=0.1.0; extra == 'voice'
85
84
  Requires-Dist: numpy>=1.24.0; extra == 'voice'
86
- Requires-Dist: sounddevice>=0.4.0; extra == 'voice'
85
+ Requires-Dist: sounddevice<1.0.0,>=0.5.5; extra == 'voice'
87
86
  Requires-Dist: webrtcvad>=2.0.10; extra == 'voice'
88
87
  Description-Content-Type: text/markdown
89
88
 
@@ -119,6 +118,8 @@ cade
119
118
  | pip | `pip install cade-cli` |
120
119
  | From source | `git clone https://github.com/arcadeai-labs/cade.git && cd cade && uv sync` |
121
120
 
121
+ Homebrew installs from a private release feed. Export `HOMEBREW_GITHUB_API_TOKEN` (a token with read access to `arcadeai-labs/cade`) before running `brew install`.
122
+
122
123
  **Prerequisites:** Python 3.11+, an Arcade account ([arcade.dev](https://arcade.dev)) for cloud tools, and an LLM provider key (`OPENAI_API_KEY` or `ANTHROPIC_API_KEY`). Skip the Arcade account with `--local-only` or `CADE_LOCAL_ONLY=1`.
123
124
 
124
125
  ```bash
@@ -126,6 +127,9 @@ cade login # Arcade Cloud OAuth (skip with --local-only)
126
127
  cade --version
127
128
  ```
128
129
 
130
+ Full install and first-run docs: [`docs/install.md`](./docs/install.md) and
131
+ [`docs/quickstart.md`](./docs/quickstart.md).
132
+
129
133
  ---
130
134
 
131
135
  ## Quick start
@@ -133,11 +137,11 @@ cade --version
133
137
  ```bash
134
138
  cade # interactive chat
135
139
  cade -m "What changed in HEAD?" # single message, then exit
136
- cat error.log | cade -m "What went wrong?" # pipe input
140
+ cade -m "What went wrong?" < error.log # prompt + file/stdin
137
141
  cade -r # resume the most recent thread
138
142
  cade resume "auth-rewrite" # resume a thread by name
139
143
  cade --persona reviewer # use a saved persona's system prompt
140
- cade --voice # speak / hear (voice mode)
144
+ cade --voice # speak / hear (requires cade-cli[voice])
141
145
  ```
142
146
 
143
147
  ### Top-level options
@@ -145,9 +149,9 @@ cade --voice # speak / hear (voice mode)
145
149
  | Flag | Description |
146
150
  |---|---|
147
151
  | `-r`, `--resume` | Resume most recent thread |
148
- | `-m`, `--message` | Single-message mode (reads stdin if no arg) |
152
+ | `-m`, `--message` | Single-message mode; piped stdin is appended to the prompt |
149
153
  | `-P`, `--persona` | Use a saved persona |
150
- | `-V`, `--voice` | Voice mode |
154
+ | `-V`, `--voice` | Voice mode; install `cade-cli[voice]` first |
151
155
  | `-v`, `--verbose` | Debug logging |
152
156
  | `--version` | Print version |
153
157
 
@@ -172,7 +176,7 @@ cade --voice # speak / hear (voice mode)
172
176
 
173
177
  Tools come from three sources and are addressed uniformly by their registered name:
174
178
 
175
- - **`Local_*`** — built-in: filesystem (`ReadFile`, `WriteFile`, `Edit`, `ListFiles`), shell (`Bash` — async with progress reporting), `Search`, `Git`, `Task*`, `Channel`, `AskUserQuestion`, `RetrieveToolResult`, `ToolSchema`, `Sleep`, `PinContext`, `PushNotification`.
179
+ - **`Local_*`** — built-in: filesystem (`ReadFile`, `WriteFile`, `Edit`, `ListFiles`), shell (`Bash`), `Search`, `Git`, `Task*`, `AskUserQuestion`, `RetrieveToolResult`, `ToolSchema`, `PinContext`, `PushNotification`, `WebFetch`, and `WebSearch`.
176
180
  - **`Memory_*`** — agent-library backed, per-persona indexed knowledge store.
177
181
  - **Arcade Cloud + user MCP servers** — anything you register.
178
182
 
@@ -215,7 +219,7 @@ Memory is persona-scoped — each persona has its own `~/.cade/data/contexts/<co
215
219
 
216
220
  ## Headless daemon: `cade serve`
217
221
 
218
- Run Cade as a long-lived daemon that replies over messaging platforms (Telegram supported today; the adapter protocol is generic). Each chat gets its own persistent thread — persona, memory, and tools carry across messages.
222
+ 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 — persona, memory, and tools carry across messages.
219
223
 
220
224
  ```bash
221
225
  cade serve init # wizard: bot token, allowed senders
@@ -226,9 +230,9 @@ cade serve status / stop # health, uptime; SIGTERM running daemo
226
230
 
227
231
  Config lives at `~/.cade/config/contexts/<context>.toml [serve]`. Edit and `kill -HUP $(cat ~/.cade/run/serve.pid)` to reload without restart.
228
232
 
229
- **Security defaults are fail-closed:** `allowed_senders = []` blocks everyone; tools are default-deny against `[tools].allow` (globs / regex / pipe lists supported); `deny` always wins.
233
+ **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.
230
234
 
231
- The MCP transport is fully bidirectional, so tools running inside the daemon can elicit structured input (rendered as Telegram inline keyboards), stream live progress (rendered as edited placeholder messages), and emit logs that flow into the daemon log. See [`docs/mcp.md`](./docs/mcp.md) for the protocol details, [`docs/configuration.md`](./docs/configuration.md) for the full daemon config reference.
235
+ 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.
232
236
 
233
237
  ---
234
238
 
@@ -252,17 +256,18 @@ See [`docs/channels.md`](./docs/channels.md) for the channels protocol; hooks an
252
256
  Cade works with any OpenAI-compatible endpoint. Three ways to wire one in (CLI flag → env → config, first match wins):
253
257
 
254
258
  ```bash
255
- # CLI flag
256
- cade chat --endpoint http://localhost:11434/v1 --model llama3
259
+ # CLI flag for Ollama. Use a model from `ollama list`.
260
+ cade chat -L --endpoint http://localhost:11434 --model qwen3:4b
257
261
 
258
- # Environment
259
- export OPENAI_BASE_URL="http://localhost:11434/v1"
260
- export OPENAI_API_KEY="ollama" # any non-empty value
261
- cade chat --model llama3
262
+ # Environment for one-shot mode
263
+ cade context create local-qwen --model qwen3:4b
264
+ cade context use local-qwen
265
+ export OLLAMA_BASE_URL="http://localhost:11434"
266
+ CADE_LOCAL_ONLY=1 cade -m "hello from a local model"
262
267
 
263
268
  # Config — ~/.cade/config/cade.toml
264
269
  [model_settings]
265
- host = "http://localhost:11434/v1"
270
+ host = "http://localhost:8000/v1"
266
271
  api_key = "ollama"
267
272
  ```
268
273
 
@@ -303,14 +308,15 @@ Full layout, env vars, and TOML schema in [`docs/configuration.md`](./docs/confi
303
308
  ```bash
304
309
  git clone https://github.com/arcadeai-labs/cade.git
305
310
  cd cade
306
- uv sync --all-extras --dev
307
- pytest # full suite
308
- ruff check src/ tests/ && ruff format src/ tests/
311
+ uv sync --extra dev
312
+ uv run pytest
313
+ uv run ruff check src/ tests/
309
314
  ```
310
315
 
311
316
  Style: Python 3.11+ with modern type hints (`dict`, `list`, `| None`); ruff for lint + format; pytest with `asyncio_mode = "auto"`. Public functions and classes get docstrings.
312
317
 
313
- PRs welcome open an issue first for anything substantial.
318
+ Build/release docs live in [`docs/development.md`](./docs/development.md). PRs
319
+ welcome — open an issue first for anything substantial.
314
320
 
315
321
  ---
316
322
 
@@ -318,7 +324,7 @@ PRs welcome — open an issue first for anything substantial.
318
324
 
319
325
  - [arcade.dev](https://arcade.dev) — product
320
326
  - [docs.arcade.dev](https://docs.arcade.dev) — API and tool catalog
321
- - [`docs/`](./docs/) — `mcp.md` (tool protocol), `configuration.md`, `channels.md`, `install.md`
327
+ - [`docs/`](./docs/) — quickstart, examples, install, config, MCP, channels, voice, development, and adapters
322
328
  - [Issues](https://github.com/arcadeai-labs/cade/issues) · [Releases](https://github.com/arcadeai-labs/cade/releases)
323
329
 
324
330
  ## License
@@ -30,6 +30,8 @@ cade
30
30
  | pip | `pip install cade-cli` |
31
31
  | From source | `git clone https://github.com/arcadeai-labs/cade.git && cd cade && uv sync` |
32
32
 
33
+ Homebrew installs from a private release feed. Export `HOMEBREW_GITHUB_API_TOKEN` (a token with read access to `arcadeai-labs/cade`) before running `brew install`.
34
+
33
35
  **Prerequisites:** Python 3.11+, an Arcade account ([arcade.dev](https://arcade.dev)) for cloud tools, and an LLM provider key (`OPENAI_API_KEY` or `ANTHROPIC_API_KEY`). Skip the Arcade account with `--local-only` or `CADE_LOCAL_ONLY=1`.
34
36
 
35
37
  ```bash
@@ -37,6 +39,9 @@ cade login # Arcade Cloud OAuth (skip with --local-only)
37
39
  cade --version
38
40
  ```
39
41
 
42
+ Full install and first-run docs: [`docs/install.md`](./docs/install.md) and
43
+ [`docs/quickstart.md`](./docs/quickstart.md).
44
+
40
45
  ---
41
46
 
42
47
  ## Quick start
@@ -44,11 +49,11 @@ cade --version
44
49
  ```bash
45
50
  cade # interactive chat
46
51
  cade -m "What changed in HEAD?" # single message, then exit
47
- cat error.log | cade -m "What went wrong?" # pipe input
52
+ cade -m "What went wrong?" < error.log # prompt + file/stdin
48
53
  cade -r # resume the most recent thread
49
54
  cade resume "auth-rewrite" # resume a thread by name
50
55
  cade --persona reviewer # use a saved persona's system prompt
51
- cade --voice # speak / hear (voice mode)
56
+ cade --voice # speak / hear (requires cade-cli[voice])
52
57
  ```
53
58
 
54
59
  ### Top-level options
@@ -56,9 +61,9 @@ cade --voice # speak / hear (voice mode)
56
61
  | Flag | Description |
57
62
  |---|---|
58
63
  | `-r`, `--resume` | Resume most recent thread |
59
- | `-m`, `--message` | Single-message mode (reads stdin if no arg) |
64
+ | `-m`, `--message` | Single-message mode; piped stdin is appended to the prompt |
60
65
  | `-P`, `--persona` | Use a saved persona |
61
- | `-V`, `--voice` | Voice mode |
66
+ | `-V`, `--voice` | Voice mode; install `cade-cli[voice]` first |
62
67
  | `-v`, `--verbose` | Debug logging |
63
68
  | `--version` | Print version |
64
69
 
@@ -83,7 +88,7 @@ cade --voice # speak / hear (voice mode)
83
88
 
84
89
  Tools come from three sources and are addressed uniformly by their registered name:
85
90
 
86
- - **`Local_*`** — built-in: filesystem (`ReadFile`, `WriteFile`, `Edit`, `ListFiles`), shell (`Bash` — async with progress reporting), `Search`, `Git`, `Task*`, `Channel`, `AskUserQuestion`, `RetrieveToolResult`, `ToolSchema`, `Sleep`, `PinContext`, `PushNotification`.
91
+ - **`Local_*`** — built-in: filesystem (`ReadFile`, `WriteFile`, `Edit`, `ListFiles`), shell (`Bash`), `Search`, `Git`, `Task*`, `AskUserQuestion`, `RetrieveToolResult`, `ToolSchema`, `PinContext`, `PushNotification`, `WebFetch`, and `WebSearch`.
87
92
  - **`Memory_*`** — agent-library backed, per-persona indexed knowledge store.
88
93
  - **Arcade Cloud + user MCP servers** — anything you register.
89
94
 
@@ -126,7 +131,7 @@ Memory is persona-scoped — each persona has its own `~/.cade/data/contexts/<co
126
131
 
127
132
  ## Headless daemon: `cade serve`
128
133
 
129
- Run Cade as a long-lived daemon that replies over messaging platforms (Telegram supported today; the adapter protocol is generic). Each chat gets its own persistent thread — persona, memory, and tools carry across messages.
134
+ 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 — persona, memory, and tools carry across messages.
130
135
 
131
136
  ```bash
132
137
  cade serve init # wizard: bot token, allowed senders
@@ -137,9 +142,9 @@ cade serve status / stop # health, uptime; SIGTERM running daemo
137
142
 
138
143
  Config lives at `~/.cade/config/contexts/<context>.toml [serve]`. Edit and `kill -HUP $(cat ~/.cade/run/serve.pid)` to reload without restart.
139
144
 
140
- **Security defaults are fail-closed:** `allowed_senders = []` blocks everyone; tools are default-deny against `[tools].allow` (globs / regex / pipe lists supported); `deny` always wins.
145
+ **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.
141
146
 
142
- The MCP transport is fully bidirectional, so tools running inside the daemon can elicit structured input (rendered as Telegram inline keyboards), stream live progress (rendered as edited placeholder messages), and emit logs that flow into the daemon log. See [`docs/mcp.md`](./docs/mcp.md) for the protocol details, [`docs/configuration.md`](./docs/configuration.md) for the full daemon config reference.
147
+ 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.
143
148
 
144
149
  ---
145
150
 
@@ -163,17 +168,18 @@ See [`docs/channels.md`](./docs/channels.md) for the channels protocol; hooks an
163
168
  Cade works with any OpenAI-compatible endpoint. Three ways to wire one in (CLI flag → env → config, first match wins):
164
169
 
165
170
  ```bash
166
- # CLI flag
167
- cade chat --endpoint http://localhost:11434/v1 --model llama3
171
+ # CLI flag for Ollama. Use a model from `ollama list`.
172
+ cade chat -L --endpoint http://localhost:11434 --model qwen3:4b
168
173
 
169
- # Environment
170
- export OPENAI_BASE_URL="http://localhost:11434/v1"
171
- export OPENAI_API_KEY="ollama" # any non-empty value
172
- cade chat --model llama3
174
+ # Environment for one-shot mode
175
+ cade context create local-qwen --model qwen3:4b
176
+ cade context use local-qwen
177
+ export OLLAMA_BASE_URL="http://localhost:11434"
178
+ CADE_LOCAL_ONLY=1 cade -m "hello from a local model"
173
179
 
174
180
  # Config — ~/.cade/config/cade.toml
175
181
  [model_settings]
176
- host = "http://localhost:11434/v1"
182
+ host = "http://localhost:8000/v1"
177
183
  api_key = "ollama"
178
184
  ```
179
185
 
@@ -214,14 +220,15 @@ Full layout, env vars, and TOML schema in [`docs/configuration.md`](./docs/confi
214
220
  ```bash
215
221
  git clone https://github.com/arcadeai-labs/cade.git
216
222
  cd cade
217
- uv sync --all-extras --dev
218
- pytest # full suite
219
- ruff check src/ tests/ && ruff format src/ tests/
223
+ uv sync --extra dev
224
+ uv run pytest
225
+ uv run ruff check src/ tests/
220
226
  ```
221
227
 
222
228
  Style: Python 3.11+ with modern type hints (`dict`, `list`, `| None`); ruff for lint + format; pytest with `asyncio_mode = "auto"`. Public functions and classes get docstrings.
223
229
 
224
- PRs welcome open an issue first for anything substantial.
230
+ Build/release docs live in [`docs/development.md`](./docs/development.md). PRs
231
+ welcome — open an issue first for anything substantial.
225
232
 
226
233
  ---
227
234
 
@@ -229,7 +236,7 @@ PRs welcome — open an issue first for anything substantial.
229
236
 
230
237
  - [arcade.dev](https://arcade.dev) — product
231
238
  - [docs.arcade.dev](https://docs.arcade.dev) — API and tool catalog
232
- - [`docs/`](./docs/) — `mcp.md` (tool protocol), `configuration.md`, `channels.md`, `install.md`
239
+ - [`docs/`](./docs/) — quickstart, examples, install, config, MCP, channels, voice, development, and adapters
233
240
  - [Issues](https://github.com/arcadeai-labs/cade/issues) · [Releases](https://github.com/arcadeai-labs/cade/releases)
234
241
 
235
242
  ## License
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cade-cli"
3
- version = "0.11.0"
3
+ version = "0.13.2"
4
4
  description = "Cade - The CLI Agent from Arcade.dev"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -27,26 +27,23 @@ dependencies = [
27
27
  "typer>0.10.0",
28
28
  "pydantic[email]>=2.0.0,<3.0.0",
29
29
  "toml>=0.10.0,<1.0.0",
30
- "pyyaml>=6.0,<7.0.0",
31
30
  "openai>=1.0.0,<2.0.0",
32
31
  "anthropic>=0.34.0,<1.0.0",
33
32
  "ulid==1.1",
34
33
  "arcade-tdk>=3.8.0,<4.0.0",
35
- "arcade-mcp-server>=1.21.0,<2.0.0",
34
+ "arcade-mcp-server>=1.22.0,<2.0.0",
36
35
  "arcade-core>=4.7.0,<5.0.0",
37
36
  "arcadepy>=1.10.0,<2.0.0",
38
37
  "authlib>=1.6.0,<2.0.0",
39
- "pyperclip>=1.8.0,<2.0.0",
40
38
  "prompt-toolkit>=3.0.52,<4.0.0",
41
- "tiktoken>=0.11.0,<1.0.0",
42
39
  "httpx>=0.27.0,<1.0.0",
43
40
  "filelock>=3.0.0,<4.0.0",
44
- "agent-library>=0.12.0,<1.0.0",
45
- "sounddevice>=0.5.5",
41
+ "agent-library>=0.13.1,<1.0.0",
46
42
  "croniter>=2.0.0,<4.0.0",
47
43
  "python-telegram-bot>=21.0,<22.0",
48
44
  "keyring>=24.0,<26.0",
49
45
  "html2text>=2024.2.26",
46
+ "websockets>=14.0,<16.0",
50
47
  ]
51
48
 
52
49
  [project.urls]
@@ -69,10 +66,13 @@ dev = [
69
66
  "pytest-cov>=4.0.0,<5.0.0",
70
67
  "mypy>=1.10.0,<2.0.0",
71
68
  ]
69
+ build = [
70
+ "pyinstaller>=6.16.0,<7.0.0",
71
+ ]
72
72
  voice = [
73
73
  "mlx-audio>=0.2.0,<0.4.0",
74
74
  "mlx-whisper>=0.1.0",
75
- "sounddevice>=0.4.0",
75
+ "sounddevice>=0.5.5,<1.0.0",
76
76
  "numpy>=1.24.0",
77
77
  "webrtcvad>=2.0.10",
78
78
  ]
@@ -100,6 +100,9 @@ packages = ["src/cadecoder", "src/cade_mcp_local"]
100
100
  testpaths = ["tests"]
101
101
  asyncio_mode = "auto"
102
102
  asyncio_default_fixture_loop_scope = "function"
103
+ markers = [
104
+ "integration: tests that cross process or network boundaries without external services",
105
+ ]
103
106
 
104
107
  [tool.ruff]
105
108
  line-length = 100
@@ -13,8 +13,12 @@ os.environ.setdefault("LOGURU_LEVEL", "WARNING")
13
13
 
14
14
  from arcade_mcp_server import MCPApp
15
15
 
16
- from cade_mcp_local.resources import register_resources
17
- from cade_mcp_local.tools import (
16
+ from cadecoder.core.frozen import patch_frozen_return_detection
17
+
18
+ patch_frozen_return_detection()
19
+
20
+ from cade_mcp_local.resources import register_resources # noqa: E402
21
+ from cade_mcp_local.tools import ( # noqa: E402
18
22
  ask_user_question_tool,
19
23
  bash_tool,
20
24
  edit_tool,
@@ -69,7 +73,7 @@ def create_app() -> MCPApp:
69
73
  name="Local",
70
74
  transport="stdio",
71
75
  instructions=SERVER_INSTRUCTIONS,
72
- log_level="info",
76
+ log_level="INFO",
73
77
  )
74
78
 
75
79
  # Filesystem tools
@@ -10,6 +10,12 @@ os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
10
10
  # Suppress pkg_resources deprecation warning from webrtcvad and other packages
11
11
  # that haven't migrated to importlib.resources yet.
12
12
  # This MUST be set before any imports that might trigger the warning.
13
+ # Note: webrtcvad emits UserWarning, not DeprecationWarning
14
+ warnings.filterwarnings(
15
+ "ignore",
16
+ message=".*pkg_resources is deprecated.*",
17
+ category=UserWarning,
18
+ )
13
19
  warnings.filterwarnings(
14
20
  "ignore",
15
21
  message=".*pkg_resources is deprecated.*",
@@ -42,7 +48,7 @@ from cadecoder.cli.commands.serve import serve_app
42
48
  from cadecoder.cli.commands.tasks import tasks_app
43
49
  from cadecoder.cli.commands.thread import thread_app
44
50
  from cadecoder.cli.commands.tools import tool_app
45
- from cadecoder.core.config import build_runtime, set_verbose_mode
51
+ from cadecoder.core.config import build_runtime, is_local_only_mode, set_verbose_mode
46
52
  from cadecoder.core.logging import setup_logging
47
53
  from cadecoder.core.runtime import set_runtime
48
54
 
@@ -191,6 +197,30 @@ def doctor_cmd(
191
197
  raise typer.Exit(1)
192
198
 
193
199
 
200
+ @app.command(name="__mcp-local", hidden=True)
201
+ def _mcp_local_cmd() -> None:
202
+ """Run the bundled local MCP server over stdio."""
203
+ from cadecoder.core.frozen import patch_frozen_return_detection
204
+
205
+ patch_frozen_return_detection()
206
+
207
+ from cade_mcp_local import main
208
+
209
+ main()
210
+
211
+
212
+ @app.command(name="__memory-mcp", hidden=True)
213
+ def _memory_mcp_cmd() -> None:
214
+ """Run the bundled agent-library MCP server over stdio."""
215
+ from cadecoder.core.frozen import patch_frozen_return_detection
216
+
217
+ patch_frozen_return_detection()
218
+
219
+ from librarian.server import app as librarian_app
220
+
221
+ librarian_app.run(transport="stdio")
222
+
223
+
194
224
  @app.callback()
195
225
  def main_callback(
196
226
  ctx: typer.Context,
@@ -237,7 +267,7 @@ def main_callback(
237
267
  "--message",
238
268
  "-m",
239
269
  help="Single message mode: process one message and exit. "
240
- "Reads from stdin if no argument given.",
270
+ "When stdin is piped, stdin is appended to this prompt.",
241
271
  ),
242
272
  ] = None,
243
273
  version: Annotated[
@@ -275,22 +305,27 @@ def main_callback(
275
305
 
276
306
  from cadecoder.ui.session import run_single_message_mode
277
307
 
278
- # If message is empty string, read from stdin
279
- if message == "":
280
- # Check if stdin has data
281
- if not sys.stdin.isatty():
282
- message = sys.stdin.read().strip()
283
- else:
284
- console.print(
285
- "[red]Error: No message provided. Use -m 'message' or pipe input.[/red]"
286
- )
287
- raise typer.Exit(1)
308
+ # If stdin is piped, append it to the prompt. This makes the natural
309
+ # shell forms work: `cade -m "Explain this" < file` and
310
+ # `git diff | cade -m "Review this diff"`.
311
+ if not sys.stdin.isatty():
312
+ stdin_text = sys.stdin.read().strip()
313
+ if stdin_text:
314
+ message = stdin_text if message == "" else f"{message}\n\n{stdin_text}"
315
+ elif message == "":
316
+ console.print("[red]Error: No message provided. Use -m 'message' or pipe input.[/red]")
317
+ raise typer.Exit(1)
288
318
 
289
319
  if not message:
290
320
  console.print("[red]Error: Empty message provided.[/red]")
291
321
  raise typer.Exit(1)
292
322
 
293
- exit_code = run_single_message_mode(message, persona=runtime.persona_name)
323
+ exit_code = run_single_message_mode(
324
+ message,
325
+ model=runtime.model,
326
+ local_only=is_local_only_mode(),
327
+ persona=runtime.persona_name,
328
+ )
294
329
  raise typer.Exit(exit_code)
295
330
 
296
331
  # Handle eager resume flag before default chat
@@ -25,7 +25,14 @@ from cadecoder.core.runtime import reset_runtime
25
25
 
26
26
  console = Console(stderr=True)
27
27
  context_app = typer.Typer(help="Manage activation contexts", invoke_without_command=True)
28
- _EXPORT_SECRET_KEYS = {"secret", "secret_ref", "auth_value", "oauth_tokens", "oauth_client_secret"}
28
+ _EXPORT_SECRET_KEYS = {
29
+ "auth_token",
30
+ "auth_value",
31
+ "oauth_client_secret",
32
+ "oauth_tokens",
33
+ "secret",
34
+ "secret_ref",
35
+ }
29
36
 
30
37
 
31
38
  @context_app.callback()
@@ -125,8 +125,20 @@ class TelegramAdapterConfig(BaseModel):
125
125
  drop_pending_updates: bool = True
126
126
 
127
127
 
128
+ class DesktopAdapterConfig(BaseModel):
129
+ model_config = ConfigDict(extra="ignore")
130
+ enabled: bool = False
131
+ host: str = "127.0.0.1"
132
+ port: int = 8765
133
+ auth_token: str = ""
134
+ allowed_senders: list[str] = Field(default_factory=lambda: ["desktop"])
135
+ allowed_conversations: list[str] = Field(default_factory=list)
136
+ speak_replies: bool = False
137
+
138
+
128
139
  class AdaptersConfig(BaseModel):
129
140
  model_config = ConfigDict(extra="allow")
141
+ desktop: DesktopAdapterConfig = Field(default_factory=DesktopAdapterConfig)
130
142
  telegram: TelegramAdapterConfig = Field(default_factory=TelegramAdapterConfig)
131
143
 
132
144
  def get(self, name: str) -> Any | None:
@@ -0,0 +1,37 @@
1
+ """Helpers for running Cade from frozen/PyInstaller binaries."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from typing import Any
7
+
8
+
9
+ def patch_frozen_return_detection() -> None:
10
+ """Avoid arcade-core source introspection failures in frozen builds.
11
+
12
+ arcade-core inspects function source to decide whether a tool returns a
13
+ value. Frozen binaries do not always expose source text through their
14
+ loader, so fall back to the function's return annotation when source is
15
+ unavailable.
16
+ """
17
+ try:
18
+ import arcade_core.catalog as catalog
19
+ import arcade_core.utils as utils
20
+ except Exception:
21
+ return
22
+
23
+ original = utils.does_function_return_value
24
+ if getattr(original, "_cade_frozen_safe", False):
25
+ return
26
+
27
+ def _safe_does_function_return_value(function: Callable[..., Any]) -> bool:
28
+ try:
29
+ return original(function)
30
+ except ValueError as exc:
31
+ if str(exc) != "Source code not found":
32
+ raise
33
+ return function.__annotations__.get("return") is not None
34
+
35
+ _safe_does_function_return_value._cade_frozen_safe = True # type: ignore[attr-defined]
36
+ utils.does_function_return_value = _safe_does_function_return_value
37
+ catalog.does_function_return_value = _safe_does_function_return_value
@@ -36,8 +36,10 @@ TINY_RESULT_BYTES = 1_024
36
36
 
37
37
  # Tools whose results must NEVER be externalized — doing so creates infinite
38
38
  # retrieval loops (agent retrieves → result externalized → agent retrieves again → ...)
39
+ # or breaks schema-recovery flows that must stay directly visible in context.
39
40
  NEVER_EXTERNALIZE_TOOLS: frozenset[str] = frozenset(
40
41
  {
42
+ "Arcade_SelectTools",
41
43
  "Local_RetrieveToolResult",
42
44
  "Local_SearchRecentContext",
43
45
  "Local_ToolSchema",
@@ -326,8 +328,8 @@ def _extract_tool_name(tool_obj: dict[str, Any]) -> str:
326
328
  func = tool_obj.get("function", {})
327
329
  if isinstance(func, dict) and func.get("name"):
328
330
  return _strip_version_suffix(func["name"])
329
- # Flat format: {"name": "..."}
330
- return _strip_version_suffix(tool_obj.get("name", "unknown"))
331
+ # Flat / Arcade format: {"name": "..."} or {"tool_name": "..."}
332
+ return _strip_version_suffix(tool_obj.get("name") or tool_obj.get("tool_name") or "unknown")
331
333
 
332
334
 
333
335
  def _extract_tool_description(tool_obj: dict[str, Any]) -> str:
@@ -135,6 +135,38 @@ class ToolSchemaCache:
135
135
  )
136
136
  return result
137
137
 
138
+ def iter_cached_tools(self, thread_id: str = "") -> list[CachedToolSchema]:
139
+ """Return cached schemas for one thread or every thread.
140
+
141
+ Args:
142
+ thread_id: Optional thread id. Empty means merge every thread cache.
143
+
144
+ Returns:
145
+ Cached schemas de-duplicated by name, preferring the newest entry.
146
+ """
147
+ entries: dict[str, CachedToolSchema] = {}
148
+ paths = (
149
+ [self._thread_path(thread_id)]
150
+ if thread_id
151
+ else self.storage_dir.glob("*/tool_schemas.json")
152
+ )
153
+ for path in paths:
154
+ if not self._disk_ok or not path.exists():
155
+ continue
156
+ try:
157
+ data = json.loads(path.read_text(encoding="utf-8"))
158
+ except (json.JSONDecodeError, OSError):
159
+ continue
160
+ for tool_data in data.get("tools", {}).values():
161
+ try:
162
+ entry = CachedToolSchema(**tool_data)
163
+ except (TypeError, ValueError):
164
+ continue
165
+ existing = entries.get(entry.name)
166
+ if existing is None or entry.cached_at > existing.cached_at:
167
+ entries[entry.name] = entry
168
+ return list(entries.values())
169
+
138
170
  def list_all_cached(self) -> list[dict[str, Any]]:
139
171
  """List unique cached tools across every thread.
140
172
 
@@ -356,7 +388,7 @@ def _extract_tool_name(tool_obj: dict[str, Any]) -> str:
356
388
  func = tool_obj.get("function", {})
357
389
  if isinstance(func, dict) and func.get("name"):
358
390
  return _strip_version_suffix(func["name"])
359
- return _strip_version_suffix(tool_obj.get("name", "unknown"))
391
+ return _strip_version_suffix(tool_obj.get("name") or tool_obj.get("tool_name") or "unknown")
360
392
 
361
393
 
362
394
  def _extract_tool_description(tool_obj: dict[str, Any]) -> str:
@@ -372,4 +404,4 @@ def _extract_tool_parameters(tool_obj: dict[str, Any]) -> dict[str, Any]:
372
404
  func = tool_obj.get("function", {})
373
405
  if isinstance(func, dict) and func.get("parameters"):
374
406
  return func["parameters"]
375
- return tool_obj.get("parameters", {})
407
+ return tool_obj.get("parameters") or tool_obj.get("input_schema") or {}