cade-cli 0.14.1__tar.gz → 0.15.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 (165) hide show
  1. {cade_cli-0.14.1 → cade_cli-0.15.0}/PKG-INFO +12 -4
  2. {cade_cli-0.14.1 → cade_cli-0.15.0}/README.md +10 -2
  3. {cade_cli-0.14.1 → cade_cli-0.15.0}/pyproject.toml +2 -2
  4. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/cli/app.py +27 -0
  5. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/cli/commands/mem.py +30 -0
  6. cade_cli-0.15.0/src/cadecoder/cli/onboarding.py +317 -0
  7. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/core/config.py +3 -0
  8. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/core/constants.py +12 -0
  9. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/core/errors.py +34 -1
  10. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/core/secrets.py +4 -1
  11. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/execution/orchestrator.py +11 -6
  12. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/providers/__init__.py +51 -9
  13. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/daemon.py +2 -5
  14. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/services.py +27 -0
  15. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/tools/manager/composite.py +274 -3
  16. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/ui/session.py +11 -0
  17. {cade_cli-0.14.1 → cade_cli-0.15.0}/.gitignore +0 -0
  18. {cade_cli-0.14.1 → cade_cli-0.15.0}/LICENSE +0 -0
  19. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/__init__.py +0 -0
  20. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/__main__.py +0 -0
  21. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/_metadata.py +0 -0
  22. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/config.py +0 -0
  23. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/errors.py +0 -0
  24. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/resources.py +0 -0
  25. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/server.py +0 -0
  26. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/tools/__init__.py +0 -0
  27. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/tools/_read_cache.py +0 -0
  28. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/tools/agents.py +0 -0
  29. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/tools/context.py +0 -0
  30. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/tools/filesystem.py +0 -0
  31. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/tools/git.py +0 -0
  32. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/tools/notifications.py +0 -0
  33. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/tools/questions.py +0 -0
  34. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/tools/results.py +0 -0
  35. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/tools/schemas.py +0 -0
  36. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/tools/search.py +0 -0
  37. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/tools/shell.py +0 -0
  38. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/tools/snippets.py +0 -0
  39. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/tools/tasks.py +0 -0
  40. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/tools/web_fetch.py +0 -0
  41. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/tools/web_search.py +0 -0
  42. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/utils.py +0 -0
  43. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/web/__init__.py +0 -0
  44. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/web/arcade_client.py +0 -0
  45. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/web/cache.py +0 -0
  46. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/web/extract.py +0 -0
  47. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/web/markdown.py +0 -0
  48. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/web/preapproved.py +0 -0
  49. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/web/prompts.py +0 -0
  50. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/web/scrape.py +0 -0
  51. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/web/search/__init__.py +0 -0
  52. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/web/search/arcade.py +0 -0
  53. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/web/search/base.py +0 -0
  54. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cade_mcp_local/web/search/http.py +0 -0
  55. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/__init__.py +0 -0
  56. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/ai/__init__.py +0 -0
  57. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/ai/prompts.py +0 -0
  58. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/cli/__init__.py +0 -0
  59. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/cli/auth.py +0 -0
  60. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/cli/commands/__init__.py +0 -0
  61. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/cli/commands/account.py +0 -0
  62. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/cli/commands/agents.py +0 -0
  63. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/cli/commands/auth.py +0 -0
  64. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/cli/commands/chat.py +0 -0
  65. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/cli/commands/cron.py +0 -0
  66. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/cli/commands/hooks.py +0 -0
  67. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/cli/commands/mcp.py +0 -0
  68. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/cli/commands/serve.py +0 -0
  69. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/cli/commands/tasks.py +0 -0
  70. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/cli/commands/thread.py +0 -0
  71. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/cli/commands/tools.py +0 -0
  72. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/core/__init__.py +0 -0
  73. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/core/agent.py +0 -0
  74. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/core/comms.py +0 -0
  75. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/core/frozen.py +0 -0
  76. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/core/git.py +0 -0
  77. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/core/inbox.py +0 -0
  78. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/core/logging.py +0 -0
  79. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/core/memory.py +0 -0
  80. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/core/model_specs.py +0 -0
  81. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/core/names.py +0 -0
  82. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/core/paths.py +0 -0
  83. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/core/runtime.py +0 -0
  84. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/core/spawn.py +0 -0
  85. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/core/types.py +0 -0
  86. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/execution/__init__.py +0 -0
  87. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/execution/context_window.py +0 -0
  88. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/execution/mcp_schema_index.py +0 -0
  89. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/execution/parallel.py +0 -0
  90. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/execution/tool_result_store.py +0 -0
  91. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/execution/tool_schema_cache.py +0 -0
  92. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/hooks/__init__.py +0 -0
  93. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/hooks/config.py +0 -0
  94. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/hooks/engine.py +0 -0
  95. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/hooks/executors/__init__.py +0 -0
  96. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/hooks/executors/callback.py +0 -0
  97. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/hooks/executors/command.py +0 -0
  98. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/hooks/executors/http.py +0 -0
  99. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/hooks/executors/prompt.py +0 -0
  100. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/hooks/executors/ssrf_guard.py +0 -0
  101. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/hooks/matchers.py +0 -0
  102. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/hooks/registry.py +0 -0
  103. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/hooks/types.py +0 -0
  104. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/providers/anthropic.py +0 -0
  105. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/providers/base.py +0 -0
  106. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/providers/ollama.py +0 -0
  107. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/providers/openai.py +0 -0
  108. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/__init__.py +0 -0
  109. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/adapters/__init__.py +0 -0
  110. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/adapters/base.py +0 -0
  111. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/adapters/desktop.py +0 -0
  112. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/adapters/registry.py +0 -0
  113. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/adapters/stub.py +0 -0
  114. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/adapters/telegram.py +0 -0
  115. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/allowlist.py +0 -0
  116. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/chunker.py +0 -0
  117. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/commands.py +0 -0
  118. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/config.py +0 -0
  119. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/cron.py +0 -0
  120. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/elicitation.py +0 -0
  121. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/format.py +0 -0
  122. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/install.py +0 -0
  123. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/lock.py +0 -0
  124. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/observability.py +0 -0
  125. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/owner.py +0 -0
  126. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/progress_sink.py +0 -0
  127. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/router.py +0 -0
  128. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/secrets.py +0 -0
  129. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/sinks.py +0 -0
  130. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/serve/worker.py +0 -0
  131. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/storage/__init__.py +0 -0
  132. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/storage/threads.py +0 -0
  133. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/tasks/__init__.py +0 -0
  134. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/tasks/lock.py +0 -0
  135. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/tasks/scheduler.py +0 -0
  136. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/tasks/store.py +0 -0
  137. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/tasks/types.py +0 -0
  138. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/templates/login_failed.html +0 -0
  139. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/templates/login_success.html +0 -0
  140. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/templates/serve/launchd.plist.tmpl +0 -0
  141. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/templates/serve/serve.toml.example +0 -0
  142. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/templates/serve/systemd.service.tmpl +0 -0
  143. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/templates/styles.css +0 -0
  144. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/tools/__init__.py +0 -0
  145. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/tools/manager/__init__.py +0 -0
  146. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/tools/manager/_request_ctx.py +0 -0
  147. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/tools/manager/base.py +0 -0
  148. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/tools/manager/config.py +0 -0
  149. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/tools/manager/mcp.py +0 -0
  150. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/tools/search/__init__.py +0 -0
  151. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/tools/search/discovered.py +0 -0
  152. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/tools/search/scoring.py +0 -0
  153. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/tools/search/service.py +0 -0
  154. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/ui/__init__.py +0 -0
  155. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/ui/completion.py +0 -0
  156. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/ui/display.py +0 -0
  157. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/ui/elicitation.py +0 -0
  158. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/ui/input.py +0 -0
  159. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/ui/slash.py +0 -0
  160. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/voice/__init__.py +0 -0
  161. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/voice/audio.py +0 -0
  162. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/voice/cleanup.py +0 -0
  163. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/voice/session.py +0 -0
  164. {cade_cli-0.14.1 → cade_cli-0.15.0}/src/cadecoder/voice/stt.py +0 -0
  165. {cade_cli-0.14.1 → cade_cli-0.15.0}/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.14.1
