agent-trellis 0.8.0 → 0.9.0

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 (251) hide show
  1. package/README.md +45 -206
  2. package/dist/adapters/zcode.d.ts +21 -0
  3. package/dist/adapters/zcode.js +234 -0
  4. package/dist/cli.js +119 -10
  5. package/dist/commands/doctor.js +2 -0
  6. package/dist/commands/init.d.ts +1 -1
  7. package/dist/commands/init.js +4 -1
  8. package/dist/commands/mcp.d.ts +56 -14
  9. package/dist/commands/mcp.js +105 -28
  10. package/dist/commands/mcpGateway.js +2 -1
  11. package/dist/commands/mcpImport.d.ts +21 -2
  12. package/dist/commands/mcpImport.js +30 -7
  13. package/dist/commands/memory.d.ts +13 -1
  14. package/dist/commands/memory.js +15 -0
  15. package/dist/commands/migrate.js +4 -1
  16. package/dist/commands/onboard.js +15 -4
  17. package/dist/commands/remoteSkill.d.ts +90 -0
  18. package/dist/commands/remoteSkill.js +392 -0
  19. package/dist/commands/rollback.js +55 -1
  20. package/dist/commands/secretsAudit.js +4 -1
  21. package/dist/commands/skill.d.ts +37 -6
  22. package/dist/commands/skill.js +73 -15
  23. package/dist/commands/sync.js +3 -1
  24. package/dist/core/adapter.d.ts +11 -1
  25. package/dist/core/canonical.d.ts +17 -1
  26. package/dist/core/canonical.js +54 -0
  27. package/dist/core/types.d.ts +1 -1
  28. package/dist/core/types.js +1 -0
  29. package/dist/lib/agentBridge.d.ts +169 -0
  30. package/dist/lib/agentBridge.js +367 -0
  31. package/dist/lib/agentBridgeProvider.d.ts +14 -0
  32. package/dist/lib/agentBridgeProvider.js +95 -0
  33. package/dist/lib/backup.d.ts +42 -0
  34. package/dist/lib/backup.js +36 -3
  35. package/dist/lib/chatAgents.d.ts +42 -0
  36. package/dist/lib/chatAgents.js +59 -0
  37. package/dist/lib/dirDigest.d.ts +11 -0
  38. package/dist/lib/dirDigest.js +52 -0
  39. package/dist/lib/envVarNames.d.ts +3 -0
  40. package/dist/lib/envVarNames.js +19 -0
  41. package/dist/lib/installAgent.d.ts +1 -1
  42. package/dist/lib/installAgent.js +1 -1
  43. package/dist/lib/mcpMigrateRead.d.ts +1 -0
  44. package/dist/lib/mcpMigrateRead.js +19 -1
  45. package/dist/lib/realHomeSnapshot.js +5 -0
  46. package/dist/lib/remoteSkillLock.d.ts +26 -0
  47. package/dist/lib/remoteSkillLock.js +88 -0
  48. package/dist/lib/remoteSkillSource.d.ts +45 -0
  49. package/dist/lib/remoteSkillSource.js +206 -0
  50. package/dist/lib/runtimeControlProvider.js +2 -0
  51. package/dist/lib/taskStore.js +1 -1
  52. package/dist/lib/terminalPicker.js +33 -6
  53. package/dist/lib/zcodeOwnership.d.ts +17 -0
  54. package/dist/lib/zcodeOwnership.js +37 -0
  55. package/dist/pi-bridge/bundle.js +2 -1
  56. package/dist/probes/codex.d.ts +7 -0
  57. package/dist/probes/codex.js +37 -5
  58. package/dist/probes/zcode.d.ts +32 -0
  59. package/dist/probes/zcode.js +140 -0
  60. package/docs/architecture.md +9 -5
  61. package/docs/assets/brand-v3/trellis-mark-v3-a.png +0 -0
  62. package/docs/assets/brand-v3/trellis-mark-v3-a.svg +10 -0
  63. package/docs/assets/poster-series-v3/README.md +60 -0
  64. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-en.png +0 -0
  65. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-en.svg +51 -0
  66. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-zh.png +0 -0
  67. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-zh.svg +51 -0
  68. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-en.png +0 -0
  69. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-en.svg +48 -0
  70. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-zh.png +0 -0
  71. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-zh.svg +48 -0
  72. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-en.png +0 -0
  73. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-en.svg +56 -0
  74. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-zh.png +0 -0
  75. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-zh.svg +56 -0
  76. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-en.png +0 -0
  77. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-en.svg +47 -0
  78. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-zh.png +0 -0
  79. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-zh.svg +47 -0
  80. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-en.png +0 -0
  81. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-en.svg +51 -0
  82. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-zh.png +0 -0
  83. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-zh.svg +51 -0
  84. package/docs/getting-started.md +244 -14
  85. package/docs/overview.md +6 -3
  86. package/docs/overview.zh-CN.md +5 -3
  87. package/docs/roadmap.md +2 -1
  88. package/docs/video/README.md +76 -0
  89. package/docs/video/build/audio/01-opening.aiff +0 -0
  90. package/docs/video/build/audio/01-opening.mp3 +0 -0
  91. package/docs/video/build/audio/02-pain.aiff +0 -0
  92. package/docs/video/build/audio/02-pain.mp3 +0 -0
  93. package/docs/video/build/audio/03-solution.aiff +0 -0
  94. package/docs/video/build/audio/03-solution.mp3 +0 -0
  95. package/docs/video/build/audio/04-install.aiff +0 -0
  96. package/docs/video/build/audio/04-install.mp3 +0 -0
  97. package/docs/video/build/audio/04-setup.mp3 +0 -0
  98. package/docs/video/build/audio/05-dryrun.mp3 +0 -0
  99. package/docs/video/build/audio/05-init.aiff +0 -0
  100. package/docs/video/build/audio/05-init.mp3 +0 -0
  101. package/docs/video/build/audio/05-interactive.mp3 +0 -0
  102. package/docs/video/build/audio/06-dryrun.aiff +0 -0
  103. package/docs/video/build/audio/06-dryrun.mp3 +0 -0
  104. package/docs/video/build/audio/06-onboard.mp3 +0 -0
  105. package/docs/video/build/audio/06-run.mp3 +0 -0
  106. package/docs/video/build/audio/07-onboard.aiff +0 -0
  107. package/docs/video/build/audio/07-onboard.mp3 +0 -0
  108. package/docs/video/build/audio/07-verify.mp3 +0 -0
  109. package/docs/video/build/audio/08-closing.mp3 +0 -0
  110. package/docs/video/build/audio/08-doctor.aiff +0 -0
  111. package/docs/video/build/audio/08-doctor.mp3 +0 -0
  112. package/docs/video/build/audio/09-rollback.aiff +0 -0
  113. package/docs/video/build/audio/09-rollback.mp3 +0 -0
  114. package/docs/video/build/audio/10-principles.aiff +0 -0
  115. package/docs/video/build/audio/10-principles.mp3 +0 -0
  116. package/docs/video/build/audio/11-closing.aiff +0 -0
  117. package/docs/video/build/audio/11-closing.mp3 +0 -0
  118. package/docs/video/build/frames/01-opening-0.png +0 -0
  119. package/docs/video/build/frames/02-pain-1.png +0 -0
  120. package/docs/video/build/frames/02-pain-2.png +0 -0
  121. package/docs/video/build/frames/03-solution-3.png +0 -0
  122. package/docs/video/build/frames/04-install-4.png +0 -0
  123. package/docs/video/build/frames/04-setup-4.png +0 -0
  124. package/docs/video/build/frames/04-setup-5.png +0 -0
  125. package/docs/video/build/frames/05-dryrun-6.png +0 -0
  126. package/docs/video/build/frames/05-init-5.png +0 -0
  127. package/docs/video/build/frames/05-interactive-5.png +0 -0
  128. package/docs/video/build/frames/05-interactive-6.png +0 -0
  129. package/docs/video/build/frames/06-dryrun-6.png +0 -0
  130. package/docs/video/build/frames/06-dryrun-7.png +0 -0
  131. package/docs/video/build/frames/06-onboard-7.png +0 -0
  132. package/docs/video/build/frames/06-onboard-8.png +0 -0
  133. package/docs/video/build/frames/06-run-7.png +0 -0
  134. package/docs/video/build/frames/06-run-8.png +0 -0
  135. package/docs/video/build/frames/07-onboard-10.png +0 -0
  136. package/docs/video/build/frames/07-onboard-7.png +0 -0
  137. package/docs/video/build/frames/07-onboard-8.png +0 -0
  138. package/docs/video/build/frames/07-onboard-9.png +0 -0
  139. package/docs/video/build/frames/07-verify-10.png +0 -0
  140. package/docs/video/build/frames/07-verify-8.png +0 -0
  141. package/docs/video/build/frames/07-verify-9.png +0 -0
  142. package/docs/video/build/frames/08-closing-10.png +0 -0
  143. package/docs/video/build/frames/08-closing-11.png +0 -0
  144. package/docs/video/build/frames/08-doctor-11.png +0 -0
  145. package/docs/video/build/frames/08-doctor-9.png +0 -0
  146. package/docs/video/build/frames/09-rollback-10.png +0 -0
  147. package/docs/video/build/frames/09-rollback-12.png +0 -0
  148. package/docs/video/build/frames/10-principles-11.png +0 -0
  149. package/docs/video/build/frames/10-principles-13.png +0 -0
  150. package/docs/video/build/frames/11-closing-12.png +0 -0
  151. package/docs/video/build/frames/11-closing-14.png +0 -0
  152. package/docs/video/build/segments/01-opening.mp4 +0 -0
  153. package/docs/video/build/segments/01-opening.txt +3 -0
  154. package/docs/video/build/segments/02-pain.mp4 +0 -0
  155. package/docs/video/build/segments/02-pain.txt +5 -0
  156. package/docs/video/build/segments/03-solution.mp4 +0 -0
  157. package/docs/video/build/segments/03-solution.txt +3 -0
  158. package/docs/video/build/segments/04-install.mp4 +0 -0
  159. package/docs/video/build/segments/04-install.txt +3 -0
  160. package/docs/video/build/segments/04-setup.mp4 +0 -0
  161. package/docs/video/build/segments/04-setup.txt +3 -0
  162. package/docs/video/build/segments/05-dryrun.mp4 +0 -0
  163. package/docs/video/build/segments/05-dryrun.txt +3 -0
  164. package/docs/video/build/segments/05-init.mp4 +0 -0
  165. package/docs/video/build/segments/05-init.txt +3 -0
  166. package/docs/video/build/segments/05-interactive.mp4 +0 -0
  167. package/docs/video/build/segments/05-interactive.txt +5 -0
  168. package/docs/video/build/segments/06-dryrun.mp4 +0 -0
  169. package/docs/video/build/segments/06-dryrun.txt +3 -0
  170. package/docs/video/build/segments/06-onboard.mp4 +0 -0
  171. package/docs/video/build/segments/06-onboard.txt +5 -0
  172. package/docs/video/build/segments/06-run.mp4 +0 -0
  173. package/docs/video/build/segments/06-run.txt +3 -0
  174. package/docs/video/build/segments/07-onboard.mp4 +0 -0
  175. package/docs/video/build/segments/07-onboard.txt +5 -0
  176. package/docs/video/build/segments/07-verify.mp4 +0 -0
  177. package/docs/video/build/segments/07-verify.txt +5 -0
  178. package/docs/video/build/segments/08-closing.mp4 +0 -0
  179. package/docs/video/build/segments/08-closing.txt +3 -0
  180. package/docs/video/build/segments/08-doctor.mp4 +0 -0
  181. package/docs/video/build/segments/08-doctor.txt +3 -0
  182. package/docs/video/build/segments/09-rollback.mp4 +0 -0
  183. package/docs/video/build/segments/09-rollback.txt +3 -0
  184. package/docs/video/build/segments/10-principles.mp4 +0 -0
  185. package/docs/video/build/segments/10-principles.txt +3 -0
  186. package/docs/video/build/segments/11-closing.mp4 +0 -0
  187. package/docs/video/build/segments/11-closing.txt +3 -0
  188. package/docs/video/build/segments/final.txt +8 -0
  189. package/docs/video/build-ai/audio/01-hook.mp3 +0 -0
  190. package/docs/video/build-ai/audio/02-pain.mp3 +0 -0
  191. package/docs/video/build-ai/audio/03-solution.mp3 +0 -0
  192. package/docs/video/build-ai/audio/04-how.mp3 +0 -0
  193. package/docs/video/build-ai/audio/05-safe.mp3 +0 -0
  194. package/docs/video/build-ai/audio/06-closing.mp3 +0 -0
  195. package/docs/video/build-ai/clips/01-hook.mp4 +0 -0
  196. package/docs/video/build-ai/clips/02-pain.mp4 +0 -0
  197. package/docs/video/build-ai/clips/03-solution.mp4 +0 -0
  198. package/docs/video/build-ai/clips/04-how.mp4 +0 -0
  199. package/docs/video/build-ai/clips/05-safe.mp4 +0 -0
  200. package/docs/video/build-ai/clips/06-closing.mp4 +0 -0
  201. package/docs/video/build-ai/first-frame-02-pain.png +0 -0
  202. package/docs/video/build-ai/first-frame-03-solution.png +0 -0
  203. package/docs/video/build-ai/first-frame-04-how.png +0 -0
  204. package/docs/video/build-ai/first-frame-05-safe.png +0 -0
  205. package/docs/video/build-ai/segments/01-hook.caption.txt +2 -0
  206. package/docs/video/build-ai/segments/01-hook.mp4 +0 -0
  207. package/docs/video/build-ai/segments/02-pain.mp4 +0 -0
  208. package/docs/video/build-ai/segments/03-solution.mp4 +0 -0
  209. package/docs/video/build-ai/segments/04-how.mp4 +0 -0
  210. package/docs/video/build-ai/segments/05-safe.mp4 +0 -0
  211. package/docs/video/build-ai/segments/06-closing.mp4 +0 -0
  212. package/docs/video/build-ai/segments/cap-01-hook.png +0 -0
  213. package/docs/video/build-ai/segments/cap-02-pain.png +0 -0
  214. package/docs/video/build-ai/segments/cap-03-solution.png +0 -0
  215. package/docs/video/build-ai/segments/cap-04-how.png +0 -0
  216. package/docs/video/build-ai/segments/cap-05-safe.png +0 -0
  217. package/docs/video/build-ai/segments/cap-06-closing.png +0 -0
  218. package/docs/video/build-ai/segments/final.txt +6 -0
  219. package/docs/video/build-posters/audio/01-hook.mp3 +0 -0
  220. package/docs/video/build-posters/audio/02-pain.mp3 +0 -0
  221. package/docs/video/build-posters/audio/03-solution.mp3 +0 -0
  222. package/docs/video/build-posters/audio/04-how.mp3 +0 -0
  223. package/docs/video/build-posters/audio/05-safe.mp3 +0 -0
  224. package/docs/video/build-posters/audio/06-closing.mp3 +0 -0
  225. package/docs/video/build-posters/cap-01-hook.png +0 -0
  226. package/docs/video/build-posters/cap-02-pain.png +0 -0
  227. package/docs/video/build-posters/cap-03-solution.png +0 -0
  228. package/docs/video/build-posters/cap-04-how.png +0 -0
  229. package/docs/video/build-posters/cap-05-safe.png +0 -0
  230. package/docs/video/build-posters/cap-06-closing.png +0 -0
  231. package/docs/video/build-posters/card-01-hook.png +0 -0
  232. package/docs/video/build-posters/card-06-closing.png +0 -0
  233. package/docs/video/build-posters/segments/01-hook.mp4 +0 -0
  234. package/docs/video/build-posters/segments/02-pain.mp4 +0 -0
  235. package/docs/video/build-posters/segments/03-solution.mp4 +0 -0
  236. package/docs/video/build-posters/segments/04-how.mp4 +0 -0
  237. package/docs/video/build-posters/segments/05-safe.mp4 +0 -0
  238. package/docs/video/build-posters/segments/06-closing.mp4 +0 -0
  239. package/docs/video/build-posters/segments/final.txt +6 -0
  240. package/docs/video/generate-video-ai.mjs +334 -0
  241. package/docs/video/generate-video-posters.mjs +204 -0
  242. package/docs/video/generate-video.mjs +426 -0
  243. package/docs/video/outline.zh-CN.md +102 -0
  244. package/docs/video/trellis-demo-video-ai.mp4 +0 -0
  245. package/docs/video/trellis-demo-video.mp4 +0 -0
  246. package/docs/video/voiceover.zh-CN.md +49 -0
  247. package/package.json +5 -2
  248. package/schema/servers.example.yaml +1 -1
  249. package/docs/assets/agent-trellis-architecture-horizontal.svg +0 -80
  250. package/docs/assets/agent-trellis-architecture-vertical.svg +0 -82
  251. package/docs/assets/trellis-mark.svg +0 -12
