cade-cli 0.13.2__tar.gz → 0.13.4__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.13.2 → cade_cli-0.13.4}/PKG-INFO +1 -1
- {cade_cli-0.13.2 → cade_cli-0.13.4}/pyproject.toml +1 -1
- cade_cli-0.13.4/src/cade_mcp_local/config.py +116 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/tools/filesystem.py +18 -14
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/utils.py +6 -2
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/cli/app.py +28 -1
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/cli/commands/chat.py +26 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/cli/commands/context.py +16 -2
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/cli/commands/cron.py +39 -19
- cade_cli-0.13.4/src/cadecoder/cli/commands/mem.py +176 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/cli/commands/model.py +112 -12
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/cli/commands/persona.py +190 -24
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/cli/commands/tools.py +110 -8
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/core/config.py +35 -10
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/core/constants.py +1 -8
- cade_cli-0.13.4/src/cadecoder/core/model_specs.py +194 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/core/runtime.py +1 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/execution/context_window.py +4 -83
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/execution/orchestrator.py +4 -2
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/providers/__init__.py +7 -2
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/providers/anthropic.py +53 -9
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/providers/base.py +8 -1
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/providers/ollama.py +30 -18
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/providers/openai.py +72 -39
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/serve/allowlist.py +34 -6
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/serve/config.py +28 -2
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/storage/personas.py +27 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/tasks/scheduler.py +90 -4
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/templates/serve/serve.toml.example +1 -1
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/ui/session.py +27 -9
- cade_cli-0.13.2/src/cade_mcp_local/config.py +0 -68
- cade_cli-0.13.2/src/cadecoder/cli/commands/mem.py +0 -149
- {cade_cli-0.13.2 → cade_cli-0.13.4}/.gitignore +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/LICENSE +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/README.md +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/__init__.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/__main__.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/_metadata.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/errors.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/resources.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/server.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/tools/__init__.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/tools/_read_cache.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/tools/context.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/tools/git.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/tools/notifications.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/tools/questions.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/tools/results.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/tools/schemas.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/tools/search.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/tools/shell.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/tools/snippets.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/tools/tasks.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/tools/web_fetch.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/tools/web_search.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/web/__init__.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/web/arcade_client.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/web/cache.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/web/extract.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/web/markdown.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/web/preapproved.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/web/prompts.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/web/scrape.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/web/search/__init__.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/web/search/arcade.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/web/search/base.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cade_mcp_local/web/search/http.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/__init__.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/ai/__init__.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/ai/prompts.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/cli/__init__.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/cli/auth.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/cli/commands/__init__.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/cli/commands/account.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/cli/commands/auth.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/cli/commands/channels.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/cli/commands/hooks.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/cli/commands/mcp.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/cli/commands/serve.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/cli/commands/tasks.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/cli/commands/thread.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/core/__init__.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/core/errors.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/core/frozen.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/core/git.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/core/logging.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/core/names.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/core/paths.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/core/types.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/execution/__init__.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/execution/mcp_schema_index.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/execution/parallel.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/execution/tool_result_store.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/execution/tool_schema_cache.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/hooks/__init__.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/hooks/config.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/hooks/engine.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/hooks/executors/__init__.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/hooks/executors/callback.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/hooks/executors/command.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/hooks/executors/http.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/hooks/executors/prompt.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/hooks/executors/ssrf_guard.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/hooks/matchers.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/hooks/registry.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/hooks/types.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/serve/__init__.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/serve/adapters/__init__.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/serve/adapters/base.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/serve/adapters/desktop.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/serve/adapters/registry.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/serve/adapters/stub.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/serve/adapters/telegram.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/serve/chunker.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/serve/commands.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/serve/daemon.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/serve/elicitation.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/serve/format.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/serve/install.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/serve/lock.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/serve/observability.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/serve/progress_sink.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/serve/router.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/serve/secrets.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/serve/sinks.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/serve/worker.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/storage/__init__.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/storage/threads.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/tasks/__init__.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/tasks/channels.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/tasks/lock.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/tasks/notifications.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/tasks/store.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/tasks/types.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/templates/login_failed.html +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/templates/login_success.html +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/templates/serve/launchd.plist.tmpl +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/templates/serve/systemd.service.tmpl +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/templates/styles.css +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/tools/__init__.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/tools/manager/__init__.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/tools/manager/_request_ctx.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/tools/manager/base.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/tools/manager/composite.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/tools/manager/config.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/tools/manager/mcp.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/tools/search/__init__.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/tools/search/discovered.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/tools/search/scoring.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/tools/search/service.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/ui/__init__.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/ui/display.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/ui/elicitation.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/ui/input.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/ui/slash.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/voice/__init__.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/voice/audio.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/voice/cleanup.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/voice/session.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/voice/stt.py +0 -0
- {cade_cli-0.13.2 → cade_cli-0.13.4}/src/cadecoder/voice/tts.py +0 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Runtime configuration for the local MCP server.
|
|
2
|
+
|
|
3
|
+
Configuration is resolved lazily at runtime, not at import time.
|
|
4
|
+
This allows proper testing and correct behavior when installed as a package.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import pathlib
|
|
9
|
+
from functools import lru_cache
|
|
10
|
+
|
|
11
|
+
# Environment variable that disables filesystem path confinement. When set to a
|
|
12
|
+
# truthy value, filesystem tools may read/write paths outside the project root.
|
|
13
|
+
#
|
|
14
|
+
# This is intentionally a single on/off switch for now. A future iteration may
|
|
15
|
+
# replace it with a richer policy (e.g. an explicit list of additional allowed
|
|
16
|
+
# roots); keep `path_restrictions_enabled()` as the single read point so callers
|
|
17
|
+
# don't need to change when that happens.
|
|
18
|
+
PATH_RESTRICTIONS_ENV: str = "CADE_DISABLE_PATH_RESTRICTIONS"
|
|
19
|
+
|
|
20
|
+
_TRUTHY_VALUES: frozenset[str] = frozenset({"1", "true", "yes", "on"})
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def path_restrictions_enabled() -> bool:
|
|
24
|
+
"""Return whether filesystem tools must stay within the project root.
|
|
25
|
+
|
|
26
|
+
Restrictions are ON by default. They are lifted only when the
|
|
27
|
+
``CADE_DISABLE_PATH_RESTRICTIONS`` environment variable is set to a truthy
|
|
28
|
+
value (``1``, ``true``, ``yes``, or ``on``, case-insensitive).
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
True when path confinement should be enforced, False when tools may
|
|
32
|
+
access any path on the filesystem.
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
>>> import os
|
|
36
|
+
>>> os.environ.pop("CADE_DISABLE_PATH_RESTRICTIONS", None)
|
|
37
|
+
>>> path_restrictions_enabled()
|
|
38
|
+
True
|
|
39
|
+
>>> os.environ["CADE_DISABLE_PATH_RESTRICTIONS"] = "1"
|
|
40
|
+
>>> path_restrictions_enabled()
|
|
41
|
+
False
|
|
42
|
+
"""
|
|
43
|
+
raw = os.environ.get(PATH_RESTRICTIONS_ENV, "").strip().lower()
|
|
44
|
+
return raw not in _TRUTHY_VALUES
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def disable_path_restrictions() -> None:
|
|
48
|
+
"""Lift filesystem path confinement for this process and its children.
|
|
49
|
+
|
|
50
|
+
Sets ``CADE_DISABLE_PATH_RESTRICTIONS`` in the environment so that child
|
|
51
|
+
processes (notably the local-tools MCP subprocess, which inherits
|
|
52
|
+
``os.environ``) and the serve sandbox hook all observe the override.
|
|
53
|
+
|
|
54
|
+
This is the single supported way to flip the toggle on from code, keeping
|
|
55
|
+
the environment-variable name encapsulated in this module.
|
|
56
|
+
"""
|
|
57
|
+
os.environ[PATH_RESTRICTIONS_ENV] = "1"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@lru_cache(maxsize=1)
|
|
61
|
+
def get_project_root() -> pathlib.Path:
|
|
62
|
+
"""Get the project root directory.
|
|
63
|
+
|
|
64
|
+
Resolution order:
|
|
65
|
+
1. CADE_PROJECT_ROOT environment variable (set by parent process)
|
|
66
|
+
2. Current working directory
|
|
67
|
+
|
|
68
|
+
This is cached for performance but can be reset with reset_config().
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Resolved path to the project root directory.
|
|
72
|
+
"""
|
|
73
|
+
if root := os.environ.get("CADE_PROJECT_ROOT"):
|
|
74
|
+
return pathlib.Path(root).resolve()
|
|
75
|
+
return pathlib.Path.cwd().resolve()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def reset_config() -> None:
|
|
79
|
+
"""Clear cached configuration.
|
|
80
|
+
|
|
81
|
+
Call this when:
|
|
82
|
+
- The working directory has changed
|
|
83
|
+
- CADE_PROJECT_ROOT environment variable has changed
|
|
84
|
+
- During testing to ensure isolation
|
|
85
|
+
"""
|
|
86
|
+
get_project_root.cache_clear()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# Default ignore patterns for file listings and searches
|
|
90
|
+
DEFAULT_IGNORE_PATTERNS: list[str] = [
|
|
91
|
+
".git",
|
|
92
|
+
"__pycache__",
|
|
93
|
+
"node_modules",
|
|
94
|
+
".venv",
|
|
95
|
+
"venv",
|
|
96
|
+
".mypy_cache",
|
|
97
|
+
".pytest_cache",
|
|
98
|
+
"*.pyc",
|
|
99
|
+
".DS_Store",
|
|
100
|
+
"*.egg-info",
|
|
101
|
+
"dist",
|
|
102
|
+
"build",
|
|
103
|
+
".tox",
|
|
104
|
+
".coverage",
|
|
105
|
+
"htmlcov",
|
|
106
|
+
".idea",
|
|
107
|
+
".vscode",
|
|
108
|
+
".cursor",
|
|
109
|
+
".claude",
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
# File operation limits
|
|
113
|
+
MAX_LIST_DEPTH: int = 10
|
|
114
|
+
MAX_LIST_RESULTS: int = 1000
|
|
115
|
+
MAX_PREVIEW_BYTES: int = 100_000
|
|
116
|
+
DEFAULT_TIMEOUT: int = 60
|
|
@@ -19,6 +19,7 @@ from cade_mcp_local.config import (
|
|
|
19
19
|
MAX_LIST_RESULTS,
|
|
20
20
|
MAX_PREVIEW_BYTES,
|
|
21
21
|
get_project_root,
|
|
22
|
+
path_restrictions_enabled,
|
|
22
23
|
)
|
|
23
24
|
from cade_mcp_local.errors import (
|
|
24
25
|
FileOperationError,
|
|
@@ -47,22 +48,25 @@ def _read_text_file(file_path: pathlib.Path) -> str:
|
|
|
47
48
|
|
|
48
49
|
|
|
49
50
|
def _write_text_file(file_path: pathlib.Path, content: str) -> None:
|
|
50
|
-
"""Write text content to a file, ensuring it's within project root.
|
|
51
|
-
|
|
51
|
+
"""Write text content to a file, ensuring it's within project root.
|
|
52
|
+
|
|
53
|
+
Confinement to the project root is skipped when path restrictions are
|
|
54
|
+
disabled (see `path_restrictions_enabled`).
|
|
55
|
+
"""
|
|
52
56
|
resolved = file_path.resolve()
|
|
53
|
-
resolved_root = project_root.resolve()
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
if path_restrictions_enabled():
|
|
59
|
+
resolved_root = get_project_root().resolve()
|
|
60
|
+
# Security check: must be within project root.
|
|
61
|
+
# Use string prefix check as fallback for symlink/worktree edge cases.
|
|
62
|
+
try:
|
|
63
|
+
resolved.relative_to(resolved_root)
|
|
64
|
+
except ValueError:
|
|
65
|
+
if not str(resolved).startswith(str(resolved_root) + "/"):
|
|
66
|
+
raise PathSecurityError(
|
|
67
|
+
str(file_path),
|
|
68
|
+
f"outside project root (resolved={resolved}, root={resolved_root})",
|
|
69
|
+
)
|
|
66
70
|
|
|
67
71
|
# Create parent directories if needed
|
|
68
72
|
resolved.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
import fnmatch
|
|
4
4
|
import pathlib
|
|
5
5
|
|
|
6
|
-
from cade_mcp_local.config import
|
|
6
|
+
from cade_mcp_local.config import (
|
|
7
|
+
DEFAULT_IGNORE_PATTERNS,
|
|
8
|
+
get_project_root,
|
|
9
|
+
path_restrictions_enabled,
|
|
10
|
+
)
|
|
7
11
|
|
|
8
12
|
|
|
9
13
|
def resolve_path(
|
|
@@ -46,7 +50,7 @@ def resolve_path(
|
|
|
46
50
|
else:
|
|
47
51
|
resolved = (base_dir / path).resolve()
|
|
48
52
|
|
|
49
|
-
if confine_to_root:
|
|
53
|
+
if confine_to_root and path_restrictions_enabled():
|
|
50
54
|
root = get_project_root().resolve()
|
|
51
55
|
try:
|
|
52
56
|
resolved.relative_to(root)
|
|
@@ -90,7 +90,12 @@ app.add_typer(
|
|
|
90
90
|
help="Activation contexts (persona + model + MCP bundle)",
|
|
91
91
|
rich_help_panel=PANEL_CONFIGURE,
|
|
92
92
|
)
|
|
93
|
-
app.add_typer(
|
|
93
|
+
app.add_typer(
|
|
94
|
+
persona_app,
|
|
95
|
+
name="persona",
|
|
96
|
+
help="Personas (custom prompt + model)",
|
|
97
|
+
rich_help_panel=PANEL_CONFIGURE,
|
|
98
|
+
)
|
|
94
99
|
app.add_typer(model_app, name="model", help="AI models", rich_help_panel=PANEL_CONFIGURE)
|
|
95
100
|
app.add_typer(mcp_app, name="mcp", help="MCP servers", rich_help_panel=PANEL_CONFIGURE)
|
|
96
101
|
|
|
@@ -261,6 +266,15 @@ def main_callback(
|
|
|
261
266
|
help="Run under a named Cade activation context.",
|
|
262
267
|
),
|
|
263
268
|
] = None,
|
|
269
|
+
no_path_restrictions: Annotated[
|
|
270
|
+
bool,
|
|
271
|
+
typer.Option(
|
|
272
|
+
"--no-path-restrictions",
|
|
273
|
+
help="Let filesystem tools access any path (disables project-root confinement). "
|
|
274
|
+
"Use with caution.",
|
|
275
|
+
is_eager=True,
|
|
276
|
+
),
|
|
277
|
+
] = False,
|
|
264
278
|
message: Annotated[
|
|
265
279
|
str | None,
|
|
266
280
|
typer.Option(
|
|
@@ -281,8 +295,21 @@ def main_callback(
|
|
|
281
295
|
] = None,
|
|
282
296
|
) -> None:
|
|
283
297
|
"""Resolve runtime, set up logging, and dispatch to chat/resume/-m when no subcommand is given."""
|
|
298
|
+
if no_path_restrictions:
|
|
299
|
+
from cade_mcp_local.config import disable_path_restrictions
|
|
300
|
+
|
|
301
|
+
disable_path_restrictions()
|
|
302
|
+
|
|
284
303
|
runtime = build_runtime(context)
|
|
285
304
|
if persona:
|
|
305
|
+
from cadecoder.storage.personas import persona_exists
|
|
306
|
+
|
|
307
|
+
if not persona_exists(persona):
|
|
308
|
+
console.print(
|
|
309
|
+
f"[red]Persona '{persona}' not found.[/red] "
|
|
310
|
+
"Run 'cade persona list' to see available personas."
|
|
311
|
+
)
|
|
312
|
+
raise typer.Exit(1)
|
|
286
313
|
runtime = replace(runtime, persona_name=persona)
|
|
287
314
|
runtime.ensure_dirs()
|
|
288
315
|
set_runtime(runtime)
|
|
@@ -114,6 +114,14 @@ def chat(
|
|
|
114
114
|
help="Use a saved persona (custom system prompt). See 'cade persona list'.",
|
|
115
115
|
),
|
|
116
116
|
] = None,
|
|
117
|
+
no_path_restrictions: Annotated[
|
|
118
|
+
bool,
|
|
119
|
+
typer.Option(
|
|
120
|
+
"--no-path-restrictions",
|
|
121
|
+
help="Let filesystem tools access any path (disables project-root confinement). "
|
|
122
|
+
"Use with caution.",
|
|
123
|
+
),
|
|
124
|
+
] = False,
|
|
117
125
|
) -> None:
|
|
118
126
|
"""
|
|
119
127
|
Start an interactive chat session with AI.
|
|
@@ -123,6 +131,11 @@ def chat(
|
|
|
123
131
|
"""
|
|
124
132
|
command_name = "chat"
|
|
125
133
|
try:
|
|
134
|
+
if no_path_restrictions:
|
|
135
|
+
from cade_mcp_local.config import disable_path_restrictions
|
|
136
|
+
|
|
137
|
+
disable_path_restrictions()
|
|
138
|
+
|
|
126
139
|
if endpoint:
|
|
127
140
|
_configure_custom_endpoint(endpoint)
|
|
128
141
|
|
|
@@ -329,6 +342,14 @@ def resume(
|
|
|
329
342
|
help="Use a saved persona (custom system prompt). See 'cade persona list'.",
|
|
330
343
|
),
|
|
331
344
|
] = None,
|
|
345
|
+
no_path_restrictions: Annotated[
|
|
346
|
+
bool,
|
|
347
|
+
typer.Option(
|
|
348
|
+
"--no-path-restrictions",
|
|
349
|
+
help="Let filesystem tools access any path (disables project-root confinement). "
|
|
350
|
+
"Use with caution.",
|
|
351
|
+
),
|
|
352
|
+
] = False,
|
|
332
353
|
) -> None:
|
|
333
354
|
"""Resume a saved chat thread.
|
|
334
355
|
|
|
@@ -343,6 +364,11 @@ def resume(
|
|
|
343
364
|
"""
|
|
344
365
|
command_name = "resume"
|
|
345
366
|
try:
|
|
367
|
+
if no_path_restrictions:
|
|
368
|
+
from cade_mcp_local.config import disable_path_restrictions
|
|
369
|
+
|
|
370
|
+
disable_path_restrictions()
|
|
371
|
+
|
|
346
372
|
if endpoint:
|
|
347
373
|
_configure_custom_endpoint(endpoint)
|
|
348
374
|
|
|
@@ -85,7 +85,14 @@ def create_context(
|
|
|
85
85
|
else:
|
|
86
86
|
ctx = ContextConfig(name=context_name)
|
|
87
87
|
if persona:
|
|
88
|
-
|
|
88
|
+
from cadecoder.storage.personas import persona_exists
|
|
89
|
+
|
|
90
|
+
persona_name = validate_name(persona, kind="persona")
|
|
91
|
+
if not persona_exists(persona_name):
|
|
92
|
+
console.print(f"[red]Persona '{persona_name}' not found.[/red]")
|
|
93
|
+
console.print("[dim]Use 'cade persona list' to see available personas.[/dim]")
|
|
94
|
+
raise typer.Exit(1)
|
|
95
|
+
ctx.persona = persona_name
|
|
89
96
|
if model:
|
|
90
97
|
ctx.model = model
|
|
91
98
|
save_context_config(ctx)
|
|
@@ -163,8 +170,15 @@ def set_context_persona(
|
|
|
163
170
|
persona: Annotated[str, typer.Argument(help="Persona name")],
|
|
164
171
|
context: Annotated[str | None, typer.Option("--context", "-c")] = None,
|
|
165
172
|
) -> None:
|
|
173
|
+
from cadecoder.storage.personas import persona_exists
|
|
174
|
+
|
|
175
|
+
persona_name = validate_name(persona, kind="persona")
|
|
176
|
+
if not persona_exists(persona_name):
|
|
177
|
+
console.print(f"[red]Persona '{persona_name}' not found.[/red]")
|
|
178
|
+
console.print("[dim]Use 'cade persona list' to see available personas.[/dim]")
|
|
179
|
+
raise typer.Exit(1)
|
|
166
180
|
ctx = load_context_config(context or _active_or_default())
|
|
167
|
-
ctx.persona =
|
|
181
|
+
ctx.persona = persona_name
|
|
168
182
|
save_context_config(ctx)
|
|
169
183
|
reset_runtime()
|
|
170
184
|
console.print(f"[green]Context '{ctx.name}' persona set to '{ctx.persona}'.[/green]")
|
|
@@ -10,12 +10,16 @@ from rich.console import Console
|
|
|
10
10
|
from rich.table import Table
|
|
11
11
|
|
|
12
12
|
from cadecoder.tasks.scheduler import (
|
|
13
|
+
AmbiguousTaskError,
|
|
14
|
+
TaskNotFoundError,
|
|
13
15
|
add_scheduled_task,
|
|
14
16
|
load_scheduled_tasks,
|
|
15
17
|
remove_scheduled_task,
|
|
18
|
+
resolve_scheduled_task,
|
|
16
19
|
save_scheduled_tasks,
|
|
17
20
|
set_scheduled_task_enabled,
|
|
18
21
|
)
|
|
22
|
+
from cadecoder.tasks.types import ScheduledTask
|
|
19
23
|
|
|
20
24
|
cron_app = typer.Typer(
|
|
21
25
|
name="cron",
|
|
@@ -33,6 +37,29 @@ def _cb(ctx: typer.Context) -> None:
|
|
|
33
37
|
raise typer.Exit(0)
|
|
34
38
|
|
|
35
39
|
|
|
40
|
+
def _resolve_or_exit(token: str) -> ScheduledTask:
|
|
41
|
+
"""Resolve a token to one task, printing a helpful error and exiting otherwise.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
token: The id, name, or id prefix supplied on the command line.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
The single matching `ScheduledTask`.
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
return resolve_scheduled_task(token)
|
|
51
|
+
except TaskNotFoundError:
|
|
52
|
+
console.print(f"[yellow]No scheduled task matching {token!r}[/yellow]")
|
|
53
|
+
raise typer.Exit(1) from None
|
|
54
|
+
except AmbiguousTaskError as e:
|
|
55
|
+
console.print(
|
|
56
|
+
f"[yellow]{token!r} matches {len(e.matches)} tasks; be more specific:[/yellow]"
|
|
57
|
+
)
|
|
58
|
+
for t in e.matches:
|
|
59
|
+
console.print(f" [cyan]{t.id}[/cyan] {t.name}")
|
|
60
|
+
raise typer.Exit(1) from None
|
|
61
|
+
|
|
62
|
+
|
|
36
63
|
@cron_app.command("list")
|
|
37
64
|
def list_cron(
|
|
38
65
|
json_out: Annotated[bool, typer.Option("--json", help="Emit JSON")] = False,
|
|
@@ -86,39 +113,32 @@ def add_cron(
|
|
|
86
113
|
|
|
87
114
|
@cron_app.command("remove")
|
|
88
115
|
def remove_cron(task_id: Annotated[str, typer.Argument()]) -> None:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
console.print(f"[green]Removed {task_id}[/green]")
|
|
116
|
+
target = _resolve_or_exit(task_id)
|
|
117
|
+
remove_scheduled_task(target.id)
|
|
118
|
+
console.print(f"[green]Removed {target.id} ({target.name})[/green]")
|
|
93
119
|
|
|
94
120
|
|
|
95
121
|
@cron_app.command("enable")
|
|
96
122
|
def enable_cron(task_id: Annotated[str, typer.Argument()]) -> None:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
console.print(f"[green]Enabled {task_id}[/green]")
|
|
123
|
+
target = _resolve_or_exit(task_id)
|
|
124
|
+
set_scheduled_task_enabled(target.id, True)
|
|
125
|
+
console.print(f"[green]Enabled {target.id} ({target.name})[/green]")
|
|
101
126
|
|
|
102
127
|
|
|
103
128
|
@cron_app.command("disable")
|
|
104
129
|
def disable_cron(task_id: Annotated[str, typer.Argument()]) -> None:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
console.print(f"[green]Disabled {task_id}[/green]")
|
|
130
|
+
target = _resolve_or_exit(task_id)
|
|
131
|
+
set_scheduled_task_enabled(target.id, False)
|
|
132
|
+
console.print(f"[green]Disabled {target.id} ({target.name})[/green]")
|
|
109
133
|
|
|
110
134
|
|
|
111
135
|
@cron_app.command("run-now")
|
|
112
136
|
def run_now(task_id: Annotated[str, typer.Argument()]) -> None:
|
|
113
137
|
"""Mark a scheduled task to fire at the next tick by clearing last_fired."""
|
|
138
|
+
target = _resolve_or_exit(task_id)
|
|
114
139
|
tasks = load_scheduled_tasks()
|
|
115
|
-
changed = False
|
|
116
140
|
for t in tasks:
|
|
117
|
-
if t.id ==
|
|
141
|
+
if t.id == target.id:
|
|
118
142
|
t.last_fired = None
|
|
119
|
-
changed = True
|
|
120
|
-
if not changed:
|
|
121
|
-
console.print(f"[yellow]No scheduled task with id {task_id}[/yellow]")
|
|
122
|
-
raise typer.Exit(1)
|
|
123
143
|
save_scheduled_tasks(tasks)
|
|
124
|
-
console.print(f"[green]Queued {
|
|
144
|
+
console.print(f"[green]Queued {target.id} ({target.name}) for next tick[/green]")
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""``cade mem`` — persona-scoped memory management backed by agent-library.
|
|
2
|
+
|
|
3
|
+
The agent-library (``librarian``) ships a Typer CLI with commands for sources
|
|
4
|
+
(``list``/``add``/``rm``), search, the MCP server, index management, and
|
|
5
|
+
settings (``config``). Rather than re-implement that surface, ``cade mem``
|
|
6
|
+
drives librarian's Typer app **in-process** with ``prog_name="cade mem"`` so the
|
|
7
|
+
entire help tree renders as ``cade mem``/``cade mem search``/… — no
|
|
8
|
+
``librarian`` or ``libr`` branding leaks through.
|
|
9
|
+
|
|
10
|
+
Storage is scoped to the active context/persona by setting librarian's
|
|
11
|
+
environment (``DATABASE_PATH`` / ``DOCUMENTS_PATH`` / ``SOURCES_CONFIG_PATH``)
|
|
12
|
+
and ``HOME`` *before* librarian is imported. Librarian reads ``HOME`` for its
|
|
13
|
+
``~/.librarian`` config dir and the env vars for its storage at import time, so
|
|
14
|
+
the order matters: each ``cade mem`` invocation runs in its own process and
|
|
15
|
+
binds to exactly one persona.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
import typer
|
|
24
|
+
from rich.console import Console
|
|
25
|
+
|
|
26
|
+
from cadecoder.cli.commands.persona import _ensure_memory_dir
|
|
27
|
+
from cadecoder.core.runtime import get_runtime
|
|
28
|
+
|
|
29
|
+
console = Console(stderr=True)
|
|
30
|
+
|
|
31
|
+
MEM_CONTEXT_SETTINGS = {
|
|
32
|
+
"allow_extra_args": True,
|
|
33
|
+
"ignore_unknown_options": True,
|
|
34
|
+
# Empty list so --help / -h flow through to librarian instead of being
|
|
35
|
+
# intercepted by Typer; `cade mem --help` renders the cade-branded help.
|
|
36
|
+
"help_option_names": [],
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
_MEM_HELP = "Manage Cade persona memory — sources, search, and indexing."
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _resolve_active_persona(ctx: typer.Context) -> str:
|
|
43
|
+
"""Return the active persona name.
|
|
44
|
+
|
|
45
|
+
Precedence: ``--persona`` flag on the root ``cade`` invocation (stored in
|
|
46
|
+
``ctx.obj``) → the active context's persona → ``"default"``.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
ctx: The Typer/Click context for the ``mem`` invocation.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
The resolved persona name.
|
|
53
|
+
"""
|
|
54
|
+
cursor: typer.Context | None = ctx
|
|
55
|
+
while cursor is not None:
|
|
56
|
+
obj = cursor.obj
|
|
57
|
+
if isinstance(obj, dict):
|
|
58
|
+
name = obj.get("persona")
|
|
59
|
+
if isinstance(name, str) and name.strip():
|
|
60
|
+
return name
|
|
61
|
+
cursor = cursor.parent
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
name = get_runtime().persona_name
|
|
65
|
+
except Exception:
|
|
66
|
+
name = None
|
|
67
|
+
return name or "default"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _apply_persona_env(persona_name: str) -> Path:
|
|
71
|
+
"""Pin librarian's storage to a persona by setting process environment.
|
|
72
|
+
|
|
73
|
+
Librarian has two code paths that locate its storage and they disagree:
|
|
74
|
+
``librarian.config`` reads ``DATABASE_PATH`` / ``DOCUMENTS_PATH`` /
|
|
75
|
+
``SOURCES_CONFIG_PATH``, but the CLI derives ``~/.librarian`` from ``HOME``.
|
|
76
|
+
Setting both — with ``HOME`` pointed at the persona directory — makes them
|
|
77
|
+
converge on ``<persona>/.librarian/*``. This MUST run before librarian is
|
|
78
|
+
imported.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
persona_name: The persona whose memory directory to bind to.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
The ``.librarian`` directory for the persona.
|
|
85
|
+
"""
|
|
86
|
+
import os
|
|
87
|
+
|
|
88
|
+
persona_dir = _ensure_memory_dir(persona_name)
|
|
89
|
+
librarian_dir = persona_dir / ".librarian"
|
|
90
|
+
os.environ["DATABASE_PATH"] = str(librarian_dir / "index.db")
|
|
91
|
+
os.environ["DOCUMENTS_PATH"] = str(librarian_dir / "documents")
|
|
92
|
+
os.environ["SOURCES_CONFIG_PATH"] = str(librarian_dir / "sources.json")
|
|
93
|
+
os.environ["HOME"] = str(persona_dir)
|
|
94
|
+
return librarian_dir
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _load_librarian_app() -> Any:
|
|
98
|
+
"""Import and return librarian's Typer app.
|
|
99
|
+
|
|
100
|
+
Imported lazily so the persona environment (see :func:`_apply_persona_env`)
|
|
101
|
+
is in place first, and so a missing agent-library install fails loudly only
|
|
102
|
+
when ``cade mem`` is actually used.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
The librarian Typer application instance.
|
|
106
|
+
"""
|
|
107
|
+
from librarian.cli import app
|
|
108
|
+
|
|
109
|
+
return app
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _run_mem(args: list[str]) -> int:
|
|
113
|
+
"""Run librarian's CLI in-process, branded as ``cade mem``.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
args: Arguments to forward to the librarian CLI (e.g. ``["search", "x"]``).
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
The process exit code.
|
|
120
|
+
"""
|
|
121
|
+
try:
|
|
122
|
+
librarian_app = _load_librarian_app()
|
|
123
|
+
except ImportError:
|
|
124
|
+
console.print(
|
|
125
|
+
"[red]agent-library is not installed.[/red] Install it into the "
|
|
126
|
+
"active environment: [bold]uv pip install agent-library[/bold]."
|
|
127
|
+
)
|
|
128
|
+
return 1
|
|
129
|
+
|
|
130
|
+
# Rebrand the app so every help screen reads "cade mem" rather than "libr".
|
|
131
|
+
librarian_app.info.name = "cade mem"
|
|
132
|
+
librarian_app.info.help = _MEM_HELP
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
librarian_app(args=args, prog_name="cade mem")
|
|
136
|
+
except SystemExit as exc:
|
|
137
|
+
code = exc.code
|
|
138
|
+
if code is None:
|
|
139
|
+
return 0
|
|
140
|
+
return code if isinstance(code, int) else 1
|
|
141
|
+
return 0
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def mem_command(ctx: typer.Context) -> None:
|
|
145
|
+
"""Run a ``cade mem`` subcommand scoped to the active persona.
|
|
146
|
+
|
|
147
|
+
Examples:
|
|
148
|
+
cade mem # show the cade mem help
|
|
149
|
+
cade mem list # list sources for the active persona
|
|
150
|
+
cade mem add ./docs # add a source under the active persona
|
|
151
|
+
cade mem search "topic" # search the active persona's memory
|
|
152
|
+
cade mem config show # show memory configuration
|
|
153
|
+
cade --persona anna mem list # scope to a different persona
|
|
154
|
+
"""
|
|
155
|
+
persona = _resolve_active_persona(ctx)
|
|
156
|
+
memory_home = _apply_persona_env(persona).parent
|
|
157
|
+
forwarded = list(ctx.args)
|
|
158
|
+
# Surface the active scope on help / no-args runs — opaque env vars are
|
|
159
|
+
# easy to get wrong and this keeps the bridge visible.
|
|
160
|
+
if not forwarded or forwarded[0] in {"--help", "-h"}:
|
|
161
|
+
console.print(
|
|
162
|
+
f"[dim]cade mem: scoped to persona [bold]{persona}[/bold] "
|
|
163
|
+
f"(storage={memory_home})[/dim]\n"
|
|
164
|
+
)
|
|
165
|
+
rc = _run_mem(forwarded)
|
|
166
|
+
raise typer.Exit(rc)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
__all__ = (
|
|
170
|
+
"MEM_CONTEXT_SETTINGS",
|
|
171
|
+
"_apply_persona_env",
|
|
172
|
+
"_load_librarian_app",
|
|
173
|
+
"_resolve_active_persona",
|
|
174
|
+
"_run_mem",
|
|
175
|
+
"mem_command",
|
|
176
|
+
)
|