agent-trellis 0.8.2 → 0.9.1

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 (178) hide show
  1. package/README.md +3 -0
  2. package/dist/cli.js +94 -1
  3. package/dist/commands/remoteSkill.d.ts +90 -0
  4. package/dist/commands/remoteSkill.js +392 -0
  5. package/dist/commands/rollback.js +36 -5
  6. package/dist/commands/skill.d.ts +2 -0
  7. package/dist/commands/skill.js +6 -2
  8. package/dist/core/canonical.d.ts +17 -1
  9. package/dist/core/canonical.js +54 -0
  10. package/dist/lib/backup.d.ts +17 -5
  11. package/dist/lib/backup.js +22 -4
  12. package/dist/lib/dirDigest.d.ts +11 -0
  13. package/dist/lib/dirDigest.js +52 -0
  14. package/dist/lib/remoteSkillLock.d.ts +26 -0
  15. package/dist/lib/remoteSkillLock.js +88 -0
  16. package/dist/lib/remoteSkillSource.d.ts +45 -0
  17. package/dist/lib/remoteSkillSource.js +206 -0
  18. package/docs/getting-started.md +71 -2
  19. package/package.json +4 -2
  20. package/docs/video/README.md +0 -76
  21. package/docs/video/build/audio/01-opening.aiff +0 -0
  22. package/docs/video/build/audio/01-opening.mp3 +0 -0
  23. package/docs/video/build/audio/02-pain.aiff +0 -0
  24. package/docs/video/build/audio/02-pain.mp3 +0 -0
  25. package/docs/video/build/audio/03-solution.aiff +0 -0
  26. package/docs/video/build/audio/03-solution.mp3 +0 -0
  27. package/docs/video/build/audio/04-install.aiff +0 -0
  28. package/docs/video/build/audio/04-install.mp3 +0 -0
  29. package/docs/video/build/audio/04-setup.mp3 +0 -0
  30. package/docs/video/build/audio/05-dryrun.mp3 +0 -0
  31. package/docs/video/build/audio/05-init.aiff +0 -0
  32. package/docs/video/build/audio/05-init.mp3 +0 -0
  33. package/docs/video/build/audio/05-interactive.mp3 +0 -0
  34. package/docs/video/build/audio/06-dryrun.aiff +0 -0
  35. package/docs/video/build/audio/06-dryrun.mp3 +0 -0
  36. package/docs/video/build/audio/06-onboard.mp3 +0 -0
  37. package/docs/video/build/audio/06-run.mp3 +0 -0
  38. package/docs/video/build/audio/07-onboard.aiff +0 -0
  39. package/docs/video/build/audio/07-onboard.mp3 +0 -0
  40. package/docs/video/build/audio/07-verify.mp3 +0 -0
  41. package/docs/video/build/audio/08-closing.mp3 +0 -0
  42. package/docs/video/build/audio/08-doctor.aiff +0 -0
  43. package/docs/video/build/audio/08-doctor.mp3 +0 -0
  44. package/docs/video/build/audio/09-rollback.aiff +0 -0
  45. package/docs/video/build/audio/09-rollback.mp3 +0 -0
  46. package/docs/video/build/audio/10-principles.aiff +0 -0
  47. package/docs/video/build/audio/10-principles.mp3 +0 -0
  48. package/docs/video/build/audio/11-closing.aiff +0 -0
  49. package/docs/video/build/audio/11-closing.mp3 +0 -0
  50. package/docs/video/build/frames/01-opening-0.png +0 -0
  51. package/docs/video/build/frames/02-pain-1.png +0 -0
  52. package/docs/video/build/frames/02-pain-2.png +0 -0
  53. package/docs/video/build/frames/03-solution-3.png +0 -0
  54. package/docs/video/build/frames/04-install-4.png +0 -0
  55. package/docs/video/build/frames/04-setup-4.png +0 -0
  56. package/docs/video/build/frames/04-setup-5.png +0 -0
  57. package/docs/video/build/frames/05-dryrun-6.png +0 -0
  58. package/docs/video/build/frames/05-init-5.png +0 -0
  59. package/docs/video/build/frames/05-interactive-5.png +0 -0
  60. package/docs/video/build/frames/05-interactive-6.png +0 -0
  61. package/docs/video/build/frames/06-dryrun-6.png +0 -0
  62. package/docs/video/build/frames/06-dryrun-7.png +0 -0
  63. package/docs/video/build/frames/06-onboard-7.png +0 -0
  64. package/docs/video/build/frames/06-onboard-8.png +0 -0
  65. package/docs/video/build/frames/06-run-7.png +0 -0
  66. package/docs/video/build/frames/06-run-8.png +0 -0
  67. package/docs/video/build/frames/07-onboard-10.png +0 -0
  68. package/docs/video/build/frames/07-onboard-7.png +0 -0
  69. package/docs/video/build/frames/07-onboard-8.png +0 -0
  70. package/docs/video/build/frames/07-onboard-9.png +0 -0
  71. package/docs/video/build/frames/07-verify-10.png +0 -0
  72. package/docs/video/build/frames/07-verify-8.png +0 -0
  73. package/docs/video/build/frames/07-verify-9.png +0 -0
  74. package/docs/video/build/frames/08-closing-10.png +0 -0
  75. package/docs/video/build/frames/08-closing-11.png +0 -0
  76. package/docs/video/build/frames/08-doctor-11.png +0 -0
  77. package/docs/video/build/frames/08-doctor-9.png +0 -0
  78. package/docs/video/build/frames/09-rollback-10.png +0 -0
  79. package/docs/video/build/frames/09-rollback-12.png +0 -0
  80. package/docs/video/build/frames/10-principles-11.png +0 -0
  81. package/docs/video/build/frames/10-principles-13.png +0 -0
  82. package/docs/video/build/frames/11-closing-12.png +0 -0
  83. package/docs/video/build/frames/11-closing-14.png +0 -0
  84. package/docs/video/build/segments/01-opening.mp4 +0 -0
  85. package/docs/video/build/segments/01-opening.txt +0 -3
  86. package/docs/video/build/segments/02-pain.mp4 +0 -0
  87. package/docs/video/build/segments/02-pain.txt +0 -5
  88. package/docs/video/build/segments/03-solution.mp4 +0 -0
  89. package/docs/video/build/segments/03-solution.txt +0 -3
  90. package/docs/video/build/segments/04-install.mp4 +0 -0
  91. package/docs/video/build/segments/04-install.txt +0 -3
  92. package/docs/video/build/segments/04-setup.mp4 +0 -0
  93. package/docs/video/build/segments/04-setup.txt +0 -3
  94. package/docs/video/build/segments/05-dryrun.mp4 +0 -0
  95. package/docs/video/build/segments/05-dryrun.txt +0 -3
  96. package/docs/video/build/segments/05-init.mp4 +0 -0
  97. package/docs/video/build/segments/05-init.txt +0 -3
  98. package/docs/video/build/segments/05-interactive.mp4 +0 -0
  99. package/docs/video/build/segments/05-interactive.txt +0 -5
  100. package/docs/video/build/segments/06-dryrun.mp4 +0 -0
  101. package/docs/video/build/segments/06-dryrun.txt +0 -3
  102. package/docs/video/build/segments/06-onboard.mp4 +0 -0
  103. package/docs/video/build/segments/06-onboard.txt +0 -5
  104. package/docs/video/build/segments/06-run.mp4 +0 -0
  105. package/docs/video/build/segments/06-run.txt +0 -3
  106. package/docs/video/build/segments/07-onboard.mp4 +0 -0
  107. package/docs/video/build/segments/07-onboard.txt +0 -5
  108. package/docs/video/build/segments/07-verify.mp4 +0 -0
  109. package/docs/video/build/segments/07-verify.txt +0 -5
  110. package/docs/video/build/segments/08-closing.mp4 +0 -0
  111. package/docs/video/build/segments/08-closing.txt +0 -3
  112. package/docs/video/build/segments/08-doctor.mp4 +0 -0
  113. package/docs/video/build/segments/08-doctor.txt +0 -3
  114. package/docs/video/build/segments/09-rollback.mp4 +0 -0
  115. package/docs/video/build/segments/09-rollback.txt +0 -3
  116. package/docs/video/build/segments/10-principles.mp4 +0 -0
  117. package/docs/video/build/segments/10-principles.txt +0 -3
  118. package/docs/video/build/segments/11-closing.mp4 +0 -0
  119. package/docs/video/build/segments/11-closing.txt +0 -3
  120. package/docs/video/build/segments/final.txt +0 -8
  121. package/docs/video/build-ai/audio/01-hook.mp3 +0 -0
  122. package/docs/video/build-ai/audio/02-pain.mp3 +0 -0
  123. package/docs/video/build-ai/audio/03-solution.mp3 +0 -0
  124. package/docs/video/build-ai/audio/04-how.mp3 +0 -0
  125. package/docs/video/build-ai/audio/05-safe.mp3 +0 -0
  126. package/docs/video/build-ai/audio/06-closing.mp3 +0 -0
  127. package/docs/video/build-ai/clips/01-hook.mp4 +0 -0
  128. package/docs/video/build-ai/clips/02-pain.mp4 +0 -0
  129. package/docs/video/build-ai/clips/03-solution.mp4 +0 -0
  130. package/docs/video/build-ai/clips/04-how.mp4 +0 -0
  131. package/docs/video/build-ai/clips/05-safe.mp4 +0 -0
  132. package/docs/video/build-ai/clips/06-closing.mp4 +0 -0
  133. package/docs/video/build-ai/first-frame-02-pain.png +0 -0
  134. package/docs/video/build-ai/first-frame-03-solution.png +0 -0
  135. package/docs/video/build-ai/first-frame-04-how.png +0 -0
  136. package/docs/video/build-ai/first-frame-05-safe.png +0 -0
  137. package/docs/video/build-ai/segments/01-hook.caption.txt +0 -2
  138. package/docs/video/build-ai/segments/01-hook.mp4 +0 -0
  139. package/docs/video/build-ai/segments/02-pain.mp4 +0 -0
  140. package/docs/video/build-ai/segments/03-solution.mp4 +0 -0
  141. package/docs/video/build-ai/segments/04-how.mp4 +0 -0
  142. package/docs/video/build-ai/segments/05-safe.mp4 +0 -0
  143. package/docs/video/build-ai/segments/06-closing.mp4 +0 -0
  144. package/docs/video/build-ai/segments/cap-01-hook.png +0 -0
  145. package/docs/video/build-ai/segments/cap-02-pain.png +0 -0
  146. package/docs/video/build-ai/segments/cap-03-solution.png +0 -0
  147. package/docs/video/build-ai/segments/cap-04-how.png +0 -0
  148. package/docs/video/build-ai/segments/cap-05-safe.png +0 -0
  149. package/docs/video/build-ai/segments/cap-06-closing.png +0 -0
  150. package/docs/video/build-ai/segments/final.txt +0 -6
  151. package/docs/video/build-posters/audio/01-hook.mp3 +0 -0
  152. package/docs/video/build-posters/audio/02-pain.mp3 +0 -0
  153. package/docs/video/build-posters/audio/03-solution.mp3 +0 -0
  154. package/docs/video/build-posters/audio/04-how.mp3 +0 -0
  155. package/docs/video/build-posters/audio/05-safe.mp3 +0 -0
  156. package/docs/video/build-posters/audio/06-closing.mp3 +0 -0
  157. package/docs/video/build-posters/cap-01-hook.png +0 -0
  158. package/docs/video/build-posters/cap-02-pain.png +0 -0
  159. package/docs/video/build-posters/cap-03-solution.png +0 -0
  160. package/docs/video/build-posters/cap-04-how.png +0 -0
  161. package/docs/video/build-posters/cap-05-safe.png +0 -0
  162. package/docs/video/build-posters/cap-06-closing.png +0 -0
  163. package/docs/video/build-posters/card-01-hook.png +0 -0
  164. package/docs/video/build-posters/card-06-closing.png +0 -0
  165. package/docs/video/build-posters/segments/01-hook.mp4 +0 -0
  166. package/docs/video/build-posters/segments/02-pain.mp4 +0 -0
  167. package/docs/video/build-posters/segments/03-solution.mp4 +0 -0
  168. package/docs/video/build-posters/segments/04-how.mp4 +0 -0
  169. package/docs/video/build-posters/segments/05-safe.mp4 +0 -0
  170. package/docs/video/build-posters/segments/06-closing.mp4 +0 -0
  171. package/docs/video/build-posters/segments/final.txt +0 -6
  172. package/docs/video/generate-video-ai.mjs +0 -334
  173. package/docs/video/generate-video-posters.mjs +0 -204
  174. package/docs/video/generate-video.mjs +0 -426
  175. package/docs/video/outline.zh-CN.md +0 -102
  176. package/docs/video/trellis-demo-video-ai.mp4 +0 -0
  177. package/docs/video/trellis-demo-video.mp4 +0 -0
  178. package/docs/video/voiceover.zh-CN.md +0 -49
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Remote Skill source handling. The public command accepts only GitHub
3
+ * identities, while the lower-level fetch function intentionally accepts a
4
+ * repository URL so unit and sandbox tests can use a local bare Git remote.
5
+ * No command in this module evaluates checked-out content.
6
+ */
7
+ import { execFileSync } from "node:child_process";
8
+ import { lstatSync, mkdtempSync, readdirSync, realpathSync, rmSync } from "node:fs";
9
+ import { tmpdir } from "node:os";
10
+ import { basename, join, relative, resolve, sep } from "node:path";
11
+ import { directoryDigest } from "./dirDigest.js";
12
+ function git(args, cwd) {
13
+ try {
14
+ return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
15
+ }
16
+ catch (error) {
17
+ const detail = error instanceof Error ? error.message : String(error);
18
+ throw new Error(`Git could not read the remote Skill source: ${detail}`);
19
+ }
20
+ }
21
+ /** Accept the exact two source syntaxes Trellis documents, with no URL state
22
+ * that could put a credential in the provenance lock. */
23
+ export function normalizeGitHubRepository(source) {
24
+ const shorthand = /^([A-Za-z0-9](?:[A-Za-z0-9-]{0,38})?)\/([A-Za-z0-9_.-]+)$/.exec(source);
25
+ if (shorthand) {
26
+ const [, owner, repository] = shorthand;
27
+ return { source, owner: owner, repository: repository, url: `https://github.com/${owner}/${repository}.git` };
28
+ }
29
+ let parsed;
30
+ try {
31
+ parsed = new URL(source);
32
+ }
33
+ catch {
34
+ throw new Error(`Remote Skill source must be GitHub owner/repository or an HTTPS github.com URL: ${source}`);
35
+ }
36
+ const parts = parsed.pathname.split("/").filter(Boolean);
37
+ if (parsed.protocol !== "https:"
38
+ || parsed.hostname.toLowerCase() !== "github.com"
39
+ || parsed.username
40
+ || parsed.password
41
+ || parsed.search
42
+ || parsed.hash
43
+ || parts.length !== 2
44
+ || !/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})?$/.test(parts[0])
45
+ || !/^[A-Za-z0-9_.-]+(?:\.git)?$/.test(parts[1])) {
46
+ throw new Error(`Remote Skill source must be a credential-free HTTPS github.com repository URL: ${source}`);
47
+ }
48
+ const owner = parts[0];
49
+ const repository = parts[1].replace(/\.git$/i, "");
50
+ return { source, owner, repository, url: `https://github.com/${owner}/${repository}.git` };
51
+ }
52
+ function parseLsRemote(output) {
53
+ return output.split("\n").flatMap((line) => {
54
+ const match = /^([0-9a-f]{40})\t(.+)$/.exec(line.trim());
55
+ return match ? [{ hash: match[1], ref: match[2] }] : [];
56
+ });
57
+ }
58
+ function resolveDefaultRef(url) {
59
+ const output = git(["ls-remote", "--symref", url, "HEAD"]);
60
+ const branch = /^ref:\s+refs\/heads\/([^\s]+)\s+HEAD$/m.exec(output)?.[1];
61
+ const commit = parseLsRemote(output).find((entry) => entry.ref === "HEAD")?.hash;
62
+ if (!branch || !commit)
63
+ throw new Error(`Could not resolve the default branch for ${url}`);
64
+ return { requestedRef: branch, commit };
65
+ }
66
+ function resolveRequestedRef(url, requestedRef) {
67
+ if (!requestedRef || /[\0\r\n]/.test(requestedRef))
68
+ throw new Error("--branch must be a non-empty Git ref");
69
+ const entries = parseLsRemote(git(["ls-remote", url, requestedRef]));
70
+ const peeled = entries.find((entry) => entry.ref.endsWith("^{}"));
71
+ const direct = entries.find((entry) => !entry.ref.endsWith("^{}"));
72
+ const selected = peeled ?? direct;
73
+ if (!selected)
74
+ throw new Error(`Remote source has no ref named ${requestedRef}`);
75
+ return { requestedRef, commit: selected.hash };
76
+ }
77
+ function isInside(root, target) {
78
+ const rel = relative(root, target);
79
+ return rel === "" || (!rel.startsWith(`..${sep}`) && rel !== ".." && !rel.startsWith("..\/"));
80
+ }
81
+ function walkDirectories(root) {
82
+ const dirs = [];
83
+ const walk = (current) => {
84
+ dirs.push(current);
85
+ for (const name of readdirSync(current).sort((a, b) => a.localeCompare(b))) {
86
+ // The Git administration directory is implementation state, never
87
+ // source content to inspect for a Skill candidate.
88
+ if (current === root && name === ".git")
89
+ continue;
90
+ const entry = join(current, name);
91
+ const stat = lstatSync(entry);
92
+ if (stat.isDirectory())
93
+ walk(entry);
94
+ }
95
+ };
96
+ walk(root);
97
+ return dirs;
98
+ }
99
+ function candidatesIn(root) {
100
+ return walkDirectories(root).flatMap((directory) => {
101
+ const names = readdirSync(directory);
102
+ const exact = names.find((name) => name === "SKILL.md");
103
+ if (!exact)
104
+ return [];
105
+ const entry = join(directory, exact);
106
+ // A non-file `SKILL.md` is deliberately not advertised as a valid
107
+ // candidate; selecting this directory produces a precise error below.
108
+ if (!lstatSync(entry).isFile())
109
+ return [];
110
+ return [{ name: basename(directory), directory, subdirectory: relative(root, directory).split(sep).join("/") }];
111
+ });
112
+ }
113
+ function validateSelectedDirectory(root, directory, expectedName) {
114
+ if (basename(directory) !== expectedName)
115
+ throw new Error(`Selected remote Skill directory is not named ${expectedName}`);
116
+ const rootReal = realpathSync(root);
117
+ const walk = (current) => {
118
+ const stat = lstatSync(current);
119
+ if (stat.isSymbolicLink())
120
+ throw new Error(`Remote Skill ${expectedName} contains a symbolic link: ${current}`);
121
+ const resolved = realpathSync(current);
122
+ if (!isInside(rootReal, resolved))
123
+ throw new Error(`Remote Skill ${expectedName} resolves outside the fetched repository`);
124
+ if (!stat.isDirectory())
125
+ return;
126
+ for (const name of readdirSync(current))
127
+ walk(join(current, name));
128
+ };
129
+ const entry = join(directory, "SKILL.md");
130
+ const entryStat = lstatSync(entry);
131
+ if (entryStat.isSymbolicLink()) {
132
+ const resolvedEntry = realpathSync(entry);
133
+ if (!isInside(rootReal, resolvedEntry))
134
+ throw new Error(`Remote Skill ${expectedName} entry file resolves outside the fetched repository`);
135
+ throw new Error(`Remote Skill ${expectedName} entry file must not be a symbolic link`);
136
+ }
137
+ if (!entryStat.isFile())
138
+ throw new Error(`Remote Skill ${expectedName} has no regular, exact-case SKILL.md`);
139
+ walk(directory);
140
+ return {
141
+ name: expectedName,
142
+ directory,
143
+ subdirectory: relative(root, directory).split(sep).join("/"),
144
+ digest: directoryDigest(directory),
145
+ };
146
+ }
147
+ /**
148
+ * Fetches a single immutable commit into a system temporary directory. It is
149
+ * exported with a URL-shaped repository argument to let tests exercise the
150
+ * real Git protocol against a local bare repo; CLI callers first use
151
+ * normalizeGitHubRepository and therefore cannot pass arbitrary hosts.
152
+ */
153
+ export function fetchRemoteRepository(repository, opts = {}) {
154
+ const resolvedRef = opts.branch === undefined ? resolveDefaultRef(repository.url) : resolveRequestedRef(repository.url, opts.branch);
155
+ const temp = mkdtempSync(join(tmpdir(), "trellis-remote-skill-"));
156
+ let disposed = false;
157
+ try {
158
+ git(["init", "--quiet"], temp);
159
+ git(["remote", "add", "origin", repository.url], temp);
160
+ git(["fetch", "--quiet", "--depth=1", "origin", resolvedRef.commit], temp);
161
+ git(["checkout", "--quiet", "--detach", "FETCH_HEAD"], temp);
162
+ const actualCommit = git(["rev-parse", "HEAD"], temp).trim();
163
+ if (!/^[0-9a-f]{40}$/.test(actualCommit))
164
+ throw new Error("Git did not produce an immutable commit identifier");
165
+ const root = resolve(temp);
166
+ const candidates = candidatesIn(root);
167
+ return {
168
+ repository: { url: repository.url },
169
+ requestedRef: resolvedRef.requestedRef,
170
+ commit: actualCommit,
171
+ root,
172
+ candidates,
173
+ select(name) {
174
+ const matchingDirectories = walkDirectories(root).filter((directory) => basename(directory) === name);
175
+ if (matchingDirectories.length === 0) {
176
+ const available = candidates.map((candidate) => candidate.name).sort().join(", ") || "none";
177
+ throw new Error(`Remote source has no Skill named ${name}. Available Skills: ${available}`);
178
+ }
179
+ const withEntry = matchingDirectories.filter((directory) => {
180
+ try {
181
+ return readdirSync(directory).includes("SKILL.md");
182
+ }
183
+ catch {
184
+ return false;
185
+ }
186
+ });
187
+ if (withEntry.length !== 1) {
188
+ if (withEntry.length === 0)
189
+ throw new Error(`Remote Skill ${name} has no regular, exact-case SKILL.md`);
190
+ throw new Error(`Remote source contains multiple Skills named ${name}; select a repository with one matching Skill`);
191
+ }
192
+ return validateSelectedDirectory(root, withEntry[0], name);
193
+ },
194
+ dispose() {
195
+ if (disposed)
196
+ return;
197
+ disposed = true;
198
+ rmSync(temp, { recursive: true, force: true });
199
+ },
200
+ };
201
+ }
202
+ catch (error) {
203
+ rmSync(temp, { recursive: true, force: true });
204
+ throw error;
205
+ }
206
+ }
@@ -614,6 +614,74 @@ trellis skill update-builtin
614
614
  The update is backed up and updates the canonical file in place; native Agent
