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.
- package/README.md +43 -207
- package/dist/adapters/zcode.d.ts +21 -0
- package/dist/adapters/zcode.js +234 -0
- package/dist/cli.js +25 -9
- package/dist/commands/doctor.js +2 -0
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +4 -1
- package/dist/commands/mcp.d.ts +56 -14
- package/dist/commands/mcp.js +105 -28
- package/dist/commands/mcpGateway.js +2 -1
- package/dist/commands/mcpImport.d.ts +21 -2
- package/dist/commands/mcpImport.js +30 -7
- package/dist/commands/memory.d.ts +13 -1
- package/dist/commands/memory.js +15 -0
- package/dist/commands/migrate.js +4 -1
- package/dist/commands/onboard.js +15 -4
- package/dist/commands/rollback.js +24 -1
- package/dist/commands/secretsAudit.js +4 -1
- package/dist/commands/skill.d.ts +35 -6
- package/dist/commands/skill.js +67 -13
- package/dist/commands/sync.js +3 -1
- package/dist/core/adapter.d.ts +11 -1
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.js +1 -0
- package/dist/lib/agentBridge.d.ts +169 -0
- package/dist/lib/agentBridge.js +367 -0
- package/dist/lib/agentBridgeProvider.d.ts +14 -0
- package/dist/lib/agentBridgeProvider.js +95 -0
- package/dist/lib/backup.d.ts +30 -0
- package/dist/lib/backup.js +16 -1
- package/dist/lib/chatAgents.d.ts +42 -0
- package/dist/lib/chatAgents.js +59 -0
- package/dist/lib/envVarNames.d.ts +3 -0
- package/dist/lib/envVarNames.js +19 -0
- package/dist/lib/installAgent.d.ts +1 -1
- package/dist/lib/installAgent.js +1 -1
- package/dist/lib/mcpMigrateRead.d.ts +1 -0
- package/dist/lib/mcpMigrateRead.js +19 -1
- package/dist/lib/realHomeSnapshot.js +5 -0
- package/dist/lib/runtimeControlProvider.js +2 -0
- package/dist/lib/taskStore.js +1 -1
- package/dist/lib/terminalPicker.js +33 -6
- package/dist/lib/zcodeOwnership.d.ts +17 -0
- package/dist/lib/zcodeOwnership.js +37 -0
- package/dist/pi-bridge/bundle.js +2 -1
- package/dist/probes/codex.d.ts +7 -0
- package/dist/probes/codex.js +37 -5
- package/dist/probes/zcode.d.ts +32 -0
- package/dist/probes/zcode.js +140 -0
- package/docs/architecture.md +9 -5
- package/docs/assets/brand-v3/trellis-mark-v3-a.png +0 -0
- package/docs/assets/brand-v3/trellis-mark-v3-a.svg +10 -0
- package/docs/assets/poster-series-v3/README.md +60 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-en.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-en.svg +51 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-zh.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-zh.svg +51 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-en.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-en.svg +48 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-zh.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-zh.svg +48 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-en.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-en.svg +56 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-zh.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-zh.svg +56 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-en.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-en.svg +47 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-zh.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-zh.svg +47 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-en.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-en.svg +51 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-zh.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-zh.svg +51 -0
- package/docs/getting-started.md +173 -12
- package/docs/overview.md +6 -3
- package/docs/overview.zh-CN.md +5 -3
- package/docs/roadmap.md +2 -1
- package/docs/video/README.md +76 -0
- package/docs/video/build/audio/01-opening.aiff +0 -0
- package/docs/video/build/audio/01-opening.mp3 +0 -0
- package/docs/video/build/audio/02-pain.aiff +0 -0
- package/docs/video/build/audio/02-pain.mp3 +0 -0
- package/docs/video/build/audio/03-solution.aiff +0 -0
- package/docs/video/build/audio/03-solution.mp3 +0 -0
- package/docs/video/build/audio/04-install.aiff +0 -0
- package/docs/video/build/audio/04-install.mp3 +0 -0
- package/docs/video/build/audio/04-setup.mp3 +0 -0
- package/docs/video/build/audio/05-dryrun.mp3 +0 -0
- package/docs/video/build/audio/05-init.aiff +0 -0
- package/docs/video/build/audio/05-init.mp3 +0 -0
- package/docs/video/build/audio/05-interactive.mp3 +0 -0
- package/docs/video/build/audio/06-dryrun.aiff +0 -0
- package/docs/video/build/audio/06-dryrun.mp3 +0 -0
- package/docs/video/build/audio/06-onboard.mp3 +0 -0
- package/docs/video/build/audio/06-run.mp3 +0 -0
- package/docs/video/build/audio/07-onboard.aiff +0 -0
- package/docs/video/build/audio/07-onboard.mp3 +0 -0
- package/docs/video/build/audio/07-verify.mp3 +0 -0
- package/docs/video/build/audio/08-closing.mp3 +0 -0
- package/docs/video/build/audio/08-doctor.aiff +0 -0
- package/docs/video/build/audio/08-doctor.mp3 +0 -0
- package/docs/video/build/audio/09-rollback.aiff +0 -0
- package/docs/video/build/audio/09-rollback.mp3 +0 -0
- package/docs/video/build/audio/10-principles.aiff +0 -0
- package/docs/video/build/audio/10-principles.mp3 +0 -0
- package/docs/video/build/audio/11-closing.aiff +0 -0
- package/docs/video/build/audio/11-closing.mp3 +0 -0
- package/docs/video/build/frames/01-opening-0.png +0 -0
- package/docs/video/build/frames/02-pain-1.png +0 -0
- package/docs/video/build/frames/02-pain-2.png +0 -0
- package/docs/video/build/frames/03-solution-3.png +0 -0
- package/docs/video/build/frames/04-install-4.png +0 -0
- package/docs/video/build/frames/04-setup-4.png +0 -0
- package/docs/video/build/frames/04-setup-5.png +0 -0
- package/docs/video/build/frames/05-dryrun-6.png +0 -0
- package/docs/video/build/frames/05-init-5.png +0 -0
- package/docs/video/build/frames/05-interactive-5.png +0 -0
- package/docs/video/build/frames/05-interactive-6.png +0 -0
- package/docs/video/build/frames/06-dryrun-6.png +0 -0
- package/docs/video/build/frames/06-dryrun-7.png +0 -0
- package/docs/video/build/frames/06-onboard-7.png +0 -0
- package/docs/video/build/frames/06-onboard-8.png +0 -0
- package/docs/video/build/frames/06-run-7.png +0 -0
- package/docs/video/build/frames/06-run-8.png +0 -0
- package/docs/video/build/frames/07-onboard-10.png +0 -0
- package/docs/video/build/frames/07-onboard-7.png +0 -0
- package/docs/video/build/frames/07-onboard-8.png +0 -0
- package/docs/video/build/frames/07-onboard-9.png +0 -0
- package/docs/video/build/frames/07-verify-10.png +0 -0
- package/docs/video/build/frames/07-verify-8.png +0 -0
- package/docs/video/build/frames/07-verify-9.png +0 -0
- package/docs/video/build/frames/08-closing-10.png +0 -0
- package/docs/video/build/frames/08-closing-11.png +0 -0
- package/docs/video/build/frames/08-doctor-11.png +0 -0
- package/docs/video/build/frames/08-doctor-9.png +0 -0
- package/docs/video/build/frames/09-rollback-10.png +0 -0
- package/docs/video/build/frames/09-rollback-12.png +0 -0
- package/docs/video/build/frames/10-principles-11.png +0 -0
- package/docs/video/build/frames/10-principles-13.png +0 -0
- package/docs/video/build/frames/11-closing-12.png +0 -0
- package/docs/video/build/frames/11-closing-14.png +0 -0
- package/docs/video/build/segments/01-opening.mp4 +0 -0
- package/docs/video/build/segments/01-opening.txt +3 -0
- package/docs/video/build/segments/02-pain.mp4 +0 -0
- package/docs/video/build/segments/02-pain.txt +5 -0
- package/docs/video/build/segments/03-solution.mp4 +0 -0
- package/docs/video/build/segments/03-solution.txt +3 -0
- package/docs/video/build/segments/04-install.mp4 +0 -0
- package/docs/video/build/segments/04-install.txt +3 -0
- package/docs/video/build/segments/04-setup.mp4 +0 -0
- package/docs/video/build/segments/04-setup.txt +3 -0
- package/docs/video/build/segments/05-dryrun.mp4 +0 -0
- package/docs/video/build/segments/05-dryrun.txt +3 -0
- package/docs/video/build/segments/05-init.mp4 +0 -0
- package/docs/video/build/segments/05-init.txt +3 -0
- package/docs/video/build/segments/05-interactive.mp4 +0 -0
- package/docs/video/build/segments/05-interactive.txt +5 -0
- package/docs/video/build/segments/06-dryrun.mp4 +0 -0
- package/docs/video/build/segments/06-dryrun.txt +3 -0
- package/docs/video/build/segments/06-onboard.mp4 +0 -0
- package/docs/video/build/segments/06-onboard.txt +5 -0
- package/docs/video/build/segments/06-run.mp4 +0 -0
- package/docs/video/build/segments/06-run.txt +3 -0
- package/docs/video/build/segments/07-onboard.mp4 +0 -0
- package/docs/video/build/segments/07-onboard.txt +5 -0
- package/docs/video/build/segments/07-verify.mp4 +0 -0
- package/docs/video/build/segments/07-verify.txt +5 -0
- package/docs/video/build/segments/08-closing.mp4 +0 -0
- package/docs/video/build/segments/08-closing.txt +3 -0
- package/docs/video/build/segments/08-doctor.mp4 +0 -0
- package/docs/video/build/segments/08-doctor.txt +3 -0
- package/docs/video/build/segments/09-rollback.mp4 +0 -0
- package/docs/video/build/segments/09-rollback.txt +3 -0
- package/docs/video/build/segments/10-principles.mp4 +0 -0
- package/docs/video/build/segments/10-principles.txt +3 -0
- package/docs/video/build/segments/11-closing.mp4 +0 -0
- package/docs/video/build/segments/11-closing.txt +3 -0
- package/docs/video/build/segments/final.txt +8 -0
- package/docs/video/build-ai/audio/01-hook.mp3 +0 -0
- package/docs/video/build-ai/audio/02-pain.mp3 +0 -0
- package/docs/video/build-ai/audio/03-solution.mp3 +0 -0
- package/docs/video/build-ai/audio/04-how.mp3 +0 -0
- package/docs/video/build-ai/audio/05-safe.mp3 +0 -0
- package/docs/video/build-ai/audio/06-closing.mp3 +0 -0
- package/docs/video/build-ai/clips/01-hook.mp4 +0 -0
- package/docs/video/build-ai/clips/02-pain.mp4 +0 -0
- package/docs/video/build-ai/clips/03-solution.mp4 +0 -0
- package/docs/video/build-ai/clips/04-how.mp4 +0 -0
- package/docs/video/build-ai/clips/05-safe.mp4 +0 -0
- package/docs/video/build-ai/clips/06-closing.mp4 +0 -0
- package/docs/video/build-ai/first-frame-02-pain.png +0 -0
- package/docs/video/build-ai/first-frame-03-solution.png +0 -0
- package/docs/video/build-ai/first-frame-04-how.png +0 -0
- package/docs/video/build-ai/first-frame-05-safe.png +0 -0
- package/docs/video/build-ai/segments/01-hook.caption.txt +2 -0
- package/docs/video/build-ai/segments/01-hook.mp4 +0 -0
- package/docs/video/build-ai/segments/02-pain.mp4 +0 -0
- package/docs/video/build-ai/segments/03-solution.mp4 +0 -0
- package/docs/video/build-ai/segments/04-how.mp4 +0 -0
- package/docs/video/build-ai/segments/05-safe.mp4 +0 -0
- package/docs/video/build-ai/segments/06-closing.mp4 +0 -0
- package/docs/video/build-ai/segments/cap-01-hook.png +0 -0
- package/docs/video/build-ai/segments/cap-02-pain.png +0 -0
- package/docs/video/build-ai/segments/cap-03-solution.png +0 -0
- package/docs/video/build-ai/segments/cap-04-how.png +0 -0
- package/docs/video/build-ai/segments/cap-05-safe.png +0 -0
- package/docs/video/build-ai/segments/cap-06-closing.png +0 -0
- package/docs/video/build-ai/segments/final.txt +6 -0
- package/docs/video/build-posters/audio/01-hook.mp3 +0 -0
- package/docs/video/build-posters/audio/02-pain.mp3 +0 -0
- package/docs/video/build-posters/audio/03-solution.mp3 +0 -0
- package/docs/video/build-posters/audio/04-how.mp3 +0 -0
- package/docs/video/build-posters/audio/05-safe.mp3 +0 -0
- package/docs/video/build-posters/audio/06-closing.mp3 +0 -0
- package/docs/video/build-posters/cap-01-hook.png +0 -0
- package/docs/video/build-posters/cap-02-pain.png +0 -0
- package/docs/video/build-posters/cap-03-solution.png +0 -0
- package/docs/video/build-posters/cap-04-how.png +0 -0
- package/docs/video/build-posters/cap-05-safe.png +0 -0
- package/docs/video/build-posters/cap-06-closing.png +0 -0
- package/docs/video/build-posters/card-01-hook.png +0 -0
- package/docs/video/build-posters/card-06-closing.png +0 -0
- package/docs/video/build-posters/segments/01-hook.mp4 +0 -0
- package/docs/video/build-posters/segments/02-pain.mp4 +0 -0
- package/docs/video/build-posters/segments/03-solution.mp4 +0 -0
- package/docs/video/build-posters/segments/04-how.mp4 +0 -0
- package/docs/video/build-posters/segments/05-safe.mp4 +0 -0
- package/docs/video/build-posters/segments/06-closing.mp4 +0 -0
- package/docs/video/build-posters/segments/final.txt +6 -0
- package/docs/video/generate-video-ai.mjs +334 -0
- package/docs/video/generate-video-posters.mjs +204 -0
- package/docs/video/generate-video.mjs +426 -0
- package/docs/video/outline.zh-CN.md +102 -0
- package/docs/video/trellis-demo-video-ai.mp4 +0 -0
- package/docs/video/trellis-demo-video.mp4 +0 -0
- package/docs/video/voiceover.zh-CN.md +49 -0
- package/package.json +5 -2
- package/schema/servers.example.yaml +1 -1
- package/docs/assets/agent-trellis-architecture-horizontal.svg +0 -80
- package/docs/assets/agent-trellis-architecture-vertical.svg +0 -82
- 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
|
package/dist/lib/envVarNames.js
CHANGED
|
@@ -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. */
|
package/dist/lib/installAgent.js
CHANGED
|
@@ -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:
|
|
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 };
|
package/dist/lib/taskStore.js
CHANGED
|
@@ -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
|
-
|
|
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 ===
|
|
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
|
+
}
|
package/dist/pi-bridge/bundle.js
CHANGED
|
@@ -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;
|
package/dist/probes/codex.d.ts
CHANGED
|
@@ -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>;
|
package/dist/probes/codex.js
CHANGED
|
@@ -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
|
|
70
|
+
const scopedCodexEnv = codexEnv(homeDir);
|
|
47
71
|
let version;
|
|
48
72
|
try {
|
|
49
|
-
version = execFileSync("codex", ["--version"], { encoding: "utf-8", timeout: 5_000, env:
|
|
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:
|
|
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
|
-
|
|
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
|
+
}
|
package/docs/architecture.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
487
|
-
|
|
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
|
|
|
Binary file
|
|
@@ -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>
|