agent-trellis 0.8.0 → 0.8.2

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 (241) hide show
  1. package/README.md +43 -207
  2. package/dist/adapters/zcode.d.ts +21 -0
  3. package/dist/adapters/zcode.js +234 -0
  4. package/dist/cli.js +25 -9
  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/rollback.js +24 -1
  18. package/dist/commands/secretsAudit.js +4 -1
  19. package/dist/commands/skill.d.ts +35 -6
  20. package/dist/commands/skill.js +67 -13
  21. package/dist/commands/sync.js +3 -1
  22. package/dist/core/adapter.d.ts +11 -1
  23. package/dist/core/types.d.ts +1 -1
  24. package/dist/core/types.js +1 -0
  25. package/dist/lib/agentBridge.d.ts +169 -0
  26. package/dist/lib/agentBridge.js +367 -0
  27. package/dist/lib/agentBridgeProvider.d.ts +14 -0
  28. package/dist/lib/agentBridgeProvider.js +95 -0
  29. package/dist/lib/backup.d.ts +30 -0
  30. package/dist/lib/backup.js +16 -1
  31. package/dist/lib/chatAgents.d.ts +42 -0
  32. package/dist/lib/chatAgents.js +59 -0
  33. package/dist/lib/envVarNames.d.ts +3 -0
  34. package/dist/lib/envVarNames.js +19 -0
  35. package/dist/lib/installAgent.d.ts +1 -1
  36. package/dist/lib/installAgent.js +1 -1
  37. package/dist/lib/mcpMigrateRead.d.ts +1 -0
  38. package/dist/lib/mcpMigrateRead.js +19 -1
  39. package/dist/lib/realHomeSnapshot.js +5 -0
  40. package/dist/lib/runtimeControlProvider.js +2 -0
  41. package/dist/lib/taskStore.js +1 -1
  42. package/dist/lib/terminalPicker.js +33 -6
  43. package/dist/lib/zcodeOwnership.d.ts +17 -0
  44. package/dist/lib/zcodeOwnership.js +37 -0
  45. package/dist/pi-bridge/bundle.js +2 -1
  46. package/dist/probes/codex.d.ts +7 -0
  47. package/dist/probes/codex.js +37 -5
  48. package/dist/probes/zcode.d.ts +32 -0
  49. package/dist/probes/zcode.js +140 -0
  50. package/docs/architecture.md +9 -5
  51. package/docs/assets/brand-v3/trellis-mark-v3-a.png +0 -0
  52. package/docs/assets/brand-v3/trellis-mark-v3-a.svg +10 -0
  53. package/docs/assets/poster-series-v3/README.md +60 -0
  54. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-en.png +0 -0
  55. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-en.svg +51 -0
  56. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-zh.png +0 -0
  57. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-zh.svg +51 -0
  58. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-en.png +0 -0
  59. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-en.svg +48 -0
  60. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-zh.png +0 -0
  61. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-zh.svg +48 -0
  62. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-en.png +0 -0
  63. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-en.svg +56 -0
  64. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-zh.png +0 -0
  65. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-zh.svg +56 -0
  66. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-en.png +0 -0
  67. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-en.svg +47 -0
  68. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-zh.png +0 -0
  69. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-zh.svg +47 -0
  70. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-en.png +0 -0
  71. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-en.svg +51 -0
  72. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-zh.png +0 -0
  73. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-zh.svg +51 -0
  74. package/docs/getting-started.md +173 -12
  75. package/docs/overview.md +6 -3
  76. package/docs/overview.zh-CN.md +5 -3
  77. package/docs/roadmap.md +2 -1
  78. package/docs/video/README.md +76 -0
  79. package/docs/video/build/audio/01-opening.aiff +0 -0
  80. package/docs/video/build/audio/01-opening.mp3 +0 -0
  81. package/docs/video/build/audio/02-pain.aiff +0 -0
  82. package/docs/video/build/audio/02-pain.mp3 +0 -0
  83. package/docs/video/build/audio/03-solution.aiff +0 -0
  84. package/docs/video/build/audio/03-solution.mp3 +0 -0
  85. package/docs/video/build/audio/04-install.aiff +0 -0
  86. package/docs/video/build/audio/04-install.mp3 +0 -0
  87. package/docs/video/build/audio/04-setup.mp3 +0 -0
  88. package/docs/video/build/audio/05-dryrun.mp3 +0 -0
  89. package/docs/video/build/audio/05-init.aiff +0 -0
  90. package/docs/video/build/audio/05-init.mp3 +0 -0
  91. package/docs/video/build/audio/05-interactive.mp3 +0 -0
  92. package/docs/video/build/audio/06-dryrun.aiff +0 -0
  93. package/docs/video/build/audio/06-dryrun.mp3 +0 -0
  94. package/docs/video/build/audio/06-onboard.mp3 +0 -0
  95. package/docs/video/build/audio/06-run.mp3 +0 -0
  96. package/docs/video/build/audio/07-onboard.aiff +0 -0
  97. package/docs/video/build/audio/07-onboard.mp3 +0 -0
  98. package/docs/video/build/audio/07-verify.mp3 +0 -0
  99. package/docs/video/build/audio/08-closing.mp3 +0 -0
  100. package/docs/video/build/audio/08-doctor.aiff +0 -0
  101. package/docs/video/build/audio/08-doctor.mp3 +0 -0
  102. package/docs/video/build/audio/09-rollback.aiff +0 -0
  103. package/docs/video/build/audio/09-rollback.mp3 +0 -0
  104. package/docs/video/build/audio/10-principles.aiff +0 -0
  105. package/docs/video/build/audio/10-principles.mp3 +0 -0
  106. package/docs/video/build/audio/11-closing.aiff +0 -0
  107. package/docs/video/build/audio/11-closing.mp3 +0 -0
  108. package/docs/video/build/frames/01-opening-0.png +0 -0
  109. package/docs/video/build/frames/02-pain-1.png +0 -0
  110. package/docs/video/build/frames/02-pain-2.png +0 -0
  111. package/docs/video/build/frames/03-solution-3.png +0 -0
  112. package/docs/video/build/frames/04-install-4.png +0 -0
  113. package/docs/video/build/frames/04-setup-4.png +0 -0
  114. package/docs/video/build/frames/04-setup-5.png +0 -0
  115. package/docs/video/build/frames/05-dryrun-6.png +0 -0
  116. package/docs/video/build/frames/05-init-5.png +0 -0
  117. package/docs/video/build/frames/05-interactive-5.png +0 -0
  118. package/docs/video/build/frames/05-interactive-6.png +0 -0
  119. package/docs/video/build/frames/06-dryrun-6.png +0 -0
  120. package/docs/video/build/frames/06-dryrun-7.png +0 -0
  121. package/docs/video/build/frames/06-onboard-7.png +0 -0
  122. package/docs/video/build/frames/06-onboard-8.png +0 -0
  123. package/docs/video/build/frames/06-run-7.png +0 -0
  124. package/docs/video/build/frames/06-run-8.png +0 -0
  125. package/docs/video/build/frames/07-onboard-10.png +0 -0
  126. package/docs/video/build/frames/07-onboard-7.png +0 -0
  127. package/docs/video/build/frames/07-onboard-8.png +0 -0
  128. package/docs/video/build/frames/07-onboard-9.png +0 -0
  129. package/docs/video/build/frames/07-verify-10.png +0 -0
  130. package/docs/video/build/frames/07-verify-8.png +0 -0
  131. package/docs/video/build/frames/07-verify-9.png +0 -0
  132. package/docs/video/build/frames/08-closing-10.png +0 -0
  133. package/docs/video/build/frames/08-closing-11.png +0 -0
  134. package/docs/video/build/frames/08-doctor-11.png +0 -0
  135. package/docs/video/build/frames/08-doctor-9.png +0 -0
  136. package/docs/video/build/frames/09-rollback-10.png +0 -0
  137. package/docs/video/build/frames/09-rollback-12.png +0 -0
  138. package/docs/video/build/frames/10-principles-11.png +0 -0
  139. package/docs/video/build/frames/10-principles-13.png +0 -0
  140. package/docs/video/build/frames/11-closing-12.png +0 -0
  141. package/docs/video/build/frames/11-closing-14.png +0 -0
  142. package/docs/video/build/segments/01-opening.mp4 +0 -0
  143. package/docs/video/build/segments/01-opening.txt +3 -0
  144. package/docs/video/build/segments/02-pain.mp4 +0 -0
  145. package/docs/video/build/segments/02-pain.txt +5 -0
  146. package/docs/video/build/segments/03-solution.mp4 +0 -0
  147. package/docs/video/build/segments/03-solution.txt +3 -0
  148. package/docs/video/build/segments/04-install.mp4 +0 -0
  149. package/docs/video/build/segments/04-install.txt +3 -0
  150. package/docs/video/build/segments/04-setup.mp4 +0 -0
  151. package/docs/video/build/segments/04-setup.txt +3 -0
  152. package/docs/video/build/segments/05-dryrun.mp4 +0 -0
  153. package/docs/video/build/segments/05-dryrun.txt +3 -0
  154. package/docs/video/build/segments/05-init.mp4 +0 -0
  155. package/docs/video/build/segments/05-init.txt +3 -0
  156. package/docs/video/build/segments/05-interactive.mp4 +0 -0
  157. package/docs/video/build/segments/05-interactive.txt +5 -0
  158. package/docs/video/build/segments/06-dryrun.mp4 +0 -0
  159. package/docs/video/build/segments/06-dryrun.txt +3 -0
  160. package/docs/video/build/segments/06-onboard.mp4 +0 -0
  161. package/docs/video/build/segments/06-onboard.txt +5 -0
  162. package/docs/video/build/segments/06-run.mp4 +0 -0
  163. package/docs/video/build/segments/06-run.txt +3 -0
  164. package/docs/video/build/segments/07-onboard.mp4 +0 -0
  165. package/docs/video/build/segments/07-onboard.txt +5 -0
  166. package/docs/video/build/segments/07-verify.mp4 +0 -0
  167. package/docs/video/build/segments/07-verify.txt +5 -0
  168. package/docs/video/build/segments/08-closing.mp4 +0 -0
  169. package/docs/video/build/segments/08-closing.txt +3 -0
  170. package/docs/video/build/segments/08-doctor.mp4 +0 -0
  171. package/docs/video/build/segments/08-doctor.txt +3 -0
  172. package/docs/video/build/segments/09-rollback.mp4 +0 -0
  173. package/docs/video/build/segments/09-rollback.txt +3 -0
  174. package/docs/video/build/segments/10-principles.mp4 +0 -0
  175. package/docs/video/build/segments/10-principles.txt +3 -0
  176. package/docs/video/build/segments/11-closing.mp4 +0 -0
  177. package/docs/video/build/segments/11-closing.txt +3 -0
  178. package/docs/video/build/segments/final.txt +8 -0
  179. package/docs/video/build-ai/audio/01-hook.mp3 +0 -0
  180. package/docs/video/build-ai/audio/02-pain.mp3 +0 -0
  181. package/docs/video/build-ai/audio/03-solution.mp3 +0 -0
  182. package/docs/video/build-ai/audio/04-how.mp3 +0 -0
  183. package/docs/video/build-ai/audio/05-safe.mp3 +0 -0
  184. package/docs/video/build-ai/audio/06-closing.mp3 +0 -0
  185. package/docs/video/build-ai/clips/01-hook.mp4 +0 -0
  186. package/docs/video/build-ai/clips/02-pain.mp4 +0 -0
  187. package/docs/video/build-ai/clips/03-solution.mp4 +0 -0
  188. package/docs/video/build-ai/clips/04-how.mp4 +0 -0
  189. package/docs/video/build-ai/clips/05-safe.mp4 +0 -0
  190. package/docs/video/build-ai/clips/06-closing.mp4 +0 -0
  191. package/docs/video/build-ai/first-frame-02-pain.png +0 -0
  192. package/docs/video/build-ai/first-frame-03-solution.png +0 -0
  193. package/docs/video/build-ai/first-frame-04-how.png +0 -0
  194. package/docs/video/build-ai/first-frame-05-safe.png +0 -0
  195. package/docs/video/build-ai/segments/01-hook.caption.txt +2 -0
  196. package/docs/video/build-ai/segments/01-hook.mp4 +0 -0
  197. package/docs/video/build-ai/segments/02-pain.mp4 +0 -0
  198. package/docs/video/build-ai/segments/03-solution.mp4 +0 -0
  199. package/docs/video/build-ai/segments/04-how.mp4 +0 -0
  200. package/docs/video/build-ai/segments/05-safe.mp4 +0 -0
  201. package/docs/video/build-ai/segments/06-closing.mp4 +0 -0
  202. package/docs/video/build-ai/segments/cap-01-hook.png +0 -0
  203. package/docs/video/build-ai/segments/cap-02-pain.png +0 -0
  204. package/docs/video/build-ai/segments/cap-03-solution.png +0 -0
  205. package/docs/video/build-ai/segments/cap-04-how.png +0 -0
  206. package/docs/video/build-ai/segments/cap-05-safe.png +0 -0
  207. package/docs/video/build-ai/segments/cap-06-closing.png +0 -0
  208. package/docs/video/build-ai/segments/final.txt +6 -0
  209. package/docs/video/build-posters/audio/01-hook.mp3 +0 -0
  210. package/docs/video/build-posters/audio/02-pain.mp3 +0 -0
  211. package/docs/video/build-posters/audio/03-solution.mp3 +0 -0
  212. package/docs/video/build-posters/audio/04-how.mp3 +0 -0
  213. package/docs/video/build-posters/audio/05-safe.mp3 +0 -0
  214. package/docs/video/build-posters/audio/06-closing.mp3 +0 -0
  215. package/docs/video/build-posters/cap-01-hook.png +0 -0
  216. package/docs/video/build-posters/cap-02-pain.png +0 -0
  217. package/docs/video/build-posters/cap-03-solution.png +0 -0
  218. package/docs/video/build-posters/cap-04-how.png +0 -0
  219. package/docs/video/build-posters/cap-05-safe.png +0 -0
  220. package/docs/video/build-posters/cap-06-closing.png +0 -0
  221. package/docs/video/build-posters/card-01-hook.png +0 -0
  222. package/docs/video/build-posters/card-06-closing.png +0 -0
  223. package/docs/video/build-posters/segments/01-hook.mp4 +0 -0
  224. package/docs/video/build-posters/segments/02-pain.mp4 +0 -0
  225. package/docs/video/build-posters/segments/03-solution.mp4 +0 -0
  226. package/docs/video/build-posters/segments/04-how.mp4 +0 -0
  227. package/docs/video/build-posters/segments/05-safe.mp4 +0 -0
  228. package/docs/video/build-posters/segments/06-closing.mp4 +0 -0
  229. package/docs/video/build-posters/segments/final.txt +6 -0
  230. package/docs/video/generate-video-ai.mjs +334 -0
  231. package/docs/video/generate-video-posters.mjs +204 -0
  232. package/docs/video/generate-video.mjs +426 -0
  233. package/docs/video/outline.zh-CN.md +102 -0
  234. package/docs/video/trellis-demo-video-ai.mp4 +0 -0
  235. package/docs/video/trellis-demo-video.mp4 +0 -0
  236. package/docs/video/voiceover.zh-CN.md +49 -0
  237. package/package.json +5 -2
  238. package/schema/servers.example.yaml +1 -1
  239. package/docs/assets/agent-trellis-architecture-horizontal.svg +0 -80
  240. package/docs/assets/agent-trellis-architecture-vertical.svg +0 -82
  241. package/docs/assets/trellis-mark.svg +0 -12