3
+ Version: 0.15.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
@@ -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.13.1
46
+ Requires-Dist: agent-library<1.0.0,>=0.14.0
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
@@ -123,7 +123,7 @@ The install script detects your OS/arch, downloads the matching prebuilt binary
123
123
  `~/.local/bin` — no GitHub token required. Pin a version or install location:
124
124
 
125
125
  ```bash
126
- CADE_VERSION=0.14.1 CADE_INSTALL_DIR=/usr/local/bin \
126
+ CADE_VERSION=0.15.0 CADE_INSTALL_DIR=/usr/local/bin \
127
127
  sh -c "$(curl -fsSL https://arcadeagent.dev/install)"
128
128
  ```
129
129
 
@@ -135,7 +135,11 @@ larger download). Force it with `CADE_VARIANT=cpu` or `CADE_VARIANT=gpu`.
135
135
 
136
136
  **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`.
137
137
 
138
+ The first run walks you through picking a model provider and key (stored in your
139
+ OS keyring); re-run it any time with `cade init`.
140
+
138
141
  ```bash
142
+ cade init # pick provider + model (runs automatically on first launch)
139
143
  cade login # Arcade Cloud OAuth (skip with --local-only)
140
144
  cade --version
141
145
  ```
@@ -227,11 +231,15 @@ cade info / cade edit reviewer # inspect / change config
227
231
  cade mem # the current agent's memory (status)
228
232
  cade mem add ~/notes # index a directory
229
233
  cade mem search "auth design" # semantic search
234
+ cade mem warm # pre-load the semantic memory model
230
235
  ```