package/dist/cli.js CHANGED
@@ -16,12 +16,13 @@ import { runMcpAuth } from "./commands/mcpAuth.js";
16
16
  import { runSecretsAudit } from "./commands/secretsAudit.js";
17
17
  import { runRollback } from "./commands/rollback.js";
18
18
  import { runSkillList, runSkillAdd, runSkillRemove, runSkillUpdateBuiltin } from "./commands/skill.js";
19
- import { runMemoryExtraction, runMemorySync } from "./commands/memory.js";
19
+ import { runRemoteSkillAdd, runRemoteSkillList, runRemoteSkillUpdate } from "./commands/remoteSkill.js";
20
+ import { collectMemoryList, runMemoryExtraction, runMemorySync } from "./commands/memory.js";
20
21
  import { parseManageArgs, runManage } from "./commands/manage.js";
21
22
  import { runKimi } from "./commands/kimi.js";
22
23
  import { TRELLIS_VERSION } from "./lib/cliMetadata.js";
23
24
  import { parseSyncArgs } from "./lib/syncArgs.js";
24
- const KNOWN_COMMANDS = ["onboard", "init", "migrate", "doctor", "sync", "mcp", "mcp-gateway", "mcp-runtime", "skill", "memory", "manage", "kimi", "secrets", "rollback"];
25
+ const KNOWN_COMMANDS = ["onboard", "init", "migrate", "doctor", "sync", "mcp", "mcp-gateway", "mcp-runtime", "skill", "add", "update", "memory", "manage", "kimi", "secrets", "rollback"];
25
26
  function printUsage() {
26
27
  console.log(`trellis - a single source of capability for every coding agent
27
28
 
@@ -90,6 +91,22 @@ Commands:
90
91
  Distribute skills/instructions to each agent (omit target for both)
91
92
  --dry-run preview the plan, write nothing
92
93
  --json machine-readable output, no report text
94
+ add <github-source> --skill <name>
95
+ Import one GitHub-hosted Skill into ~/.trellis/skills/, record
96
+ immutable provenance, then sync it only to managed Agents.
97
+ --skill, -s <name> required unless --list
98
+ --branch <ref> Git branch, tag, or ref; default is
99
+ the source repository's default branch
100
+ --agent, -a <ids|*> restrict delivery to managed Agent ids
101
+ --list show discovered Skills without import
102
+ --global, --yes accepted skills-CLI compatibility flags
103
+ --dry-run fetch and preview; write nothing
104
+ --json machine-readable output
105
+ update [skills...]
106
+ Safely refresh remotely tracked Skills. Refuses to overwrite a
107
+ canonical Skill edited since its recorded import.
108
+ --dry-run preview writes only
109
+ --json machine-readable output
93
110
  mcp sync Distribute MCP servers to each agent's native config
94
111
  (create/repair, plus ownership-ledger-gated removal — an
95
112
  entry is only ever removed when it's still exactly what
@@ -130,8 +147,9 @@ Commands:
130
147
  upstream capabilities; existing mcp-gateway configs remain valid
131
148
  as a compatibility alias
132
149
  mcp remove <name>
133
- Remove a canonical MCP server (canonical-side only — does
134
- not touch any agent's already-synced native config)
150
+ Remove a canonical MCP server, then run the same sync as
151
+ mcp sync so every managed agent's Trellis-owned entry for
152
+ it is removed too
135
153
  --dry-run preview the plan, write nothing
136
154
  --json machine-readable output, no report text
137
155
  skill list
@@ -202,6 +220,31 @@ See docs/roadmap.md for what's built vs. planned.`);
202
220
  function printVersion() {
203
221
  console.log(TRELLIS_VERSION);
204
222
  }
223
+ function remoteOptionValues(args, flags) {
224
+ const values = [];
225
+ const positions = new Set();
226
+ for (let index = 0; index < args.length; index += 1) {
227
+ const arg = args[index];
228
+ const matching = flags.find((flag) => arg === flag || arg.startsWith(`${flag}=`));
229
+ if (!matching)
230
+ continue;
231
+ positions.add(index);
232
+ if (arg.startsWith(`${matching}=`)) {
233
+ values.push(arg.slice(matching.length + 1));
234
+ continue;
235
+ }
236
+ const value = args[index + 1];
237
+ if (!value || value.startsWith("-"))
238
+ return { values, positions, error: `${matching} needs a value` };
239
+ positions.add(index + 1);
240
+ values.push(value);
241
+ index += 1;
242
+ }
243
+ return { values, positions };
244
+ }
245
+ function remotePositionals(args, consumed) {
246
+ return args.filter((arg, index) => !consumed.has(index) && !arg.startsWith("-"));
247
+ }
205
248
  async function main(argv) {
206
249
  const [command, ...rest] = argv;
207
250
  if (!command || command === "--help" || command === "-h") {
@@ -310,6 +353,57 @@ async function main(argv) {
310
353
  process.exitCode = exitCode;
311
354
  return;
312
355
  }
356
+ if (command === "add") {
357
+ const skill = remoteOptionValues(rest, ["--skill", "-s"]);
358
+ const branch = remoteOptionValues(rest, ["--branch"]);
359
+ const agents = remoteOptionValues(rest, ["--agent", "-a"]);
360
+ const consumed = new Set([...skill.positions, ...branch.positions, ...agents.positions]);
361
+ const positionals = remotePositionals(rest, consumed);
362
+ const json = rest.includes("--json");
363
+ const prohibited = rest.find((arg) => /^(--copy|--project|--local)(?:=|$)/.test(arg));
364
+ if (prohibited) {
365
+ const detail = "Trellis remote Skills are always imported into ~/.trellis/skills; --copy and project-local installation are not supported.";
366
+ if (json)
367
+ console.log(JSON.stringify({ action: "invalid-options", detail }, null, 2));
368
+ else
369
+ console.error(detail);
370
+ process.exitCode = 1;
371
+ return;
372
+ }
373
+ if (skill.error || branch.error || agents.error || positionals.length !== 1) {
374
+ const detail = skill.error ?? branch.error ?? agents.error ?? "Usage: trellis add <github-source> --skill <name>";
375
+ if (json)
376
+ console.log(JSON.stringify({ action: "invalid-options", detail }, null, 2));
377
+ else
378
+ console.error(detail);
379
+ process.exitCode = 1;
380
+ return;
381
+ }
382
+ const source = positionals[0];
383
+ if (rest.includes("--list")) {
384
+ process.exitCode = (await runRemoteSkillList(source, { branch: branch.values[0], json })).exitCode;
385
+ return;
386
+ }
387
+ if (skill.values.length !== 1 || branch.values.length > 1) {
388
+ const detail = skill.values.length !== 1 ? "Use exactly one --skill value" : "Use at most one --branch value";
389
+ if (json)
390
+ console.log(JSON.stringify({ action: "invalid-options", detail }, null, 2));
391
+ else
392
+ console.error(detail);
393
+ process.exitCode = 1;
394
+ return;
395
+ }
396
+ process.exitCode = (await runRemoteSkillAdd(source, skill.values[0], {
397
+ branch: branch.values[0], agents: agents.values, dryRun: rest.includes("--dry-run"), json,
398
+ })).exitCode;
399
+ return;
400
+ }
401
+ if (command === "update") {
402
+ const json = rest.includes("--json");
403
+ const names = rest.filter((arg) => !arg.startsWith("-"));
404
+ process.exitCode = (await runRemoteSkillUpdate(names, { dryRun: rest.includes("--dry-run"), json })).exitCode;
405
+ return;
406
+ }
313
407
  if (command === "mcp") {
314
408
  const [subcommand, ...mcpRest] = rest;
315
409
  const json = mcpRest.includes("--json");
@@ -325,12 +419,12 @@ async function main(argv) {
325
419
  }
326
420
  if (subcommand === "add") {
327
421
  const [name] = mcpRest;
328
- process.exitCode = runMcpAdd(name, parseMcpAddArgs(mcpRest), { json, dryRun }).exitCode;
422
+ process.exitCode = (await runMcpAdd(name, parseMcpAddArgs(mcpRest), { json, dryRun })).exitCode;
329
423
  return;
330
424
  }
331
425
  if (subcommand === "import") {
332
426
  const [file] = mcpRest;
333
- process.exitCode = runMcpImport(file, { json, dryRun }).exitCode;
427
+ process.exitCode = (await runMcpImport(file, { json, dryRun })).exitCode;
334
428
  return;
335
429
  }
336
430
  if (subcommand === "set") {
@@ -347,7 +441,7 @@ async function main(argv) {
347
441
  process.exitCode = 1;
348
442
  return;
349
443
  }
350
- process.exitCode = runMcpRemove(name, { json, dryRun }).exitCode;
444
+ process.exitCode = (await runMcpRemove(name, { json, dryRun })).exitCode;
351
445
  return;
352
446
  }
353
447
  if (subcommand === "auth") {
@@ -382,7 +476,7 @@ async function main(argv) {
382
476
  process.exitCode = 1;
383
477
  return;
384
478
  }
385
- process.exitCode = runSkillAdd(name, from, { json, dryRun }).exitCode;
479
+ process.exitCode = (await runSkillAdd(name, from, { json, dryRun })).exitCode;
386
480
  return;
387
481
  }
388
482
  if (subcommand === "remove") {
@@ -392,7 +486,7 @@ async function main(argv) {
392
486
  process.exitCode = 1;
393
487
  return;
394
488
  }
395
- process.exitCode = runSkillRemove(name, { json, dryRun }).exitCode;
489
+ process.exitCode = (await runSkillRemove(name, { json, dryRun })).exitCode;
396
490
  return;
397
491
  }
398
492
  if (subcommand === "update-builtin") {
@@ -407,6 +501,21 @@ async function main(argv) {
407
501
  const [subcommand, ...memoryRest] = rest;
408
502
  const json = memoryRest.includes("--json");
409
503
  const dryRun = memoryRest.includes("--dry-run");
504
+ if (subcommand === "list") {
505
+ const entries = collectMemoryList();
506
+ if (json) {
507
+ console.log(JSON.stringify(entries, null, 2));
508
+ }
509
+ else if (entries.length === 0) {
510
+ console.log("No memories in canonical source yet.");
511
+ }
512
+ else {
513
+ for (const { name, scope } of entries) {
514
+ console.log(`${name} — ${scope.length > 0 ? scope.join(", ") : "(no managed agent reaches it)"}`);
515
+ }
516
+ }
517
+ return;
518
+ }
410
519
  if (subcommand === "sync") {
411
520
  process.exitCode = runMemorySync({ json, dryRun }).exitCode;
412
521
  return;
@@ -415,7 +524,7 @@ async function main(argv) {
415
524
  process.exitCode = runMemoryExtraction({ json, dryRun }).exitCode;
416
525
  return;
417
526
  }
418
- console.error(`Unknown memory subcommand: ${subcommand ?? "(none)"}\nUsage: trellis memory sync|extract\n`);
527
+ console.error(`Unknown memory subcommand: ${subcommand ?? "(none)"}\nUsage: trellis memory list|sync|extract\n`);
419
528
  process.exitCode = 1;
420
529
  return;
421
530
  }
@@ -10,6 +10,7 @@ import * as codexProbe from "../probes/codex.js";
10
10
  import * as kiroProbe from "../probes/kiro.js";
11
11
  import * as piProbe from "../probes/pi.js";
12
12
  import * as kimiCodeProbe from "../probes/kimi-code.js";
13
+ import * as zcodeProbe from "../probes/zcode.js";
13
14
  import { loadCanonicalSource } from "../core/canonical.js";
14
15
  import { GATEWAY_ENTRY_NAME, RUNTIME_ENTRY_NAME, resolveMcpPlan } from "../adapters/mcpPlan.js";
15
16
  import { ALL_AGENTS } from "../core/types.js";
@@ -76,6 +77,7 @@ export async function collectDoctorReport(homeDir = homedir(), knownHostInjected
76
77
  { agent: "kiro", run: () => kiroProbe.probe(homeDir, { probeMcp }) },
77
78
  { agent: "pi", run: () => piProbe.probe(homeDir) },
78
79
  { agent: "kimi-code", run: () => kimiCodeProbe.probe(homeDir, { probeMcp }) },
80
+ { agent: "zcode", run: () => zcodeProbe.probe(homeDir, { probeMcp }) },
79
81
  ];
80
82
  // One agent erroring must not blank the other three's results.
81
83
  const settled = await Promise.allSettled(probeList.map((p) => p.run()));
@@ -29,7 +29,7 @@ export interface InitReport {
29
29
  /** Exported so `migrate` can byte-compare canonical `agents.md` against
30
30
  * this exact string (design.md D3, trellis-cli-migrate) rather than
31
31
  * retyping an equivalent one that could silently drift from this. */
32
- export declare const AGENTS_MD_TEMPLATE = "# Shared instructions\n\nWrite what every agent should know here \u2014 communication preferences,\nproject conventions, anything you'd otherwise repeat per agent.\n\nAlready using Claude Code, Codex, Kiro, pi, or Kimi Code? `trellis migrate --from\n<agent>` imports its real instructions and skills instead of starting\nfrom this placeholder.\n";
32
+ export declare const AGENTS_MD_TEMPLATE = "# Shared instructions\n\nWrite what every agent should know here \u2014 communication preferences,\nproject conventions, anything you'd otherwise repeat per agent.\n\nAlready using Claude Code, Codex, Kiro, pi, Kimi Code, or ZCode? `trellis migrate --from\n<agent>` imports its real instructions and skills instead of starting\nfrom this placeholder.\n";
33
33
  /**
34
34
  * Trellis never spawns an installer itself (global package installs are
35
35
  * exactly the kind of irreversible, system-wide action that needs the
@@ -16,6 +16,7 @@ import * as codexProbe from "../probes/codex.js";
16
16
  import * as kiroProbe from "../probes/kiro.js";
17
17
  import * as piProbe from "../probes/pi.js";
18
18
  import * as kimiCodeProbe from "../probes/kimi-code.js";
19
+ import * as zcodeProbe from "../probes/zcode.js";
19
20
  import { ALL_AGENTS } from "../core/types.js";
20
21
  /** Exported so `migrate` can byte-compare canonical `agents.md` against
21
22
  * this exact string (design.md D3, trellis-cli-migrate) rather than
@@ -25,7 +26,7 @@ export const AGENTS_MD_TEMPLATE = `# Shared instructions
25
26
  Write what every agent should know here — communication preferences,
26
27
  project conventions, anything you'd otherwise repeat per agent.
27
28
 
28
- Already using Claude Code, Codex, Kiro, pi, or Kimi Code? \`trellis migrate --from
29
+ Already using Claude Code, Codex, Kiro, pi, Kimi Code, or ZCode? \`trellis migrate --from
29
30
  <agent>\` imports its real instructions and skills instead of starting
30
31
  from this placeholder.
31
32
  `;
@@ -95,6 +96,7 @@ export const INSTALL_HINTS = {
95
96
  kiro: "https://kiro.dev/downloads/",
96
97
  pi: "npm install -g @earendil-works/pi-coding-agent",
97
98
  "kimi-code": "curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash",
99
+ zcode: "https://zcode.z.ai/en/docs/install",
98
100
  };
99
101
  function ensureFile(path, template, write = true) {
100
102
  if (existsSync(path)) {
@@ -132,6 +134,7 @@ export async function collectInitReport(homeDir = homedir(), options = {}) {
132
134
  { agent: "kiro", run: () => kiroProbe.probe(homeDir) },
133
135
  { agent: "pi", run: () => piProbe.probe(homeDir) },
134
136
  { agent: "kimi-code", run: () => kimiCodeProbe.probe(homeDir) },
137
+ { agent: "zcode", run: () => zcodeProbe.probe(homeDir) },
135
138
  ];
136
139
  const settled = await Promise.allSettled(probes.map((p) => p.run()));
137
140
  const agents = settled.map((result, index) => {
@@ -5,10 +5,12 @@
5
5
  * gated — remove), and reports what happened. Kept as a separate
6
6
  * command from bare `trellis sync` (skills/instructions) since the two
7
7
  * have different write mechanisms entirely, not because MCP removal is
8
- * unsafe (see `src/lib/mcpOwnership.ts`). `list`/`add`/`remove`
9
- * (trellis-canonical-cli-crud) are canonical-side only — an alternative
10
- * to hand-editing `~/.trellis/mcp/servers.yaml`, never touching any
11
- * agent's native config (that stays `sync`'s job).
8
+ * unsafe (see `src/lib/mcpOwnership.ts`). `add`/`remove` mutate the
9
+ * canonical source and then run the same sync `mcp sync` performs, so
10
+ * one command both edits the list and maps it onto the managed agents
11
+ * (removal propagates through the ownership ledger). `list` is
12
+ * read-only. All three are an alternative to hand-editing
13
+ * `~/.trellis/mcp/servers.yaml`.
12
14
  */
13
15
  import { type ServersYamlWriteResult } from "../core/canonical.js";
14
16
  import type { AdapterPlanItem } from "../core/adapter.js";
@@ -128,29 +130,69 @@ export declare function runMcpSetAuth(name: string | undefined, auth: string | u
128
130
  }): {
129
131
  exitCode: number;
130
132
  };
131
- export declare function applyMcpAddPlan(plan: McpAddPlan, homeDir?: string): ServersYamlWriteResult;
133
+ export declare function applyMcpAddPlan(plan: McpAddPlan, homeDir?: string, backup?: BackupSession): ServersYamlWriteResult;
134
+ export interface McpAddOutcome {
135
+ plan: McpAddPlan;
136
+ writeError?: string;
137
+ sync?: McpSyncReport;
138
+ }
139
+ /**
140
+ * The apply-and-cascade-sync orchestration `runMcpAdd` used to do inline
141
+ * and only expose via a console.log'd payload — split out so a
142
+ * non-CLI caller (trellis-gui's sidecar) can get the same structured
143
+ * result a JSON-mode CLI run prints, without scraping stdout or
144
+ * re-deriving the same sequencing itself (trellis-gui design.md
145
+ * Decision 1). Behavior-preserving: `runMcpAdd` below is now a thin
146
+ * wrapper over this plus its own printing, same as every other command
147
+ * in this file already splits compute from CLI I/O.
148
+ *
149
+ * Opens its own backup session around the direct `servers.yaml` write
150
+ * when the caller doesn't already have one open — this canonical write
151
+ * used to be the one path in this file that bypassed backup/rollback
152
+ * entirely (trellis-gui tasks.md 3.5 found this empirically: a real `mcp
153
+ * add` left zero trace in `trellis rollback --list`). Mirrors the
154
+ * `ownSession` pattern `collectMcpSyncReport` below already uses for
155
+ * exactly the same reason.
156
+ */
157
+ export declare function applyMcpAddWithSync(plan: McpAddPlan, opts?: {
158
+ homeDir?: string;
159
+ dryRun?: boolean;
160
+ backupSession?: BackupSession;
161
+ }): Promise<McpAddOutcome>;
132
162
  export declare function runMcpAdd(name: string | undefined, raw: McpAddRawArgs, opts?: {
133
163
  homeDir?: string;
134
164
  json?: boolean;
135
165
  dryRun?: boolean;
136
- }): {
166
+ }): Promise<{
137
167
  exitCode: number;
138
- };
168
+ }>;
139
169
  export type McpRemoveAction = "removed" | "not-found";
140
170
  export interface McpRemovePlan {
141
171
  name: string;
142
172
  action: McpRemoveAction;
143
173
  }
144
- /** Canonical-side only — never touches an agent's native config. Making
145
- * an already-synced agent forget it is `mcp sync`'s own removal-
146
- * propagation gap, tracked separately (roadmap.md P14), not this
147
- * command's job. */
174
+ /** Canonical plan only — `runMcpRemove` follows a real removal with the
175
+ * same sync `mcp sync` performs, which propagates the deletion to every
176
+ * agent whose entry the ownership ledger proves Trellis wrote
177
+ * (trellis-mcp-lifecycle-parity, roadmap P14). */
148
178
  export declare function collectMcpRemovePlan(name: string, homeDir?: string): McpRemovePlan;
149
- export declare function applyMcpRemovePlan(plan: McpRemovePlan, homeDir?: string): ServersYamlWriteResult;
179
+ export declare function applyMcpRemovePlan(plan: McpRemovePlan, homeDir?: string, backup?: BackupSession): ServersYamlWriteResult;
180
+ export interface McpRemoveOutcome {
181
+ plan: McpRemovePlan;
182
+ writeError?: string;
183
+ sync?: McpSyncReport;
184
+ }
185
+ /** See `applyMcpAddWithSync`'s doc comment — same split, same reason, same
186
+ * own-backup-session fix. */
187
+ export declare function applyMcpRemoveWithSync(plan: McpRemovePlan, opts?: {
188
+ homeDir?: string;
189
+ dryRun?: boolean;
190
+ backupSession?: BackupSession;
191
+ }): Promise<McpRemoveOutcome>;
150
192
  export declare function runMcpRemove(name: string, opts?: {
151
193
  homeDir?: string;
152
194
  json?: boolean;
153
195
  dryRun?: boolean;
154
- }): {
196
+ }): Promise<{
155
197
  exitCode: number;
156
- };
198
+ }>;
@@ -5,12 +5,15 @@
5
5
  * gated — remove), and reports what happened. Kept as a separate
6
6
  * command from bare `trellis sync` (skills/instructions) since the two
7
7
  * have different write mechanisms entirely, not because MCP removal is
8
- * unsafe (see `src/lib/mcpOwnership.ts`). `list`/`add`/`remove`
9
- * (trellis-canonical-cli-crud) are canonical-side only — an alternative
10
- * to hand-editing `~/.trellis/mcp/servers.yaml`, never touching any
11
- * agent's native config (that stays `sync`'s job).
8
+ * unsafe (see `src/lib/mcpOwnership.ts`). `add`/`remove` mutate the
9
+ * canonical source and then run the same sync `mcp sync` performs, so
10
+ * one command both edits the list and maps it onto the managed agents
11
+ * (removal propagates through the ownership ledger). `list` is
12
+ * read-only. All three are an alternative to hand-editing
13
+ * `~/.trellis/mcp/servers.yaml`.
12
14
  */
