cade-cli 0.15.7__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.
Files changed (166) hide show
  1. {cade_cli-0.15.7 → cade_cli-0.15.8}/PKG-INFO +1 -1
  2. {cade_cli-0.15.7 → cade_cli-0.15.8}/pyproject.toml +1 -1
  3. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/cli/commands/agents.py +11 -16
  4. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/cli/commands/serve.py +93 -2
  5. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/core/agent.py +7 -31
  6. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/adapters/__init__.py +1 -0
  7. cade_cli-0.15.8/src/cadecoder/serve/adapters/imessage.py +648 -0
  8. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/config.py +40 -11
  9. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/observability.py +3 -0
  10. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/templates/serve/serve.toml.example +16 -0
  11. {cade_cli-0.15.7 → cade_cli-0.15.8}/.gitignore +0 -0
  12. {cade_cli-0.15.7 → cade_cli-0.15.8}/LICENSE +0 -0
  13. {cade_cli-0.15.7 → cade_cli-0.15.8}/README.md +0 -0
  14. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/__init__.py +0 -0
  15. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/__main__.py +0 -0
  16. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/_metadata.py +0 -0
  17. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/config.py +0 -0
  18. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/errors.py +0 -0
  19. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/resources.py +0 -0
  20. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/server.py +0 -0
  21. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/tools/__init__.py +0 -0
  22. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/tools/_read_cache.py +0 -0
  23. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/tools/agents.py +0 -0
  24. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/tools/context.py +0 -0
  25. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/tools/filesystem.py +0 -0
  26. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/tools/git.py +0 -0
  27. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/tools/notifications.py +0 -0
  28. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/tools/questions.py +0 -0
  29. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/tools/results.py +0 -0
  30. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/tools/schemas.py +0 -0
  31. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/tools/search.py +0 -0
  32. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/tools/shell.py +0 -0
  33. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/tools/snippets.py +0 -0
  34. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/tools/tasks.py +0 -0
  35. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/tools/web_fetch.py +0 -0
  36. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/tools/web_search.py +0 -0
  37. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/utils.py +0 -0
  38. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/web/__init__.py +0 -0
  39. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/web/arcade_client.py +0 -0
  40. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/web/cache.py +0 -0
  41. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/web/extract.py +0 -0
  42. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/web/markdown.py +0 -0
  43. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/web/preapproved.py +0 -0
  44. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/web/prompts.py +0 -0
  45. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/web/scrape.py +0 -0
  46. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/web/search/__init__.py +0 -0
  47. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/web/search/arcade.py +0 -0
  48. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/web/search/base.py +0 -0
  49. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cade_mcp_local/web/search/http.py +0 -0
  50. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/__init__.py +0 -0
  51. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/ai/__init__.py +0 -0
  52. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/ai/prompts.py +0 -0
  53. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/cli/__init__.py +0 -0
  54. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/cli/app.py +0 -0
  55. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/cli/auth.py +0 -0
  56. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/cli/commands/__init__.py +0 -0
  57. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/cli/commands/account.py +0 -0
  58. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/cli/commands/auth.py +0 -0
  59. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/cli/commands/chat.py +0 -0
  60. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/cli/commands/cron.py +0 -0
  61. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/cli/commands/hooks.py +0 -0
  62. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/cli/commands/mcp.py +0 -0
  63. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/cli/commands/mem.py +0 -0
  64. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/cli/commands/tasks.py +0 -0
  65. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/cli/commands/thread.py +0 -0
  66. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/cli/commands/tools.py +0 -0
  67. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/cli/onboarding.py +0 -0
  68. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/core/__init__.py +0 -0
  69. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/core/comms.py +0 -0
  70. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/core/config.py +0 -0
  71. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/core/constants.py +0 -0
  72. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/core/errors.py +0 -0
  73. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/core/frozen.py +0 -0
  74. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/core/git.py +0 -0
  75. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/core/inbox.py +0 -0
  76. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/core/logging.py +0 -0
  77. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/core/memory.py +0 -0
  78. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/core/model_specs.py +0 -0
  79. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/core/names.py +0 -0
  80. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/core/paths.py +0 -0
  81. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/core/runtime.py +0 -0
  82. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/core/secrets.py +0 -0
  83. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/core/spawn.py +0 -0
  84. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/core/types.py +0 -0
  85. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/execution/__init__.py +0 -0
  86. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/execution/context_window.py +0 -0
  87. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/execution/mcp_schema_index.py +0 -0
  88. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/execution/orchestrator.py +0 -0
  89. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/execution/parallel.py +0 -0
  90. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/execution/tool_result_store.py +0 -0
  91. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/execution/tool_schema_cache.py +0 -0
  92. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/hooks/__init__.py +0 -0
  93. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/hooks/config.py +0 -0
  94. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/hooks/engine.py +0 -0
  95. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/hooks/executors/__init__.py +0 -0
  96. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/hooks/executors/callback.py +0 -0
  97. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/hooks/executors/command.py +0 -0
  98. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/hooks/executors/http.py +0 -0
  99. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/hooks/executors/prompt.py +0 -0
  100. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/hooks/executors/ssrf_guard.py +0 -0
  101. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/hooks/matchers.py +0 -0
  102. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/hooks/registry.py +0 -0
  103. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/hooks/types.py +0 -0
  104. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/providers/__init__.py +0 -0
  105. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/providers/anthropic.py +0 -0
  106. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/providers/base.py +0 -0
  107. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/providers/ollama.py +0 -0
  108. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/providers/openai.py +0 -0
  109. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/__init__.py +0 -0
  110. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/adapters/base.py +0 -0
  111. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/adapters/desktop.py +0 -0
  112. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/adapters/registry.py +0 -0
  113. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/adapters/stub.py +0 -0
  114. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/adapters/telegram.py +0 -0
  115. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/allowlist.py +0 -0
  116. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/chunker.py +0 -0
  117. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/commands.py +0 -0
  118. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/cron.py +0 -0
  119. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/daemon.py +0 -0
  120. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/elicitation.py +0 -0
  121. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/format.py +0 -0
  122. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/install.py +0 -0
  123. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/lock.py +0 -0
  124. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/owner.py +0 -0
  125. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/progress_sink.py +0 -0
  126. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/router.py +0 -0
  127. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/secrets.py +0 -0
  128. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/services.py +0 -0
  129. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/sinks.py +0 -0
  130. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/serve/worker.py +0 -0
  131. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/storage/__init__.py +0 -0
  132. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/storage/threads.py +0 -0
  133. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/tasks/__init__.py +0 -0
  134. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/tasks/lock.py +0 -0
  135. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/tasks/scheduler.py +0 -0
  136. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/tasks/store.py +0 -0
  137. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/tasks/types.py +0 -0
  138. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/templates/login_failed.html +0 -0
  139. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/templates/login_success.html +0 -0
  140. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/templates/serve/launchd.plist.tmpl +0 -0
  141. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/templates/serve/systemd.service.tmpl +0 -0
  142. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/templates/styles.css +0 -0
  143. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/tools/__init__.py +0 -0
  144. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/tools/manager/__init__.py +0 -0
  145. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/tools/manager/_request_ctx.py +0 -0
  146. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/tools/manager/base.py +0 -0
  147. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/tools/manager/composite.py +0 -0
  148. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/tools/manager/config.py +0 -0
  149. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/tools/manager/mcp.py +0 -0
  150. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/tools/search/__init__.py +0 -0
  151. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/tools/search/discovered.py +0 -0
  152. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/tools/search/scoring.py +0 -0
  153. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/tools/search/service.py +0 -0
  154. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/ui/__init__.py +0 -0
  155. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/ui/completion.py +0 -0
  156. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/ui/display.py +0 -0
  157. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/ui/elicitation.py +0 -0
  158. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/ui/input.py +0 -0
  159. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/ui/session.py +0 -0
  160. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/ui/slash.py +0 -0
  161. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/voice/__init__.py +0 -0
  162. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/voice/audio.py +0 -0
  163. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/voice/cleanup.py +0 -0
  164. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/voice/session.py +0 -0
  165. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/voice/stt.py +0 -0
  166. {cade_cli-0.15.7 → cade_cli-0.15.8}/src/cadecoder/voice/tts.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cade-cli
3
- Version: 0.15.7
3
+ Version: 0.15.8
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cade-cli"
3
- version = "0.15.7"
3
+ version = "0.15.8"
4
4
  description = "Cade - The CLI Agent from Arcade.dev"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -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:
@@ -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: persist Telegram token and write the agent's serve config."""
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
- class TelegramAdapter(BaseModel):
78
- """Telegram messaging adapter settings for a served agent."""
79
-
80
- model_config = ConfigDict(extra="ignore")
81
- enabled: bool = False
82
- secret_ref: str = ""
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: Adapters = Field(default_factory=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 (