231
236
 
232
237
  Memory is per-agent — each agent has its own
233
238
  `~/.cade/data/agents/<id>/memory/.librarian/` index, so a `reviewer` agent's
234
- notes never leak into another.
239
+ notes never leak into another. Cade advertises `Memory_*` tools without starting
240
+ the memory server; the first actual memory call starts it and keeps it warm for
241
+ later calls. Use `cade mem warm` when you want to pay that semantic-search
242
+ warmup before a conversation.
235
243
 
236
244
  ---
237
245
 
@@ -35,7 +35,7 @@ The install script detects your OS/arch, downloads the matching prebuilt binary
35
35
  `~/.local/bin` — no GitHub token required. Pin a version or install location:
36
36
 
37
37
  ```bash
38
- CADE_VERSION=0.14.1 CADE_INSTALL_DIR=/usr/local/bin \
38
+ CADE_VERSION=0.15.0 CADE_INSTALL_DIR=/usr/local/bin \
39
39
  sh -c "$(curl -fsSL https://arcadeagent.dev/install)"
40
40
  ```
41
41
 
@@ -47,7 +47,11 @@ larger download). Force it with `CADE_VARIANT=cpu` or `CADE_VARIANT=gpu`.
47
47
 
48
48
  **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`.
49
49
 
50
+ The first run walks you through picking a model provider and key (stored in your
51
+ OS keyring); re-run it any time with `cade init`.
52
+
50
53
  ```bash
54
+ cade init # pick provider + model (runs automatically on first launch)
51
55
  cade login # Arcade Cloud OAuth (skip with --local-only)
52
56
  cade --version
53
57
  ```
@@ -139,11 +143,15 @@ cade info / cade edit reviewer # inspect / change config
139
143
  cade mem # the current agent's memory (status)
140
144
  cade mem add ~/notes # index a directory
141
145
  cade mem search "auth design" # semantic search
146
+ cade mem warm # pre-load the semantic memory model
142
147
  ```
143
148
 
144
149
  Memory is per-agent — each agent has its own
145
150
  `~/.cade/data/agents/<id>/memory/.librarian/` index, so a `reviewer` agent's
146
- notes never leak into another.
151
+ notes never leak into another. Cade advertises `Memory_*` tools without starting
152
+ the memory server; the first actual memory call starts it and keeps it warm for
153
+ later calls. Use `cade mem warm` when you want to pay that semantic-search
154
+ warmup before a conversation.
147
155
 
148
156
  ---
149
157
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cade-cli"
3
- version = "0.14.1"
3
+ version = "0.15.0"
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.13.1,<1.0.0",
41
+ "agent-library>=0.14.0,<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",
@@ -53,6 +53,7 @@ from cadecoder.core.runtime import set_runtime
53
53
  # output like `cade list` / `cade status` on a fresh install).
