cade-cli 0.15.5__tar.gz → 0.15.7__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 (166) hide show
  1. {cade_cli-0.15.5 → cade_cli-0.15.7}/PKG-INFO +2 -2
  2. {cade_cli-0.15.5 → cade_cli-0.15.7}/pyproject.toml +2 -2
  3. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/resources.py +1 -1
  4. cade_cli-0.15.7/src/cade_mcp_local/web/extract.py +115 -0
  5. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/mem.py +142 -5
  6. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/ui/display.py +12 -1
  7. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/ui/session.py +10 -4
  8. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/ui/slash.py +1 -1
  9. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/voice/audio.py +49 -0
  10. cade_cli-0.15.5/src/cade_mcp_local/web/extract.py +0 -54
  11. {cade_cli-0.15.5 → cade_cli-0.15.7}/.gitignore +0 -0
  12. {cade_cli-0.15.5 → cade_cli-0.15.7}/LICENSE +0 -0
  13. {cade_cli-0.15.5 → cade_cli-0.15.7}/README.md +0 -0
  14. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/__init__.py +0 -0
  15. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/__main__.py +0 -0
  16. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/_metadata.py +0 -0
  17. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/config.py +0 -0
  18. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/errors.py +0 -0
  19. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/server.py +0 -0
  20. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/__init__.py +0 -0
  21. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/_read_cache.py +0 -0
  22. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/agents.py +0 -0
  23. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/context.py +0 -0
  24. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/filesystem.py +0 -0
  25. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/git.py +0 -0
  26. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/notifications.py +0 -0
  27. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/questions.py +0 -0
  28. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/results.py +0 -0
  29. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/schemas.py +0 -0
  30. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/search.py +0 -0
  31. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/shell.py +0 -0
  32. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/snippets.py +0 -0
  33. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/tasks.py +0 -0
  34. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/web_fetch.py +0 -0
  35. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/web_search.py +0 -0
  36. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/utils.py +0 -0
  37. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/web/__init__.py +0 -0
  38. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/web/arcade_client.py +0 -0
  39. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/web/cache.py +0 -0
  40. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/web/markdown.py +0 -0
  41. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/web/preapproved.py +0 -0
  42. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/web/prompts.py +0 -0
  43. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/web/scrape.py +0 -0
  44. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/web/search/__init__.py +0 -0
  45. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/web/search/arcade.py +0 -0
  46. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/web/search/base.py +0 -0
  47. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/web/search/http.py +0 -0
  48. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/__init__.py +0 -0
  49. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/ai/__init__.py +0 -0
  50. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/ai/prompts.py +0 -0
  51. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/__init__.py +0 -0
  52. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/app.py +0 -0
  53. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/auth.py +0 -0
  54. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/__init__.py +0 -0
  55. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/account.py +0 -0
  56. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/agents.py +0 -0
  57. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/auth.py +0 -0
  58. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/chat.py +0 -0
  59. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/cron.py +0 -0
  60. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/hooks.py +0 -0
  61. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/mcp.py +0 -0
  62. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/serve.py +0 -0
  63. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/tasks.py +0 -0
  64. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/thread.py +0 -0
  65. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/tools.py +0 -0
  66. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/onboarding.py +0 -0
  67. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/__init__.py +0 -0
  68. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/agent.py +0 -0
  69. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/comms.py +0 -0
  70. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/config.py +0 -0
  71. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/constants.py +0 -0
  72. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/errors.py +0 -0
  73. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/frozen.py +0 -0
  74. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/git.py +0 -0
  75. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/inbox.py +0 -0
  76. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/logging.py +0 -0
  77. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/memory.py +0 -0
  78. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/model_specs.py +0 -0
  79. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/names.py +0 -0
  80. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/paths.py +0 -0
  81. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/runtime.py +0 -0
  82. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/secrets.py +0 -0
  83. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/spawn.py +0 -0
  84. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/types.py +0 -0
  85. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/execution/__init__.py +0 -0
  86. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/execution/context_window.py +0 -0
  87. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/execution/mcp_schema_index.py +0 -0
  88. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/execution/orchestrator.py +0 -0
  89. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/execution/parallel.py +0 -0
  90. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/execution/tool_result_store.py +0 -0
  91. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/execution/tool_schema_cache.py +0 -0
  92. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/__init__.py +0 -0
  93. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/config.py +0 -0
  94. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/engine.py +0 -0
  95. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/executors/__init__.py +0 -0
  96. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/executors/callback.py +0 -0
  97. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/executors/command.py +0 -0
  98. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/executors/http.py +0 -0
  99. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/executors/prompt.py +0 -0
  100. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/executors/ssrf_guard.py +0 -0
  101. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/matchers.py +0 -0
  102. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/registry.py +0 -0
  103. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/types.py +0 -0
  104. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/providers/__init__.py +0 -0
  105. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/providers/anthropic.py +0 -0
  106. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/providers/base.py +0 -0
  107. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/providers/ollama.py +0 -0
  108. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/providers/openai.py +0 -0
  109. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/__init__.py +0 -0
  110. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/adapters/__init__.py +0 -0
  111. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/adapters/base.py +0 -0
  112. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/adapters/desktop.py +0 -0
  113. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/adapters/registry.py +0 -0
  114. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/adapters/stub.py +0 -0
  115. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/adapters/telegram.py +0 -0
  116. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/allowlist.py +0 -0
  117. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/chunker.py +0 -0
  118. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/commands.py +0 -0
  119. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/config.py +0 -0
  120. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/cron.py +0 -0
  121. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/daemon.py +0 -0
  122. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/elicitation.py +0 -0
  123. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/format.py +0 -0
  124. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/install.py +0 -0
  125. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/lock.py +0 -0
  126. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/observability.py +0 -0
  127. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/owner.py +0 -0
  128. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/progress_sink.py +0 -0
  129. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/router.py +0 -0
  130. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/secrets.py +0 -0
  131. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/services.py +0 -0
  132. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/sinks.py +0 -0
  133. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/worker.py +0 -0
  134. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/storage/__init__.py +0 -0
  135. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/storage/threads.py +0 -0
  136. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tasks/__init__.py +0 -0
  137. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tasks/lock.py +0 -0
  138. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tasks/scheduler.py +0 -0
  139. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tasks/store.py +0 -0
  140. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tasks/types.py +0 -0
  141. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/templates/login_failed.html +0 -0
  142. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/templates/login_success.html +0 -0
  143. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/templates/serve/launchd.plist.tmpl +0 -0
  144. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/templates/serve/serve.toml.example +0 -0
  145. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/templates/serve/systemd.service.tmpl +0 -0
  146. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/templates/styles.css +0 -0
  147. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tools/__init__.py +0 -0
  148. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tools/manager/__init__.py +0 -0
  149. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tools/manager/_request_ctx.py +0 -0
  150. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tools/manager/base.py +0 -0
  151. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tools/manager/composite.py +0 -0
  152. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tools/manager/config.py +0 -0
  153. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tools/manager/mcp.py +0 -0
  154. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tools/search/__init__.py +0 -0
  155. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tools/search/discovered.py +0 -0
  156. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tools/search/scoring.py +0 -0
  157. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tools/search/service.py +0 -0
  158. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/ui/__init__.py +0 -0
  159. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/ui/completion.py +0 -0
  160. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/ui/elicitation.py +0 -0
  161. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/ui/input.py +0 -0
  162. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/voice/__init__.py +0 -0
  163. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/voice/cleanup.py +0 -0
  164. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/voice/session.py +0 -0
  165. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/voice/stt.py +0 -0
  166. {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/voice/tts.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cade-cli
3
- Version: 0.15.5
3
+ Version: 0.15.7
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,7 +43,7 @@ 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.14.0
46
+ Requires-Dist: agent-library<1.0.0,>=0.14.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
49
  Requires-Dist: arcade-mcp-server<2.0.0,>=1.22.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cade-cli"
3
- version = "0.15.5"
3
+ version = "0.15.7"
4
4
  description = "Cade - The CLI Agent from Arcade.dev"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -38,7 +38,7 @@ dependencies = [
38
38
  "prompt-toolkit>=3.0.52,<4.0.0",
39
39
  "httpx>=0.27.0,<1.0.0",
40
40
  "filelock>=3.0.0,<4.0.0",
41
- "agent-library>=0.14.0,<1.0.0",
41
+ "agent-library>=0.14.1,<1.0.0",
42
42
  "croniter>=2.0.0,<4.0.0",
43
43
  "python-telegram-bot>=21.0,<22.0",
44
44
  "keyring>=24.0,<26.0",
@@ -116,7 +116,7 @@ def register_resources(app: MCPApp, instructions: str) -> None:
116
116
  title="cade mem sources (active persona)",
117
117
  description=(
118
118
  "JSON listing of memory sources registered for the active persona "
119
- "(equivalent to `cade mem list`). Read each call so the listing "
119
+ "(equivalent to `cade mem sources`). Read each call so the listing "
120
120
  "reflects sources added after server start."
121
121
  ),
122
122
  mime_type="application/json",
@@ -0,0 +1,115 @@
1
+ """Secondary-model extraction for WebFetch.
2
+
3
+ Single non-streaming ``Provider.complete()`` call so the same code path
4
+ works for Anthropic, OpenAI, and Ollama users alike. The extraction model is
5
+ resolved from (in priority order) an explicit override, ``CADE_WEB_FETCH_MODEL``,
6
+ the user's configured default model, then ``DEFAULT_EXTRACT_MODEL``. When the
7
+ preferred model's provider isn't configured (e.g. a Claude default on an
8
+ OpenAI-only setup), extraction falls back to whatever provider the user *did*
9
+ configure rather than failing outright.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import os
15
+
16
+ from cade_mcp_local.errors import WebFetchError
17
+ from cade_mcp_local.web.prompts import make_secondary_model_prompt
18
+ from cadecoder.providers import get_provider_for_model, provider_registry
19
+ from cadecoder.providers.base import Provider, ProviderRequest, ProviderType
20
+
21
+ DEFAULT_EXTRACT_MODEL = "claude-haiku-4-5-20251001"
22
+
23
+ # Cheap/fast per-provider extraction models, used only as a fallback when the
24
+ # preferred model's provider is unavailable but another provider is registered.
25
+ # Extraction is a one-shot summarization pass, so a small model is sufficient.
26
+ _FALLBACK_MODEL_BY_PROVIDER: dict[ProviderType, str] = {
27
+ ProviderType.ANTHROPIC: DEFAULT_EXTRACT_MODEL,
28
+ ProviderType.OPENAI: "gpt-5-mini",
29
+ ProviderType.OLLAMA: "llama3.1",
30
+ }
31
+
32
+
33
+ def _configured_default_model() -> str | None:
34
+ """Return the user's configured default model, or None if unavailable.
35
+
36
+ Reads the global settings directly so the extraction step honors whatever
37
+ provider/model the user set up during onboarding.
38
+ """
39
+ try:
40
+ from cadecoder.core.config import _load_global_settings
41
+
42
+ return _load_global_settings().model or None
43
+ except Exception: # noqa: BLE001 - config is best-effort here; fall back to default
44
+ return None
45
+
46
+
47
+ def _resolve_extraction_target(model: str | None) -> tuple[str, Provider]:
48
+ """Resolve the extraction model and a usable provider.
49
+
50
+ Args:
51
+ model: An explicit model override, if any.
52
+
53
+ Returns:
54
+ A ``(model, provider)`` tuple ready to run an extraction.
55
+
56
+ Raises:
57
+ LookupError: If no LLM provider is registered at all.
58
+ """
59
+ chosen = (
60
+ model
61
+ or os.environ.get("CADE_WEB_FETCH_MODEL")
62
+ or _configured_default_model()
63
+ or DEFAULT_EXTRACT_MODEL
64
+ )
65
+
66
+ provider = get_provider_for_model(chosen)
67
+ if provider is not None:
68
+ return chosen, provider
69
+
70
+ # The preferred model's provider isn't configured. Fall back to whatever
71
+ # provider the user did configure (the registry's default) so non-preapproved
72
+ # fetches still work on OpenAI/Ollama-only setups.
73
+ fallback = provider_registry.get_default()
74
+ if fallback is None:
75
+ raise LookupError("no LLM provider is registered")
76
+
77
+ fallback_model = _FALLBACK_MODEL_BY_PROVIDER.get(fallback.provider_type, chosen)
78
+ return fallback_model, fallback
79
+
80
+
81
+ async def extract_with_model(
82
+ prompt: str,
83
+ markdown: str,
84
+ *,
85
+ model: str | None = None,
86
+ url: str = "",
87
+ ) -> tuple[str, str]:
88
+ """Run a one-shot extraction. Returns (answer, model_used).
89
+
90
+ ``url`` is only used to make the error message useful when no provider is
91
+ available or the provider rejects the request.
92
+ """
93
+ try:
94
+ chosen, provider = _resolve_extraction_target(model)
95
+ except LookupError:
96
+ raise WebFetchError(
97
+ url or "(no url)",
98
+ "no provider available for extraction; set ANTHROPIC_API_KEY/OPENAI_API_KEY, "
99
+ "configure a model via cade onboarding, or override CADE_WEB_FETCH_MODEL",
100
+ ) from None
101
+
102
+ full_prompt = make_secondary_model_prompt(prompt, markdown)
103
+ request = ProviderRequest(
104
+ messages=[{"role": "user", "content": full_prompt}],
105
+ model=chosen,
106
+ max_tokens=2048,
107
+ temperature=0.2,
108
+ stream=False,
109
+ )
110
+ try:
111
+ response = await provider.complete(request)
112
+ except Exception as e:
113
+ raise WebFetchError(url or "(no url)", f"extraction model failed: {e}") from e
114
+
115
+ return (response.content or "").strip(), chosen
@@ -10,7 +10,7 @@ from __future__ import annotations
10
10
 
11
11
  import os
12
12
  from pathlib import Path
13
- from typing import Any
13
+ from typing import Annotated, Any
14
14
 
15
15
  import typer
16
16
  from rich.console import Console
@@ -43,20 +43,29 @@ def _apply_memory_env() -> Path:
43
43
  os.environ["DOCUMENTS_PATH"] = str(librarian_dir / "documents")
44
44
  os.environ["SOURCES_CONFIG_PATH"] = str(librarian_dir / "sources.json")
45
45
  os.environ["HOME"] = str(persona_dir)
46
+ # Rebrand librarian's usage hints (e.g. "Add a source with: libr add ...")
47
+ # to "cade mem". Must be set before librarian.cli is imported, which only
48
+ # happens later via _load_librarian_app().
49
+ os.environ["LIBRARIAN_PROG_NAME"] = "cade mem"
46
50
  return persona_dir
47
51
 
48
52
 
49
53
  def _load_librarian_app() -> Any:
50
54
  """Import and return librarian's Typer app (lazy, after the env is set).
51
55
 
52
- The app is rebranded under ``cade mem``: the low-level ``index`` group is
53
- replaced by a top-level ``update`` command (the former ``index build``) and
54
- a cade-native ``status`` command that reports the current agent's memory
55
- under "Agent Memory" branding.
56
+ The app is rebranded under ``cade mem``:
57
+
58
+ * the low-level ``index`` group becomes a top-level ``update`` command (the
59
+ former ``index build``);
60
+ * a cade-native ``status`` command reports the current agent's memory under
61
+ "Agent Memory" branding;
62
+ * ``list`` shows the agent's memory **documents** (what users expect from
63
+ "list"), while the source-directory listing moves to ``sources``.
56
64
  """
57
65
  from librarian.cli import app
58
66
 
59
67
  _remap_index_to_update(app)
68
+ _remap_list_to_docs(app)
60
69
  _add_status_command(app)
61
70
  return app
62
71
 
@@ -93,6 +102,52 @@ def _remap_index_to_update(app: Any) -> None:
93
102
  )(build_callback)
94
103
 
95
104
 
105
+ def _remap_list_to_docs(app: Any) -> None:
106
+ """Make ``cade mem list`` show memory *documents*, not source directories.
107
+
108
+ librarian's top-level ``list`` lists registered source directories — a
109
+ power-user concept. Under ``cade mem``, "list" should show what's actually
110
+ in the agent's memory (its indexed documents, including anything the agent
111
+ saved via ``Memory_*`` tools). So the source listing moves to
112
+ ``cade mem sources`` and ``list`` is replaced by a cade-native command that
113
+ renders documents with the same output (and flags) as the in-chat
114
+ ``/mem list``.
115
+
116
+ Power users keep librarian's ``cade mem docs list`` (``--source`` /
117
+ ``--format`` / ``paths``) untouched.
118
+
119
+ Idempotent: safe to call more than once on the same app singleton.
120
+ """
121
+ if "sources" in _command_names(app):
122
+ return # already remapped
123
+
124
+ commands = list(app.registered_commands)
125
+ list_sources_callback = next(
126
+ (
127
+ rc.callback
128
+ for rc in commands
129
+ if (rc.name or getattr(rc.callback, "__name__", "")) == "list"
130
+ ),
131
+ None,
132
+ )
133
+ if list_sources_callback is None:
134
+ return # librarian build without a top-level `list` — leave as-is
135
+
136
+ # Drop the original top-level `list` (sources), re-register it as `sources`,
137
+ # and install the cade-native document listing as `list`.
138
+ app.registered_commands = [
139
+ rc for rc in commands if (rc.name or getattr(rc.callback, "__name__", "")) != "list"
140
+ ]
141
+ app.command(
142
+ name="sources",
143
+ help="List the source directories that feed this agent's memory.",
144
+ )(list_sources_callback)
145
+ app.command(
146
+ name="list",
147
+ help="List the documents stored in this agent's memory.",
148
+ )(_cade_mem_list)
149
+
150
+
96
151
  def _add_status_command(app: Any) -> None:
97
152
  """Register the cade-native ``status`` command (idempotent)."""
98
153
  if "status" in _command_names(app):
@@ -157,6 +212,86 @@ def _mem_status() -> None:
157
212
  out.print(table)
158
213
 
159
214
 
215
+ def _mem_list(*, json_output: bool = False, limit: int = 50) -> None:
216
+ """Render the documents stored in the current agent's memory.
217
+
218
+ The single source of truth for both ``cade mem list`` (CLI) and the in-chat
219
+ ``/mem list`` command, so the two surfaces always agree. Reads the agent's
220
+ index directly via ``Database(db_path=...)`` (no env / no HOME side effects)
221
+ so it never clobbers a long-lived session, mirroring ``_read_memory_stats``.
222
+
223
+ Args:
224
+ json_output: Emit a JSON array (id/title/path/type) to stdout instead of
225
+ a table — for scripting and piping.
226
+ limit: Maximum number of documents to display.
227
+ """
228
+ import json
229
+
230
+ from rich.console import Console as _Console
231
+ from rich.table import Table
232
+
233
+ runtime = get_runtime()
234
+ memory_dir = ensure_memory_dir(runtime.persona_name)
235
+ db_path = memory_dir / ".librarian" / "index.db"
236
+
237
+ out = _Console()
238
+
239
+ documents: list[Any] = []
240
+ if db_path.exists():
241
+ try:
242
+ from librarian.storage.database import Database
243
+
244
+ documents = Database(db_path=str(db_path)).list_documents()
245
+ except Exception as exc: # noqa: BLE001
246
+ if json_output:
247
+ print("[]")
248
+ else:
249
+ out.print(f"[red]Could not read this agent's memory:[/red] {exc}")
250
+ return
251
+
252
+ if json_output:
253
+ payload = [
254
+ {
255
+ "id": doc.id,
256
+ "title": doc.title,
257
+ "path": doc.path,
258
+ "type": doc.asset_type.value if getattr(doc, "asset_type", None) else "text",
259
+ }
260
+ for doc in documents[:limit]
261
+ ]
262
+ print(json.dumps(payload, indent=2, default=str))
263
+ return
264
+
265
+ if not documents:
266
+ out.print(
267
+ "[dim]No documents in this agent's memory yet — add docs or run 'cade mem update'.[/dim]"
268
+ )
269
+ return
270
+
271
+ home = str(Path.home())
272
+ table = Table(
273
+ title=f"Agent Memory — {len(documents)} document(s)",
274
+ show_header=True,
275
+ header_style="bold cyan",
276
+ )
277
+ table.add_column("ID", style="dim", width=4)
278
+ table.add_column("Title", style="green", max_width=44)
279
+ table.add_column("Path", style="blue", overflow="fold")
280
+ for doc in documents[:limit]:
281
+ table.add_row(str(doc.id), doc.title or "Untitled", doc.path.replace(home, "~"))
282
+ out.print(table)
283
+ if len(documents) > limit:
284
+ out.print(f"[dim]Showing {limit} of {len(documents)}. Use --limit to see more.[/dim]")
285
+
286
+
287
+ def _cade_mem_list(
288
+ json_output: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
289
+ limit: Annotated[int, typer.Option("--limit", "-l", help="Max documents to show")] = 50,
290
+ ) -> None:
291
+ """List the documents stored in this agent's memory."""
292
+ _mem_list(json_output=json_output, limit=limit)
293
+
294
+
160
295
  def _run_mem(args: list[str]) -> int:
