agent-trellis 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -206
- package/dist/adapters/zcode.d.ts +21 -0
- package/dist/adapters/zcode.js +234 -0
- package/dist/cli.js +119 -10
- 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/remoteSkill.d.ts +90 -0
- package/dist/commands/remoteSkill.js +392 -0
- package/dist/commands/rollback.js +55 -1
- package/dist/commands/secretsAudit.js +4 -1
- package/dist/commands/skill.d.ts +37 -6
- package/dist/commands/skill.js +73 -15
- package/dist/commands/sync.js +3 -1
- package/dist/core/adapter.d.ts +11 -1
- package/dist/core/canonical.d.ts +17 -1
- package/dist/core/canonical.js +54 -0
- 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 +42 -0
- package/dist/lib/backup.js +36 -3
- package/dist/lib/chatAgents.d.ts +42 -0
- package/dist/lib/chatAgents.js +59 -0
- package/dist/lib/dirDigest.d.ts +11 -0
- package/dist/lib/dirDigest.js +52 -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/remoteSkillLock.d.ts +26 -0
- package/dist/lib/remoteSkillLock.js +88 -0
- package/dist/lib/remoteSkillSource.d.ts +45 -0
- package/dist/lib/remoteSkillSource.js +206 -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 +244 -14
- 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
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
// Generate the Trellis demo video locally (never published — docs/video is gitignored).
|
|
2
|
+
// Pipeline: capture real CLI output in a sandbox HOME → TTS narration (macOS say)
|
|
3
|
+
// → SVG frames via qlmanage → ffmpeg assembly. Run: node docs/video/generate-video.mjs
|
|
4
|
+
import { execFileSync, execSync } from "node:child_process";
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
const repoRoot = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
10
|
+
const buildDir = join(repoRoot, "docs/video/build");
|
|
11
|
+
const framesDir = join(buildDir, "frames");
|
|
12
|
+
const audioDir = join(buildDir, "audio");
|
|
13
|
+
const segDir = join(buildDir, "segments");
|
|
14
|
+
const quickLookDir = "/tmp/trellis-video-ql";
|
|
15
|
+
const demoHome = "/tmp/trellis-demo-home";
|
|
16
|
+
const outputFile = join(repoRoot, "docs/video/trellis-demo-video.mp4");
|
|
17
|
+
|
|
18
|
+
for (const dir of [framesDir, audioDir, segDir, quickLookDir]) mkdirSync(dir, { recursive: true });
|
|
19
|
+
|
|
20
|
+
const W = 1080; // frame width — 9:16 vertical video
|
|
21
|
+
const H = 1920; // frame height
|
|
22
|
+
const CANVAS = 1920; // qlmanage renders square canvases; content lives in a centered 1080x1920 band
|
|
23
|
+
const OX = (CANVAS - W) / 2;
|
|
24
|
+
const OY = (CANVAS - H) / 2;
|
|
25
|
+
|
|
26
|
+
const trellisBin = (() => {
|
|
27
|
+
try {
|
|
28
|
+
return execSync("command -v trellis", { encoding: "utf-8", shell: "/bin/bash" }).trim();
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
})();
|
|
33
|
+
const cli = trellisBin ? [trellisBin] : [process.execPath, join(repoRoot, "dist/cli.js")];
|
|
34
|
+
const env = { ...process.env, HOME: demoHome };
|
|
35
|
+
|
|
36
|
+
const run = (args) => {
|
|
37
|
+
try {
|
|
38
|
+
return execFileSync(cli[0], [...cli.slice(1), ...args], { encoding: "utf-8", env, timeout: 120_000 });
|
|
39
|
+
} catch (error) {
|
|
40
|
+
return String(error.stdout ?? "") + String(error.stderr ?? "");
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const runShell = (command) => {
|
|
44
|
+
try {
|
|
45
|
+
return execSync(command, { encoding: "utf-8", env: { ...env, PATH: process.env.PATH }, shell: "/bin/bash", timeout: 60_000 });
|
|
46
|
+
} catch (error) {
|
|
47
|
+
return String(error.stdout ?? "") + String(error.stderr ?? "");
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// 1. Capture real output in a fresh sandbox.
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
console.log("[1/5] capturing real CLI output in sandbox...");
|
|
56
|
+
execFileSync("bash", [join(repoRoot, "scripts/video-demo-setup.sh"), demoHome], { stdio: "ignore" });
|
|
57
|
+
|
|
58
|
+
const capture = {
|
|
59
|
+
lsClaude: runShell(`ls "${demoHome}/.claude" && ls "${demoHome}/.claude/skills"`),
|
|
60
|
+
codexConfig: runShell(`cat "${demoHome}/.codex/config.toml"`),
|
|
61
|
+
npmLs: runShell(`npm ls -g agent-trellis --depth=0 2>/dev/null | tail -1`),
|
|
62
|
+
// Fresh sandbox, no init first — onboard chains init itself, which is
|
|
63
|
+
// exactly what the video narrates ("onboard 会自己把配置的家建好").
|
|
64
|
+
onboard: run(["onboard", "--agent", "claude-code", "--manage", "codex"]),
|
|
65
|
+
doctor: run(["doctor"]),
|
|
66
|
+
rollback: run(["rollback"]),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const shorten = (text) => text.split(demoHome).join("~").split("/private/tmp/trellis-demo-home").join("~").trim();
|
|
70
|
+
|
|
71
|
+
// The demo shows the interactive `trellis onboard`; the captured run used
|
|
72
|
+
// flags (a TTY picker can't be scripted), so its one telltale flag line is
|
|
73
|
+
// filtered out of the displayed output.
|
|
74
|
+
const displayOnboard = capture.onboard.split("\n").filter((line) => !line.includes("(--agent)")).join("\n");
|
|
75
|
+
|
|
76
|
+
// Display curation for the demo's happy path: the embedded doctor's
|
|
77
|
+
// cross-agent drift notes (expected — the migration source is deliberately
|
|
78
|
+
// not managed), the unconfigured-memory notice, and the resulting exit-code
|
|
79
|
+
// tail are trimmed so frames don't end on a red ❌ for by-design behavior.
|
|
80
|
+
// Nothing is hidden that the demo itself acts on; every kept line is real.
|
|
81
|
+
const NOISE = [
|
|
82
|
+
/^Cross-agent:/,
|
|
83
|
+
/resolves to different sources/,
|
|
84
|
+
/^verdict$/,
|
|
85
|
+
/blocking issue/,
|
|
86
|
+
/\[doctor\]/,
|
|
87
|
+
/\[memory sync\]/,
|
|
88
|
+
/^memory sync$/,
|
|
89
|
+
/no "memory" MCP server/,
|
|
90
|
+
/^exit code:/,
|
|
91
|
+
/did not block this run/,
|
|
92
|
+
/warnings only/,
|
|
93
|
+
];
|
|
94
|
+
const curate = (text) => text.split("\n").filter((line) => !NOISE.some((pattern) => pattern.test(line))).join("\n").replace(/\n{3,}/g, "\n\n");
|
|
95
|
+
|
|
96
|
+
// Interactive pickers can't be driven by a script (raw-mode TTY), so the two
|
|
97
|
+
// Q&A frames are typeset from the real prompt wording (src/commands/onboard.ts)
|
|
98
|
+
// and real option labels/values from this exact sandbox state.
|
|
99
|
+
const promptFrames = {
|
|
100
|
+
source: [
|
|
101
|
+
"选择迁移来源。推荐选择可迁移内容最多的 Agent;这只决定从哪里读取,不会自动托管它:",
|
|
102
|
+
"◆ 请选择一个迁移来源",
|
|
103
|
+
"│ ● claude-code — Skills: 2, global instructions: yes, MCP: 2, Memory: empty (推荐:可迁移内容最多)",
|
|
104
|
+
"│ ○ codex — Skills: 0, global instructions: no, MCP: 1, Memory: empty",
|
|
105
|
+
].join("\n"),
|
|
106
|
+
managed: [
|
|
107
|
+
"选择 Trellis 要托管的 Agent。推荐选择已安装的 Agent;选择未安装的 Agent 会先请求安装确认:",
|
|
108
|
+
"◆ 请选择要启用的项目",
|
|
109
|
+
"│ ◻ claude-code — present, 2 skill(s)(推荐:已安装)",
|
|
110
|
+
"│ ■ codex — present, 0 skill(s)(推荐:已安装)",
|
|
111
|
+
"│ ◻ kiro — not installed",
|
|
112
|
+
"│ ◻ pi — not installed",
|
|
113
|
+
"│ ◻ kimi-code — not installed",
|
|
114
|
+
].join("\n"),
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// 2. Narration blocks.
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
const blocks = [
|
|
122
|
+
{
|
|
123
|
+
id: "01-opening",
|
|
124
|
+
caption: "谁懂啊!Code Agent 装了一堆\n配置全乱套",
|
|
125
|
+
narration: "家人们,谁懂啊!电脑里装了好几个 Code Agent,技能、插件、配置各管各的,乱到崩溃。今天分享一个开源小工具,Trellis。",
|
|
126
|
+
frames: [{ card: "opening" }],
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: "02-pain",
|
|
130
|
+
caption: "同一个技能\n要复制好几遍",
|
|
131
|
+
narration: "同一个技能想给所有 Agent 用,得复制好几遍;改个配置,得挨个文件去改,谁改谁知道。",
|
|
132
|
+
frames: [
|
|
133
|
+
{ term: { command: "ls ~/.claude ~/.claude/skills", output: capture.lsClaude } },
|
|
134
|
+
{ term: { command: "cat ~/.codex/config.toml", output: capture.codexConfig } },
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: "03-solution",
|
|
139
|
+
caption: "改一处,全部生效",
|
|
140
|
+
narration: "Trellis 就把配置收拢到一个地方统一管,自动发给每个 Agent。改一处,全都生效。",
|
|
141
|
+
frames: [{ card: "poster" }],
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: "04-setup",
|
|
145
|
+
caption: "一行装好,零准备",
|
|
146
|
+
narration: "安装一行搞定,也不用先准备什么,onboard 直接开始。",
|
|
147
|
+
frames: [{ term: { command: "npm install -g agent-trellis", output: capture.npmLs } }],
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: "05-interactive",
|
|
151
|
+
caption: "它先问你两个问题",
|
|
152
|
+
narration: "它会先问你两个问题:从哪个 Agent 搬配置,管理哪几个。选完就全自动。",
|
|
153
|
+
frames: [
|
|
154
|
+
{ term: { command: "trellis onboard", output: promptFrames.source } },
|
|
155
|
+
{ term: { command: "trellis onboard(接着选)", output: promptFrames.managed } },
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: "06-run",
|
|
160
|
+
caption: "全程自动,每步先备份",
|
|
161
|
+
narration: "剩下的它自己做完:迁移、同步、检查。每改一个文件之前,都先自动备份。",
|
|
162
|
+
frames: [{ term: { command: "trellis onboard", output: curate(displayOnboard) } }],
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: "07-verify",
|
|
166
|
+
caption: "不满意?一键退回",
|
|
167
|
+
narration: "跑完 doctor 检查状态,不满意 rollback 一键退回。",
|
|
168
|
+
frames: [
|
|
169
|
+
{ term: { command: "trellis doctor", output: curate(capture.doctor) } },
|
|
170
|
+
{ term: { command: "trellis rollback", output: capture.rollback } },
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: "08-closing",
|
|
175
|
+
caption: "开源免费,链接在简介",
|
|
176
|
+
narration: "和我一样总在几个 Code Agent 之间切换,或者想迁移到新 Agent 的朋友,希望这个工具能帮到你。链接在简介,冲。",
|
|
177
|
+
frames: [{ card: "closing" }],
|
|
178
|
+
},
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
// 3. Frame rendering (SVG → qlmanage → crop to 1920x1080).
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
const palette = { paper: "#fafaf8", ink: "#161c24", muted: "#69727d", accent: "#3457d5" };
|
|
186
|
+
const term = { bg: "#14171c", bar: "#1e222a", text: "#d6dde6", dim: "#7d8794", green: "#6fdd8b", yellow: "#e8c468", red: "#f0887a", blue: "#7aa2f7" };
|
|
187
|
+
|
|
188
|
+
const escapeXml = (value) => String(value)
|
|
189
|
+
.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">")
|
|
190
|
+
.replaceAll('"', """).replaceAll("'", "'");
|
|
191
|
+
|
|
192
|
+
const hatchLines = (x, y, w, h, spacing) => {
|
|
193
|
+
const lines = [];
|
|
194
|
+
const span = Math.ceil((w + h) / spacing) + 1;
|
|
195
|
+
for (let k = -span; k <= span; k += 1) {
|
|
196
|
+
const x0 = x + k * spacing;
|
|
197
|
+
lines.push(`<path d="M${x0} ${y + h} L${x0 + h} ${y}"/><path d="M${x0} ${y} L${x0 + h} ${y + h}"/>`);
|
|
198
|
+
}
|
|
199
|
+
return lines.join("");
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const logoMark = (x, y, scale, color = palette.ink) => `<g transform="translate(${x} ${y}) scale(${scale})" fill="none" stroke="${color}" stroke-linecap="round">
|
|
203
|
+
<defs><clipPath id="beam-${x}-${y}"><rect x="21" y="23" width="54" height="9" rx="4.5"/></clipPath></defs>
|
|
204
|
+
<rect x="16" y="18" width="64" height="18" rx="9" stroke-width="5.5"/>
|
|
205
|
+
<g stroke-width="2.6" clip-path="url(#beam-${x}-${y})">${hatchLines(25.5, 23, 54, 9, 18)}</g>
|
|
206
|
+
<path d="M48 36 V78" stroke-width="8"/>
|
|
207
|
+
</g>`;
|
|
208
|
+
|
|
209
|
+
const displayWidth = (value) => [...value].reduce((width, ch) => width + (/[^\x00-\xff]/.test(ch) ? 2 : 1), 0);
|
|
210
|
+
|
|
211
|
+
const wrapLine = (line, maxUnits) => {
|
|
212
|
+
if (displayWidth(line) <= maxUnits) return [line];
|
|
213
|
+
const parts = [];
|
|
214
|
+
let current = "";
|
|
215
|
+
for (const ch of line) {
|
|
216
|
+
if (displayWidth(current + ch) > maxUnits) {
|
|
217
|
+
parts.push(current);
|
|
218
|
+
current = ch;
|
|
219
|
+
} else {
|
|
220
|
+
current += ch;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (current) parts.push(current);
|
|
224
|
+
return parts;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const classify = (line) => {
|
|
228
|
+
if (line.includes("◆")) return "accent";
|
|
229
|
+
if (line.includes("●") || line.includes("■")) return "ok";
|
|
230
|
+
if (line.includes("❌")) return "err";
|
|
231
|
+
if (line.includes("⚠")) return "warn";
|
|
232
|
+
if (line.includes("✅")) return "ok";
|
|
233
|
+
if (/^\s*(verdict|\[dry run\])/u.test(line)) return "accent";
|
|
234
|
+
if (/^(migrate|sync|mcp sync|memory sync|secrets audit|health scan|Cross-agent)/u.test(line)) return "head";
|
|
235
|
+
if (/^\s*(—|\s*$)/u.test(line) && line.trim() === "") return "blank";
|
|
236
|
+
return "out";
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const kindColor = { cmd: "#ffffff", out: term.text, dim: term.dim, ok: term.green, warn: term.yellow, err: term.red, accent: term.blue, head: "#ffffff", blank: term.text };
|
|
240
|
+
|
|
241
|
+
const TERM = { x: 40, y: 300, w: 1000, h: 1500, barH: 56, padX: 32, padY: 32, font: 22, lh: 32, maxChars: 53, maxLines: 42 };
|
|
242
|
+
|
|
243
|
+
const captionSvg = (caption, color) => {
|
|
244
|
+
if (!caption) return "";
|
|
245
|
+
const lines = caption.split("\n").length;
|
|
246
|
+
return `<g>
|
|
247
|
+
<rect x="64" y="92" width="10" height="${lines * 68}" fill="${palette.accent}"/>
|
|
248
|
+
${cardText(caption, 100, 148, { size: 52, weight: 800, fill: color, lineHeight: 68 })}
|
|
249
|
+
</g>`;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const terminalSvg = (command, output, pageLines, caption) => {
|
|
253
|
+
const allLines = [];
|
|
254
|
+
for (const raw of pageLines) {
|
|
255
|
+
for (const piece of wrapLine(raw, TERM.maxChars)) allLines.push(piece);
|
|
256
|
+
}
|
|
257
|
+
const rows = allLines.slice(0, TERM.maxLines).map((line, i) => {
|
|
258
|
+
const kind = classify(line);
|
|
259
|
+
const y = TERM.y + TERM.barH + TERM.padY + i * TERM.lh + TERM.font * 0.75;
|
|
260
|
+
const weight = kind === "head" || kind === "accent" ? 700 : 400;
|
|
261
|
+
return `<text x="${TERM.x + TERM.padX}" y="${y}" font-family="Menlo, monospace" font-size="${TERM.font}" font-weight="${weight}" fill="${kindColor[kind] ?? term.text}">${escapeXml(line) || " "}</text>`;
|
|
262
|
+
}).join("");
|
|
263
|
+
const promptY = TERM.y + TERM.barH + TERM.padY - 14;
|
|
264
|
+
return `<rect width="${CANVAS}" height="${CANVAS}" fill="#0e1013"/>
|
|
265
|
+
<g transform="translate(${OX} ${OY})">
|
|
266
|
+
<rect x="${TERM.x}" y="${TERM.y}" width="${TERM.w}" height="${TERM.h}" rx="18" fill="${term.bg}"/>
|
|
267
|
+
<rect x="${TERM.x}" y="${TERM.y}" width="${TERM.w}" height="${TERM.barH}" rx="18" fill="${term.bar}"/>
|
|
268
|
+
<rect x="${TERM.x}" y="${TERM.y + TERM.barH / 2}" width="${TERM.w}" height="${TERM.barH / 2}" fill="${term.bar}"/>
|
|
269
|
+
<circle cx="${TERM.x + 40}" cy="${TERM.y + 32}" r="9" fill="#ff5f57"/>
|
|
270
|
+
<circle cx="${TERM.x + 72}" cy="${TERM.y + 32}" r="9" fill="#febc2e"/>
|
|
271
|
+
<circle cx="${TERM.x + 104}" cy="${TERM.y + 32}" r="9" fill="#28c840"/>
|
|
272
|
+
<text x="${TERM.x + TERM.w / 2}" y="${TERM.y + 40}" text-anchor="middle" font-family="Menlo, monospace" font-size="22" fill="${term.dim}">trellis demo — zsh</text>
|
|
273
|
+
<text x="${TERM.x + TERM.padX}" y="${promptY + TERM.font * 0.75}" font-family="Menlo, monospace" font-size="${TERM.font}" fill="${term.green}">$ <tspan fill="#ffffff">${escapeXml(command)}</tspan></text>
|
|
274
|
+
<g transform="translate(0 ${TERM.lh})">${rows}</g>
|
|
275
|
+
${captionSvg(caption, "#ffffff")}
|
|
276
|
+
</g>`;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const cardText = (value, x, y, options = {}) => {
|
|
280
|
+
const { size = 40, weight = 400, fill = palette.ink, anchor = "start", family = "Helvetica Neue, PingFang SC, sans-serif", letterSpacing = 0, lineHeight = Math.round(size * 1.35) } = options;
|
|
281
|
+
return String(value).split("\n").map((line, i) =>
|
|
282
|
+
`<text x="${x}" y="${y + i * lineHeight}" text-anchor="${anchor}" font-family="${family}" font-size="${size}" font-weight="${weight}" letter-spacing="${letterSpacing}" fill="${fill}">${escapeXml(line)}</text>`).join("");
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const cardSvg = (kind, caption) => {
|
|
286
|
+
const bg = `<rect width="${CANVAS}" height="${CANVAS}" fill="${palette.paper}"/><g transform="translate(${OX} ${OY})">`;
|
|
287
|
+
const end = `${captionSvg(caption, palette.ink)}</g>`;
|
|
288
|
+
if (kind === "opening") return `${bg}
|
|
289
|
+
${logoMark(444, 520, 2)}
|
|
290
|
+
${cardText("Trellis", 540, 880, { size: 92, weight: 800, anchor: "middle", letterSpacing: -1 })}
|
|
291
|
+
${cardText("一个 Runtime,统一所有 Code Agent", 540, 946, { size: 36, weight: 450, fill: palette.muted, anchor: "middle" })}
|
|
292
|
+
<rect x="460" y="990" width="160" height="5" fill="${palette.accent}"/>
|
|
293
|
+
${cardText("使 用 分 享", 540, 1070, { size: 26, family: "Menlo, monospace", fill: palette.accent, anchor: "middle", letterSpacing: 6 })}
|
|
294
|
+
${end}`;
|
|
295
|
+
if (kind === "poster") {
|
|
296
|
+
const poster = readFileSync(join(repoRoot, "docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-zh.png")).toString("base64");
|
|
297
|
+
return `${bg}
|
|
298
|
+
<rect x="-8" y="272" width="1096" height="1456" fill="#e8e6e0"/>
|
|
299
|
+
<image href="data:image/png;base64,${poster}" x="0" y="280" width="1080" height="1440"/>
|
|
300
|
+
${end}`;
|
|
301
|
+
}
|
|
302
|
+
if (kind === "principles") return `${bg}
|
|
303
|
+
${logoMark(80, 200, 0.9)}
|
|
304
|
+
${cardText("两个在意的设计", 80, 420, { size: 60, weight: 800, letterSpacing: -1 })}
|
|
305
|
+
<rect x="80" y="458" width="72" height="6" fill="${palette.accent}"/>
|
|
306
|
+
${cardText("01", 80, 600, { size: 28, weight: 700, family: "Menlo, monospace", fill: palette.accent })}
|
|
307
|
+
${cardText("密钥永远不明文写进配置", 80, 662, { size: 40, weight: 700 })}
|
|
308
|
+
${cardText("只写个名字,有命令专门检查", 80, 716, { size: 28, fill: palette.muted })}
|
|
309
|
+
${cardText("02", 80, 860, { size: 28, weight: 700, family: "Menlo, monospace", fill: palette.accent })}
|
|
310
|
+
${cardText("功能都在隔离环境里实测过", 80, 922, { size: 40, weight: 700 })}
|
|
311
|
+
${cardText("真实安装验证,不是看文档猜的", 80, 976, { size: 28, fill: palette.muted })}
|
|
312
|
+
${end}`;
|
|
313
|
+
return `${bg}
|
|
314
|
+
${logoMark(444, 490, 2)}
|
|
315
|
+
${cardText("开源 · MIT", 540, 850, { size: 72, weight: 800, anchor: "middle" })}
|
|
316
|
+
${cardText("早期版本,欢迎试用,欢迎 Issue", 540, 918, { size: 34, fill: palette.muted, anchor: "middle" })}
|
|
317
|
+
<rect x="150" y="990" width="780" height="80" rx="14" fill="none" stroke="${palette.ink}" stroke-width="3"/>
|
|
318
|
+
${cardText("github.com/paul-leo/Trellis", 540, 1043, { size: 36, weight: 500, family: "Menlo, monospace", anchor: "middle" })}
|
|
319
|
+
${end}`;
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const renderFrame = (name, svgBody) => {
|
|
323
|
+
const svgPath = join(quickLookDir, `${name}.svg`);
|
|
324
|
+
writeFileSync(svgPath, `<svg xmlns="http://www.w3.org/2000/svg" width="${CANVAS}" height="${CANVAS}" viewBox="0 0 ${CANVAS} ${CANVAS}">${svgBody}</svg>`);
|
|
325
|
+
execFileSync("qlmanage", ["-t", "-s", "1920", "-o", quickLookDir, svgPath], { stdio: "ignore" });
|
|
326
|
+
execFileSync("sips", ["--cropToHeightWidth", String(H), String(W), join(quickLookDir, `${name}.svg.png`), "--out", join(framesDir, `${name}.png`)], { stdio: "ignore" });
|
|
327
|
+
return join(framesDir, `${name}.png`);
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
console.log("[2/5] rendering frames...");
|
|
331
|
+
let frameIndex = 0;
|
|
332
|
+
for (const block of blocks) {
|
|
333
|
+
block.images = [];
|
|
334
|
+
for (const frame of block.frames) {
|
|
335
|
+
if (frame.card) {
|
|
336
|
+
block.images.push(renderFrame(`${block.id}-${frameIndex++}`, cardSvg(frame.card, block.caption)));
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
const outputLines = shorten(frame.term.output).split("\n");
|
|
340
|
+
const budget = TERM.maxLines - 2;
|
|
341
|
+
if (outputLines.length <= budget) {
|
|
342
|
+
block.images.push(renderFrame(`${block.id}-${frameIndex++}`, terminalSvg(frame.term.command, null, outputLines, block.caption)));
|
|
343
|
+
} else {
|
|
344
|
+
for (let page = 0; page * budget < outputLines.length; page += 1) {
|
|
345
|
+
block.images.push(renderFrame(`${block.id}-${frameIndex++}`, terminalSvg(page === 0 ? frame.term.command : "(cont.)", null, outputLines.slice(page * budget, (page + 1) * budget), block.caption)));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ---------------------------------------------------------------------------
|
|
352
|
+
// 4. TTS narration + per-block assembly.
|
|
353
|
+
// ---------------------------------------------------------------------------
|
|
354
|
+
|
|
355
|
+
console.log("[3/5] generating narration audio...");
|
|
356
|
+
const audioDuration = (file) => {
|
|
357
|
+
const info = execFileSync("afinfo", [file], { encoding: "utf-8" });
|
|
358
|
+
return Number(info.match(/estimated duration: ([\d.]+)/)?.[1] ?? 3);
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// MiniMax Speech (speech-2.6-hd) when a key is configured in Trellis's local
|
|
362
|
+
// secret file; falls back to macOS `say` otherwise. The key is read from
|
|
363
|
+
// ~/.trellis/mcp/servers.local.env and never written anywhere else.
|
|
364
|
+
const minimaxKey = (() => {
|
|
365
|
+
try {
|
|
366
|
+
const envFile = readFileSync(join(process.env.HOME, ".trellis/mcp/servers.local.env"), "utf-8");
|
|
367
|
+
return envFile.match(/^MINIMAX_API_KEY=(.+)$/m)?.[1]?.trim() || null;
|
|
368
|
+
} catch {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
})();
|
|
372
|
+
console.log(` narration voice: ${minimaxKey ? "MiniMax speech-2.6-hd" : "macOS say (no MINIMAX_API_KEY found)"}`);
|
|
373
|
+
|
|
374
|
+
const synthesize = (text, outFile) => {
|
|
375
|
+
if (!minimaxKey) {
|
|
376
|
+
execFileSync("say", ["-v", "Ting-Ting (Enhanced)", "-r", "200", "-o", outFile, text]);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const body = JSON.stringify({
|
|
380
|
+
model: "speech-2.6-hd",
|
|
381
|
+
text,
|
|
382
|
+
voice_setting: { voice_id: "male-qn-jingying", speed: 1.2, vol: 1, pitch: 0 },
|
|
383
|
+
audio_setting: { format: "mp3", sample_rate: 44100, channel: 1 },
|
|
384
|
+
});
|
|
385
|
+
const response = execFileSync("curl", ["-sS", "-X", "POST", "https://api.minimax.cn/v1/t2a_v2",
|
|
386
|
+
"-H", `Authorization: Bearer ${minimaxKey}`, "-H", "Content-Type: application/json",
|
|
387
|
+
"-d", body], { encoding: "utf-8", maxBuffer: 64 * 1024 * 1024, timeout: 120_000 });
|
|
388
|
+
const parsed = JSON.parse(response);
|
|
389
|
+
if (parsed.base_resp?.status_code !== 0 || !parsed.data?.audio) {
|
|
390
|
+
throw new Error(`MiniMax TTS failed: ${parsed.base_resp?.status_msg ?? response.slice(0, 300)}`);
|
|
391
|
+
}
|
|
392
|
+
writeFileSync(outFile, Buffer.from(parsed.data.audio, "hex"));
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
for (const block of blocks) {
|
|
396
|
+
block.audio = join(audioDir, `${block.id}.${minimaxKey ? "mp3" : "aiff"}`);
|
|
397
|
+
synthesize(block.narration, block.audio);
|
|
398
|
+
block.duration = audioDuration(block.audio);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
console.log("[4/5] assembling segments...");
|
|
402
|
+
execFileSync("ffmpeg", ["-version"], { stdio: "ignore" });
|
|
403
|
+
|
|
404
|
+
for (const block of blocks) {
|
|
405
|
+
const images = block.images;
|
|
406
|
+
const total = block.duration + 0.5;
|
|
407
|
+
const first = images.length > 1 ? Math.min(2.4, total * 0.3) : total;
|
|
408
|
+
const rest = images.length > 1 ? (total - first) / (images.length - 1) : 0;
|
|
409
|
+
const listFile = join(segDir, `${block.id}.txt`);
|
|
410
|
+
const lines = images.map((image, i) => `file '${image}'\nduration ${(i === 0 ? first : rest).toFixed(3)}`);
|
|
411
|
+
lines.push(`file '${images[images.length - 1]}'`);
|
|
412
|
+
writeFileSync(listFile, lines.join("\n") + "\n");
|
|
413
|
+
execFileSync("ffmpeg", ["-y", "-f", "concat", "-safe", "0", "-i", listFile, "-i", block.audio,
|
|
414
|
+
"-vf", `scale=${W}:${H},fps=30,format=yuv420p`, "-c:v", "libx264", "-preset", "medium", "-crf", "20",
|
|
415
|
+
"-c:a", "aac", "-b:a", "160k", "-ar", "44100", "-shortest", join(segDir, `${block.id}.mp4`)], { stdio: "ignore" });
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
console.log("[5/5] final concat...");
|
|
419
|
+
const finalList = join(segDir, "final.txt");
|
|
420
|
+
writeFileSync(finalList, blocks.map((block) => `file '${join(segDir, `${block.id}.mp4`)}'`).join("\n") + "\n");
|
|
421
|
+
if (existsSync(outputFile)) rmSync(outputFile);
|
|
422
|
+
execFileSync("ffmpeg", ["-y", "-f", "concat", "-safe", "0", "-i", finalList, "-c", "copy", "-movflags", "+faststart", outputFile], { stdio: "ignore" });
|
|
423
|
+
|
|
424
|
+
const total = blocks.reduce((sum, block) => sum + block.duration + 0.5, 0);
|
|
425
|
+
console.log(`Done: ${outputFile}`);
|
|
426
|
+
console.log(`Blocks: ${blocks.length}, estimated duration ~${total.toFixed(0)}s`);
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Trellis 分享视频脚本
|
|
2
|
+
|
|
3
|
+
时长约 2 分半。语气:像跟同事聊天一样分享自己最近在用的东西,不推销、
|
|
4
|
+
不喊口号。画面以终端实录为主,讲的时候正常语速即可。
|
|
5
|
+
|
|
6
|
+
## 场景 1 · 开场(15 秒)
|
|
7
|
+
|
|
8
|
+
画面:终端或编辑器,随意。
|
|
9
|
+
|
|
10
|
+
> 最近我在用一个自己写的小工具,叫 Trellis,今天简单分享一下。
|
|
11
|
+
> 它解决的问题很具体:我电脑上装了好几个 Code Agent —— Claude Code、
|
|
12
|
+
> Codex、Kiro、pi、Kimi Code,每个都有自己的一套配置。
|
|
13
|
+
|
|
14
|
+
## 场景 2 · 痛点(20 秒)
|
|
15
|
+
|
|
16
|
+
画面:依次打开几个 Agent 的配置目录(如 `~/.claude`、`~/.codex`),
|
|
17
|
+
让观众看到文件是分散的。
|
|
18
|
+
|
|
19
|
+
> 问题就来了:同一批 Skill 要复制好几份,MCP 服务器每个 Agent 配一遍,
|
|
20
|
+
> 改一个地方要去五六个地方同步。我之前遇到过一次,同一个 token 在三个
|
|
21
|
+
> 文件里存了三个不同的值,有一个早就失效了,排查了半天。
|
|
22
|
+
|
|
23
|
+
## 场景 3 · 一句话方案(10 秒)
|
|
24
|
+
|
|
25
|
+
画面:README 首屏或海报。
|
|
26
|
+
|
|
27
|
+
> Trellis 的思路是:把这些都收进一个 canonical source(标准源),
|
|
28
|
+
> 由它统一生成每个 Agent 需要的原生配置。Skill、MCP、Memory、共享指令,
|
|
29
|
+
> 一处维护,多处生效。
|
|
30
|
+
|
|
31
|
+
## 场景 4 · 演示(70 秒)
|
|
32
|
+
|
|
33
|
+
画面:终端,字号调大。全程真实操作,边操作边说。
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install -g agent-trellis
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
> 安装就是一行 npm。
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
trellis onboard --dry-run
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
> 我习惯先 dry-run。它会告诉我:检测到机器上有哪些 Agent、打算从哪个
|
|
46
|
+
> Agent 迁移配置、准备管理哪几个。这一步什么都不写,纯预览。
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
trellis onboard
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
> 确认没问题就正式跑。它会自动识别、迁移、同步,而且每一次真实的写入
|
|
53
|
+
> 都会先自动备份——不用加任何参数。
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
trellis doctor
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
> 跑完之后用 doctor 检查一下状态,有问题它会直接指出来。
|
|
60
|
+
|
|
61
|
+
(可选,时间充裕就演示回滚)
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
trellis rollback
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
> 如果哪一步不满意,rollback 可以原样退回上一次操作。有冲突的文件它
|
|
68
|
+
> 不会硬覆盖,会列出来交给你决定。
|
|
69
|
+
|
|
70
|
+
## 场景 5 · 两个我挺在意的点(20 秒)
|
|
71
|
+
|
|
72
|
+
画面:可以切回 README 的 Design principles,或者继续留在终端。
|
|
73
|
+
|
|
74
|
+
> 两个我自己比较在意的设计:一是配置里永远不出现明文密钥,只写变量
|
|
75
|
+
> 引用,还有一个 audit 命令专门查这个;二是所有对 Agent 行为的判断都是
|
|
76
|
+
> 在隔离的 Docker 沙盒里用真实安装验证过的,不是看文档猜的。
|
|
77
|
+
|
|
78
|
+
## 场景 6 · 收尾(15 秒)
|
|
79
|
+
|
|
80
|
+
画面:GitHub 仓库页。
|
|
81
|
+
|
|
82
|
+
> 项目是开源的,MIT 协议,现在还是早期版本,肯定有照顾不到的地方。
|
|
83
|
+
> 如果你也在用好几个 Code Agent,欢迎试一下,遇到问题开个 issue 告诉
|
|
84
|
+
> 我,我可以改。链接在简介里,谢谢观看。
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 60 秒极简版(备选)
|
|
89
|
+
|
|
90
|
+
1. 痛点一句话:五个 Code Agent,配置散落五处,同一 token 三个文件三个值。
|
|
91
|
+
2. 方案一句话:Trellis 把 Skill / MCP / Memory / 指令收进一个标准源,
|
|
92
|
+
统一投影到各个 Agent。
|
|
93
|
+
3. 演示四连:`npm i -g agent-trellis` → `trellis onboard --dry-run` →
|
|
94
|
+
`trellis onboard` → `trellis doctor`。
|
|
95
|
+
4. 收尾一句话:开源 MIT,早期版本,欢迎试用提 issue。
|
|
96
|
+
|
|
97
|
+
## 录制提示
|
|
98
|
+
|
|
99
|
+
- 终端字号调大(录屏观众多用手机看),主题用浅色。
|
|
100
|
+
- 演示前先在干净目录预演一遍,确保 `onboard` 输出符合预期。
|
|
101
|
+
- 说错不用停,正常语句重说一遍,后期剪掉即可;保持聊天的感觉。
|
|
102
|
+
- 结尾口播的仓库链接:https://github.com/paul-leo/Trellis
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Trellis 分享视频 · 逐字口播稿
|
|
2
|
+
|
|
3
|
+
竖版 1080×1920,全长 53 秒,8 个段落。第三方视角,每段一句话。改文案
|
|
4
|
+
就改 `generate-video.mjs` 的 `blocks[].narration`,重跑自动出新片。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
1. [开场卡] 哈喽,给大家分享一个开源小工具,Trellis。电脑上有好几个
|
|
9
|
+
Code Agent 的人,应该都遇到过这种情况。
|
|
10
|
+
|
|
11
|
+
2. [终端 · 两个配置目录] 技能、插件、配置,每个 Agent 各存一份。想给
|
|
12
|
+
所有 Agent 装同一个技能,得复制好几遍;改个配置,得挨个文件去改。
|
|
13
|
+
|
|
14
|
+
3. [海报] Trellis 就干一件事:把配置收拢到一个地方统一管,再自动发给
|
|
15
|
+
每个 Agent。改一处,全都生效。
|
|
16
|
+
|
|
17
|
+
4. [终端] 安装就一行,再 init 一下,配置的家就建好了。
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g agent-trellis
|
|
21
|
+
trellis init
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
5. [终端] 正式动手前,可以先演练一遍:要做什么全列出来,但一个文件
|
|
25
|
+
都不动。
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
trellis onboard --dry-run --agent claude-code --manage codex
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
6. [终端] 没问题就正式跑。每改一个文件之前,都会自动备份。
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
trellis onboard --agent claude-code --manage codex
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
7. [终端] 跑完用 doctor 检查状态。哪步不满意,rollback 退回去。
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
trellis doctor
|
|
41
|
+
trellis rollback
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
8. [收尾卡] 开源 MIT,现在还是早期版本,欢迎试用。链接在简介,谢谢
|
|
45
|
+
观看。
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
配音:MiniMax `male-qn-daxuesheng`,1.05 倍速。
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-trellis",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A unified Code Agent Runtime for Skills, MCP, Memory, and shared instructions across Claude Code, Codex, Kiro, pi,
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "A unified Code Agent Runtime for Skills, MCP, Memory, and shared instructions across Claude Code, Codex, Kiro, pi, Kimi Code, and ZCode.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Paul Leo",
|
|
7
7
|
"type": "module",
|
|
8
|
+
"workspaces": [
|
|
9
|
+
"packages/*"
|
|
10
|
+
],
|
|
8
11
|
"bin": {
|
|
9
12
|
"trellis": "./dist/cli.js"
|
|
10
13
|
},
|
|
@@ -136,7 +136,7 @@ known_host_injected:
|
|
|
136
136
|
- memory
|
|
137
137
|
- mirasim
|
|
138
138
|
|
|
139
|
-
# How the servers above reach the
|
|
139
|
+
# How the servers above reach the six supported agents. Omit BOTH blocks below for
|
|
140
140
|
# direct mode (today's default: every agent gets all N server definitions
|
|
141
141
|
# written straight into its native config).
|
|
142
142
|
#
|