615
615
  symlinks and Runtime providers then consume the same refreshed content.
616
616
 
617
+ ### Importing a GitHub Skill with Trellis ownership
618
+
619
+ If you would normally run `npx skills add`, use Trellis's top-level `add`
620
+ command instead. It is a remote-source importer, while `trellis skill add`
621
+ remains the command for a local directory:
622
+
623
+ ```sh
624
+ # Review exact-case SKILL.md candidates before selecting one.
625
+ npx trellis add mattpocock/skills --list
626
+
627
+ # Fetch and inspect the exact plan without writing canonical or Agent files.
628
+ npx trellis add mattpocock/skills --skill loop-me --dry-run
629
+
630
+ # Import to the user-level canonical store, then sync to every managed Agent.
631
+ npx trellis add mattpocock/skills --skill loop-me --yes
632
+ ```
633
+
634
+ The source may be `owner/repository` or a credential-free HTTPS
635
+ `github.com` repository URL. Trellis fetches the selected ref into a system
636
+ temporary directory, discovers a directory containing an exact-case
637
+ `SKILL.md`, and copies that directory into
638
+ `~/.trellis/skills/<name>/`. It does not run repository scripts, install
639
+ dependencies, or write into `.agents/skills`, `~/.agents/skills`, or any
640
+ Agent-specific Skill directory during import. Review the imported
641
+ `SKILL.md` before relying on it in an Agent session.
642
+
643
+ By default the imported Skill is unscoped, which means every Agent currently
644
+ listed in `~/.trellis/managed.yaml` receives it on sync. Restrict delivery to
645
+ managed Agents with `--agent`; comma-separated values and repeated flags are
646
+ accepted. `--agent '*'` writes the current managed set as an explicit scope.
647
+ An Agent that is installed but unmanaged is refused before any canonical or
648
+ native write. Add it to Trellis's managed boundary first:
649
+
650
+ ```sh
651
+ trellis manage add zcode
652
+ npx trellis add mattpocock/skills --skill loop-me --agent zcode --yes
653
+ ```
654
+
655
+ `--branch <ref>` selects a Git branch, tag, or ref. Without it, Trellis asks
656
+ Git for the repository's default branch. `--global` is accepted for
657
+ `skills`-CLI compatibility and still means Trellis's existing user-level
658
+ canonical store; `--copy` and project-local modes are refused because they
659
+ would create another Skill source outside Trellis.
660
+
661
+ Each successful remote import writes
662
+ `~/.trellis/skills.lock.json` in the same backup session as its canonical
663
+ Skill directory. The lock records the normalized GitHub URL, requested ref,
664
+ resolved immutable commit, source subdirectory, and deterministic content
665
+ digest. It never records credentials or a temporary path. `trellis skill
666
+ list` marks remote Skills with their source and short locked commit; JSON
667
+ output includes the complete credential-free provenance record.
668
+
669
+ Refresh tracked Skills with:
670
+
671
+ ```sh
672
+ trellis update loop-me --dry-run
673
+ trellis update loop-me
674
+ trellis update # check every tracked remote Skill
675
+ ```
676
+
677
+ Before replacement, `trellis update` compares the current canonical directory
678
+ to its locked digest. A local canonical edit is a conflict and is never
679
+ overwritten. A successful update preserves the Skill's scope, replaces its
680
+ directory and provenance record in one backup session, and then runs Skill
681
+ sync. Use `trellis rollback --list` to find a `remote-skill-add` or
682
+ `remote-skill-update` run; rolling it back restores the previous directory
683
+ and matching lock record when neither has changed since the run.
684
+
617
685
  ```
618
686
  $ trellis mcp list
619
687
  tanka (http) — codex, pi
@@ -1012,9 +1080,10 @@ scripts/agent-sandbox.sh --status kiro
1012
1080
  Git,也不会自动读取或删除宿主机凭据。清理时请明确执行
1013
1081
  `docker volume rm <volume-name>`。
1014
1082
 
1015
- ## `trellis rollback` — undoing a `sync`/`mcp sync`/`onboard` run
1083
+ ## `trellis rollback` — undoing a managed write run
1016
1084
 
1017
- Every real write those three commands perform is recorded, before it
1085
+ Every real `sync`, `mcp sync`, `onboard`, remote Skill import, and remote Skill
1086
+ update write is recorded, before it
1018
1087
  happens, to a structured run directory under `~/.trellis/backups/` — no
1019
1088
  flag needed, this is always on for any run that actually writes
1020
1089
  something. `--dry-run` never creates one, since nothing was written.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-trellis",
3
- "version": "0.8.2",
3
+ "version": "0.9.1",
4
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",
@@ -23,7 +23,9 @@
23
23
  "files": [
24
24
  "dist",
25
25
  "schema",
26
- "docs"
26
+ "docs/*.md",
27
+ "docs/assets/brand-v3",
28
+ "docs/assets/poster-series-v3"
27
29
  ],
28
30
  "scripts": {
29
31
  "build": "tsc -p tsconfig.json && node scripts/build-pi-bridge.mjs",
@@ -1,76 +0,0 @@
1
- # Trellis 分享视频 · 素材与录制清单
2
-
3
- > 本目录(`docs/video/`)在 `.gitignore` 中,所有内容只存在于本地,
4
- > 不会进入开源仓库。
5
-
6
- ## 自动生成视频
7
-
8
- ```bash
9
- node docs/video/generate-video.mjs # 输出 docs/video/trellis-demo-video.mp4
10
- ```
11
-
12
- 一条命令完成:重置沙盒 → 真实跑一遍 CLI 并捕获输出 → 渲染终端帧与卡片
13
- (qlmanage)→ 系统 TTS 生成中文口播(Ting-Ting Enhanced)→ ffmpeg 合成
14
- 1920×1080 mp4。改口播文案直接编辑脚本里的 `blocks[].narration`;终端帧
15
- 内容来自真实命令输出,无需手改。依赖:ffmpeg(`brew install ffmpeg`)、
16
- macOS 自带的 qlmanage/sips/say/afinfo。
17
-
18
- ## 演示沙盒(不碰真实配置)
19
-
20
- ```bash
21
- bash scripts/video-demo-setup.sh # 生成/重置演示环境到 /tmp/trellis-demo-home
22
- export HOME=/tmp/trellis-demo-home # 之后所有 trellis 命令都在沙盒里运行
23
- ```
24
-
25
- 沙盒内容:Claude Code(1 份指令 + 2 个 Skill + 2 个 MCP 服务器)、
26
- Codex(config.toml + 1 个 MCP 服务器)。每次重录前重跑 setup 脚本即可
27
- 回到初始状态。已验证全流程可跑通:`init → onboard --dry-run → onboard →
28
- doctor → rollback`。
29
-
30
- 录制用命令(已跳过交互选择,流程不打断):
31
-
32
- ```bash
33
- trellis init
34
- trellis onboard --dry-run --agent claude-code --manage codex
35
- trellis onboard --agent claude-code --manage codex
36
- trellis doctor
37
- trellis rollback
38
- ```
39
-
40
- ## 终端与录屏设置
41
-
42
- | 项 | 建议 |
43
- |---|---|
44
- | 终端主题 | 浅色(手机端观看对比度更好) |
45
- | 字号 | 20–24pt,观众多用手机看 |
46
- | 提示符 | `export PS1='$ '`,干净无干扰 |
47
- | 窗口 | 1280×720 或 1440×810 逻辑分辨率 |
48
- | 录屏 | Kap / QuickTime;每条 take 前 `clear` |
49
- | 导出 | 横屏 1920×1080(B 站/YouTube);竖屏 1080×1920(视频号/小红书)则后期裁切,注意命令别超出安全区 |
50
-
51
- ## 分镜与素材对照
52
-
53
- | 场景 | 画面 | 素材 |
54
- |---|---|---|
55
- | 1 开场 | 终端/编辑器随意状态 | 现场录 |
56
- | 2 痛点 | `ls ~/.claude` `ls ~/.codex`(真实目录,只读) | 现场录 |
57
- | 3 方案 | 海报全屏 | [`../assets/poster-series-v3/agent-trellis-poster-v3-unified-zh.png`](../assets/poster-series-v3/agent-trellis-poster-v3-unified-zh.png) |
58
- | 4 演示 | 终端实录 | 沙盒命令(见上) |
59
- | 5 设计点 | README Design principles 滚动 | github.com/paul-leo/Trellis |
60
- | 6 收尾 | GitHub 仓库首页 + Star 按钮停留 2 秒 | 同上 |
61
-
62
- ## 其他可用素材
63
-
64
- - 封面图/片尾卡:[`../assets/poster-series-v3/agent-trellis-poster-v3-horizontal-zh.png`](../assets/poster-series-v3/agent-trellis-poster-v3-horizontal-zh.png)
65
- - Logo 叠层(角标):[`../assets/brand-v3/trellis-mark-v3-a.svg`](../assets/brand-v3/trellis-mark-v3-a.svg)
66
- - 口播逐字稿:[voiceover.zh-CN.md](voiceover.zh-CN.md)
67
- - 场景大纲:[outline.zh-CN.md](outline.zh-CN.md)
68
- - 60 秒极简版:见 [outline.zh-CN.md](outline.zh-CN.md) 末尾
69
-
70
- ## 录制前 Checklist
71
-
72
- - [ ] 重跑 `bash scripts/video-demo-setup.sh` 重置沙盒
73
- - [ ] `export HOME=/tmp/trellis-demo-home && export PS1='$ ' && clear`
74
- - [ ] 终端字号调大,关闭通知(勿扰模式)
75
- - [ ] 预演一遍场景 4,确认输出与口播一致
76
- - [ ] 麦克风电平测试 5 秒
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,3 +0,0 @@
1
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/01-opening-0.png'
2
- duration 11.053
3
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/01-opening-0.png'
@@ -1,5 +0,0 @@
1
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/02-pain-1.png'
2
- duration 2.172
3
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/02-pain-2.png'
4
- duration 5.068
5
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/02-pain-2.png'
@@ -1,3 +0,0 @@
1
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/03-solution-3.png'
2
- duration 6.482
3
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/03-solution-3.png'
@@ -1,3 +0,0 @@
1
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/04-install-4.png'
2
- duration 2.407
3
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/04-install-4.png'
@@ -1,3 +0,0 @@
1
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/04-setup-4.png'
2
- duration 4.131
3
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/04-setup-4.png'
@@ -1,3 +0,0 @@
1
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/05-dryrun-6.png'
2
- duration 7.031
3
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/05-dryrun-6.png'
@@ -1,3 +0,0 @@
1
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/05-init-5.png'
2
- duration 6.587
3
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/05-init-5.png'
@@ -1,5 +0,0 @@
1
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/05-interactive-5.png'
2
- duration 1.952
3
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/05-interactive-6.png'
4
- duration 4.556
5
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/05-interactive-6.png'
@@ -1,3 +0,0 @@
1
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/06-dryrun-6.png'
2
- duration 14.763
3
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/06-dryrun-6.png'
@@ -1,5 +0,0 @@
1
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/06-onboard-7.png'
2
- duration 1.537
3
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/06-onboard-8.png'
4
- duration 3.587
5
- file '/Users/pengzai/www/Trellis/docs/video/build/frames/06-onboard-8.png'