cade-cli 0.15.4__tar.gz → 0.15.6__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.15.4 → cade_cli-0.15.6}/PKG-INFO +3 -3
- {cade_cli-0.15.4 → cade_cli-0.15.6}/README.md +1 -1
- {cade_cli-0.15.4 → cade_cli-0.15.6}/pyproject.toml +2 -2
- cade_cli-0.15.6/src/cade_mcp_local/web/extract.py +115 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/cli/commands/mem.py +4 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/ui/display.py +12 -1
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/voice/audio.py +49 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/voice/stt.py +35 -23
- cade_cli-0.15.4/src/cade_mcp_local/web/extract.py +0 -54
- {cade_cli-0.15.4 → cade_cli-0.15.6}/.gitignore +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/LICENSE +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/__init__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/__main__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/_metadata.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/config.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/errors.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/resources.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/server.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/tools/__init__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/tools/_read_cache.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/tools/agents.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/tools/context.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/tools/filesystem.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/tools/git.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/tools/notifications.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/tools/questions.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/tools/results.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/tools/schemas.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/tools/search.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/tools/shell.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/tools/snippets.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/tools/tasks.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/tools/web_fetch.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/tools/web_search.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/utils.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/web/__init__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/web/arcade_client.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/web/cache.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/web/markdown.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/web/preapproved.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/web/prompts.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/web/scrape.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/web/search/__init__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/web/search/arcade.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/web/search/base.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cade_mcp_local/web/search/http.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/__init__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/ai/__init__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/ai/prompts.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/cli/__init__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/cli/app.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/cli/auth.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/cli/commands/__init__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/cli/commands/account.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/cli/commands/agents.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/cli/commands/auth.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/cli/commands/chat.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/cli/commands/cron.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/cli/commands/hooks.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/cli/commands/mcp.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/cli/commands/serve.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/cli/commands/tasks.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/cli/commands/thread.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/cli/commands/tools.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/cli/onboarding.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/core/__init__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/core/agent.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/core/comms.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/core/config.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/core/constants.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/core/errors.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/core/frozen.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/core/git.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/core/inbox.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/core/logging.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/core/memory.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/core/model_specs.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/core/names.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/core/paths.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/core/runtime.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/core/secrets.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/core/spawn.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/core/types.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/execution/__init__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/execution/context_window.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/execution/mcp_schema_index.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/execution/orchestrator.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/execution/parallel.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/execution/tool_result_store.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/execution/tool_schema_cache.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/hooks/__init__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/hooks/config.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/hooks/engine.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/hooks/executors/__init__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/hooks/executors/callback.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/hooks/executors/command.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/hooks/executors/http.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/hooks/executors/prompt.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/hooks/executors/ssrf_guard.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/hooks/matchers.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/hooks/registry.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/hooks/types.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/providers/__init__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/providers/anthropic.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/providers/base.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/providers/ollama.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/providers/openai.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/__init__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/adapters/__init__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/adapters/base.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/adapters/desktop.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/adapters/registry.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/adapters/stub.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/adapters/telegram.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/allowlist.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/chunker.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/commands.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/config.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/cron.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/daemon.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/elicitation.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/format.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/install.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/lock.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/observability.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/owner.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/progress_sink.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/router.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/secrets.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/services.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/sinks.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/serve/worker.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/storage/__init__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/storage/threads.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/tasks/__init__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/tasks/lock.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/tasks/scheduler.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/tasks/store.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/tasks/types.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/templates/login_failed.html +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/templates/login_success.html +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/templates/serve/launchd.plist.tmpl +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/templates/serve/serve.toml.example +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/templates/serve/systemd.service.tmpl +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/templates/styles.css +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/tools/__init__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/tools/manager/__init__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/tools/manager/_request_ctx.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/tools/manager/base.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/tools/manager/composite.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/tools/manager/config.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/tools/manager/mcp.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/tools/search/__init__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/tools/search/discovered.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/tools/search/scoring.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/tools/search/service.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/ui/__init__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/ui/completion.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/ui/elicitation.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/ui/input.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/ui/session.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/ui/slash.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/voice/__init__.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/voice/cleanup.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/src/cadecoder/voice/session.py +0 -0
- {cade_cli-0.15.4 → cade_cli-0.15.6}/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.
|
|
3
|
+
Version: 0.15.6
|
|
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.
|
|
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
|
|
@@ -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.15.
|
|
126
|
+
CADE_VERSION=0.15.5 CADE_INSTALL_DIR=/usr/local/bin \
|
|
127
127
|
sh -c "$(curl -fsSL https://arcadeagent.dev/install)"
|
|
128
128
|
```
|
|
129
129
|
|
|
@@ -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.15.
|
|
38
|
+
CADE_VERSION=0.15.5 CADE_INSTALL_DIR=/usr/local/bin \
|
|
39
39
|
sh -c "$(curl -fsSL https://arcadeagent.dev/install)"
|
|
40
40
|
```
|
|
41
41
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cade-cli"
|
|
3
|
-
version = "0.15.
|
|
3
|
+
version = "0.15.6"
|
|
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.
|
|
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",
|
|
@@ -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
|
|
@@ -43,6 +43,10 @@ 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
|
|
|
@@ -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:
|
|
@@ -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"
|
|
@@ -7,8 +7,6 @@ Models are downloaded from HuggingFace on first use and cached automatically.
|
|
|
7
7
|
import asyncio
|
|
8
8
|
import logging
|
|
9
9
|
import os
|
|
10
|
-
import tempfile
|
|
11
|
-
import wave
|
|
12
10
|
|
|
13
11
|
import numpy as np
|
|
14
12
|
|
|
@@ -17,6 +15,10 @@ from cadecoder.core.errors import VoiceError
|
|
|
17
15
|
from cadecoder.core.logging import log
|
|
18
16
|
from cadecoder.voice.audio import DEFAULT_SAMPLE_RATE
|
|
19
17
|
|
|
18
|
+
# Whisper always operates at 16 kHz. When given a numpy array (rather than a
|
|
19
|
+
# file path) mlx-whisper does NOT resample, so the samples must already be 16 kHz.
|
|
20
|
+
_WHISPER_SAMPLE_RATE = 16_000
|
|
21
|
+
|
|
20
22
|
|
|
21
23
|
def _suppress_hf_logging() -> None:
|
|
22
24
|
"""Suppress noisy HuggingFace Hub download/progress logging.
|
|
@@ -109,17 +111,21 @@ class WhisperSTT:
|
|
|
109
111
|
raise VoiceError("STT model not loaded — call _ensure_loaded() first")
|
|
110
112
|
|
|
111
113
|
try:
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
114
|
+
# Pass the raw float32 samples directly. Writing a temp WAV and
|
|
115
|
+
# passing the *path* makes mlx-whisper shell out to `ffmpeg` to
|
|
116
|
+
# decode it (not bundled in the frozen binary, and often absent on
|
|
117
|
+
# user machines). Whisper accepts a 16 kHz mono float32 array with
|
|
118
|
+
# no external tools — the recorder already produces exactly that.
|
|
119
|
+
samples = np.asarray(audio, dtype=np.float32).flatten()
|
|
120
|
+
if sample_rate != _WHISPER_SAMPLE_RATE:
|
|
121
|
+
samples = _resample(samples, sample_rate, _WHISPER_SAMPLE_RATE)
|
|
122
|
+
|
|
123
|
+
result = self._mlx_whisper.transcribe( # type: ignore[union-attr]
|
|
124
|
+
samples,
|
|
125
|
+
path_or_hf_repo=self.model,
|
|
126
|
+
language=self.language,
|
|
127
|
+
fp16=False,
|
|
128
|
+
)
|
|
123
129
|
|
|
124
130
|
text = result.get("text", "").strip()
|
|
125
131
|
log.debug(f"Transcribed {len(audio) / sample_rate:.1f}s -> '{text[:80]}'")
|
|
@@ -128,13 +134,19 @@ class WhisperSTT:
|
|
|
128
134
|
except Exception as exc:
|
|
129
135
|
raise VoiceError(f"Transcription failed: {exc}") from exc
|
|
130
136
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
|
|
138
|
+
def _resample(samples: np.ndarray, src_rate: int, dst_rate: int) -> np.ndarray:
|
|
139
|
+
"""Linearly resample mono float32 audio to ``dst_rate`` (no external deps).
|
|
140
|
+
|
|
141
|
+
Adequate for speech fed to Whisper. Only used on the rare path where the
|
|
142
|
+
recorder's rate differs from Whisper's 16 kHz.
|
|
143
|
+
"""
|
|
144
|
+
if src_rate == dst_rate or samples.size == 0:
|
|
145
|
+
return samples
|
|
146
|
+
duration = samples.shape[0] / float(src_rate)
|
|
147
|
+
n_out = int(round(duration * dst_rate))
|
|
148
|
+
if n_out <= 0:
|
|
149
|
+
return np.array([], dtype=np.float32)
|
|
150
|
+
x_old = np.linspace(0.0, duration, num=samples.shape[0], endpoint=False)
|
|
151
|
+
x_new = np.linspace(0.0, duration, num=n_out, endpoint=False)
|
|
152
|
+
return np.interp(x_new, x_old, samples).astype(np.float32)
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|