cade-cli 0.15.6__tar.gz → 0.15.8__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.6 → cade_cli-0.15.8}/PKG-INFO +1 -1
- {cade_cli-0.15.6 → cade_cli-0.15.8}/pyproject.toml +1 -1
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/resources.py +1 -1
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/cli/commands/agents.py +11 -16
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/cli/commands/mem.py +138 -5
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/cli/commands/serve.py +93 -2
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/core/agent.py +7 -31
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/adapters/__init__.py +1 -0
- cade_cli-0.15.8/src/cadecoder/serve/adapters/imessage.py +648 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/config.py +40 -11
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/observability.py +3 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/templates/serve/serve.toml.example +16 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/ui/session.py +10 -4
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/ui/slash.py +1 -1
- {cade_cli-0.15.6 → cade_cli-0.15.8}/.gitignore +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/LICENSE +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/README.md +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/__init__.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/__main__.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/_metadata.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/config.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/errors.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/server.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/tools/__init__.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/tools/_read_cache.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/tools/agents.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/tools/context.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/tools/filesystem.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/tools/git.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/tools/notifications.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/tools/questions.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/tools/results.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/tools/schemas.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/tools/search.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/tools/shell.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/tools/snippets.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/tools/tasks.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/tools/web_fetch.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/tools/web_search.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/utils.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/web/__init__.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/web/arcade_client.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/web/cache.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/web/extract.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/web/markdown.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/web/preapproved.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/web/prompts.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/web/scrape.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/web/search/__init__.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/web/search/arcade.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/web/search/base.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cade_mcp_local/web/search/http.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/__init__.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/ai/__init__.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/ai/prompts.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/cli/__init__.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/cli/app.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/cli/auth.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/cli/commands/__init__.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/cli/commands/account.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/cli/commands/auth.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/cli/commands/chat.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/cli/commands/cron.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/cli/commands/hooks.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/cli/commands/mcp.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/cli/commands/tasks.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/cli/commands/thread.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/cli/commands/tools.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/cli/onboarding.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/core/__init__.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/core/comms.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/core/config.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/core/constants.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/core/errors.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/core/frozen.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/core/git.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/core/inbox.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/core/logging.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/core/memory.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/core/model_specs.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/core/names.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/core/paths.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/core/runtime.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/core/secrets.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/core/spawn.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/core/types.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/execution/__init__.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/execution/context_window.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/execution/mcp_schema_index.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/execution/orchestrator.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/execution/parallel.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/execution/tool_result_store.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/execution/tool_schema_cache.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/hooks/__init__.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/hooks/config.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/hooks/engine.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/hooks/executors/__init__.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/hooks/executors/callback.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/hooks/executors/command.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/hooks/executors/http.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/hooks/executors/prompt.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/hooks/executors/ssrf_guard.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/hooks/matchers.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/hooks/registry.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/hooks/types.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/providers/__init__.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/providers/anthropic.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/providers/base.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/providers/ollama.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/providers/openai.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/__init__.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/adapters/base.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/adapters/desktop.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/adapters/registry.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/adapters/stub.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/adapters/telegram.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/allowlist.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/chunker.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/commands.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/cron.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/daemon.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/elicitation.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/format.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/install.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/lock.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/owner.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/progress_sink.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/router.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/secrets.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/services.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/sinks.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/serve/worker.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/storage/__init__.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/storage/threads.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/tasks/__init__.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/tasks/lock.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/tasks/scheduler.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/tasks/store.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/tasks/types.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/templates/login_failed.html +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/templates/login_success.html +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/templates/serve/launchd.plist.tmpl +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/templates/serve/systemd.service.tmpl +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/templates/styles.css +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/tools/__init__.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/tools/manager/__init__.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/tools/manager/_request_ctx.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/tools/manager/base.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/tools/manager/composite.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/tools/manager/config.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/tools/manager/mcp.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/tools/search/__init__.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/tools/search/discovered.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/tools/search/scoring.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/tools/search/service.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/ui/__init__.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/ui/completion.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/ui/display.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/ui/elicitation.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/ui/input.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/voice/__init__.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/voice/audio.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/voice/cleanup.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/voice/session.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/voice/stt.py +0 -0
- {cade_cli-0.15.6 → cade_cli-0.15.8}/src/cadecoder/voice/tts.py +0 -0
|
@@ -116,7 +116,7 @@ def register_resources(app: MCPApp, instructions: str) -> None:
|
|
|
116
116
|
title="cade mem sources (active persona)",
|
|
117
117
|
description=(
|
|
118
118
|
"JSON listing of memory sources registered for the active persona "
|
|
119
|
-
"(equivalent to `cade mem
|
|
119
|
+
"(equivalent to `cade mem sources`). Read each call so the listing "
|
|
120
120
|
"reflects sources added after server start."
|
|
121
121
|
),
|
|
122
122
|
mime_type="application/json",
|
|
@@ -113,6 +113,15 @@ def list_agents() -> None:
|
|
|
113
113
|
console.print(table)
|
|
114
114
|
|
|
115
115
|
|
|
116
|
+
def _enabled_adapter_names(agent: AgentConfig) -> list[str]:
|
|
117
|
+
"""Names of adapters enabled on an agent, sorted for stable display."""
|
|
118
|
+
return sorted(
|
|
119
|
+
name
|
|
120
|
+
for name, settings in agent.adapters.items()
|
|
121
|
+
if isinstance(settings, dict) and settings.get("enabled")
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
116
125
|
def _resolve_or_current(name: str | None) -> AgentConfig:
|
|
117
126
|
if name:
|
|
118
127
|
agent = agent_mod.resolve(name)
|
|
@@ -147,14 +156,7 @@ def agent_info(
|
|
|
147
156
|
)
|
|
148
157
|
if agent.mcp:
|
|
149
158
|
console.print(f" [bold]MCP servers:[/bold] {', '.join(s.name for s in agent.mcp)}")
|
|
150
|
-
enabled_adapters =
|
|
151
|
-
adapter
|
|
152
|
-
for adapter, settings in (
|
|
153
|
-
("telegram", agent.adapters.telegram),
|
|
154
|
-
("desktop", agent.adapters.desktop),
|
|
155
|
-
)
|
|
156
|
-
if settings.enabled
|
|
157
|
-
]
|
|
159
|
+
enabled_adapters = _enabled_adapter_names(agent)
|
|
158
160
|
if enabled_adapters:
|
|
159
161
|
console.print(f" [bold]Adapters:[/bold] {', '.join(enabled_adapters)}")
|
|
160
162
|
|
|
@@ -188,14 +190,7 @@ def agent_status() -> None:
|
|
|
188
190
|
else:
|
|
189
191
|
console.print(" [bold]Serve daemon:[/bold] [dim]stopped[/dim]")
|
|
190
192
|
|
|
191
|
-
enabled =
|
|
192
|
-
adapter
|
|
193
|
-
for adapter, settings in (
|
|
194
|
-
("telegram", agent.adapters.telegram),
|
|
195
|
-
("desktop", agent.adapters.desktop),
|
|
196
|
-
)
|
|
197
|
-
if settings.enabled
|
|
198
|
-
]
|
|
193
|
+
enabled = _enabled_adapter_names(agent)
|
|
199
194
|
console.print(f" [bold]Adapters:[/bold] {', '.join(enabled) or '(none)'}")
|
|
200
195
|
|
|
201
196
|
try:
|
|
@@ -10,7 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
import os
|
|
12
12
|
from pathlib import Path
|
|
13
|
-
from typing import Any
|
|
13
|
+
from typing import Annotated, Any
|
|
14
14
|
|
|
15
15
|
import typer
|
|
16
16
|
from rich.console import Console
|
|
@@ -53,14 +53,19 @@ def _apply_memory_env() -> Path:
|
|
|
53
53
|
def _load_librarian_app() -> Any:
|
|
54
54
|
"""Import and return librarian's Typer app (lazy, after the env is set).
|
|
55
55
|
|
|
56
|
-
The app is rebranded under ``cade mem``:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
The app is rebranded under ``cade mem``:
|
|
57
|
+
|
|
58
|
+
* the low-level ``index`` group becomes a top-level ``update`` command (the
|
|
59
|
+
former ``index build``);
|
|
60
|
+
* a cade-native ``status`` command reports the current agent's memory under
|
|
61
|
+
"Agent Memory" branding;
|
|
62
|
+
* ``list`` shows the agent's memory **documents** (what users expect from
|
|
63
|
+
"list"), while the source-directory listing moves to ``sources``.
|
|
60
64
|
"""
|
|
61
65
|
from librarian.cli import app
|
|
62
66
|
|
|
63
67
|
_remap_index_to_update(app)
|
|
68
|
+
_remap_list_to_docs(app)
|
|
64
69
|
_add_status_command(app)
|
|
65
70
|
return app
|
|
66
71
|
|
|
@@ -97,6 +102,52 @@ def _remap_index_to_update(app: Any) -> None:
|
|
|
97
102
|
)(build_callback)
|
|
98
103
|
|
|
99
104
|
|
|
105
|
+
def _remap_list_to_docs(app: Any) -> None:
|
|
106
|
+
"""Make ``cade mem list`` show memory *documents*, not source directories.
|
|
107
|
+
|
|
108
|
+
librarian's top-level ``list`` lists registered source directories — a
|
|
109
|
+
power-user concept. Under ``cade mem``, "list" should show what's actually
|
|
110
|
+
in the agent's memory (its indexed documents, including anything the agent
|
|
111
|
+
saved via ``Memory_*`` tools). So the source listing moves to
|
|
112
|
+
``cade mem sources`` and ``list`` is replaced by a cade-native command that
|
|
113
|
+
renders documents with the same output (and flags) as the in-chat
|
|
114
|
+
``/mem list``.
|
|
115
|
+
|
|
116
|
+
Power users keep librarian's ``cade mem docs list`` (``--source`` /
|
|
117
|
+
``--format`` / ``paths``) untouched.
|
|
118
|
+
|
|
119
|
+
Idempotent: safe to call more than once on the same app singleton.
|
|
120
|
+
"""
|
|
121
|
+
if "sources" in _command_names(app):
|
|
122
|
+
return # already remapped
|
|
123
|
+
|
|
124
|
+
commands = list(app.registered_commands)
|
|
125
|
+
list_sources_callback = next(
|
|
126
|
+
(
|
|
127
|
+
rc.callback
|
|
128
|
+
for rc in commands
|
|
129
|
+
if (rc.name or getattr(rc.callback, "__name__", "")) == "list"
|
|
130
|
+
),
|
|
131
|
+
None,
|
|
132
|
+
)
|
|
133
|
+
if list_sources_callback is None:
|
|
134
|
+
return # librarian build without a top-level `list` — leave as-is
|
|
135
|
+
|
|
136
|
+
# Drop the original top-level `list` (sources), re-register it as `sources`,
|
|
137
|
+
# and install the cade-native document listing as `list`.
|
|
138
|
+
app.registered_commands = [
|
|
139
|
+
rc for rc in commands if (rc.name or getattr(rc.callback, "__name__", "")) != "list"
|
|
140
|
+
]
|
|
141
|
+
app.command(
|
|
142
|
+
name="sources",
|
|
143
|
+
help="List the source directories that feed this agent's memory.",
|
|
144
|
+
)(list_sources_callback)
|
|
145
|
+
app.command(
|
|
146
|
+
name="list",
|
|
147
|
+
help="List the documents stored in this agent's memory.",
|
|
148
|
+
)(_cade_mem_list)
|
|
149
|
+
|
|
150
|
+
|
|
100
151
|
def _add_status_command(app: Any) -> None:
|
|
101
152
|
"""Register the cade-native ``status`` command (idempotent)."""
|
|
102
153
|
if "status" in _command_names(app):
|
|
@@ -161,6 +212,86 @@ def _mem_status() -> None:
|
|
|
161
212
|
out.print(table)
|
|
162
213
|
|
|
163
214
|
|
|
215
|
+
def _mem_list(*, json_output: bool = False, limit: int = 50) -> None:
|
|
216
|
+
"""Render the documents stored in the current agent's memory.
|
|
217
|
+
|
|
218
|
+
The single source of truth for both ``cade mem list`` (CLI) and the in-chat
|
|
219
|
+
``/mem list`` command, so the two surfaces always agree. Reads the agent's
|
|
220
|
+
index directly via ``Database(db_path=...)`` (no env / no HOME side effects)
|
|
221
|
+
so it never clobbers a long-lived session, mirroring ``_read_memory_stats``.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
json_output: Emit a JSON array (id/title/path/type) to stdout instead of
|
|
225
|
+
a table — for scripting and piping.
|
|
226
|
+
limit: Maximum number of documents to display.
|
|
227
|
+
"""
|
|
228
|
+
import json
|
|
229
|
+
|
|
230
|
+
from rich.console import Console as _Console
|
|
231
|
+
from rich.table import Table
|
|
232
|
+
|
|
233
|
+
runtime = get_runtime()
|
|
234
|
+
memory_dir = ensure_memory_dir(runtime.persona_name)
|
|
235
|
+
db_path = memory_dir / ".librarian" / "index.db"
|
|
236
|
+
|
|
237
|
+
out = _Console()
|
|
238
|
+
|
|
239
|
+
documents: list[Any] = []
|
|
240
|
+
if db_path.exists():
|
|
241
|
+
try:
|
|
242
|
+
from librarian.storage.database import Database
|
|
243
|
+
|
|
244
|
+
documents = Database(db_path=str(db_path)).list_documents()
|
|
245
|
+
except Exception as exc: # noqa: BLE001
|
|
246
|
+
if json_output:
|
|
247
|
+
print("[]")
|
|
248
|
+
else:
|
|
249
|
+
out.print(f"[red]Could not read this agent's memory:[/red] {exc}")
|
|
250
|
+
return
|
|
251
|
+
|
|
252
|
+
if json_output:
|
|
253
|
+
payload = [
|
|
254
|
+
{
|
|
255
|
+
"id": doc.id,
|
|
256
|
+
"title": doc.title,
|
|
257
|
+
"path": doc.path,
|
|
258
|
+
"type": doc.asset_type.value if getattr(doc, "asset_type", None) else "text",
|
|
259
|
+
}
|
|
260
|
+
for doc in documents[:limit]
|
|
261
|
+
]
|
|
262
|
+
print(json.dumps(payload, indent=2, default=str))
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
if not documents:
|
|
266
|
+
out.print(
|
|
267
|
+
"[dim]No documents in this agent's memory yet — add docs or run 'cade mem update'.[/dim]"
|
|
268
|
+
)
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
home = str(Path.home())
|
|
272
|
+
table = Table(
|
|
273
|
+
title=f"Agent Memory — {len(documents)} document(s)",
|
|
274
|
+
show_header=True,
|
|
275
|
+
header_style="bold cyan",
|
|
276
|
+
)
|
|
277
|
+
table.add_column("ID", style="dim", width=4)
|
|
278
|
+
table.add_column("Title", style="green", max_width=44)
|
|
279
|
+
table.add_column("Path", style="blue", overflow="fold")
|
|
280
|
+
for doc in documents[:limit]:
|
|
281
|
+
table.add_row(str(doc.id), doc.title or "Untitled", doc.path.replace(home, "~"))
|
|
282
|
+
out.print(table)
|
|
283
|
+
if len(documents) > limit:
|
|
284
|
+
out.print(f"[dim]Showing {limit} of {len(documents)}. Use --limit to see more.[/dim]")
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _cade_mem_list(
|
|
288
|
+
json_output: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
|
|
289
|
+
limit: Annotated[int, typer.Option("--limit", "-l", help="Max documents to show")] = 50,
|
|
290
|
+
) -> None:
|
|
291
|
+
"""List the documents stored in this agent's memory."""
|
|
292
|
+
_mem_list(json_output=json_output, limit=limit)
|
|
293
|
+
|
|
294
|
+
|
|
164
295
|
def _run_mem(args: list[str]) -> int:
|
|
165
296
|
"""Run librarian's CLI in-process, branded as ``cade mem``.
|
|
166
297
|
|
|
@@ -248,7 +379,9 @@ def mem_command(ctx: typer.Context) -> None:
|
|
|
248
379
|
__all__ = (
|
|
249
380
|
"MEM_CONTEXT_SETTINGS",
|
|
250
381
|
"_apply_memory_env",
|
|
382
|
+
"_cade_mem_list",
|
|
251
383
|
"_load_librarian_app",
|
|
384
|
+
"_mem_list",
|
|
252
385
|
"_run_mem",
|
|
253
386
|
"_warm_memory",
|
|
254
387
|
"mem_command",
|
|
@@ -28,7 +28,7 @@ from cadecoder.serve.secrets import store_secret
|
|
|
28
28
|
|
|
29
29
|
serve_app = typer.Typer(
|
|
30
30
|
name="serve",
|
|
31
|
-
help="Run a Cade persona over messaging channels (Telegram, etc.)",
|
|
31
|
+
help="Run a Cade persona over messaging channels (Telegram, iMessage, etc.)",
|
|
32
32
|
invoke_without_command=True,
|
|
33
33
|
)
|
|
34
34
|
|
|
@@ -131,7 +131,7 @@ def init_cmd(
|
|
|
131
131
|
persona: Annotated[str | None, typer.Option(help="Persona for the daemon")] = None,
|
|
132
132
|
yes: Annotated[bool, typer.Option("-y", "--yes", help="Skip confirmations")] = False,
|
|
133
133
|
) -> None:
|
|
134
|
-
"""Interactive wizard:
|
|
134
|
+
"""Interactive wizard: configure messaging adapters and write the agent's serve config."""
|
|
135
135
|
target = config_path()
|
|
136
136
|
cfg = load_config()
|
|
137
137
|
if not yes and any(cfg.enabled_adapters()):
|
|
@@ -169,6 +169,11 @@ def init_cmd(
|
|
|
169
169
|
if sender not in telegram.allowed_senders:
|
|
170
170
|
telegram.allowed_senders.append(sender)
|
|
171
171
|
|
|
172
|
+
if sys.platform == "darwin" and typer.confirm(
|
|
173
|
+
"Configure iMessage adapter now? (macOS Messages.app)", default=False
|
|
174
|
+
):
|
|
175
|
+
_init_imessage(cfg)
|
|
176
|
+
|
|
172
177
|
if not cfg.tools.allow and not cfg.tools.deny:
|
|
173
178
|
# Local_* covers all cade_mcp_local tools (read/write/edit/search/tasks
|
|
174
179
|
# /git/…); Memory_* covers the agent-library (librarian) tools renamed
|
|
@@ -186,6 +191,92 @@ def init_cmd(
|
|
|
186
191
|
)
|
|
187
192
|
|
|
188
193
|
|
|
194
|
+
def _init_imessage(cfg) -> None: # noqa: ANN001 — ServeConfig, kept lazy for typer help speed
|
|
195
|
+
"""Interactive iMessage adapter setup: binary check, permission probe, allowlist."""
|
|
196
|
+
import shutil
|
|
197
|
+
import subprocess
|
|
198
|
+
|
|
199
|
+
from cadecoder.serve.adapters.imessage import canonical_handle
|
|
200
|
+
|
|
201
|
+
imessage = cfg.adapters.get("imessage")
|
|
202
|
+
assert imessage is not None
|
|
203
|
+
|
|
204
|
+
binary = shutil.which(imessage.cli_path or "imsg")
|
|
205
|
+
if binary is None:
|
|
206
|
+
console.print("[yellow]imsg is not installed.[/yellow]")
|
|
207
|
+
console.print("Install it first: [bold]brew install imsg[/bold]")
|
|
208
|
+
console.print("[dim]Then re-run `cade serve init`. Skipping iMessage setup.[/dim]")
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
# Full Disk Access probe — a failed read means macOS is blocking chat.db.
|
|
212
|
+
fda_ok = False
|
|
213
|
+
try:
|
|
214
|
+
probe = subprocess.run(
|
|
215
|
+
[binary, "chats", "--limit", "1", "--json"],
|
|
216
|
+
capture_output=True,
|
|
217
|
+
timeout=20,
|
|
218
|
+
check=False,
|
|
219
|
+
)
|
|
220
|
+
fda_ok = probe.returncode == 0
|
|
221
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
222
|
+
fda_ok = False
|
|
223
|
+
if fda_ok:
|
|
224
|
+
console.print("[green]Messages database is readable (Full Disk Access OK).[/green]")
|
|
225
|
+
else:
|
|
226
|
+
console.print("[yellow]Cannot read the Messages database yet.[/yellow]")
|
|
227
|
+
console.print(
|
|
228
|
+
"Grant Full Disk Access to your terminal (System Settings → "
|
|
229
|
+
"Privacy & Security → Full Disk Access), then fully restart it. "
|
|
230
|
+
"See docs/adapters/imessage.md for the launchd case."
|
|
231
|
+
)
|
|
232
|
+
if not typer.confirm("Enable the adapter anyway (grant access later)?", default=True):
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
# Fail-closed allowlist: loop until we get at least one plausible handle,
|
|
236
|
+
# mirroring the Telegram wizard — an empty list means nobody is answered.
|
|
237
|
+
console.print(
|
|
238
|
+
"[dim]Allowed senders: phone numbers (+15551234567) or iMessage emails. "
|
|
239
|
+
"Only these people can talk to the agent.[/dim]"
|
|
240
|
+
)
|
|
241
|
+
while True:
|
|
242
|
+
raw = typer.prompt("Allowed sender (phone/email)").strip()
|
|
243
|
+
handle = canonical_handle(raw, imessage.region or "US")
|
|
244
|
+
if "@" in handle or (handle.startswith("+") and len(handle) >= 9):
|
|
245
|
+
break
|
|
246
|
+
console.print("[yellow]That doesn't look like a phone/email. Ctrl-C to abort.[/yellow]")
|
|
247
|
+
if handle not in imessage.allowed_senders:
|
|
248
|
+
imessage.allowed_senders.append(handle)
|
|
249
|
+
while typer.confirm("Add another allowed sender?", default=False):
|
|
250
|
+
raw = typer.prompt("Allowed sender (phone/email)").strip()
|
|
251
|
+
extra = canonical_handle(raw, imessage.region or "US")
|
|
252
|
+
if ("@" in extra or (extra.startswith("+") and len(extra) >= 9)) and (
|
|
253
|
+
extra not in imessage.allowed_senders
|
|
254
|
+
):
|
|
255
|
+
imessage.allowed_senders.append(extra)
|
|
256
|
+
else:
|
|
257
|
+
console.print("[yellow]Skipped (invalid or duplicate).[/yellow]")
|
|
258
|
+
|
|
259
|
+
imessage.allow_groups = typer.confirm("Allow group chats?", default=False)
|
|
260
|
+
imessage.enabled = True
|
|
261
|
+
|
|
262
|
+
# Typing indicators need the optional imsg IMCore bridge; report status
|
|
263
|
+
# so the operator isn't surprised either way. Never suggest disabling SIP.
|
|
264
|
+
typing_state = "unavailable (adapter will silently skip typing bubbles)"
|
|
265
|
+
try:
|
|
266
|
+
status = subprocess.run(
|
|
267
|
+
[binary, "status", "--json"], capture_output=True, timeout=10, check=False
|
|
268
|
+
)
|
|
269
|
+
if status.returncode == 0 and b"true" in status.stdout.lower():
|
|
270
|
+
typing_state = "bridge detected — typing bubbles active"
|
|
271
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
272
|
+
pass
|
|
273
|
+
console.print(f"[dim]Typing indicators: {typing_state}.[/dim]")
|
|
274
|
+
console.print(
|
|
275
|
+
"[dim]Note: the first send will trigger a macOS Automation prompt "
|
|
276
|
+
"('control Messages') — approve it once.[/dim]"
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
189
280
|
# --------------------------------------------------------------------------- status
|
|
190
281
|
|
|
191
282
|
|
|
@@ -74,36 +74,12 @@ class McpServer(BaseModel):
|
|
|
74
74
|
tool_count: int = 0
|
|
75
75
|
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
allowed_senders: list[str] = Field(default_factory=list)
|
|
84
|
-
allowed_chats: list[str] = Field(default_factory=list)
|
|
85
|
-
drop_pending_updates: bool = True
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
class DesktopAdapter(BaseModel):
|
|
89
|
-
"""Desktop messaging adapter settings for a served agent."""
|
|
90
|
-
|
|
91
|
-
model_config = ConfigDict(extra="ignore")
|
|
92
|
-
enabled: bool = False
|
|
93
|
-
host: str = "127.0.0.1"
|
|
94
|
-
port: int = 8765
|
|
95
|
-
auth_token: str = ""
|
|
96
|
-
allowed_senders: list[str] = Field(default_factory=lambda: ["desktop"])
|
|
97
|
-
allowed_conversations: list[str] = Field(default_factory=list)
|
|
98
|
-
speak_replies: bool = False
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
class Adapters(BaseModel):
|
|
102
|
-
"""Container of per-adapter settings for a served agent."""
|
|
103
|
-
|
|
104
|
-
model_config = ConfigDict(extra="ignore")
|
|
105
|
-
telegram: TelegramAdapter = Field(default_factory=TelegramAdapter)
|
|
106
|
-
desktop: DesktopAdapter = Field(default_factory=DesktopAdapter)
|
|
77
|
+
# Per-adapter serve settings, keyed by adapter name (``telegram``, ``desktop``,
|
|
78
|
+
# ``imessage``, …). The agent file stores raw TOML tables; validation against
|
|
79
|
+
# each adapter's typed settings class happens in ``cadecoder.serve.config``
|
|
80
|
+
# via the adapter registry, so adding a new adapter never requires touching
|
|
81
|
+
# this module.
|
|
82
|
+
AdapterSections = dict[str, dict[str, Any]]
|
|
107
83
|
|
|
108
84
|
|
|
109
85
|
class AgentConfig(BaseModel):
|
|
@@ -133,7 +109,7 @@ class AgentConfig(BaseModel):
|
|
|
133
109
|
workspace: str | None = None
|
|
134
110
|
log_level: str = "info"
|
|
135
111
|
allow_spawn: bool = False
|
|
136
|
-
adapters:
|
|
112
|
+
adapters: AdapterSections = Field(default_factory=dict)
|
|
137
113
|
|
|
138
114
|
# Guardrails (event-keyed table, merged with global cade.toml hooks at
|
|
139
115
|
# runtime). ``disable_hooks`` opts out of named global hooks.
|
|
@@ -10,6 +10,7 @@ to the registry, never to concrete adapter modules.
|
|
|
10
10
|
# drop a module in this directory and add one import line here.
|
|
11
11
|
from cadecoder.serve.adapters import (
|
|
12
12
|
desktop, # noqa: F401
|
|
13
|
+
imessage, # noqa: F401
|
|
13
14
|
telegram, # noqa: F401
|
|
14
15
|
)
|
|
15
16
|
from cadecoder.serve.adapters.base import (
|