13
15
  import { homedir } from "node:os";
16
+ import { existsSync } from "node:fs";
14
17
  import { join } from "node:path";
15
18
  import { loadCanonicalSource, upsertServerYaml, removeServerYaml } from "../core/canonical.js";
16
19
  import { ALL_AGENTS, resolveScope } from "../core/types.js";
@@ -19,6 +22,7 @@ import { CodexAdapter } from "../adapters/codex.js";
19
22
  import { KiroAdapter } from "../adapters/kiro.js";
20
23
  import { PiAdapter } from "../adapters/pi.js";
21
24
  import { KimiCodeAdapter } from "../adapters/kimi-code.js";
25
+ import { ZcodeAdapter } from "../adapters/zcode.js";
22
26
  import { openBackupSession } from "../lib/backup.js";
23
27
  const ADAPTER_FACTORY = {
24
28
  "claude-code": (homeDir) => new ClaudeCodeAdapter(homeDir),
@@ -26,6 +30,7 @@ const ADAPTER_FACTORY = {
26
30
  kiro: (homeDir) => new KiroAdapter(homeDir),
27
31
  pi: (homeDir) => new PiAdapter(homeDir),
28
32
  "kimi-code": (homeDir) => new KimiCodeAdapter(homeDir),
33
+ zcode: (homeDir) => new ZcodeAdapter(homeDir),
29
34
  };
30
35
  /** Only agents in `canonical.managedAgents` — see src/commands/sync.ts's
31
36
  * own copy of this same restriction (trellis-managed-agents). */
@@ -297,12 +302,48 @@ export function runMcpSetAuth(name, auth, opts = {}) {
297
302
  }
298
303
  return { exitCode: ["invalid-input", "not-found"].includes(plan.action) || Boolean(writeError) ? 1 : 0 };
299
304
  }
