cade-cli 0.14.0__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.
- {cade_cli-0.14.0 → cade_cli-0.15.0}/PKG-INFO +16 -4
- {cade_cli-0.14.0 → cade_cli-0.15.0}/README.md +14 -2
- {cade_cli-0.14.0 → cade_cli-0.15.0}/pyproject.toml +2 -2
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/cli/app.py +27 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/cli/commands/mem.py +30 -0
- cade_cli-0.15.0/src/cadecoder/cli/onboarding.py +317 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/core/config.py +3 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/core/constants.py +12 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/core/errors.py +34 -1
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/core/secrets.py +4 -1
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/execution/orchestrator.py +11 -6
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/providers/__init__.py +51 -9
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/daemon.py +2 -5
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/services.py +27 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/tools/manager/composite.py +274 -3
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/ui/session.py +11 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/.gitignore +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/LICENSE +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/__init__.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/__main__.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/_metadata.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/config.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/errors.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/resources.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/server.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/tools/__init__.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/tools/_read_cache.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/tools/agents.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/tools/context.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/tools/filesystem.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/tools/git.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/tools/notifications.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/tools/questions.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/tools/results.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/tools/schemas.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/tools/search.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/tools/shell.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/tools/snippets.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/tools/tasks.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/tools/web_fetch.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/tools/web_search.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/utils.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/web/__init__.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/web/arcade_client.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/web/cache.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/web/extract.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/web/markdown.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/web/preapproved.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/web/prompts.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/web/scrape.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/web/search/__init__.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/web/search/arcade.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/web/search/base.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cade_mcp_local/web/search/http.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/__init__.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/ai/__init__.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/ai/prompts.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/cli/__init__.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/cli/auth.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/cli/commands/__init__.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/cli/commands/account.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/cli/commands/agents.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/cli/commands/auth.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/cli/commands/chat.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/cli/commands/cron.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/cli/commands/hooks.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/cli/commands/mcp.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/cli/commands/serve.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/cli/commands/tasks.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/cli/commands/thread.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/cli/commands/tools.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/core/__init__.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/core/agent.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/core/comms.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/core/frozen.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/core/git.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/core/inbox.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/core/logging.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/core/memory.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/core/model_specs.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/core/names.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/core/paths.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/core/runtime.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/core/spawn.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/core/types.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/execution/__init__.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/execution/context_window.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/execution/mcp_schema_index.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/execution/parallel.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/execution/tool_result_store.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/execution/tool_schema_cache.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/hooks/__init__.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/hooks/config.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/hooks/engine.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/hooks/executors/__init__.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/hooks/executors/callback.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/hooks/executors/command.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/hooks/executors/http.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/hooks/executors/prompt.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/hooks/executors/ssrf_guard.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/hooks/matchers.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/hooks/registry.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/hooks/types.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/providers/anthropic.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/providers/base.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/providers/ollama.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/providers/openai.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/__init__.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/adapters/__init__.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/adapters/base.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/adapters/desktop.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/adapters/registry.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/adapters/stub.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/adapters/telegram.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/allowlist.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/chunker.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/commands.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/config.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/cron.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/elicitation.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/format.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/install.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/lock.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/observability.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/owner.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/progress_sink.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/router.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/secrets.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/sinks.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/serve/worker.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/storage/__init__.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/storage/threads.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/tasks/__init__.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/tasks/lock.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/tasks/scheduler.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/tasks/store.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/tasks/types.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/templates/login_failed.html +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/templates/login_success.html +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/templates/serve/launchd.plist.tmpl +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/templates/serve/serve.toml.example +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/templates/serve/systemd.service.tmpl +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/templates/styles.css +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/tools/__init__.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/tools/manager/__init__.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/tools/manager/_request_ctx.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/tools/manager/base.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/tools/manager/config.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/tools/manager/mcp.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/tools/search/__init__.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/tools/search/discovered.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/tools/search/scoring.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/tools/search/service.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/ui/__init__.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/ui/completion.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/ui/display.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/ui/elicitation.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/ui/input.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/ui/slash.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/voice/__init__.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/voice/audio.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/voice/cleanup.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/voice/session.py +0 -0
- {cade_cli-0.14.0 → cade_cli-0.15.0}/src/cadecoder/voice/stt.py +0 -0
- {cade_cli-0.14.0 → 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.
|
|
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.
|
|
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,15 +123,23 @@ 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.
|
|
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
|
|
|
130
|
+
On Linux the installer auto-picks a **cpu** or **gpu** build (GPU when
|
|
131
|
+
`nvidia-smi` detects a card; the GPU build bundles the CUDA stack and is a much
|
|
132
|
+
larger download). Force it with `CADE_VARIANT=cpu` or `CADE_VARIANT=gpu`.
|
|
133
|
+
|
|
130
134
|
(Windows: grab the `.zip` from the releases page, or `pip install cade-cli`.)
|
|
131
135
|
|
|
132
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`.
|
|
133
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
|
+
|
|
134
141
|
```bash
|
|
142
|
+
cade init # pick provider + model (runs automatically on first launch)
|
|
135
143
|
cade login # Arcade Cloud OAuth (skip with --local-only)
|
|
136
144
|
cade --version
|
|
137
145
|
```
|
|
@@ -223,11 +231,15 @@ cade info / cade edit reviewer # inspect / change config
|
|
|
223
231
|
cade mem # the current agent's memory (status)
|
|
224
232
|
cade mem add ~/notes # index a directory
|
|
225
233
|
cade mem search "auth design" # semantic search
|
|
234
|
+
cade mem warm # pre-load the semantic memory model
|
|
226
235
|
```
|
|
227
236
|
|
|
228
237
|
Memory is per-agent — each agent has its own
|
|
229
238
|
`~/.cade/data/agents/<id>/memory/.librarian/` index, so a `reviewer` agent's
|
|
230
|
-
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.
|
|
231
243
|
|
|
232
244
|
---
|
|
233
245
|
|
|
@@ -35,15 +35,23 @@ 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.
|
|
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
|
|
|
42
|
+
On Linux the installer auto-picks a **cpu** or **gpu** build (GPU when
|
|
43
|
+
`nvidia-smi` detects a card; the GPU build bundles the CUDA stack and is a much
|
|
44
|
+
larger download). Force it with `CADE_VARIANT=cpu` or `CADE_VARIANT=gpu`.
|
|
45
|
+
|
|
42
46
|
(Windows: grab the `.zip` from the releases page, or `pip install cade-cli`.)
|
|
43
47
|
|
|
44
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`.
|
|
45
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
|
+
|
|
46
53
|
```bash
|
|
54
|
+
cade init # pick provider + model (runs automatically on first launch)
|
|
47
55
|
cade login # Arcade Cloud OAuth (skip with --local-only)
|
|
48
56
|
cade --version
|
|
49
57
|
```
|
|
@@ -135,11 +143,15 @@ cade info / cade edit reviewer # inspect / change config
|
|
|
135
143
|
cade mem # the current agent's memory (status)
|
|
136
144
|
cade mem add ~/notes # index a directory
|
|
137
145
|
cade mem search "auth design" # semantic search
|
|
146
|
+
cade mem warm # pre-load the semantic memory model
|
|
138
147
|
```
|
|
139
148
|
|
|
140
149
|
Memory is per-agent — each agent has its own
|
|
141
150
|
`~/.cade/data/agents/<id>/memory/.librarian/` index, so a `reviewer` agent's
|
|
142
|
-
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.
|
|
143
155
|
|
|
144
156
|
---
|
|
145
157
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cade-cli"
|
|
3
|
-
version = "0.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|