@@ -44,6 +44,9 @@ export declare function declaredEnvNames(def: McpServerDef): string[];
44
44
  * different command, not this extractor's job.
45
45
  */
46
46
  export declare function extractJsonEnvVarNames(content: string): string[];
47
+ /** ZCode stores the same server value shape under `mcp.servers` rather than
48
+ * the top-level `mcpServers` map used by Claude Code and Kiro. */
49
+ export declare function extractZcodeEnvVarNames(content: string): string[];
47
50
  /** Codex's config is TOML. The shapes audit needs: every `env_vars = [...]`
48
51
  * line's quoted string contents, plus a `bearer_token_env_var = "VAR"`
49
52
  * line's bare name (a name directly, not a `${VAR}` template — Codex's
@@ -71,6 +71,25 @@ export function extractJsonEnvVarNames(content) {
71
71
  }
72
72
  return names;
73
73
  }
74
+ /** ZCode stores the same server value shape under `mcp.servers` rather than
75
+ * the top-level `mcpServers` map used by Claude Code and Kiro. */
76
+ export function extractZcodeEnvVarNames(content) {
77
+ let parsed;
78
+ try {
79
+ parsed = JSON.parse(content);
80
+ }
81
+ catch {
82
+ return [];
83
+ }
84
+ const names = [];
85
+ for (const server of Object.values(parsed.mcp?.servers ?? {})) {
86
+ for (const value of Object.values(server.env ?? {}))
87
+ names.push(...extractTemplateVarNames(value));
88
+ for (const value of Object.values(server.headers ?? {}))
89
+ names.push(...extractTemplateVarNames(value));
90
+ }
91
+ return names;
92
+ }
74
93
  const ENV_VARS_LINE_RE = /^\s*env_vars\s*=\s*\[(.*)\]\s*$/;
