cade-cli 0.15.10__tar.gz → 0.16.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.15.10 → cade_cli-0.16.0}/.gitignore +3 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/PKG-INFO +19 -18
- {cade_cli-0.15.10 → cade_cli-0.16.0}/README.md +15 -16
- {cade_cli-0.15.10 → cade_cli-0.16.0}/pyproject.toml +5 -2
- cade_cli-0.16.0/src/cade_mcp_local/grants.py +279 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/resources.py +52 -6
- cade_cli-0.16.0/src/cade_mcp_local/sandbox.py +273 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/server.py +9 -1
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/__init__.py +4 -1
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/_read_cache.py +2 -2
- cade_cli-0.16.0/src/cade_mcp_local/tools/access.py +190 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/agents.py +27 -1
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/shell.py +183 -27
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/utils.py +11 -5
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/ai/prompts.py +1 -1
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/app.py +47 -5
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/agents.py +7 -6
- cade_cli-0.16.0/src/cadecoder/cli/commands/daemon.py +413 -0
- cade_cli-0.16.0/src/cadecoder/cli/commands/grant.py +121 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/tools.py +13 -2
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/agent.py +16 -1
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/config.py +1 -1
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/paths.py +20 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/runtime.py +5 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/secrets.py +35 -1
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/spawn.py +17 -0
- cade_cli-0.16.0/src/cadecoder/daemon/__init__.py +9 -0
- {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/adapters/__init__.py +2 -3
- cade_cli-0.16.0/src/cadecoder/daemon/adapters/factory.py +34 -0
- {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/adapters/imessage.py +7 -7
- {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/adapters/registry.py +2 -2
- {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/adapters/stub.py +1 -1
- {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/adapters/telegram.py +6 -6
- {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/allowlist.py +32 -3
- cade_cli-0.16.0/src/cadecoder/daemon/attach.py +115 -0
- cade_cli-0.16.0/src/cadecoder/daemon/client.py +206 -0
- {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/config.py +16 -12
- cade_cli-0.16.0/src/cadecoder/daemon/control.py +224 -0
- cade_cli-0.16.0/src/cadecoder/daemon/cron.py +94 -0
- {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/elicitation.py +2 -2
- cade_cli-0.16.0/src/cadecoder/daemon/ensure.py +123 -0
- cade_cli-0.16.0/src/cadecoder/daemon/gateway/__init__.py +10 -0
- cade_cli-0.16.0/src/cadecoder/daemon/gateway/app.py +180 -0
- cade_cli-0.16.0/src/cadecoder/daemon/gateway/arcade_export.py +31 -0
- cade_cli-0.16.0/src/cadecoder/daemon/gateway/auth.py +106 -0
- cade_cli-0.16.0/src/cadecoder/daemon/gateway/constants.py +24 -0
- cade_cli-0.16.0/src/cadecoder/daemon/gateway/elicitation.py +104 -0
- cade_cli-0.16.0/src/cadecoder/daemon/gateway/exposure.py +98 -0
- cade_cli-0.16.0/src/cadecoder/daemon/gateway/limits.py +61 -0
- cade_cli-0.16.0/src/cadecoder/daemon/gateway/middleware.py +204 -0
- cade_cli-0.16.0/src/cadecoder/daemon/gateway/resources.py +129 -0
- cade_cli-0.16.0/src/cadecoder/daemon/gateway/subscriptions.py +224 -0
- cade_cli-0.16.0/src/cadecoder/daemon/gateway/turn_bridge.py +166 -0
- cade_cli-0.16.0/src/cadecoder/daemon/grants.py +122 -0
- cade_cli-0.15.10/src/cadecoder/serve/cron.py → cade_cli-0.16.0/src/cadecoder/daemon/inbox_cron.py +1 -2
- {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/install.py +36 -55
- cade_cli-0.16.0/src/cadecoder/daemon/live.py +23 -0
- {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/lock.py +2 -2
- cade_cli-0.16.0/src/cadecoder/daemon/mesh.py +105 -0
- cade_cli-0.16.0/src/cadecoder/daemon/message_pump.py +254 -0
- {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/observability.py +6 -6
- {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/owner.py +3 -6
- {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/progress_sink.py +2 -2
- cade_cli-0.16.0/src/cadecoder/daemon/registry.py +43 -0
- {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/router.py +10 -3
- cade_cli-0.16.0/src/cadecoder/daemon/runner.py +243 -0
- cade_cli-0.16.0/src/cadecoder/daemon/runner_main.py +65 -0
- cade_cli-0.16.0/src/cadecoder/daemon/secrets_broker.py +120 -0
- cade_cli-0.16.0/src/cadecoder/daemon/server.py +374 -0
- {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/services.py +1 -1
- cade_cli-0.16.0/src/cadecoder/daemon/shim.py +186 -0
- {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/sinks.py +3 -3
- cade_cli-0.16.0/src/cadecoder/daemon/supervisor.py +220 -0
- cade_cli-0.16.0/src/cadecoder/daemon/tokens.py +170 -0
- {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/traffic.py +3 -3
- cade_cli-0.16.0/src/cadecoder/daemon/turns.py +285 -0
- cade_cli-0.16.0/src/cadecoder/daemon/wake.py +166 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/execution/orchestrator.py +1 -1
- cade_cli-0.16.0/src/cadecoder/serve/__init__.py +23 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/storage/threads.py +95 -17
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tasks/__init__.py +0 -2
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tasks/scheduler.py +5 -21
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/templates/serve/launchd.plist.tmpl +3 -2
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/templates/serve/serve.toml.example +21 -28
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/templates/serve/systemd.service.tmpl +2 -2
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tools/manager/_request_ctx.py +1 -1
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tools/manager/composite.py +7 -1
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/ui/elicitation.py +1 -1
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/ui/session.py +104 -18
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/voice/session.py +17 -0
- cade_cli-0.15.10/src/cadecoder/cli/commands/serve.py +0 -437
- cade_cli-0.15.10/src/cadecoder/serve/__init__.py +0 -11
- cade_cli-0.15.10/src/cadecoder/serve/adapters/desktop.py +0 -408
- cade_cli-0.15.10/src/cadecoder/serve/daemon.py +0 -682
- cade_cli-0.15.10/src/cadecoder/serve/worker.py +0 -682
- cade_cli-0.15.10/src/cadecoder/tasks/lock.py +0 -124
- {cade_cli-0.15.10 → cade_cli-0.16.0}/LICENSE +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/__init__.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/__main__.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/_metadata.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/config.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/errors.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/context.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/filesystem.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/git.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/notifications.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/questions.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/results.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/schemas.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/search.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/snippets.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/tasks.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/web_fetch.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/tools/web_search.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/__init__.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/arcade_client.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/cache.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/extract.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/markdown.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/preapproved.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/prompts.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/scrape.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/search/__init__.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/search/arcade.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/search/base.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cade_mcp_local/web/search/http.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/__init__.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/ai/__init__.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/__init__.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/auth.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/__init__.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/account.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/auth.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/chat.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/cron.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/hooks.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/mcp.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/mem.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/tasks.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/commands/thread.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/cli/onboarding.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/__init__.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/comms.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/constants.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/errors.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/frozen.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/git.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/inbox.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/logging.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/memory.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/model_specs.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/names.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/core/types.py +0 -0
- {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/adapters/base.py +0 -0
- {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/chunker.py +0 -0
- {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/commands.py +0 -0
- {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/format.py +0 -0
- {cade_cli-0.15.10/src/cadecoder/serve → cade_cli-0.16.0/src/cadecoder/daemon}/secrets.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/execution/__init__.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/execution/context_window.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/execution/mcp_schema_index.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/execution/parallel.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/execution/tool_result_store.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/execution/tool_schema_cache.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/__init__.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/config.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/engine.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/executors/__init__.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/executors/callback.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/executors/command.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/executors/http.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/executors/prompt.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/executors/ssrf_guard.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/matchers.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/registry.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/hooks/types.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/providers/__init__.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/providers/anthropic.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/providers/base.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/providers/ollama.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/providers/openai.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/storage/__init__.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tasks/store.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tasks/types.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/templates/login_failed.html +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/templates/login_success.html +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/templates/styles.css +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tools/__init__.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tools/manager/__init__.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tools/manager/base.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tools/manager/config.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tools/manager/mcp.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tools/search/__init__.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tools/search/discovered.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tools/search/scoring.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/tools/search/service.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/ui/__init__.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/ui/completion.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/ui/display.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/ui/input.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/ui/slash.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/voice/__init__.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/voice/audio.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/voice/cleanup.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.0}/src/cadecoder/voice/stt.py +0 -0
- {cade_cli-0.15.10 → cade_cli-0.16.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.16.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
|
|
@@ -63,7 +63,6 @@ Requires-Dist: rich<14.0.0,>=13.0.0
|
|
|
63
63
|
Requires-Dist: toml<1.0.0,>=0.10.0
|
|
64
64
|
Requires-Dist: typer>0.10.0
|
|
65
65
|
Requires-Dist: ulid==1.1
|
|
66
|
-
Requires-Dist: websockets<16.0,>=14.0
|
|
67
66
|
Provides-Extra: build
|
|
68
67
|
Requires-Dist: pyinstaller<7.0.0,>=6.16.0; extra == 'build'
|
|
69
68
|
Provides-Extra: dev
|
|
@@ -73,6 +72,9 @@ Requires-Dist: pytest-cov<5.0.0,>=4.0.0; extra == 'dev'
|
|
|
73
72
|
Requires-Dist: pytest-mock<4.0.0,>=3.11.0; extra == 'dev'
|
|
74
73
|
Requires-Dist: pytest<9.0.0,>=8.0.0; extra == 'dev'
|
|
75
74
|
Requires-Dist: ruff<1.0.0,>=0.5.0; extra == 'dev'
|
|
75
|
+
Provides-Extra: docs
|
|
76
|
+
Requires-Dist: mkdocs-material<10.0.0,>=9.5.0; extra == 'docs'
|
|
77
|
+
Requires-Dist: mkdocs<2.0.0,>=1.6.0; extra == 'docs'
|
|
76
78
|
Provides-Extra: training
|
|
77
79
|
Requires-Dist: accelerate>=0.27.0; extra == 'training'
|
|
78
80
|
Requires-Dist: safetensors>=0.4.2; extra == 'training'
|
|
@@ -100,7 +102,7 @@ cade
|
|
|
100
102
|
|
|
101
103
|
## Highlights
|
|
102
104
|
|
|
103
|
-
- **Three deployment modes from the same binary.** Interactive chat (`cade`), one-shot or pipe (`cade -m "…"`), or a headless daemon that
|
|
105
|
+
- **Three deployment modes from the same binary.** Interactive chat (`cade`), one-shot or pipe (`cade -m "…"`), or a headless daemon (`cade daemon`) that serves MCP hosts and messaging.
|
|
104
106
|
- **Tools from anywhere.** Built-in local tools (filesystem / shell / git / search / tasks / memory), [Arcade Cloud](https://arcade.dev) tools (Slack, GitHub, Gmail, calendars, …), and any [MCP](https://modelcontextprotocol.io/) server you register — all unified under one tool surface.
|
|
105
107
|
- **Bidirectional MCP.** Tools can call `context.ui.elicit(…)` for structured input, `context.log.*` for live operator logs, and `context.progress.report(…)` for editable progress UI — routed end-to-end through whichever adapter is active (Telegram inline keyboard, terminal prompt).
|
|
106
108
|
- **Agents + per-agent memory.** Multiple agents, each with their own model, system prompt, tools, and an indexed memory store backed by [agent-library](https://pypi.org/project/agent-library/).
|
|
@@ -202,7 +204,7 @@ cade tools search "send slack" # keyword search
|
|
|
202
204
|
cade tools info Local_ReadFile # full schema + description
|
|
203
205
|
```
|
|
204
206
|
|
|
205
|
-
The full local tool surface (including the bidirectional MCP capabilities and `cade
|
|
207
|
+
The full local tool surface (including the bidirectional MCP capabilities and `cade daemon` integration) is documented in [`docs/mcp.md`](./docs/mcp.md).
|
|
206
208
|
|
|
207
209
|
### Custom MCP servers
|
|
208
210
|
|
|
@@ -243,24 +245,22 @@ warmup before a conversation.
|
|
|
243
245
|
|
|
244
246
|
---
|
|
245
247
|
|
|
246
|
-
## Headless daemon: `cade
|
|
248
|
+
## Headless daemon: `cade daemon`
|
|
247
249
|
|
|
248
|
-
|
|
250
|
+
One long-lived supervisor serves **all** your agents to MCP hosts (Claude Desktop, Cursor) and messaging adapters (Telegram, iMessage). Each conversation gets its own persistent thread — model, memory, and tools carry across messages. See [`docs/daemon.md`](./docs/daemon.md).
|
|
249
251
|
|
|
250
252
|
```bash
|
|
251
|
-
cade
|
|
252
|
-
cade
|
|
253
|
-
cade
|
|
254
|
-
cade
|
|
255
|
-
cade
|
|
256
|
-
cade
|
|
253
|
+
cade daemon init # mint a token + optional Telegram/iMessage wizard
|
|
254
|
+
cade daemon start # foreground (gateway + messaging)
|
|
255
|
+
cade daemon start -v # also print message traffic
|
|
256
|
+
cade daemon install launchd # macOS LaunchAgent (or: systemd)
|
|
257
|
+
cade daemon status / stop # health; SIGTERM
|
|
258
|
+
cade daemon token new cursor -e delegate -e resources
|
|
257
259
|
```
|
|
258
260
|
|
|
259
|
-
|
|
261
|
+
Adapter + tool config lives in `~/.cade/config/agents/<name>.toml`. Edit and `kill -HUP $(cat ~/.cade/run/daemon.pid)` to reload without restart.
|
|
260
262
|
|
|
261
|
-
**Security defaults are fail-closed:** `allowed_senders = []` blocks everyone; tools are default-deny against `[
|
|
262
|
-
|
|
263
|
-
The MCP transport is fully bidirectional, so tools running inside the daemon can elicit structured input, stream live progress, and emit logs through whichever adapter is active. See [`docs/adapters/`](./docs/adapters/) for adapter details, [`docs/mcp.md`](./docs/mcp.md) for protocol details, and [`docs/configuration.md`](./docs/configuration.md) for the full daemon config reference.
|
|
263
|
+
**Security defaults are fail-closed:** `allowed_senders = []` blocks everyone on messengers; MCP hosts authenticate with bearer tokens; tools are default-deny against `[tools].allow`.
|
|
264
264
|
|
|
265
265
|
---
|
|
266
266
|
|
|
@@ -313,7 +313,8 @@ All state lives under `~/.cade/`. Override the location with `CADE_HOME=/path/to
|
|
|
313
313
|
| `config/cade.toml` | Global settings + `[agent_defaults]` (model, provider, UI, …) |
|
|
314
314
|
| `config/agents/<name>.toml` | Per-agent config (model, prompt, `[[mcp]]`, `[serve]`, hooks) |
|
|
315
315
|
| `data/agents/<id>/` | Per-agent state: `threads/ memory/ tasks/ inbox/` |
|
|
316
|
-
| `run/
|
|
316
|
+
| `run/daemon.pid` | Single gateway-supervisor pidfile |
|
|
317
|
+
| `run/agents/<name>/` | Per-agent runtime: mcp-services, grants |
|
|
317
318
|
| `logs/cade.log` | Rotating log file |
|
|
318
319
|
|
|
319
320
|
Full layout, env vars, and TOML schema in [`docs/configuration.md`](./docs/configuration.md).
|
|
@@ -352,7 +353,7 @@ welcome — open an issue first for anything substantial.
|
|
|
352
353
|
|
|
353
354
|
- [arcade.dev](https://arcade.dev) — product
|
|
354
355
|
- [docs.arcade.dev](https://docs.arcade.dev) — API and tool catalog
|
|
355
|
-
- [`docs/`](./docs/) — quickstart, examples, install, config, MCP, channels, voice, development, and adapters
|
|
356
|
+
- [`docs/`](./docs/) — quickstart, examples, install, config, MCP, agents, automation, channels, voice, development, and adapters. Browse as a site with `make docs` (MkDocs Material)
|
|
356
357
|
- [Issues](https://github.com/arcadeai-labs/cade/issues) · [Releases](https://github.com/arcadeai-labs/cade/releases)
|
|
357
358
|
|
|
358
359
|
## License
|
|
@@ -12,7 +12,7 @@ cade
|
|
|
12
12
|
|
|
13
13
|
## Highlights
|
|
14
14
|
|
|
15
|
-
- **Three deployment modes from the same binary.** Interactive chat (`cade`), one-shot or pipe (`cade -m "…"`), or a headless daemon that
|
|
15
|
+
- **Three deployment modes from the same binary.** Interactive chat (`cade`), one-shot or pipe (`cade -m "…"`), or a headless daemon (`cade daemon`) that serves MCP hosts and messaging.
|
|
16
16
|
- **Tools from anywhere.** Built-in local tools (filesystem / shell / git / search / tasks / memory), [Arcade Cloud](https://arcade.dev) tools (Slack, GitHub, Gmail, calendars, …), and any [MCP](https://modelcontextprotocol.io/) server you register — all unified under one tool surface.
|
|
17
17
|
- **Bidirectional MCP.** Tools can call `context.ui.elicit(…)` for structured input, `context.log.*` for live operator logs, and `context.progress.report(…)` for editable progress UI — routed end-to-end through whichever adapter is active (Telegram inline keyboard, terminal prompt).
|
|
18
18
|
- **Agents + per-agent memory.** Multiple agents, each with their own model, system prompt, tools, and an indexed memory store backed by [agent-library](https://pypi.org/project/agent-library/).
|
|
@@ -114,7 +114,7 @@ cade tools search "send slack" # keyword search
|
|
|
114
114
|
cade tools info Local_ReadFile # full schema + description
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
-
The full local tool surface (including the bidirectional MCP capabilities and `cade
|
|
117
|
+
The full local tool surface (including the bidirectional MCP capabilities and `cade daemon` integration) is documented in [`docs/mcp.md`](./docs/mcp.md).
|
|
118
118
|
|
|
119
119
|
### Custom MCP servers
|
|
120
120
|
|
|
@@ -155,24 +155,22 @@ warmup before a conversation.
|
|
|
155
155
|
|
|
156
156
|
---
|
|
157
157
|
|
|
158
|
-
## Headless daemon: `cade
|
|
158
|
+
## Headless daemon: `cade daemon`
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
One long-lived supervisor serves **all** your agents to MCP hosts (Claude Desktop, Cursor) and messaging adapters (Telegram, iMessage). Each conversation gets its own persistent thread — model, memory, and tools carry across messages. See [`docs/daemon.md`](./docs/daemon.md).
|
|
161
161
|
|
|
162
162
|
```bash
|
|
163
|
-
cade
|
|
164
|
-
cade
|
|
165
|
-
cade
|
|
166
|
-
cade
|
|
167
|
-
cade
|
|
168
|
-
cade
|
|
163
|
+
cade daemon init # mint a token + optional Telegram/iMessage wizard
|
|
164
|
+
cade daemon start # foreground (gateway + messaging)
|
|
165
|
+
cade daemon start -v # also print message traffic
|
|
166
|
+
cade daemon install launchd # macOS LaunchAgent (or: systemd)
|
|
167
|
+
cade daemon status / stop # health; SIGTERM
|
|
168
|
+
cade daemon token new cursor -e delegate -e resources
|
|
169
169
|
```
|
|
170
170
|
|
|
171
|
-
|
|
171
|
+
Adapter + tool config lives in `~/.cade/config/agents/<name>.toml`. Edit and `kill -HUP $(cat ~/.cade/run/daemon.pid)` to reload without restart.
|
|
172
172
|
|
|
173
|
-
**Security defaults are fail-closed:** `allowed_senders = []` blocks everyone; tools are default-deny against `[
|
|
174
|
-
|
|
175
|
-
The MCP transport is fully bidirectional, so tools running inside the daemon can elicit structured input, stream live progress, and emit logs through whichever adapter is active. See [`docs/adapters/`](./docs/adapters/) for adapter details, [`docs/mcp.md`](./docs/mcp.md) for protocol details, and [`docs/configuration.md`](./docs/configuration.md) for the full daemon config reference.
|
|
173
|
+
**Security defaults are fail-closed:** `allowed_senders = []` blocks everyone on messengers; MCP hosts authenticate with bearer tokens; tools are default-deny against `[tools].allow`.
|
|
176
174
|
|
|
177
175
|
---
|
|
178
176
|
|
|
@@ -225,7 +223,8 @@ All state lives under `~/.cade/`. Override the location with `CADE_HOME=/path/to
|
|
|
225
223
|
| `config/cade.toml` | Global settings + `[agent_defaults]` (model, provider, UI, …) |
|
|
226
224
|
| `config/agents/<name>.toml` | Per-agent config (model, prompt, `[[mcp]]`, `[serve]`, hooks) |
|
|
227
225
|
| `data/agents/<id>/` | Per-agent state: `threads/ memory/ tasks/ inbox/` |
|
|
228
|
-
| `run/
|
|
226
|
+
| `run/daemon.pid` | Single gateway-supervisor pidfile |
|
|
227
|
+
| `run/agents/<name>/` | Per-agent runtime: mcp-services, grants |
|
|
229
228
|
| `logs/cade.log` | Rotating log file |
|
|
230
229
|
|
|
231
230
|
Full layout, env vars, and TOML schema in [`docs/configuration.md`](./docs/configuration.md).
|
|
@@ -264,7 +263,7 @@ welcome — open an issue first for anything substantial.
|
|
|
264
263
|
|
|
265
264
|
- [arcade.dev](https://arcade.dev) — product
|
|
266
265
|
- [docs.arcade.dev](https://docs.arcade.dev) — API and tool catalog
|
|
267
|
-
- [`docs/`](./docs/) — quickstart, examples, install, config, MCP, channels, voice, development, and adapters
|
|
266
|
+
- [`docs/`](./docs/) — quickstart, examples, install, config, MCP, agents, automation, channels, voice, development, and adapters. Browse as a site with `make docs` (MkDocs Material)
|
|
268
267
|
- [Issues](https://github.com/arcadeai-labs/cade/issues) · [Releases](https://github.com/arcadeai-labs/cade/releases)
|
|
269
268
|
|
|
270
269
|
## License
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cade-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.16.0"
|
|
4
4
|
description = "Cade - The CLI Agent from Arcade.dev"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -43,7 +43,6 @@ dependencies = [
|
|
|
43
43
|
"python-telegram-bot>=21.0,<22.0",
|
|
44
44
|
"keyring>=24.0,<26.0",
|
|
45
45
|
"html2text>=2024.2.26",
|
|
46
|
-
"websockets>=14.0,<16.0",
|
|
47
46
|
]
|
|
48
47
|
|
|
49
48
|
[project.urls]
|
|
@@ -69,6 +68,10 @@ dev = [
|
|
|
69
68
|
build = [
|
|
70
69
|
"pyinstaller>=6.16.0,<7.0.0",
|
|
71
70
|
]
|
|
71
|
+
docs = [
|
|
72
|
+
"mkdocs>=1.6.0,<2.0.0",
|
|
73
|
+
"mkdocs-material>=9.5.0,<10.0.0",
|
|
74
|
+
]
|
|
72
75
|
voice = [
|
|
73
76
|
"mlx-audio>=0.2.0,<0.4.0",
|
|
74
77
|
"mlx-whisper>=0.1.0",
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""Live filesystem GrantSet shared across processes.
|
|
2
|
+
|
|
3
|
+
The GrantSet is the single source of truth for which directories the agent's
|
|
4
|
+
tools may touch, split into three scopes:
|
|
5
|
+
|
|
6
|
+
* **scratch** — per-agent scratch directory, always granted, writable, and
|
|
7
|
+
identical in every scope (a script written there under ``ScratchBash`` runs
|
|
8
|
+
unchanged under workspace ``Bash``);
|
|
9
|
+
* **workspace** — the agent's working directory (``CADE_PROJECT_ROOT``
|
|
10
|
+
today);
|
|
11
|
+
* **grants** — additional directories, each read-only or read-write, added at
|
|
12
|
+
startup from config or at runtime with operator consent.
|
|
13
|
+
|
|
14
|
+
Because the local-tools server may run in a separate process from the daemon
|
|
15
|
+
or CLI that mutates grants, the GrantSet is published as a JSON file whose
|
|
16
|
+
path travels in the ``CADE_GRANTS_FILE`` environment variable. Readers cache
|
|
17
|
+
by mtime, so a runtime grant is visible to the next tool call with no
|
|
18
|
+
restarts. When no grants file exists the GrantSet degrades to exactly the
|
|
19
|
+
pre-GrantSet behavior: workspace = project root, no extra grants.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import logging
|
|
26
|
+
import os
|
|
27
|
+
import pathlib
|
|
28
|
+
import tempfile
|
|
29
|
+
from dataclasses import dataclass, field
|
|
30
|
+
|
|
31
|
+
from cade_mcp_local.config import get_project_root
|
|
32
|
+
|
|
33
|
+
log = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
GRANTS_FILE_ENV = "CADE_GRANTS_FILE"
|
|
36
|
+
SCRATCH_DIR_ENV = "CADE_SCRATCH_DIR"
|
|
37
|
+
|
|
38
|
+
MODE_RO = "ro"
|
|
39
|
+
MODE_RW = "rw"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class Grant:
|
|
44
|
+
"""One granted directory and its access mode."""
|
|
45
|
+
|
|
46
|
+
path: pathlib.Path
|
|
47
|
+
mode: str = MODE_RW # "ro" | "rw"
|
|
48
|
+
|
|
49
|
+
def to_json(self) -> dict[str, str]:
|
|
50
|
+
return {"path": str(self.path), "mode": self.mode}
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def from_json(cls, data: dict[str, object]) -> Grant | None:
|
|
54
|
+
raw_path = data.get("path")
|
|
55
|
+
mode = data.get("mode", MODE_RW)
|
|
56
|
+
if not isinstance(raw_path, str) or mode not in (MODE_RO, MODE_RW):
|
|
57
|
+
return None
|
|
58
|
+
try:
|
|
59
|
+
return cls(path=pathlib.Path(raw_path).expanduser().resolve(), mode=str(mode))
|
|
60
|
+
except OSError:
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass(frozen=True)
|
|
65
|
+
class GrantSet:
|
|
66
|
+
"""The agent's live filesystem scopes."""
|
|
67
|
+
|
|
68
|
+
scratch: pathlib.Path
|
|
69
|
+
workspace: pathlib.Path
|
|
70
|
+
grants: tuple[Grant, ...] = field(default_factory=tuple)
|
|
71
|
+
|
|
72
|
+
# ------------------------------------------------------------- queries
|
|
73
|
+
|
|
74
|
+
def roots(self, *, scope: str = "workspace") -> list[Grant]:
|
|
75
|
+
"""Return the accessible roots for a scope, scratch always included.
|
|
76
|
+
|
|
77
|
+
``scope="scratch"`` → scratch only. ``scope="workspace"`` → scratch +
|
|
78
|
+
workspace + grants.
|
|
79
|
+
"""
|
|
80
|
+
scratch_grant = Grant(path=self.scratch, mode=MODE_RW)
|
|
81
|
+
if scope == "scratch":
|
|
82
|
+
return [scratch_grant]
|
|
83
|
+
out = [scratch_grant, Grant(path=self.workspace, mode=MODE_RW)]
|
|
84
|
+
out.extend(self.grants)
|
|
85
|
+
return out
|
|
86
|
+
|
|
87
|
+
def contains(self, path: pathlib.Path, *, write: bool = False) -> bool:
|
|
88
|
+
"""True when ``path`` falls inside an accessible root (rw if writing)."""
|
|
89
|
+
try:
|
|
90
|
+
resolved = path.resolve()
|
|
91
|
+
except OSError:
|
|
92
|
+
return False
|
|
93
|
+
for grant in self.roots():
|
|
94
|
+
if write and grant.mode != MODE_RW:
|
|
95
|
+
continue
|
|
96
|
+
try:
|
|
97
|
+
resolved.relative_to(grant.path)
|
|
98
|
+
return True
|
|
99
|
+
except ValueError:
|
|
100
|
+
continue
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
def describe(self) -> str:
|
|
104
|
+
"""Human-readable scope summary for tool descriptions and prompts."""
|
|
105
|
+
parts = [f"{self.workspace} (rw)"]
|
|
106
|
+
parts.extend(f"{g.path} ({g.mode})" for g in self.grants)
|
|
107
|
+
parts.append(f"scratch: {self.scratch}")
|
|
108
|
+
return ", ".join(parts)
|
|
109
|
+
|
|
110
|
+
# -------------------------------------------------------------- persist
|
|
111
|
+
|
|
112
|
+
def to_json(self) -> dict[str, object]:
|
|
113
|
+
return {
|
|
114
|
+
"scratch": str(self.scratch),
|
|
115
|
+
"workspace": str(self.workspace),
|
|
116
|
+
"grants": [g.to_json() for g in self.grants],
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
def save(self, path: pathlib.Path) -> None:
|
|
120
|
+
"""Atomically write the grants file (rename over temp)."""
|
|
121
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
122
|
+
handle, tmp_name = tempfile.mkstemp(dir=path.parent, prefix=".grants-")
|
|
123
|
+
try:
|
|
124
|
+
with os.fdopen(handle, "w", encoding="utf-8") as fh:
|
|
125
|
+
json.dump(self.to_json(), fh)
|
|
126
|
+
os.replace(tmp_name, path)
|
|
127
|
+
except OSError:
|
|
128
|
+
try:
|
|
129
|
+
os.unlink(tmp_name)
|
|
130
|
+
except OSError:
|
|
131
|
+
pass
|
|
132
|
+
raise
|
|
133
|
+
_load_grants_file.cache_bust()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def default_scratch_dir() -> pathlib.Path:
|
|
137
|
+
"""Resolve the scratch directory: env override → per-user temp fallback."""
|
|
138
|
+
override = os.environ.get(SCRATCH_DIR_ENV, "").strip()
|
|
139
|
+
if override:
|
|
140
|
+
return pathlib.Path(override).expanduser().resolve()
|
|
141
|
+
base = pathlib.Path(tempfile.gettempdir()) / f"cade-scratch-{os.getuid()}"
|
|
142
|
+
return base.resolve()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def default_grant_set() -> GrantSet:
|
|
146
|
+
"""Pre-GrantSet behavior: workspace = project root, no extra grants."""
|
|
147
|
+
return GrantSet(scratch=default_scratch_dir(), workspace=get_project_root())
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class _GrantsFileLoader:
|
|
151
|
+
"""mtime-cached loader so runtime grants are cheap to read per call."""
|
|
152
|
+
|
|
153
|
+
def __init__(self) -> None:
|
|
154
|
+
self._cached: GrantSet | None = None
|
|
155
|
+
self._cached_key: tuple[str, float] | None = None
|
|
156
|
+
|
|
157
|
+
def __call__(self) -> GrantSet:
|
|
158
|
+
raw_path = os.environ.get(GRANTS_FILE_ENV, "").strip()
|
|
159
|
+
if not raw_path:
|
|
160
|
+
return default_grant_set()
|
|
161
|
+
path = pathlib.Path(raw_path)
|
|
162
|
+
try:
|
|
163
|
+
mtime = path.stat().st_mtime
|
|
164
|
+
except OSError:
|
|
165
|
+
return default_grant_set()
|
|
166
|
+
key = (raw_path, mtime)
|
|
167
|
+
if self._cached is not None and self._cached_key == key:
|
|
168
|
+
return self._cached
|
|
169
|
+
grant_set = self._parse(path)
|
|
170
|
+
self._cached, self._cached_key = grant_set, key
|
|
171
|
+
return grant_set
|
|
172
|
+
|
|
173
|
+
def cache_bust(self) -> None:
|
|
174
|
+
self._cached = None
|
|
175
|
+
self._cached_key = None
|
|
176
|
+
|
|
177
|
+
@staticmethod
|
|
178
|
+
def _parse(path: pathlib.Path) -> GrantSet:
|
|
179
|
+
try:
|
|
180
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
181
|
+
except (OSError, json.JSONDecodeError) as e:
|
|
182
|
+
log.warning("unreadable grants file at %s: %s; using defaults", path, e)
|
|
183
|
+
return default_grant_set()
|
|
184
|
+
if not isinstance(data, dict):
|
|
185
|
+
return default_grant_set()
|
|
186
|
+
try:
|
|
187
|
+
scratch = pathlib.Path(str(data.get("scratch") or default_scratch_dir()))
|
|
188
|
+
workspace = pathlib.Path(str(data.get("workspace") or get_project_root()))
|
|
189
|
+
except OSError:
|
|
190
|
+
return default_grant_set()
|
|
191
|
+
grants: list[Grant] = []
|
|
192
|
+
raw_grants = data.get("grants")
|
|
193
|
+
if isinstance(raw_grants, list):
|
|
194
|
+
for entry in raw_grants:
|
|
195
|
+
if isinstance(entry, dict):
|
|
196
|
+
grant = Grant.from_json(entry)
|
|
197
|
+
if grant is not None:
|
|
198
|
+
grants.append(grant)
|
|
199
|
+
return GrantSet(
|
|
200
|
+
scratch=scratch.expanduser().resolve(),
|
|
201
|
+
workspace=workspace.expanduser().resolve(),
|
|
202
|
+
grants=tuple(grants),
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
_load_grants_file = _GrantsFileLoader()
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def current_grant_set() -> GrantSet:
|
|
210
|
+
"""Return the live GrantSet (grants file when published, defaults otherwise)."""
|
|
211
|
+
return _load_grants_file()
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def ensure_scratch_dir(grant_set: GrantSet | None = None) -> pathlib.Path:
|
|
215
|
+
"""Create the scratch directory if missing; return its path."""
|
|
216
|
+
scratch = (grant_set or current_grant_set()).scratch
|
|
217
|
+
scratch.mkdir(parents=True, exist_ok=True)
|
|
218
|
+
return scratch
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def grants_file_path() -> pathlib.Path | None:
|
|
222
|
+
"""The published grants-file path, or None when no grants file is in use."""
|
|
223
|
+
raw = os.environ.get(GRANTS_FILE_ENV, "").strip()
|
|
224
|
+
return pathlib.Path(raw) if raw else None
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def add_grant(path: str | pathlib.Path, mode: str = MODE_RW) -> GrantSet:
|
|
228
|
+
"""Add (or upgrade the mode of) a runtime grant; persist and return the set.
|
|
229
|
+
|
|
230
|
+
Requires a published grants file (``CADE_GRANTS_FILE``); raises otherwise
|
|
231
|
+
so a caller never believes a grant took effect when it silently didn't.
|
|
232
|
+
Adding an ``rw`` grant for a path already granted ``ro`` upgrades it.
|
|
233
|
+
"""
|
|
234
|
+
target = grants_file_path()
|
|
235
|
+
if target is None:
|
|
236
|
+
raise RuntimeError("no grants file published (CADE_GRANTS_FILE unset)")
|
|
237
|
+
if mode not in (MODE_RO, MODE_RW):
|
|
238
|
+
raise ValueError(f"invalid grant mode: {mode!r}")
|
|
239
|
+
resolved = pathlib.Path(path).expanduser().resolve()
|
|
240
|
+
|
|
241
|
+
current = current_grant_set()
|
|
242
|
+
kept = [g for g in current.grants if g.path != resolved]
|
|
243
|
+
kept.append(Grant(path=resolved, mode=mode))
|
|
244
|
+
updated = GrantSet(scratch=current.scratch, workspace=current.workspace, grants=tuple(kept))
|
|
245
|
+
updated.save(target)
|
|
246
|
+
return updated
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def remove_grant(path: str | pathlib.Path) -> GrantSet:
|
|
250
|
+
"""Remove a runtime grant; persist and return the updated set.
|
|
251
|
+
|
|
252
|
+
Removing a path that isn't granted is a no-op (idempotent revoke).
|
|
253
|
+
"""
|
|
254
|
+
target = grants_file_path()
|
|
255
|
+
if target is None:
|
|
256
|
+
raise RuntimeError("no grants file published (CADE_GRANTS_FILE unset)")
|
|
257
|
+
resolved = pathlib.Path(path).expanduser().resolve()
|
|
258
|
+
current = current_grant_set()
|
|
259
|
+
kept = tuple(g for g in current.grants if g.path != resolved)
|
|
260
|
+
updated = GrantSet(scratch=current.scratch, workspace=current.workspace, grants=kept)
|
|
261
|
+
updated.save(target)
|
|
262
|
+
return updated
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
__all__ = [
|
|
266
|
+
"GRANTS_FILE_ENV",
|
|
267
|
+
"MODE_RO",
|
|
268
|
+
"MODE_RW",
|
|
269
|
+
"SCRATCH_DIR_ENV",
|
|
270
|
+
"Grant",
|
|
271
|
+
"GrantSet",
|
|
272
|
+
"add_grant",
|
|
273
|
+
"current_grant_set",
|
|
274
|
+
"default_grant_set",
|
|
275
|
+
"default_scratch_dir",
|
|
276
|
+
"ensure_scratch_dir",
|
|
277
|
+
"grants_file_path",
|
|
278
|
+
"remove_grant",
|
|
279
|
+
]
|
|
@@ -8,9 +8,9 @@ Two resources today:
|
|
|
8
8
|
- ``cade://memory/sources`` — a JSON listing of the active persona's
|
|
9
9
|
``cade mem`` sources, read from the librarian sources registry.
|
|
10
10
|
|
|
11
|
-
Persona
|
|
12
|
-
``"default"``.
|
|
13
|
-
``CADE_CONTEXT_DATA_DIR``.
|
|
11
|
+
Persona / agent identity prefers ``CADE_AGENT`` (daemon runners) then
|
|
12
|
+
``CADE_PERSONA``, falling back to ``"default"``. Memory paths prefer the
|
|
13
|
+
agent data dir (``CADE_AGENT_DATA_DIR``) then ``CADE_CONTEXT_DATA_DIR``.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
@@ -28,6 +28,7 @@ log = logging.getLogger(__name__)
|
|
|
28
28
|
|
|
29
29
|
INSTRUCTIONS_URI = "cade://server/instructions"
|
|
30
30
|
MEMORY_SOURCES_URI = "cade://memory/sources"
|
|
31
|
+
FILESYSTEM_URI = "cade://filesystem"
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
def _cade_home() -> Path:
|
|
@@ -39,15 +40,23 @@ def _cade_home() -> Path:
|
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
def _resolve_persona() -> str:
|
|
42
|
-
"""Return the active persona name
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
"""Return the active agent/persona name for memory paths."""
|
|
44
|
+
for key in ("CADE_AGENT", "CADE_PERSONA"):
|
|
45
|
+
name = (os.environ.get(key) or "").strip()
|
|
46
|
+
if name:
|
|
47
|
+
return name
|
|
48
|
+
return "default"
|
|
45
49
|
|
|
46
50
|
|
|
47
51
|
def _sources_path(persona: str) -> Path:
|
|
52
|
+
"""Resolve ``sources.json`` under the agent (preferred) or legacy context tree."""
|
|
53
|
+
agent_data = os.environ.get("CADE_AGENT_DATA_DIR")
|
|
54
|
+
if agent_data:
|
|
55
|
+
return Path(agent_data) / "memory" / ".librarian" / "sources.json"
|
|
48
56
|
context_data = os.environ.get("CADE_CONTEXT_DATA_DIR")
|
|
49
57
|
if context_data:
|
|
50
58
|
return Path(context_data) / "memory" / persona / ".librarian" / "sources.json"
|
|
59
|
+
# Legacy contexts/ layout — kept for older installs only.
|
|
51
60
|
context = os.environ.get("CADE_CONTEXT") or "default"
|
|
52
61
|
return (
|
|
53
62
|
_cade_home()
|
|
@@ -95,6 +104,28 @@ def _memory_sources_payload() -> str:
|
|
|
95
104
|
return json.dumps(payload, indent=2, default=str)
|
|
96
105
|
|
|
97
106
|
|
|
107
|
+
def _filesystem_payload() -> str:
|
|
108
|
+
"""Build the JSON body describing the agent's current filesystem scopes."""
|
|
109
|
+
from cade_mcp_local.grants import current_grant_set
|
|
110
|
+
from cade_mcp_local.sandbox import describe_backend, sandbox_available
|
|
111
|
+
|
|
112
|
+
grant_set = current_grant_set()
|
|
113
|
+
payload = {
|
|
114
|
+
"scratch": str(grant_set.scratch),
|
|
115
|
+
"workspace": str(grant_set.workspace),
|
|
116
|
+
"grants": [{"path": str(g.path), "mode": g.mode} for g in grant_set.grants],
|
|
117
|
+
"sandbox_backend": describe_backend(),
|
|
118
|
+
"sandbox_available": sandbox_available(),
|
|
119
|
+
"summary": grant_set.describe(),
|
|
120
|
+
"note": (
|
|
121
|
+
"Local_Bash sees scratch + workspace + grants; Local_ScratchBash "
|
|
122
|
+
"sees only scratch (network off). Request more with "
|
|
123
|
+
"Local_RequestAccess(path, mode, reason)."
|
|
124
|
+
),
|
|
125
|
+
}
|
|
126
|
+
return json.dumps(payload, indent=2, default=str)
|
|
127
|
+
|
|
128
|
+
|
|
98
129
|
def register_resources(app: MCPApp, instructions: str) -> None:
|
|
99
130
|
"""Register the local server's static + dynamic MCP resources."""
|
|
100
131
|
app.add_text_resource(
|
|
@@ -123,8 +154,23 @@ def register_resources(app: MCPApp, instructions: str) -> None:
|
|
|
123
154
|
handler=lambda _uri: _memory_sources_payload(),
|
|
124
155
|
)
|
|
125
156
|
|
|
157
|
+
app.add_resource(
|
|
158
|
+
FILESYSTEM_URI,
|
|
159
|
+
name="filesystem-scopes",
|
|
160
|
+
title="Filesystem scopes (workspace, grants, scratch)",
|
|
161
|
+
description=(
|
|
162
|
+
"JSON description of the directories the agent's tools may touch: "
|
|
163
|
+
"workspace, runtime grants (rw/ro), and the isolated scratch dir, "
|
|
164
|
+
"plus the OS sandbox backend. Read each call so it reflects grants "
|
|
165
|
+
"added at runtime via Local_RequestAccess or the operator."
|
|
166
|
+
),
|
|
167
|
+
mime_type="application/json",
|
|
168
|
+
handler=lambda _uri: _filesystem_payload(),
|
|
169
|
+
)
|
|
170
|
+
|
|
126
171
|
|
|
127
172
|
__all__ = (
|
|
173
|
+
"FILESYSTEM_URI",
|
|
128
174
|
"INSTRUCTIONS_URI",
|
|
129
175
|
"MEMORY_SOURCES_URI",
|
|
130
176
|
"register_resources",
|