161
296
  """Run librarian's CLI in-process, branded as ``cade mem``.
162
297
 
@@ -244,7 +379,9 @@ def mem_command(ctx: typer.Context) -> None:
244
379
  __all__ = (
245
380
  "MEM_CONTEXT_SETTINGS",
246
381
  "_apply_memory_env",
382
+ "_cade_mem_list",
247
383
  "_load_librarian_app",
384
+ "_mem_list",
248
385
  "_run_mem",
249
386
  "_warm_memory",
250
387
  "mem_command",
@@ -465,7 +465,6 @@ _PRIMARY_ARG_KEYS: tuple[str, ...] = (
465
465
  # Generic last-resort labels.
466
466
  "name",
467
467
  "title",
468
- "tool_name",
469
468
  )
470
469
 
471
470
 
@@ -811,6 +810,18 @@ def get_display_tool_name(tool_name: str, args: dict[str, Any]) -> str:
811
810
 
812
811
  def get_tool_context(tool_name: str, args: dict[str, Any]) -> str:
813
812
  """Return parenthesized context for the tool (used by session for 'Calling tool' line)."""
813
+ # Unwrap the Arcade meta-tools so the context describes the *inner* tool
814
+ # (e.g. the doc title) instead of echoing the wrapper's tool_name arg —
815
+ # which produced redundant headlines like
816
+ # ``GoogleDocs_CreateDocumentFromText("GoogleDocs_CreateDocumentFromText")``.
817
+ if tool_name in ("Arcade_UseTool", "Arcade_ExecuteTool"):
818
+ inner_name = get_display_tool_name(tool_name, args)
819
+ if inner_name and inner_name != tool_name:
820
+ inner_args = args.get("inputs")
821
+ if not isinstance(inner_args, dict):
822
+ inner_args = {}
823
+ return get_tool_context(inner_name, inner_args)
824
+
814
825
  server, tool = _parse_tool_name(tool_name)
815
826
  handler = _find_registry_handler(server, tool)
816
827
  if handler:
@@ -884,10 +884,10 @@ class ChatSession:
884
884
  console.print("[cyan]Switching to voice mode — keeping this conversation…[/cyan]")
885
885
 
886
886
  async def _handle_mem_command(self, cmd: str) -> None:
887
- """Handle /mem (Agent Memory status) and /mem search <query>.
887
+ """Handle /mem (status), /mem list (documents), and /mem search <query>.
888
888
 
889
- The bare/`status` view reuses the same renderer as the ``cade mem`` CLI
890
- so the Agent Memory experience is identical in both surfaces.
889
+ The status and list views reuse the same renderers as the ``cade mem``
890
+ CLI so the Agent Memory experience is identical in both surfaces.
891
891
  """