300
- export function applyMcpAddPlan(plan, homeDir = homedir()) {
305
+ export function applyMcpAddPlan(plan, homeDir = homedir(), backup) {
301
306
  if (plan.action !== "create" || !plan.def)
302
307
  return { ok: true };
303
- return upsertServerYaml(serversYamlPath(homeDir), plan.name, plan.def);
308
+ return upsertServerYaml(serversYamlPath(homeDir), plan.name, plan.def, backup);
304
309
  }
305
- export function runMcpAdd(name, raw, opts = {}) {
310
+ /**
311
+ * The apply-and-cascade-sync orchestration `runMcpAdd` used to do inline
312
+ * and only expose via a console.log'd payload — split out so a
313
+ * non-CLI caller (trellis-gui's sidecar) can get the same structured
314
+ * result a JSON-mode CLI run prints, without scraping stdout or
315
+ * re-deriving the same sequencing itself (trellis-gui design.md
316
+ * Decision 1). Behavior-preserving: `runMcpAdd` below is now a thin
317
+ * wrapper over this plus its own printing, same as every other command
318
+ * in this file already splits compute from CLI I/O.
319
+ *
320
+ * Opens its own backup session around the direct `servers.yaml` write
321
+ * when the caller doesn't already have one open — this canonical write
322
+ * used to be the one path in this file that bypassed backup/rollback
323
+ * entirely (trellis-gui tasks.md 3.5 found this empirically: a real `mcp
324
+ * add` left zero trace in `trellis rollback --list`). Mirrors the
325
+ * `ownSession` pattern `collectMcpSyncReport` below already uses for
326
+ * exactly the same reason.
327
+ */
328
+ export async function applyMcpAddWithSync(plan, opts = {}) {
329
+ const homeDir = opts.homeDir ?? homedir();
330
+ const ownSession = !opts.dryRun && !opts.backupSession && plan.action === "create" ? openBackupSession(homeDir, "mcp-add") : undefined;
331
+ const session = opts.backupSession ?? ownSession;
332
+ let writeError;
333
+ if (!opts.dryRun && plan.action === "create") {
334
+ const result = applyMcpAddPlan(plan, homeDir, session);
335
+ if (!result.ok)
336
+ writeError = result.error;
337
+ }
338
+ ownSession?.finalize();
339
+ const planFailed = plan.action === "conflict" || plan.action === "invalid-input" || Boolean(writeError);
340
+ let syncReport;
341
+ if (!planFailed && plan.action === "create" && existsSync(join(homeDir, ".trellis"))) {
342
+ syncReport = await collectMcpSyncReport({ homeDir, dryRun: opts.dryRun });
343
+ }
344
+ return { plan, ...(writeError ? { writeError } : {}), ...(syncReport ? { sync: syncReport } : {}) };
345
+ }
346
+ export async function runMcpAdd(name, raw, opts = {}) {
306
347
  const homeDir = opts.homeDir ?? homedir();
307
348
  let plan;
308
349
  try {
@@ -312,37 +353,65 @@ export function runMcpAdd(name, raw, opts = {}) {
312
353
  console.error(err instanceof Error ? err.message : String(err));
313
354
  return { exitCode: 1 };
314
355
  }
315
- let writeError;
316
- if (!opts.dryRun && plan.action === "create") {
317
- const result = applyMcpAddPlan(plan, homeDir);
318
- if (!result.ok)
319
- writeError = result.error;
356
+ let outcome;
357
+ try {
358
+ outcome = await applyMcpAddWithSync(plan, { homeDir, dryRun: opts.dryRun });
359
+ }
360
+ catch (err) {
361
+ console.error(err instanceof Error ? err.message : String(err));
362
+ return { exitCode: 1 };
320
363
  }
364
+ const { writeError, sync: syncReport } = outcome;
321
365
  if (opts.json) {
322
- console.log(JSON.stringify(writeError ? { ...plan, writeError } : plan, null, 2));
366
+ const payload = writeError ? { ...plan, writeError } : plan;
367
+ console.log(JSON.stringify(syncReport ? { ...payload, sync: syncReport } : payload, null, 2));
323
368
  }
324
369
  else {
325
370
  console.log(`${opts.dryRun ? "[dry run] " : ""}mcp add ${plan.name}`);
326
371
  console.log(` [${plan.action}] ${plan.detail}`);
327
372
  if (writeError)
328
373
  console.error(` write failed: ${writeError}`);
374
+ if (syncReport)
375
+ printReport(syncReport, opts.dryRun ?? false);
329
376
  }
330
- return { exitCode: plan.action === "conflict" || plan.action === "invalid-input" || writeError ? 1 : 0 };
377
+ const planFailed = plan.action === "conflict" || plan.action === "invalid-input" || Boolean(writeError);
378
+ const syncConflict = syncReport?.reports.some((r) => r.items.some((i) => i.action === "conflict")) ?? false;
379
+ return { exitCode: planFailed || syncConflict ? 1 : 0 };
331
380
  }
332
- /** Canonical-side only — never touches an agent's native config. Making
333
- * an already-synced agent forget it is `mcp sync`'s own removal-
334
- * propagation gap, tracked separately (roadmap.md P14), not this
335
- * command's job. */
381
+ /** Canonical plan only — `runMcpRemove` follows a real removal with the
382
+ * same sync `mcp sync` performs, which propagates the deletion to every
383
+ * agent whose entry the ownership ledger proves Trellis wrote
384
+ * (trellis-mcp-lifecycle-parity, roadmap P14). */
336
385
  export function collectMcpRemovePlan(name, homeDir = homedir()) {
337
386
  const canonical = loadCanonicalSource(homeDir);
338
387
  return { name, action: canonical.mcp.servers[name] ? "removed" : "not-found" };
339
388
  }
340
- export function applyMcpRemovePlan(plan, homeDir = homedir()) {
389
+ export function applyMcpRemovePlan(plan, homeDir = homedir(), backup) {
341
390
  if (plan.action !== "removed")
342
391
  return { ok: true };
343
- return removeServerYaml(serversYamlPath(homeDir), plan.name);
392
+ return removeServerYaml(serversYamlPath(homeDir), plan.name, backup);
393
+ }
394
+ /** See `applyMcpAddWithSync`'s doc comment — same split, same reason, same
395
+ * own-backup-session fix. */
396
+ export async function applyMcpRemoveWithSync(plan, opts = {}) {
397
+ const homeDir = opts.homeDir ?? homedir();
398
+ const ownSession = !opts.dryRun && !opts.backupSession && plan.action === "removed" ? openBackupSession(homeDir, "mcp-remove") : undefined;
399
+ const session = opts.backupSession ?? ownSession;
400
+ let writeError;
401
+ if (!opts.dryRun && plan.action === "removed") {
402
+ const result = applyMcpRemovePlan(plan, homeDir, session);
403
+ if (!result.ok)
404
+ writeError = result.error;
405
+ }
406
+ ownSession?.finalize();
407
+ const planFailed = plan.action === "not-found" || Boolean(writeError);
408
+ let syncReport;
409
+ if (!planFailed && existsSync(join(homeDir, ".trellis"))) {
410
+ syncReport = await collectMcpSyncReport({ homeDir, dryRun: opts.dryRun });
411
+ }
412
+ return { plan, ...(writeError ? { writeError } : {}), ...(syncReport ? { sync: syncReport } : {}) };
344
413
  }
345
- export function runMcpRemove(name, opts = {}) {
414
+ export async function runMcpRemove(name, opts = {}) {
346
415
  const homeDir = opts.homeDir ?? homedir();
347
416
  let plan;
348
417
  try {
@@ -352,14 +421,19 @@ export function runMcpRemove(name, opts = {}) {
352
421
  console.error(err instanceof Error ? err.message : String(err));
353
422
  return { exitCode: 1 };
354
423
  }
355
- let writeError;
356
- if (!opts.dryRun && plan.action === "removed") {
357
- const result = applyMcpRemovePlan(plan, homeDir);
358
- if (!result.ok)
359
- writeError = result.error;
424
+ let outcome;
425
+ try {
426
+ outcome = await applyMcpRemoveWithSync(plan, { homeDir, dryRun: opts.dryRun });
360
427
  }
428
+ catch (err) {
429
+ console.error(err instanceof Error ? err.message : String(err));
430
+ return { exitCode: 1 };
431
+ }
432
+ const { writeError, sync: syncReport } = outcome;
433
+ const planFailed = plan.action === "not-found" || Boolean(writeError);
361
434
  if (opts.json) {
362
- console.log(JSON.stringify(writeError ? { ...plan, writeError } : plan, null, 2));
435
+ const payload = writeError ? { ...plan, writeError } : plan;
436
+ console.log(JSON.stringify(syncReport ? { ...payload, sync: syncReport } : payload, null, 2));
363
437
  }
364
438
  else if (plan.action === "not-found") {
365
439
  console.error(`"${name}" is not a canonical MCP server — nothing to remove.`);
@@ -369,6 +443,9 @@ export function runMcpRemove(name, opts = {}) {
369
443
  }
370
444
  else {
371
445
  console.log(`${opts.dryRun ? "[dry run] " : ""}removed MCP server "${name}" from canonical source.`);
446
+ if (syncReport)
447
+ printReport(syncReport, opts.dryRun ?? false);
372
448
  }
373
- return { exitCode: plan.action === "not-found" || writeError ? 1 : 0 };
449
+ const syncConflict = syncReport?.reports.some((r) => r.items.some((i) => i.action === "conflict")) ?? false;
450
+ return { exitCode: planFailed || syncConflict ? 1 : 0 };
374
451
  }
@@ -24,6 +24,7 @@ import { BuiltinRegistry, createRuntimeServer, UpstreamProvider } from "../lib/m
24
24
  import { McpStatusProvider } from "../lib/mcpStatusProvider.js";
25
25
  import { RuntimeControlProvider } from "../lib/runtimeControlProvider.js";
26
26
  import { TaskProvider } from "../lib/taskProvider.js";
27
+ import { AgentBridgeProvider } from "../lib/agentBridgeProvider.js";
27
28
  import { RuntimeMemoryProvider } from "../lib/memoryProvider.js";
28
29
  import { SkillProvider } from "../lib/skillProvider.js";
29
30
  import { ALL_AGENTS } from "../core/types.js";
@@ -121,7 +122,7 @@ async function runMcpEdge(opts, clientInfo, serverInfo) {
121
122
  // process has no terminal to run one in (design.md D9).
122
123
  homeDir,
123
124
  });
124
- const registry = new BuiltinRegistry([new SkillProvider(), new RuntimeMemoryProvider(), new RuntimeControlProvider(backend), new McpStatusProvider(backend), new TaskProvider(), new UpstreamProvider(backend)]);
125
+ const registry = new BuiltinRegistry([new SkillProvider(), new RuntimeMemoryProvider(), new RuntimeControlProvider(backend), new McpStatusProvider(backend), new TaskProvider(), new AgentBridgeProvider(), new UpstreamProvider(backend)]);
125
126
  const server = createRuntimeServer(serverInfo, { agentId: opts.agentId, homeDir }, registry);
126
127
  const transport = new StdioServerTransport();
127
128
  await server.connect(transport);
@@ -6,6 +6,7 @@
6
6
  * read-only; plans never carry secret values.
7
7
  */
8
8
  import { type BackupSession } from "../lib/backup.js";
9
+ import type { McpSyncReport } from "./mcp.js";
9
10
  export type McpImportAction = "create" | "already-present" | "duplicate" | "skipped" | "conflict" | "invalid-input";
10
11
  export interface McpImportItem {
11
12
  name: string;
@@ -18,10 +19,28 @@ export interface McpImportPlan {
18
19
  }
19
20
  export declare function collectMcpImportPlan(path: string, homeDir?: string): McpImportPlan;
20
21
  export declare function applyMcpImportPlan(path: string, homeDir?: string, backup?: BackupSession): McpImportPlan;
22
+ export interface McpImportOutcome {
23
+ plan: McpImportPlan;
24
+ sync?: McpSyncReport;
25
+ }
26
+ /**
27
+ * See `applyMcpAddWithSync`'s doc comment (mcp.ts) — same split, same
28
+ * reason: a non-CLI caller (trellis-gui's sidecar) gets the same
29
+ * structured result a JSON-mode CLI run prints, without scraping
30
+ * stdout or re-deriving the plan-then-apply-then-cascade-sync
31
+ * sequencing itself. Unlike `mcp add`/`mcp remove`, `applyMcpImportPlan`
32
+ * already opens its own backup session unconditionally (it always has,
33
+ * per design.md's original survey of `openBackupSession` call sites) —
34
+ * no tasks.md 3.5-style fix needed here.
35
+ */
36
+ export declare function applyMcpImportWithSync(path: string, opts?: {
37
+ homeDir?: string;
38
+ dryRun?: boolean;
39
+ }): Promise<McpImportOutcome>;
21
40
  export declare function runMcpImport(path: string | undefined, opts?: {
22
41
  homeDir?: string;
23
42
  json?: boolean;
24
43
  dryRun?: boolean;
25
- }): {
44
+ }): Promise<{
26
45
  exitCode: number;
27
- };
46
+ }>;