54
54
  _NO_RUNTIME_SUBCOMMANDS = frozenset(
55
55
  {
56
+ "init",
56
57
  "new",
57
58
  "use",
58
59
  "list",
@@ -225,6 +226,23 @@ def _doctor_paths(runtime: Any) -> dict[str, Any]:
225
226
  }
226
227
 
227
228
 
229
+ @app.command(
230
+ name="init",
231
+ help="Set up (or change) your model provider and default model",
232
+ rich_help_panel=PANEL_SETUP,
233
+ )
234
+ def init_cmd() -> None:
235
+ from cadecoder.cli.onboarding import can_prompt, run_onboarding
236
+
237
+ if not can_prompt():
238
+ console.print(
239
+ "[red]cade init needs an interactive terminal.[/red] "
240
+ "Set OPENAI_API_KEY or ANTHROPIC_API_KEY instead."
241
+ )
242
+ raise typer.Exit(1)
243
+ run_onboarding(console, force=True)
244
+
245
+
228
246
  @app.command(
229
247
  name="doctor",
230
248
  help="Show paths, the current agent, and check the Cade home for problems",
@@ -366,6 +384,15 @@ def main_callback(
366
384
  if ctx.invoked_subcommand in _NO_RUNTIME_SUBCOMMANDS:
367
385
  return
368
386
 
387
+ # First-run setup: on a brand-new install, walk the user through picking a
388
+ # provider + model before we build the runtime, so the first session uses
389
+ # what they chose (not the hardcoded default). No-ops when already set up or
390
+ # when not attached to an interactive terminal (piped/CI).
391
+ from cadecoder.cli.onboarding import needs_onboarding, run_onboarding
392
+
393
+ if needs_onboarding():
394
+ run_onboarding(console)
395
+
369
396
  # Interaction + runtime-backed commands run as the current agent, auto-creating
370
397
  # a `default` agent on first run via build_runtime → ensure_current_agent.
371
398
  runtime = build_runtime()
@@ -188,6 +188,32 @@ def _run_mem(args: list[str]) -> int:
188
188
  return 0
189
189
 
190
190
 
191
+ def _warm_memory() -> int:
192
+ """Preload the text embedding model for the current agent's memory."""
193
+ try:
194
+ from librarian.processing.embed import get_embedder
195
+ except ImportError:
196
+ console.print(
197
+ "[red]agent-library is not installed.[/red] Install it into the "
198
+ "active environment: [bold]uv pip install agent-library[/bold]."
199
+ )
200
+ return 1
201
+
202
+ console.print("[cyan]Warming semantic memory search...[/cyan]")
203
+ try:
204
+ embedder = get_embedder()
205
+ dimension = embedder.dimension
206
+ except Exception as exc: # noqa: BLE001
207
+ console.print(f"[red]Memory warmup failed:[/red] {exc}")
208
+ return 1
209
+
210
+ console.print(
211
+ f"[green]Memory semantic search is warm[/green] "
212
+ f"[dim](model={embedder.model_name}, dimension={dimension})[/dim]"
213
+ )
214
+ return 0
215
+
216
+
191
217
  def mem_command(ctx: typer.Context) -> None:
192
218
  """Run a ``cade mem`` subcommand scoped to the current agent.
193
219
 
@@ -208,6 +234,9 @@ def mem_command(ctx: typer.Context) -> None:
208
234
  f"[dim]cade mem: scoped to agent [bold]{agent_name}[/bold] "
209
235
  f"(storage={memory_home})[/dim]\n"
210
236
  )
237
+ elif forwarded[0] == "warm":
238
+ rc = _warm_memory()
239
+ raise typer.Exit(rc)
211
240
  rc = _run_mem(forwarded)
212
241
  raise typer.Exit(rc)
213
242
 
@@ -217,5 +246,6 @@ __all__ = (
217
246
  "_apply_memory_env",
218
247
  "_load_librarian_app",
219
248
  "_run_mem",
249
+ "_warm_memory",
220
250
  "mem_command",
221
251
  )
@@ -0,0 +1,317 @@
1
+ """First-run onboarding for the Cade CLI.
2
+
3
+ A brand-new install has no obvious way to pick a model provider, and the silent
4
+ provider fallback used to log a GPT model even when only an Anthropic key was
5
+ set. This wizard runs once on first launch (and on demand via ``cade init``):
6
+ it asks which provider to use, captures/stores an API key in the OS keyring,
7
+ records a recommended default model, and writes the global config + default
8
+ agent so the very first session uses exactly what the user chose.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+
15
+ from rich.console import Console
16
+ from rich.panel import Panel
17
+ from rich.prompt import Confirm, Prompt
18
+
19
+ from cadecoder.core import constants
20
+ from cadecoder.core.paths import get_paths
21
+
22
+ # Provider option ids surfaced in the wizard.
23
+ _PROVIDER_ANTHROPIC = "anthropic"
24
+ _PROVIDER_OPENAI = "openai"
25
+ _PROVIDER_LOCAL = "local"
26
+
27
+ # Keyring namespace for stored model keys.
28
+ _KEYRING_SERVICE = "cade/model"
29
+
30
+
31
+ def _marker_path():
32
+ """Path to the sentinel that records onboarding has completed."""
33
+ return get_paths().state_dir / "onboarded"
34
+
35
+
36
+ def is_onboarded() -> bool:
37
+ """Return whether onboarding has completed at least once."""
38
+ return _marker_path().exists()
39
+
40
+
41
+ def mark_onboarded() -> None:
42
+ """Record that onboarding has completed (idempotent)."""
43
+ paths = get_paths()
44
+ paths.ensure_base_dirs()
45
+ _marker_path().write_text("ok\n", encoding="utf-8")
46
+
47
+
48
+ def _already_configured() -> bool:
49
+ """Heuristic: a pre-existing install that already has a usable provider.
50
+
51
+ Used to avoid wizarding users who upgrade into this feature with a working
52
+ setup. Env keys deliberately do NOT count here — a fresh install should
53
+ still see the wizard even if a key is exported (the user asked for that).
54
+ """
55
+ try:
56
+ from cadecoder.core.config import load_account_config
57
+
58
+ if load_account_config().email:
59
+ return True
60
+ except Exception:
61
+ pass
62
+ try:
63
+ from cadecoder.core.config import _load_global_settings
64
+
65
+ settings = _load_global_settings()
66
+ if settings.model_settings is not None and settings.model_settings.api_key:
67
+ return True
68
+ except Exception:
69
+ pass
70
+ return False
71
+
72
+
73
+ def needs_onboarding() -> bool:
74
+ """Return whether the first-run wizard should run.
75
+
76
+ True only when onboarding has never completed and there's no evidence of a
77
+ prior working configuration (which is migrated by stamping the marker).
78
+ """
79
+ if is_onboarded():
80
+ return False
81
+ if _already_configured():
82
+ mark_onboarded()
83
+ return False
84
+ return True
85
+
86
+
87
+ def can_prompt() -> bool:
88
+ """Return whether we're attached to an interactive terminal."""
89
+ import sys
90
+
91
+ try:
92
+ return sys.stdin.isatty() and sys.stderr.isatty()
93
+ except Exception:
94
+ return False
95
+
96
+
97
+ def _store_key(provider: str, key: str) -> str:
98
+ """Store ``key`` for ``provider`` and return a secret reference.
99
+
100
+ Falls back to a plaintext config value only if secret storage is entirely
101
+ unavailable, so onboarding never hard-fails on a headless box.
102
+ """
103
+ from cadecoder.core.secrets import store_secret
104
+
105
+ try:
106
+ return store_secret(_KEYRING_SERVICE, f"{provider}_api_key", key)
107
+ except Exception:
108
+ # Last resort: persist the literal value in config. Not ideal, but the
109
+ # user explicitly entered it during setup.
110
+ return key
111
+
112
+
113
+ def _gather_anthropic(console: Console) -> tuple[str | None, str | None]:
114
+ """Collect an Anthropic key. Returns (api_key_ref_or_none, host=None)."""
115
+ env_key = os.environ.get("ANTHROPIC_API_KEY")
116
+ if env_key:
117
+ console.print("[green]Found ANTHROPIC_API_KEY in your environment.[/green]")
118
+ if Confirm.ask(
119
+ "Save it to your keyring so Cade works in every session?",
120
+ default=True,
121
+ console=console,
122
+ ):
123
+ return _store_key(_PROVIDER_ANTHROPIC, env_key), None
124
+ return None, None # rely on the environment variable
125
+
126
+ key = Prompt.ask("Paste your Anthropic API key", password=True, console=console).strip()
127
+ if not key:
128
+ raise OnboardingAborted("No API key entered.")
129
+ return _store_key(_PROVIDER_ANTHROPIC, key), None
130
+
131
+
132
+ def _gather_openai(console: Console) -> tuple[str | None, str | None]:
133
+ """Collect an OpenAI key. Returns (api_key_ref_or_none, host=None)."""
134
+ env_key = os.environ.get("OPENAI_API_KEY")
135
+ if env_key:
136
+ console.print("[green]Found OPENAI_API_KEY in your environment.[/green]")
137
+ if Confirm.ask(
138
+ "Save it to your keyring so Cade works in every session?",
139
+ default=True,
140
+ console=console,
141
+ ):
142
+ return _store_key(_PROVIDER_OPENAI, env_key), None
143
+ return None, None
144
+
145
+ key = Prompt.ask("Paste your OpenAI API key", password=True, console=console).strip()
146
+ if not key:
147
+ raise OnboardingAborted("No API key entered.")
148
+ return _store_key(_PROVIDER_OPENAI, key), None
149
+
150
+
151
+ def _gather_local(console: Console) -> tuple[str | None, str]:
152
+ """Collect a local/OpenAI-compatible endpoint. Returns (api_key, host)."""
153
+ host = Prompt.ask(
154
+ "Endpoint base URL",
155
+ default=constants.DEFAULT_OLLAMA_BASE_URL,
156
+ console=console,
157
+ ).strip()
158
+ # Many local servers ignore the key but require a non-empty value.
159
+ key = Prompt.ask(
160
+ "API key (most local servers accept any value)",
161
+ default="local",
162
+ console=console,
163
+ ).strip()
164
+ return (key or "local"), host
165
+
166
+
167
+ class OnboardingAborted(Exception):
168
+ """Raised when the user cancels onboarding (Ctrl-C / empty required input)."""
169
+
170
+
171
+ def run_onboarding(console: Console, *, force: bool = False) -> bool:
172
+ """Run the interactive setup wizard.
173
+
174
+ Args:
175
+ console: Rich console (stderr) to render prompts on.
176
+ force: Re-run even if already onboarded (``cade init``).
177
+
178
+ Returns:
179
+ True if a provider was configured; False if skipped/aborted.
180
+ """
181
+ if not force and not needs_onboarding():
182
+ return False
183
+
184
+ if not can_prompt():
185
+ # Non-interactive (piped/CI). Don't block; the provider error path will
186
+ # guide the user if nothing is configured.
187
+ return False
188
+
189
+ console.print(
190
+ Panel.fit(
191
+ "Welcome to [bold]Cade[/bold] — let's pick a model provider.\n"
192
+ "You can change this anytime with [bold]cade init[/bold].",
193
+ border_style="cyan",
194
+ )
195
+ )
196
+
197
+ has_anthropic = bool(os.environ.get("ANTHROPIC_API_KEY"))
198
+ has_openai = bool(os.environ.get("OPENAI_API_KEY"))
199
+
200
+ def _label(text: str, detected: bool) -> str:
201
+ return f"{text} [green](detected)[/green]" if detected else text
202
+
203
+ console.print()
204
+ console.print(f" [bold]1[/bold]. {_label('Anthropic (Claude)', has_anthropic)}")
205
+ console.print(f" [bold]2[/bold]. {_label('OpenAI (GPT)', has_openai)}")
206
+ console.print(" [bold]3[/bold]. Local / OpenAI-compatible (Ollama, vLLM, …)")
207
+ console.print()
208
+
209
+ default_choice = "1"
210
+ if has_openai and not has_anthropic:
211
+ default_choice = "2"
212
+
213
+ try:
214
+ choice = Prompt.ask(
215
+ "Choose your model provider",
216
+ choices=["1", "2", "3"],
217
+ default=default_choice,
218
+ console=console,
219
+ )
220
+
221
+ if choice == "1":
222
+ provider = _PROVIDER_ANTHROPIC
223
+ api_key_ref, host = _gather_anthropic(console)
224
+ agent_provider = "anthropic"
225
+ elif choice == "2":
226
+ provider = _PROVIDER_OPENAI
227
+ api_key_ref, host = _gather_openai(console)
228
+ agent_provider = "openai"
229
+ else:
230
+ provider = _PROVIDER_OPENAI # OpenAI-compatible transport
231
+ api_key_ref, host = _gather_local(console)
232
+ agent_provider = "openai"
233
+
234
+ default_model = constants.RECOMMENDED_MODELS.get(
235
+ _PROVIDER_LOCAL if choice == "3" else provider,
236
+ constants.DEFAULT_AI_MODEL,
237
+ )
238
+ model = Prompt.ask("Model", default=default_model, console=console).strip() or default_model
239
+ except (KeyboardInterrupt, EOFError):
240
+ console.print("\n[yellow]Setup cancelled.[/yellow]")
241
+ return False
242
+ except OnboardingAborted as e:
243
+ console.print(f"[yellow]Setup cancelled: {e}[/yellow]")
244
+ return False
245
+
246
+ _persist(
247
+ provider=provider,
248
+ agent_provider=agent_provider,
249
+ model=model,
250
+ host=host,
251
+ api_key_ref=api_key_ref,
252
+ )
253
+
254
+ label = {"anthropic": "Anthropic", "openai": "OpenAI"}.get(agent_provider, agent_provider)
255
+ if host:
256
+ label = f"{label} @ {host}"
257
+ console.print()
258
+ console.print(
259
+ Panel.fit(
260
+ f"[bold]You're set up.[/bold]\n"
261
+ f"Model: [cyan]{model}[/cyan]\n"
262
+ f"Provider: [cyan]{label}[/cyan]\n\n"
263
+ "Change it later with [bold]cade init[/bold] or edit "
264
+ f"[dim]{get_paths().global_config}[/dim].",
265
+ border_style="green",
266
+ )
267
+ )
268
+ return True
269
+
270
+
271
+ def _persist(
272
+ *,
273
+ provider: str,
274
+ agent_provider: str,
275
+ model: str,
276
+ host: str | None,
277
+ api_key_ref: str | None,
278
+ ) -> None:
279
+ """Write the chosen provider/model to global config + the default agent."""
280
+ from cadecoder.core import agent as agent_mod
281
+ from cadecoder.core.config import (
282
+ ModelConfig,
283
+ _load_global_settings,
284
+ _save_global_settings,
285
+ reset_config,
286
+ )
287
+
288
+ settings = _load_global_settings()
289
+ settings.provider = agent_provider
290
+ settings.model = model
291
+ settings.model_settings = ModelConfig(
292
+ provider=agent_provider, model=model, host=host, api_key=api_key_ref
293
+ )
294
+ _save_global_settings(settings)
295
+
296
+ # Make the active/default agent match the choice so the first session uses
297
+ # it (the agent — not the global config — is the runtime source of truth).
298
+ name = agent_mod.get_active_agent_name() or "default"
299
+ existing = agent_mod.resolve(name)
300
+ if existing is None:
301
+ existing = agent_mod.new_agent(name, description="Default Cade agent", model=model)
302
+ existing.model = model
303
+ existing.provider = agent_provider
304
+ agent_mod.save_agent(existing)
305
+ agent_mod.set_active_agent(existing.name)
306
+
307
+ mark_onboarded()
308
+
309
+ # Apply immediately: refresh config caches and re-register providers so the
310
+ # current process uses the new key/model without a restart.
311
+ reset_config()
312
+ try:
313
+ from cadecoder.providers import initialize_providers
314
+
315
+ initialize_providers()
316
+ except Exception:
317
+ pass
@@ -399,6 +399,9 @@ def get_config() -> AppConfig:
399
399
  def reset_config() -> None:
400
400
  global _config
401
401
  _config = None
402
+ # AppConfig caches its own class-level singleton; drop it too so a fresh
403
+ # get_config() reflects on-disk changes (e.g. after onboarding writes config).
404
+ AppConfig._instance = None
402
405
  _load_global_settings.cache_clear()
403
406
  load_account_config.cache_clear()
404
407
 
@@ -12,6 +12,18 @@ from typing import Final
12
12
 
13
13
  DEFAULT_AI_MODEL: Final[str] = "claude-opus-4-7"
14
14
 
15
+ # Recommended default model per provider. Used by onboarding and provider
16
+ # selection so that, e.g., a user who only configures OpenAI gets a GPT model
17
+ # rather than the Anthropic default (which would have no usable provider).
18
+ RECOMMENDED_MODELS: Final[dict[str, str]] = {
19
+ "anthropic": "claude-opus-4-7",
20
+ "openai": "gpt-5.2",
21
+ "ollama": "qwen3:4b",
22
+ }
23
+
24
+ # Suggested base URL for a local Ollama (OpenAI-compatible) endpoint.
25
+ DEFAULT_OLLAMA_BASE_URL: Final[str] = "http://localhost:11434/v1"
26
+
15
27
  # ------------------------------------------------------------------------------
16
28
  # Project & File Operations
17
29
  # ------------------------------------------------------------------------------
@@ -90,7 +90,8 @@ def get_provider_config_help() -> str:
90
90
  return """
91
91
  No AI provider configured.
92
92
 
93
- Options:
93
+ Run 'cade init' to set up a provider interactively, or set a key:
94
+
94
95
  Remote LLM:
95
96
  export OPENAI_API_KEY="your-key"
96
97
  or
@@ -102,3 +103,35 @@ Options:
102
103
 
103
104
  Or just: --endpoint <url> --model <model>
104
105
  """.strip()
106
+
107
+
108
+ def provider_unavailable_help(model: str) -> str:
109
+ """Return an actionable message when a model's provider isn't configured.
110
+
111
+ Names the specific key the configured model needs, instead of silently
112
+ falling back to a different provider/model (which produced confusing
113
+ "used gpt-* but I set an Anthropic key" reports).
114
+
115
+ Args:
116
+ model: The configured model identifier.
117
+
118
+ Returns:
119
+ A multi-line help string naming the required provider key.
120
+ """
121
+ from cadecoder.core.model_specs import provider_family
122
+
123
+ family = provider_family(model)
124
+ if family == "anthropic":
125
+ need = "It's an Anthropic model — set ANTHROPIC_API_KEY."
126
+ elif family == "openai":
127
+ need = "It's an OpenAI model — set OPENAI_API_KEY."
128
+ else:
129
+ need = (
130
+ "Set OPENAI_API_KEY (with OPENAI_BASE_URL for a local/compatible "
131
+ "endpoint) or ANTHROPIC_API_KEY."
132
+ )
133
+ return (
134
+ f"No API key is configured for the active model '{model}'.\n"
135
+ f"{need}\n"
136
+ "Run 'cade init' to set up (or switch) a provider."
137
+ )
@@ -52,7 +52,10 @@ def resolve(ref: str) -> str:
52
52
  rest = ref[len("keyring://") :]
53
53
  if "/" not in rest:
54
54
  raise SecretError(f"keyring ref must be service/name, got {ref!r}")
55
- service, name = rest.split("/", 1)
55
+ # Split on the LAST slash so service names that themselves contain a
56
+ # slash (e.g. "cade/model") round-trip correctly. Matches the resolver
57
+ # in core.config._secret_get.
58
+ service, name = rest.rsplit("/", 1)
56
59
  value = _keyring_get(service, name)
57
60
  if not value:
58
61
  raise SecretError(f"keyring {service}/{name} not set")
@@ -11,7 +11,7 @@ from cadecoder.ai.prompts import build_system_prompt
11
11
  from cadecoder.core.constants import (
12
12
  MAX_EXECUTION_ITERATIONS,
13
13
  )
14
- from cadecoder.core.errors import get_provider_config_help
14
+ from cadecoder.core.errors import get_provider_config_help, provider_unavailable_help
15
15
  from cadecoder.core.logging import log
16
16
  from cadecoder.core.runtime import get_runtime
17
17
  from cadecoder.core.types import ExecutionEventType, extract_tool_output_content
@@ -176,17 +176,22 @@ class Orchestrator:
176
176
  # user has explicitly configured a temperature (global or per-context).
177
177
  self.temperature = runtime.temperature
178
178
 
179
- # Select provider based on model name if not explicitly provided
179
+ # Select provider based on model name if not explicitly provided.
180
+ # IMPORTANT: do NOT silently fall back to a different default provider
181
+ # when the configured model's provider is unavailable — that produced
182
+ # confusing behavior (e.g. logging a GPT model while the user had set an
183
+ # Anthropic key). Fail loudly with a model-specific message instead.
180
184
  if provider is not None:
181
185
  self.provider = provider
182
186
  else:
183
187
  self.provider = get_provider_for_model(self.default_model)
184
- if not self.provider:
185
- # Fallback to default if model-based selection fails
186
- self.provider = provider_registry.get_default()
187
188
 
188
189
  if not self.provider:
189
- raise ValueError(get_provider_config_help())
190
+ # No provider at all → generic setup help; otherwise name the key the
191
+ # configured model needs.
192
+ if not provider_registry.available_providers:
193
+ raise ValueError(get_provider_config_help())
194
+ raise ValueError(provider_unavailable_help(self.default_model))
190
195
 
191
196
  self.tool_manager = tool_manager
192
197
  self._tools_cache: list[dict[str, Any]] | None = None