892
892
  parts = cmd.split(None, 2)
893
893
  sub = parts[1] if len(parts) >= 2 else "status"
@@ -898,6 +898,12 @@ class ChatSession:
898
898
  _mem_status()
899
899
  return
900
900
 
901
+ if sub in ("list", "ls", "docs"):
902
+ from cadecoder.cli.commands.mem import _mem_list
903
+
904
+ _mem_list()
905
+ return
906
+
901
907
  if sub == "search" and len(parts) >= 3:
902
908
  query = parts[2]
903
909
  try:
@@ -913,7 +919,7 @@ class ChatSession:
913
919
  )
914
920
  return
915
921
 
916
- console.print("[dim]Usage: /mem [status] | /mem search <query>[/dim]")
922
+ console.print("[dim]Usage: /mem [status] | /mem list | /mem search <query>[/dim]")
917
923
 
918
924
  @staticmethod
919
925
  def _print_mem_search_results(result: object) -> None:
@@ -51,7 +51,7 @@ SLASH_COMMANDS: tuple[SlashCommand, ...] = (
51
51
  ("list", "add", "remove", "enable", "disable"),
52
52
  ),
53
53
  SlashCommand("/hooks", " …", "Manage lifecycle hooks", ("list", "reload")),
54
- SlashCommand("/mem", " …", "Search/manage memory", ("status", "search")),
54
+ SlashCommand("/mem", " …", "Search/manage memory", ("status", "list", "search")),
55
55
  SlashCommand("/notify", " <msg>", "Send a push notification"),