75
94
  const BEARER_TOKEN_ENV_VAR_LINE_RE = /^\s*bearer_token_env_var\s*=\s*"([^"]*)"\s*$/;
76
95
  const QUOTED_STRING_RE = /"((?:[^"\\]|\\.)*)"/g;
@@ -10,7 +10,7 @@ import type { AgentId } from "../core/types.js";
10
10
  /** Only agents with a real `npm install -g <pkg>` command — Kiro's own
11
11
  * `INSTALL_HINTS` entry is a download URL, not a package, and is never
12
12
  * looked up here. */
13
- export declare const NPM_INSTALLABLE: Record<Exclude<AgentId, "kiro" | "kimi-code">, string>;
13
+ export declare const NPM_INSTALLABLE: Record<Exclude<AgentId, "kiro" | "kimi-code" | "zcode">, string>;
14
14
  export interface ConfirmAndInstallOptions {
15
15
  /** Test/real seam, same pattern as onboard's `promptForAgent` — a real
16
16
  * terminal confirmation by default. */
@@ -32,7 +32,7 @@ function runInstallReal(pkg) {
32
32
  execFileSync("npm", ["install", "-g", pkg], { stdio: "inherit" });
33
33
  }
34
34
  export async function confirmAndInstall(agent, opts = {}) {
35
- if (agent === "kiro" || agent === "kimi-code") {
35
+ if (agent === "kiro" || agent === "kimi-code" || agent === "zcode") {
36
36
  return { installed: false, installable: false };
37
37
  }
38
38
  const pkg = NPM_INSTALLABLE[agent];
@@ -38,6 +38,7 @@ export declare function resolvedEnvTextMap(def: Pick<McpServerDef, "env" | "envA
38
38
  export declare function readClaudeCodeMcpDefs(homeDir: string): McpMigrateReadResult;
39
39
  export declare function readKiroMcpDefs(homeDir: string): McpMigrateReadResult;
40
40
  export declare function readKimiCodeMcpDefs(homeDir: string): McpMigrateReadResult;
41
+ export declare function readZcodeMcpDefs(homeDir: string): McpMigrateReadResult;
41
42
  export interface CodexMcpEntryRich {
42
43
  name: string;
43
44
  enabled: boolean;
@@ -15,6 +15,8 @@ import { readFileSync } from "node:fs";
15
15
  import { join } from "node:path";
16
16
  import { readJsonFile } from "./probeCommon.js";
17
17
  import { readServerEnvTable } from "./tomlSection.js";
18
+ import { resolveZcodeProfile } from "../probes/zcode.js";
19
+ import { codexEnv } from "../probes/codex.js";
18
20
  const EMPTY_RESULT = { entries: [], unsupported: [] };
19
21
  /**
20
22
  * Trellis's own `env` (self-referencing name) vs. `envAliases`
@@ -111,6 +113,22 @@ export function readKiroMcpDefs(homeDir) {
111
113
  export function readKimiCodeMcpDefs(homeDir) {
112
114
  return readRichJsonMcpDefs(join(homeDir, ".kimi-code", "mcp.json"));
113
115
  }
116
+ export function readZcodeMcpDefs(homeDir) {
117
+ const profile = resolveZcodeProfile(homeDir);
118
+ if (!profile)
119
+ return EMPTY_RESULT;
120
+ const parsed = readJsonFile(profile.configPath);
121
+ const entries = [];
122
+ const unsupported = [];
123
+ for (const [name, raw] of Object.entries(parsed?.mcp?.servers ?? {})) {
124
+ const def = fromRichJsonServerDef(raw);
125
+ if (def)
126
+ entries.push({ name, def });
127
+ else
128
+ unsupported.push({ name, reason: "stdio entry has no command — malformed, skipped" });
129
+ }
130
+ return { entries, unsupported };
131
+ }
114
132
  /**
115
133
  * Pure conversion half — separated from the subprocess/file-read effects
116
134
  * below so the stdio/non-stdio/malformed decision logic is unit-testable
@@ -202,7 +220,7 @@ export function readCodexMcpDefs(homeDir) {
202
220
  // `[]`, not the real machine's servers), so this must be scoped to
203
221
  // `homeDir` explicitly — `src/probes/codex.ts`'s own equivalent call
204
222
  // has this same gap, unaddressed there; not touched by this change.
205
- raw = execFileSync("codex", ["mcp", "list", "--json"], { encoding: "utf-8", timeout: 5_000, env: { ...process.env, HOME: homeDir } });
223
+ raw = execFileSync("codex", ["mcp", "list", "--json"], { encoding: "utf-8", timeout: 5_000, env: codexEnv(homeDir) });
206
224
  }
207
225
  catch {
208
226
  return EMPTY_RESULT;
@@ -21,6 +21,11 @@ export const REAL_HOME_ALLOWLIST = [
21
21
  ".codex/config.toml",
22
22
  ".agents/skills",
23
23
  ".codex/skills",
24
+ // zcode (src/probes/zcode.ts)
25
+ ".zcode/cli/config.json",
26
+ ".zcode/cli/setting.json",
27
+ ".zcode/skills",
28
+ ".zcode/AGENTS.md",
24
29
  // kiro (src/probes/kiro.ts)
25
30
  ".kiro/settings/mcp.json",
26
31
  ".kiro/skills",
@@ -9,6 +9,7 @@ import * as codexProbe from "../probes/codex.js";
9
9
  import * as kiroProbe from "../probes/kiro.js";
10
10
  import * as piProbe from "../probes/pi.js";
11
11
  import * as kimiCodeProbe from "../probes/kimi-code.js";
12
+ import * as zcodeProbe from "../probes/zcode.js";
12
13
  import { TRELLIS_VERSION } from "./cliMetadata.js";
13
14
  import { inspectMemoryReadiness } from "./memoryStatus.js";
14
15
  const MAX_INSTRUCTIONS_BYTES = 256 * 1024;
@@ -37,6 +38,7 @@ const PROBES = {
37
38
  kiro: (homeDir) => kiroProbe.probe(homeDir),
38
39
  pi: (homeDir) => piProbe.probe(homeDir),
39
40
  "kimi-code": (homeDir) => kimiCodeProbe.probe(homeDir),
41
+ zcode: (homeDir) => zcodeProbe.probe(homeDir),
40
42
  };
41
43
  function textResult(value) {
42
44
  return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }], structuredContent: value };
@@ -42,7 +42,7 @@ function now() {
42
42
  return new Date().toISOString();
43
43
  }
44
44
  function assertAgent(value, label) {
45
- if (value !== "claude-code" && value !== "codex" && value !== "kiro" && value !== "pi" && value !== "kimi-code") {
45
+ if (value !== "claude-code" && value !== "codex" && value !== "kiro" && value !== "pi" && value !== "kimi-code" && value !== "zcode") {
46
46
  throw new Error(`${label} must be a supported Agent id`);
47
47
  }
48
48
  }
@@ -6,7 +6,31 @@
6
6
  * handling, color, wrapping, cursor cleanup, and cancellation; the injected
7
7
  * streams keep the prompt loop testable without a real terminal.
8
8
  */
9
- import { CANCEL_SYMBOL, autocompleteMultiselect, isCancel, multiselect, select } from "@clack/prompts";
9
+ /**
10
+ * `@clack/prompts` (and its `@clack/core` dependency) is loaded lazily,
11
+ * inside each picker function below, rather than via a top-level
12
+ * `import` — deliberately, not an oversight. A top-level import runs the
13
+ * moment ANY module that transitively imports this file is evaluated,
14
+ * which for `trellis-gui`'s sidecar means every process start, since
15
+ * `onboard.ts` (imported unconditionally by the sidecar's `/plan/onboard`
16
+ * route) imports these functions even though the sidecar's `json: true`
17
+ * contract (see `planApply.ts`'s `OnboardWizardInput` doc comment)
18
+ * guarantees they are never actually called. Compiling the sidecar to a
19
+ * single-file binary (tasks.md 5.1) surfaced a real, current limitation
20
+ * in `@yao-pkg/pkg`'s ESM support: `@clack/prompts`' `exports`-map
21
+ * `.mjs` entry fails to resolve inside pkg's snapshot filesystem
22
+ * (`Cannot find module '.../@clack/prompts/dist/index.mjs'`), which
23
+ * previously crashed the packaged sidecar at startup even though its own
24
+ * code never uses the interactive picker at all. A dynamic `import()`
25
+ * only resolves at the point it actually runs, so the sidecar (whose
26
+ * `!opts.json` gates keep every real call site below unreached) never
27
+ * triggers that resolution attempt — while a real, interactive CLI run
28
+ * (`tsx`/`node`, not a pkg snapshot) resolves the dynamic import exactly
29
+ * as it would a static one, so this is behavior-preserving for the CLI.
30
+ */
31
+ async function clack() {
32
+ return import("@clack/prompts");
33
+ }
10
34
  function defaultStreams() {
11
35
  return { input: process.stdin, output: process.stderr };
12
36
  }
@@ -29,14 +53,15 @@ export function toggled(checked, index) {
29
53
  next[index] = !next[index];
30
54
  return next;
31
55
  }
32
- function wasCancelled(value) {
33
- return value === CANCEL_SYMBOL || isCancel(value);
56
+ function wasCancelled(value, cancelSymbol, isCancelFn) {
57
+ return value === cancelSymbol || isCancelFn(value);
34
58
  }
35
59
  /**
36
60
  * Single-select prompt. The returned number is the original item index, so
37
61
  * callers do not depend on display labels or prompt-library values.
38
62
  */
39
63
  export async function runSingleSelectPicker(items, streams = defaultStreams(), message = "请选择一个迁移来源") {
64
+ const { select, CANCEL_SYMBOL, isCancel } = await clack();
40
65
  const result = await select({
41
66
  message,
42
67
  options: items.map((item, value) => ({
@@ -48,13 +73,14 @@ export async function runSingleSelectPicker(items, streams = defaultStreams(), m
48
73
  output: streams.output,
49
74
  showInstructions: true,
50
75
  });
51
- return wasCancelled(result) ? null : result;
76
+ return wasCancelled(result, CANCEL_SYMBOL, isCancel) ? null : result;
52
77
  }
53
78
  /**
54
79
  * Checkbox prompt. The returned indices preserve the existing onboard
55
80
  * contract, while Clack handles checked state, wrapping, colors, and cleanup.
56
81
  */
57
82
  export async function runMultiSelectPicker(items, initiallyChecked, streams = defaultStreams(), message = "请选择要启用的项目") {
83
+ const { multiselect, CANCEL_SYMBOL, isCancel } = await clack();
58
84
  const result = await multiselect({
59
85
  message,
60
86
  options: items.map((label, value) => ({ label, value })),
@@ -64,12 +90,13 @@ export async function runMultiSelectPicker(items, initiallyChecked, streams = de
64
90
  required: false,
65
91
  showInstructions: true,
66
92
  });
67
- return wasCancelled(result) ? null : [...result].sort((left, right) => left - right);
93
+ return wasCancelled(result, CANCEL_SYMBOL, isCancel) ? null : [...result].sort((left, right) => left - right);
68
94
  }
69
95
  /** Searchable multiselect for large skill/MCP inventories. It returns the
70
96
  * selected labels as stable names, preserving the semantic adapter contract
71
97
  * while avoiding a wall of dozens of rows in the onboarding terminal. */
72
98
  export async function runSearchMultiSelectPicker(items, streams = defaultStreams(), message = "选择要迁移的项目") {
99
+ const { autocompleteMultiselect, CANCEL_SYMBOL, isCancel } = await clack();
73
100
  const result = await autocompleteMultiselect({
74
101
  message,
75
102
  placeholder: "输入关键词筛选",
@@ -79,5 +106,5 @@ export async function runSearchMultiSelectPicker(items, streams = defaultStreams
79
106
  output: streams.output,
80
107
  required: false,
81
108
  });
82
- return wasCancelled(result) ? null : result;
109
+ return wasCancelled(result, CANCEL_SYMBOL, isCancel) ? null : result;
83
110
  }
@@ -0,0 +1,17 @@
1
+ /** Ownership record for the two ZCode native-Skill controls Trellis changes
2
+ * during strict Runtime delivery. MCP ownership stays in mcpOwnership.ts;
3
+ * this separate record lets a later native/both delivery restore the user's
4
+ * prior values without treating an ordinary config key as an MCP server. */
5
+ export interface ZcodeSkillControlValues {
6
+ featuresSkill: boolean | null;
7
+ skillsEnabled: boolean | null;
8
+ }
9
+ export interface ZcodeSettingsOwnership {
10
+ configPath: string;
11
+ managed: ZcodeSkillControlValues;
12
+ previous: ZcodeSkillControlValues;
13
+ }
14
+ export declare function zcodeSettingsOwnershipPath(homeDir: string): string;
15
+ export declare function loadZcodeSettingsOwnership(homeDir: string): ZcodeSettingsOwnership | undefined;
16
+ export declare function saveZcodeSettingsOwnership(homeDir: string, value: ZcodeSettingsOwnership): void;
17
+ export declare function forgetZcodeSettingsOwnership(homeDir: string): void;
@@ -0,0 +1,37 @@
1
+ /** Ownership record for the two ZCode native-Skill controls Trellis changes
2
+ * during strict Runtime delivery. MCP ownership stays in mcpOwnership.ts;
3
+ * this separate record lets a later native/both delivery restore the user's
4
+ * prior values without treating an ordinary config key as an MCP server. */
5
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ export function zcodeSettingsOwnershipPath(homeDir) {
8
+ return join(homeDir, ".trellis", "zcode", "settings-ownership.json");
9
+ }
10
+ export function loadZcodeSettingsOwnership(homeDir) {
11
+ const path = zcodeSettingsOwnershipPath(homeDir);
12
+ if (!existsSync(path))
13
+ return undefined;
14
+ try {
15
+ const value = JSON.parse(readFileSync(path, "utf8"));
16
+ if (typeof value.configPath !== "string"
17
+ || typeof value.managed?.featuresSkill !== "boolean"
18
+ || typeof value.managed?.skillsEnabled !== "boolean"
19
+ || (value.previous?.featuresSkill !== null && typeof value.previous?.featuresSkill !== "boolean")
20
+ || (value.previous?.skillsEnabled !== null && typeof value.previous?.skillsEnabled !== "boolean"))
21
+ return undefined;
22
+ return value;
23
+ }
24
+ catch {
25
+ return undefined;
26
+ }
27
+ }
28
+ export function saveZcodeSettingsOwnership(homeDir, value) {
29
+ const path = zcodeSettingsOwnershipPath(homeDir);
30
+ mkdirSync(join(homeDir, ".trellis", "zcode"), { recursive: true });
31
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
32
+ }
33
+ export function forgetZcodeSettingsOwnership(homeDir) {
34
+ const path = zcodeSettingsOwnershipPath(homeDir);
35
+ if (existsSync(path))
36
+ writeFileSync(path, "");
37
+ }
@@ -15150,7 +15150,8 @@ var ALL_AGENTS = [
15150
15150
  "codex",
15151
15151
  "kiro",
15152
15152
  "pi",
15153
- "kimi-code"
15153
+ "kimi-code",
15154
+ "zcode"
15154
15155
  ];
15155
15156
  function resolveScope(scope, managedAgents) {
15156
15157
  const candidates = scope ?? managedAgents;
@@ -21,4 +21,11 @@ export interface ProbeOptions {
21
21
  * default — see docs/architecture.md "MCP handshake probing is opt-in". */
22
22
  probeMcp?: boolean;
23
23
  }
24
+ /**
25
+ * Codex resolves its config through HOME, but Volta's shim resolves its own
26
+ * installation through VOLTA_HOME. A probe against a scratch HOME must keep
27
+ * those identities separate or `codex mcp list` fails before it can read the
28
+ * scratch config.
29
+ */
30
+ export declare function codexEnv(homeDir: string, env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
24
31
  export declare function probe(homeDir?: string, opts?: ProbeOptions): Promise<AgentSnapshot>;
@@ -11,7 +11,7 @@
11
11
  import { execFileSync } from "node:child_process";
12
12
  import { existsSync, readFileSync } from "node:fs";
13
13
  import { homedir } from "node:os";
14
- import { join } from "node:path";
14
+ import { delimiter, join } from "node:path";
15
15
  import { probeMcpServer } from "../lib/mcpProbe.js";
16
16
  import { pathRef, resolveEnvRefs, scanSkillRoot } from "../lib/probeCommon.js";
17
17
  /**
@@ -30,6 +30,30 @@ export function readInstructionsPath(configTomlPath) {
30
30
  }
31
31
  return /^\s*instructions\s*=\s*"([^"]*)"/m.exec(content)?.[1];
32
32
  }
33
+ /**
34
+ * Codex resolves its config through HOME, but Volta's shim resolves its own
35
+ * installation through VOLTA_HOME. A probe against a scratch HOME must keep
36
+ * those identities separate or `codex mcp list` fails before it can read the
37
+ * scratch config.
38
+ */
39
+ export function codexEnv(homeDir, env = process.env) {
40
+ const voltaHome = env.VOLTA_HOME ?? join(homedir(), ".volta");
41
+ const voltaBin = join(voltaHome, "bin");
42
+ const pathParts = (env.PATH ?? "").split(delimiter).filter(Boolean);
43
+ const hasVolta = existsSync(voltaBin);
44
+ if (hasVolta && !pathParts.includes(voltaBin))
45
+ pathParts.unshift(voltaBin);
46
+ return {
47
+ ...env,
48
+ HOME: homeDir,
49
+ // A host may inject CODEX_HOME for its own relay/session state. It has
50
+ // higher priority than HOME inside Codex, so override it as well or a
51
+ // scratch-home probe silently reads the host's real configuration.
52
+ CODEX_HOME: join(homeDir, ".codex"),
53
+ ...(hasVolta ? { VOLTA_HOME: voltaHome } : {}),
54
+ ...(pathParts.length ? { PATH: pathParts.join(delimiter) } : {}),
55
+ };
56
+ }
33
57
  export async function probe(homeDir = homedir(), opts = {}) {
34
58
  const configTomlPath = join(homeDir, ".codex", "config.toml");
35
59
  if (!existsSync(configTomlPath)) {
@@ -43,21 +67,29 @@ export async function probe(homeDir = homedir(), opts = {}) {
43
67
  // default `homeDir` would silently get this real machine's real MCP
44
68
  // server list instead (trellis-migrate-mcp-servers found this gap in
45
69
  // src/lib/mcpMigrateRead.ts's own equivalent call; fixed there first).
46
- const codexEnv = { ...process.env, HOME: homeDir };
70
+ const scopedCodexEnv = codexEnv(homeDir);
47
71
  let version;
48
72
  try {
49
- version = execFileSync("codex", ["--version"], { encoding: "utf-8", timeout: 5_000, env: codexEnv }).trim();
73
+ version = execFileSync("codex", ["--version"], { encoding: "utf-8", timeout: 5_000, env: scopedCodexEnv }).trim();
50
74
  }
51
75
  catch {
52
76
  // config exists without the binary on PATH — unusual, still probeable
53
77
  }
54
78
  let mcpEntries = [];
55
79
  try {
56
- const raw = execFileSync("codex", ["mcp", "list", "--json"], { encoding: "utf-8", timeout: 5_000, env: codexEnv });
80
+ const raw = execFileSync("codex", ["mcp", "list", "--json"], { encoding: "utf-8", timeout: 5_000, env: scopedCodexEnv });
57
81
  mcpEntries = JSON.parse(raw);
58
82
  }
59
83
  catch (err) {
60
- diagnostics.push(`codex mcp list --json failed: ${err instanceof Error ? err.message : String(err)}`);
84
+ // ENOENT means the binary itself isn't on PATH — the same benign,
85
+ // config-without-binary condition the `--version` check above already
86
+ // swallows silently, not a real failure worth alarming the user with a
87
+ // raw spawn error string. Anything else (malformed JSON, a non-zero
88
+ // exit for another reason, a timeout) is a genuine, unexpected failure
89
+ // and stays a diagnostic.
90
+ if (err?.code !== "ENOENT") {
91
+ diagnostics.push(`codex mcp list --json failed: ${err instanceof Error ? err.message : String(err)}`);
92
+ }
61
93
  }
62
94
  const mcpServers = mcpEntries.map((entry) => ({
63
95
  name: entry.name,
@@ -0,0 +1,32 @@
1
+ /**
2
+ * ZCode probe and profile selection.
3
+ *
4
+ * ZCode has two public configuration layouts in circulation. The official
5
+ * source/distribution reads ~/.zcode/cli/config.json, while the installed
6
+ * zcode-app-cli compatibility distribution keeps runtime settings (including
7
+ * mcp.servers) in ~/.zcode/cli/setting.json. We select one profile before any
8
+ * adapter plans a write; writing both would make ZCode's native precedence
9
+ * ambiguous and can hide a managed MCP entry.
10
+ */
11
+ import type { AgentSnapshot } from "../core/types.js";
12
+ export type ZcodeProfileKind = "community-cli" | "official" | "configuration-only";
13
+ export interface ZcodeProfile {
14
+ kind: ZcodeProfileKind;
15
+ configPath: string;
16
+ executable?: string;
17
+ executableVersion?: string;
18
+ supportsExecution: boolean;
19
+ }
20
+ export interface ProbeOptions {
21
+ probeMcp?: boolean;
22
+ env?: NodeJS.ProcessEnv;
23
+ }
24
+ export declare function zcodeEnv(homeDir: string, env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
25
+ export declare function selectZcodeProfile(homeDir: string, facts?: {
26
+ command?: string;
27
+ version?: string;
28
+ help?: string;
29
+ outputFormatSupported?: boolean;
30
+ }): ZcodeProfile | undefined;
31
+ export declare function resolveZcodeProfile(homeDir?: string, env?: NodeJS.ProcessEnv): ZcodeProfile | undefined;
32
+ export declare function probe(homeDir?: string, opts?: ProbeOptions): Promise<AgentSnapshot>;
@@ -0,0 +1,140 @@
1
+ /**
2
+ * ZCode probe and profile selection.
3
+ *
4
+ * ZCode has two public configuration layouts in circulation. The official
5
+ * source/distribution reads ~/.zcode/cli/config.json, while the installed
6
+ * zcode-app-cli compatibility distribution keeps runtime settings (including
7
+ * mcp.servers) in ~/.zcode/cli/setting.json. We select one profile before any
8
+ * adapter plans a write; writing both would make ZCode's native precedence
9
+ * ambiguous and can hide a managed MCP entry.
10
+ */
11
+ import { execFileSync } from "node:child_process";
12
+ import { existsSync } from "node:fs";
13
+ import { homedir } from "node:os";
14
+ import { delimiter, join } from "node:path";
15
+ import { probeMcpServer } from "../lib/mcpProbe.js";
16
+ import { pathRef, readJsonFile, resolveEnvRefs, scanSkillRoot } from "../lib/probeCommon.js";
17
+ export function zcodeEnv(homeDir, env = process.env) {
18
+ // A Node host can inherit Volta's selected Node binary without inheriting
19
+ // Volta's shim directory in PATH. Keep the user's explicit PATH order, but
20
+ // add the documented Volta bin location when necessary so `zcode` resolves
21
+ // the same way it does in their interactive terminal.
22
+ const voltaBin = env.VOLTA_HOME ? join(env.VOLTA_HOME, "bin") : undefined;
23
+ const pathParts = (env.PATH ?? "").split(delimiter).filter(Boolean);
24
+ if (voltaBin && !pathParts.includes(voltaBin))
25
+ pathParts.unshift(voltaBin);
26
+ return { ...env, HOME: homeDir, ...(pathParts.length ? { PATH: pathParts.join(delimiter) } : {}) };
27
+ }
28
+ function configPaths(homeDir) {
29
+ const root = join(homeDir, ".zcode");
30
+ return {
31
+ root,
32
+ community: join(root, "cli", "setting.json"),
33
+ official: join(root, "cli", "config.json"),
34
+ };
35
+ }
36
+ function publicCommand(env) {
37
+ return env.TRELLIS_ZCODE_BIN?.trim() || "zcode";
38
+ }
39
+ function cliFacts(homeDir, env) {
40
+ const command = publicCommand(env);
41
+ const childEnv = zcodeEnv(homeDir, env);
42
+ try {
43
+ const version = execFileSync(command, ["version"], { encoding: "utf-8", timeout: 5_000, env: childEnv }).trim();
44
+ let help;
45
+ let outputFormatSupported = false;
46
+ try {
47
+ help = execFileSync(command, ["--help"], { encoding: "utf-8", timeout: 5_000, env: childEnv });
48
+ }
49
+ catch {
50
+ // A version identity still permits a configuration-only profile.
51
+ }
52
+ try {
53
+ // Some zcode-app-cli builds accept --output-format but omit it from
54
+ // their generated help text. --version never starts a model turn, so
55
+ // it is a safe capability probe for the public argument contract.
56
+ execFileSync(command, ["--output-format", "stream-json", "--version"], { encoding: "utf-8", timeout: 5_000, env: childEnv });
57
+ outputFormatSupported = true;
58
+ }
59
+ catch {
60
+ // Keep the false value; callers then leave execution disabled.
61
+ }
62
+ return { command, version, ...(help ? { help } : {}), outputFormatSupported };
63
+ }
64
+ catch {
65
+ return {};
66
+ }
67
+ }
68
+ export function selectZcodeProfile(homeDir, facts = {}) {
69
+ const paths = configPaths(homeDir);
70
+ const version = facts.version ?? "";
71
+ const supportsExecution = Boolean(facts.command
72
+ && facts.help?.includes("--prompt")
73
+ && facts.help.includes("--resume")
74
+ && (facts.help.includes("--output-format") || facts.outputFormatSupported === true));
75
+ if (/^zcode-app-cli\b/im.test(version) || (existsSync(paths.community) && !existsSync(paths.official))) {
76
+ return {
77
+ kind: "community-cli",
78
+ configPath: paths.community,
79
+ ...(facts.command ? { executable: facts.command } : {}),
80
+ ...(facts.version ? { executableVersion: facts.version } : {}),
81
+ supportsExecution,
82
+ };
83
+ }
84
+ if (facts.command || existsSync(paths.official)) {
85
+ return {
86
+ kind: facts.command ? "official" : "configuration-only",
87
+ configPath: paths.official,
88
+ ...(facts.command ? { executable: facts.command } : {}),
89
+ ...(facts.version ? { executableVersion: facts.version } : {}),
90
+ supportsExecution,
91
+ };
92
+ }
93
+ if (existsSync(paths.root)) {
94
+ return { kind: "configuration-only", configPath: paths.official, supportsExecution: false };
95
+ }
96
+ return undefined;
97
+ }
98
+ export function resolveZcodeProfile(homeDir = homedir(), env = process.env) {
99
+ // A public `zcode` binary is installed system-wide, whereas probes run
100
+ // against an explicit home directory. Do not let the real machine's CLI
101
+ // make an empty sandbox home appear to have ZCode installed; the profile
102
+ // must have local `.zcode` state before any executable is queried.
103
+ if (!existsSync(configPaths(homeDir).root))
104
+ return undefined;
105
+ return selectZcodeProfile(homeDir, cliFacts(homeDir, env));
106
+ }
107
+ export async function probe(homeDir = homedir(), opts = {}) {
108
+ const env = opts.env ?? process.env;
109
+ const profile = resolveZcodeProfile(homeDir, env);
110
+ if (!profile)
111
+ return { agent: "zcode", present: false, skillRoots: [], mcpServers: [], diagnostics: [] };
112
+ const config = readJsonFile(profile.configPath);
113
+ const mcpServers = Object.entries(config?.mcp?.servers ?? {}).map(([name, def]) => ({
114
+ name,
115
+ transport: def.url ? (def.type === "sse" ? "sse" : "http") : "stdio",
116
+ }));
117
+ if (opts.probeMcp) {
118
+ await Promise.all(Object.entries(config?.mcp?.servers ?? {}).map(async ([name, def], index) => {
119
+ if (def.url || !def.command)
120
+ return;
121
+ mcpServers[index].probe = await probeMcpServer({ transport: "stdio", command: def.command, args: def.args }, resolveEnvRefs(def.env, zcodeEnv(homeDir, env)));
122
+ }));
123
+ }
124
+ const skillRoots = [
125
+ scanSkillRoot(join(homeDir, ".zcode", "skills")),
126
+ scanSkillRoot(join(homeDir, ".agents", "skills")),
127
+ ].filter((root) => root !== undefined);
128
+ const diagnostics = [];
129
+ if (!profile.supportsExecution)
130
+ diagnostics.push("ZCode public CLI execution is unavailable; configuration-only management is supported");
131
+ return {
132
+ agent: "zcode",
133
+ present: true,
134
+ ...(profile.executableVersion ? { version: profile.executableVersion } : {}),
135
+ skillRoots,
136
+ mcpServers,
137
+ instructionsFile: pathRef(join(homeDir, ".zcode", "AGENTS.md")),
138
+ diagnostics,
139
+ };
140
+ }
@@ -18,7 +18,7 @@ required reading for that.
18
18
  │ trellis sync / trellis doctor
19
19
  ┌───────┼────────┬─────────────┬──────────────┐
20
20
  ▼ ▼ ▼ ▼
21
- Claude Code Codex Kiro pi Kimi Code
21
+ Claude Code Codex Kiro pi Kimi Code ZCode
22
22
  (symlink adapter) (incremental (symlink adapter) (bridge
23
23
  TOML writer) extension)
24
24
  ```
@@ -57,7 +57,7 @@ Aligned to the `.agents Protocol` draft, extended where the draft is silent:
57
57
  ## Global vs. workspace scope
58
58
 
59
59
  **Global only, for now.** Trellis manages `~/.trellis` — one canonical
60
- source per machine, applied to that machine's five supported agents. It does not yet
60
+ source per machine, applied to that machine's six supported agents. It does not yet
61
61
  read or merge a project-local `.trellis/` in a specific repo.
62
62
 
63
63
  The `.agents Protocol` draft (see `research.md`) describes a two-layer
@@ -72,7 +72,7 @@ that's scope creep against this section, not a natural extension.
72
72
 
73
73
  ## Private / agent-specific capabilities
74
74
 
75
- Not everything belongs to all five agents. A skill built around Claude
75
+ Not everything belongs to all six agents. A skill built around Claude
76
76
  Code's Task-based subagent delegation has no equivalent to delegate to on
77
77
  Codex; an MCP server might only make sense for one agent's workflow. Every
78
78
  scopable item — skill, subagent profile, memory entry, MCP server —
@@ -483,8 +483,12 @@ portable Skill contract.
483
483
  provides — `~/.config/agent-env/secrets.env`, 1Password's `op run`,
484
484
  anything that populates `process.env` before an adapter's generated
485
485
  command runs)
486
- - A GUI (P5 in the roadmap evaluates embedding into an existing one —
487
- mcp-router's or skills-hub's — before building a new one)
486
+ - A GUI is now `@trellis/gui` — P10 in the roadmap, not P5 (P5 is
487
+ `@trellis/sdk`, the read-only API the GUI's own read views could have
488
+ used but didn't, per `openspec/changes/trellis-gui/design.md` Decision
489
+ 1 — the GUI's sidecar imports internal command modules directly
490
+ instead, to reuse write/backup mechanics `@trellis/sdk`'s read-only
491
+ barrel deliberately excludes)
488
492
 
489
493
  ## MCP handshake probing is opt-in, not default
490
494
 
@@ -0,0 +1,10 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" role="img" aria-labelledby="title desc">
2
+ <title id="title">Trellis mark</title>
3
+ <desc id="desc">A letter T whose beam is a trellis lattice.</desc>
4
+ <g fill="none" stroke="#161c24" stroke-linecap="round">
5
+ <rect x="16" y="18" width="64" height="18" rx="9" stroke-width="5.5"/>
6
+ <g stroke-width="2.6" clip-path="url(#a-beam)"><path d="M-64.5 32 L-55.5 23"/><path d="M-64.5 23 L-55.5 32"/><path d="M-46.5 32 L-37.5 23"/><path d="M-46.5 23 L-37.5 32"/><path d="M-28.5 32 L-19.5 23"/><path d="M-28.5 23 L-19.5 32"/><path d="M-10.5 32 L-1.5 23"/><path d="M-10.5 23 L-1.5 32"/><path d="M7.5 32 L16.5 23"/><path d="M7.5 23 L16.5 32"/><path d="M25.5 32 L34.5 23"/><path d="M25.5 23 L34.5 32"/><path d="M43.5 32 L52.5 23"/><path d="M43.5 23 L52.5 32"/><path d="M61.5 32 L70.5 23"/><path d="M61.5 23 L70.5 32"/><path d="M79.5 32 L88.5 23"/><path d="M79.5 23 L88.5 32"/><path d="M97.5 32 L106.5 23"/><path d="M97.5 23 L106.5 32"/><path d="M115.5 32 L124.5 23"/><path d="M115.5 23 L124.5 32"/></g>
7
+ <path d="M48 36 V78" stroke-width="8"/>
8
+ </g>
9
+ <defs><clipPath id="a-beam"><rect x="21" y="23" width="54" height="9" rx="4.5"/></clipPath></defs>
10
+ </svg>