56
56
  SlashCommand("/pwd", "", "Show current directory", hidden=True),
57
57
  SlashCommand("/cd", " <path>", "Change directory", hidden=True),
@@ -52,6 +52,34 @@ def _rms(audio: np.ndarray) -> float:
52
52
  return float(np.sqrt(np.mean(audio.astype(np.float64) ** 2)))
53
53
 
54
54
 
55
+ # Substrings that identify a Bluetooth headset reported as an input device.
56
+ # Such devices frequently return all-zero samples on macOS because the input
57
+ # route fails to switch to the headset mic — a routing issue, not a permission
58
+ # one — so the silence error gives different advice in that case.
59
+ _BLUETOOTH_MIC_HINTS: Final[tuple[str, ...]] = (
60
+ "airpods",
61
+ "beats",
62
+ "bluetooth",
63
+ "headphones",
64
+ "headset",
65
+ "buds",
66
+ "powerbeats",
67
+ )
68
+
69
+
70
+ def _looks_like_bluetooth_mic(device_name: str) -> bool:
71
+ """Return True when *device_name* looks like a Bluetooth headset.
72
+
73
+ Args:
74
+ device_name: The human-readable input device name from PortAudio.
75
+
76
+ Returns:
77
+ True if the name matches a known Bluetooth-headset substring.
78
+ """
79
+ name = device_name.lower()
80
+ return any(hint in name for hint in _BLUETOOTH_MIC_HINTS)
81
+
82
+
55
83
  def check_mic_permission(
56
84
  device: str | int | None = None,
57
85
  sample_rate: int = DEFAULT_SAMPLE_RATE,
@@ -103,6 +131,27 @@ def check_mic_permission(
103
131
  import platform
104
132
 
105
133
  if platform.system() == "Darwin":
134
+ if _looks_like_bluetooth_mic(device_name):
135
+ # Bluetooth headsets (AirPods, Beats, etc.) commonly report
136
+ # silence on macOS because the input route fails to switch to
137
+ # the headset's mic — a routing problem, not a permission one.
138
+ return False, (
139
+ f"Microphone '{device_name}' returns silence (all zeros).\n"
140
+ "Bluetooth headsets (AirPods, Beats, etc.) often can't be "
141
+ "used as a mic on macOS.\n"
142
+ "\n"
143
+ "Try one of these:\n"
144
+ " • Switch input to the built-in mic: System Settings > "
145
+ "Sound > Input > MacBook/Built-in Microphone\n"
146
+ " • Or pin a specific device in ~/.cade/config/cade.toml "
147
+ 'under [voice]: audio_input_device = "MacBook Pro Microphone"\n'
148
+ " (type /devices in the chat to list available devices)\n"
149
+ " • Or disconnect/reconnect the headset and try again\n"
150
+ "\n"
151
+ "If you intended to use the built-in mic, also confirm your "
152
+ "terminal has mic access in System Settings > Privacy & "
153
+ "Security > Microphone."
154
+ )
106
155
  return False, (
107
156
  f"Microphone '{device_name}' returns silence (all zeros).\n"
108
157
  "This usually means macOS is blocking microphone access.\n"
@@ -1,54 +0,0 @@
1
- """Secondary-model extraction for WebFetch.
2
-
3
- Single non-streaming ``Provider.complete()`` call so the same code path
4
- works for Anthropic, OpenAI, and Ollama users alike. Default model is
5
- ``claude-haiku-4-5-20251001``; override via ``CADE_WEB_FETCH_MODEL``.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- import os
11
-
12
- from cade_mcp_local.errors import WebFetchError
13
- from cade_mcp_local.web.prompts import make_secondary_model_prompt
14
- from cadecoder.providers import get_provider_for_model
15
- from cadecoder.providers.base import ProviderRequest
16
-
17
- DEFAULT_EXTRACT_MODEL = "claude-haiku-4-5-20251001"
18
-
19
-
20
- async def extract_with_model(
21
- prompt: str,
22
- markdown: str,
23
- *,
24
- model: str | None = None,
25
- url: str = "",
26
- ) -> tuple[str, str]:
27
- """Run a one-shot extraction. Returns (answer, model_used).
28
-
29
- ``url`` is only used to make the error message useful when the
30
- provider rejects the request.
31
- """
32
- chosen = model or os.environ.get("CADE_WEB_FETCH_MODEL") or DEFAULT_EXTRACT_MODEL
33
- provider = get_provider_for_model(chosen)
34
- if provider is None:
35
- raise WebFetchError(
36
- url or "(no url)",
37
- f"no provider available for extraction model {chosen!r}; "
38
- "set ANTHROPIC_API_KEY/OPENAI_API_KEY or CADE_WEB_FETCH_MODEL",
39
- )
40
-
41
- full_prompt = make_secondary_model_prompt(prompt, markdown)
42
- request = ProviderRequest(
43
- messages=[{"role": "user", "content": full_prompt}],
44
- model=chosen,
45
- max_tokens=2048,
46
- temperature=0.2,
47
- stream=False,
48
- )
49
- try:
50
- response = await provider.complete(request)
51
- except Exception as e:
52
- raise WebFetchError(url or "(no url)", f"extraction model failed: {e}") from e
53
-
54
- return (response.content or "").strip(), chosen
File without changes
File without changes
File without changes