codiedev 0.10.0-beta.15 → 0.10.0-beta.17

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.
@@ -0,0 +1,291 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.SHA256_HEX = exports.PACK_FILES_MAX_TOTAL_BYTES = exports.PACK_FILE_MAX_BYTES = exports.PACK_MAX_FILES = void 0;
37
+ exports.packFilePathProblem = packFilePathProblem;
38
+ exports.blobsDir = blobsDir;
39
+ exports.blobPath = blobPath;
40
+ exports.sha256Of = sha256Of;
41
+ exports.ensureBlobs = ensureBlobs;
42
+ exports.parseFilesSection = parseFilesSection;
43
+ exports.stripFilesSection = stripFilesSection;
44
+ exports.expandHome = expandHome;
45
+ exports.collectLocalFiles = collectLocalFiles;
46
+ exports.rewritePluginRoot = rewritePluginRoot;
47
+ const crypto_1 = require("crypto");
48
+ const fs = __importStar(require("fs"));
49
+ const os = __importStar(require("os"));
50
+ const path = __importStar(require("path"));
51
+ exports.PACK_MAX_FILES = 500;
52
+ exports.PACK_FILE_MAX_BYTES = 15 * 1024 * 1024;
53
+ exports.PACK_FILES_MAX_TOTAL_BYTES = 50 * 1024 * 1024;
54
+ const PATH_MAX = 300;
55
+ const MAX_DEPTH = 16;
56
+ const FILE_SEGMENT = /^[A-Za-z0-9_@+][A-Za-z0-9._ @+=,-]{0,127}$/;
57
+ exports.SHA256_HEX = /^[0-9a-f]{64}$/;
58
+ function packFilePathProblem(raw) {
59
+ if (typeof raw !== "string" || !raw)
60
+ return "empty path";
61
+ if (raw.length > PATH_MAX)
62
+ return `longer than ${PATH_MAX} characters`;
63
+ if (raw.includes("\\"))
64
+ return "use / as the separator";
65
+ if (raw.startsWith("/") || /^[A-Za-z]:/.test(raw))
66
+ return "must be relative to the pack folder";
67
+ const segments = raw.split("/");
68
+ if (segments.length > MAX_DEPTH)
69
+ return `nested deeper than ${MAX_DEPTH} folders`;
70
+ for (const seg of segments) {
71
+ if (!seg || seg === "." || seg === "..")
72
+ return "empty, . or .. segment";
73
+ if (!FILE_SEGMENT.test(seg) || seg.trim() !== seg || seg.endsWith("."))
74
+ return `unsupported name "${seg}"`;
75
+ }
76
+ const lower = raw.toLowerCase();
77
+ if (lower.startsWith(".claude-plugin/"))
78
+ return ".claude-plugin/ is written by Codiedev";
79
+ if (lower === "hooks/hooks.json")
80
+ return "hooks/hooks.json comes from the pack's hooks";
81
+ if (segments.length === 2 && segments[0].toLowerCase() === "agents" && lower.endsWith(".md"))
82
+ return "agents/*.md come from the pack's agents";
83
+ if (segments.length === 3 && segments[0].toLowerCase() === "skills" && segments[2].toLowerCase() === "skill.md") {
84
+ return "SKILL.md comes from the skill library";
85
+ }
86
+ return null;
87
+ }
88
+ function blobsDir(root) {
89
+ return path.join(root, ".blobs");
90
+ }
91
+ function blobPath(dir, sha256) {
92
+ if (!exports.SHA256_HEX.test(sha256))
93
+ throw new Error(`bad sha256: ${sha256}`);
94
+ return path.join(dir, sha256);
95
+ }
96
+ function sha256Of(buf) {
97
+ return (0, crypto_1.createHash)("sha256").update(buf).digest("hex");
98
+ }
99
+ function hasBlob(dir, file) {
100
+ try {
101
+ return fs.statSync(blobPath(dir, file.sha256)).size === file.size;
102
+ }
103
+ catch {
104
+ return false;
105
+ }
106
+ }
107
+ async function ensureBlobs(dir, files, download) {
108
+ let downloaded = 0;
109
+ const seen = new Set();
110
+ for (const file of files) {
111
+ if (seen.has(file.sha256) || hasBlob(dir, file))
112
+ continue;
113
+ seen.add(file.sha256);
114
+ const body = await download(file);
115
+ if (body.length !== file.size || sha256Of(body) !== file.sha256) {
116
+ throw new Error(`${file.path}: downloaded bytes don't match the pack (checksum mismatch)`);
117
+ }
118
+ fs.mkdirSync(dir, { recursive: true });
119
+ const target = blobPath(dir, file.sha256);
120
+ const tmp = `${target}.${process.pid}.${Date.now()}.tmp`;
121
+ fs.writeFileSync(tmp, body, { mode: 0o644 });
122
+ fs.renameSync(tmp, target);
123
+ downloaded += 1;
124
+ }
125
+ return { downloaded };
126
+ }
127
+ function parseFilesSection(markdown) {
128
+ const lines = markdown.replace(/^/, "").split("\n").map((l) => l.replace(/\r$/, ""));
129
+ let fence = null;
130
+ let inFiles = false;
131
+ let found = false;
132
+ const out = [];
133
+ for (let i = 0; i < lines.length; i++) {
134
+ const line = lines[i];
135
+ const f = line.match(/^(`{3,})/);
136
+ if (f) {
137
+ if (!fence)
138
+ fence = f[1];
139
+ else if (f[1].length >= fence.length && /^`+\s*$/.test(line))
140
+ fence = null;
141
+ continue;
142
+ }
143
+ if (fence)
144
+ continue;
145
+ const h2 = line.match(/^##\s+(.+?)\s*$/);
146
+ if (h2 && !line.startsWith("###")) {
147
+ inFiles = h2[1].toLowerCase() === "files";
148
+ if (inFiles)
149
+ found = true;
150
+ continue;
151
+ }
152
+ if (!inFiles || !line.trim())
153
+ continue;
154
+ const m = line.match(/^\s*[-*]\s+(.+?)\s+<-\s+(.+?)\s*$/);
155
+ if (!m)
156
+ throw new Error(`pack.md line ${i + 1}: expected "- <path in pack> <- <local path>" under ## Files`);
157
+ out.push({ dest: unquote(m[1]), source: unquote(m[2]), line: i + 1 });
158
+ }
159
+ return found ? out : null;
160
+ }
161
+ function unquote(value) {
162
+ const v = value.trim();
163
+ return (v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'")) ? v.slice(1, -1) : v;
164
+ }
165
+ function stripFilesSection(markdown) {
166
+ const lines = markdown.split("\n");
167
+ const out = [];
168
+ let fence = null;
169
+ let skipping = false;
170
+ for (const raw of lines) {
171
+ const line = raw.replace(/\r$/, "");
172
+ const f = line.match(/^(`{3,})/);
173
+ if (f && !skipping) {
174
+ if (!fence)
175
+ fence = f[1];
176
+ else if (f[1].length >= fence.length && /^`+\s*$/.test(line))
177
+ fence = null;
178
+ out.push(raw);
179
+ continue;
180
+ }
181
+ if (!fence) {
182
+ const h2 = line.match(/^##\s+(.+?)\s*$/);
183
+ if (h2 && !line.startsWith("###"))
184
+ skipping = h2[1].toLowerCase() === "files";
185
+ }
186
+ if (!skipping)
187
+ out.push(raw);
188
+ }
189
+ return out.join("\n");
190
+ }
191
+ const SKIP_DIRS = new Set(["__pycache__", ".git", "node_modules", ".venv", "venv", ".mypy_cache", ".pytest_cache", ".ruff_cache", ".idea", ".vscode"]);
192
+ const SKIP_FILES = new Set([".DS_Store", "Thumbs.db", "desktop.ini"]);
193
+ const SKIP_EXT = /\.(pyc|pyo|swp|tmp)$/i;
194
+ function expandHome(p, home = os.homedir()) {
195
+ return p === "~" ? home : p.startsWith("~/") ? path.join(home, p.slice(2)) : p;
196
+ }
197
+ function collectLocalFiles(entries, baseDir, home = os.homedir(), opts = {}) {
198
+ const files = new Map();
199
+ const notes = [];
200
+ let total = 0;
201
+ const add = (dest, abs) => {
202
+ if (opts.lenient && dest.toLowerCase() === "skill.md")
203
+ return;
204
+ const problem = packFilePathProblem(opts.checkAs ? opts.checkAs(dest) : dest);
205
+ if (problem) {
206
+ const parts = dest.split("/");
207
+ if (parts.length === 3 && parts[0] === "skills" && parts[2].toLowerCase() === "skill.md") {
208
+ notes.push(`skipped ${dest} (the skill itself comes from the skill library; push it with \`codiedev push\`)`);
209
+ return;
210
+ }
211
+ if (parts.some((s) => s.startsWith(".") && s !== "." && s !== "..")) {
212
+ notes.push(`skipped hidden file ${dest}`);
213
+ return;
214
+ }
215
+ if (opts.lenient) {
216
+ notes.push(`skipped ${dest} (${problem})`);
217
+ return;
218
+ }
219
+ throw new Error(`can't ship ${dest}: ${problem}`);
220
+ }
221
+ const stat = fs.statSync(abs);
222
+ if (stat.size > exports.PACK_FILE_MAX_BYTES && opts.lenient) {
223
+ notes.push(`skipped ${dest} (over ${exports.PACK_FILE_MAX_BYTES / (1024 * 1024)}MB)`);
224
+ return;
225
+ }
226
+ if (stat.size > exports.PACK_FILE_MAX_BYTES)
227
+ throw new Error(`${abs} is over ${exports.PACK_FILE_MAX_BYTES / (1024 * 1024)}MB`);
228
+ const key = dest.toLowerCase();
229
+ if (files.has(key))
230
+ throw new Error(`two sources write ${dest}`);
231
+ const buf = fs.readFileSync(abs);
232
+ total += buf.length;
233
+ files.set(key, { path: dest, abs, size: buf.length, sha256: sha256Of(buf), ...((stat.mode & 0o111) !== 0 ? { executable: true } : {}) });
234
+ };
235
+ const walk = (dir, dest, depth) => {
236
+ if (depth > MAX_DEPTH)
237
+ throw new Error(`${dir} is nested too deep`);
238
+ for (const name of fs.readdirSync(dir).sort()) {
239
+ const abs = path.join(dir, name);
240
+ const lst = fs.lstatSync(abs);
241
+ const child = dest ? `${dest}/${name}` : name;
242
+ if (lst.isSymbolicLink()) {
243
+ notes.push(`skipped symlink ${child}`);
244
+ continue;
245
+ }
246
+ if (lst.isDirectory()) {
247
+ if (!SKIP_DIRS.has(name))
248
+ walk(abs, child, depth + 1);
249
+ continue;
250
+ }
251
+ if (!lst.isFile() || SKIP_FILES.has(name) || SKIP_EXT.test(name))
252
+ continue;
253
+ add(child, abs);
254
+ }
255
+ };
256
+ for (const entry of entries) {
257
+ const dest = entry.dest.replace(/^\.\/+/, "").replace(/\/+$/, "");
258
+ if (dest !== "." && (!dest || dest.startsWith("/") || dest.split("/").some((seg) => !seg || seg === "." || seg === ".."))) {
259
+ throw new Error(`pack.md line ${entry.line}: "${entry.dest}" must be a path inside the pack (or "." for its root)`);
260
+ }
261
+ const source = path.resolve(baseDir, expandHome(entry.source, home));
262
+ let stat;
263
+ try {
264
+ stat = fs.statSync(source);
265
+ }
266
+ catch {
267
+ throw new Error(`pack.md line ${entry.line}: ${entry.source} doesn't exist`);
268
+ }
269
+ if (stat.isDirectory())
270
+ walk(source, dest === "." ? "" : dest, 0);
271
+ else if (stat.isFile()) {
272
+ if (dest === "." || !dest)
273
+ throw new Error(`pack.md line ${entry.line}: give a file a path, e.g. "- tools/${path.basename(source)} <- ${entry.source}"`);
274
+ add(dest, source);
275
+ }
276
+ else
277
+ throw new Error(`pack.md line ${entry.line}: ${entry.source} is not a file or folder`);
278
+ }
279
+ const list = [...files.values()].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
280
+ if (opts.lenient)
281
+ return { files: list, notes, total };
282
+ if (list.length > exports.PACK_MAX_FILES)
283
+ throw new Error(`a pack holds at most ${exports.PACK_MAX_FILES} files (found ${list.length})`);
284
+ if (total > exports.PACK_FILES_MAX_TOTAL_BYTES)
285
+ throw new Error(`pack files total over ${exports.PACK_FILES_MAX_TOTAL_BYTES / (1024 * 1024)}MB`);
286
+ return { files: list, notes, total };
287
+ }
288
+ function rewritePluginRoot(content, pluginRoot) {
289
+ const root = pluginRoot.split(path.sep).join("/");
290
+ return content.replace(/\$\{CLAUDE_PLUGIN_ROOT\}|\$CLAUDE_PLUGIN_ROOT(?![A-Za-z0-9_])/g, root);
291
+ }
@@ -242,8 +242,7 @@ function copyCursorSkills(home, manifest, pluginDirPath) {
242
242
  }
243
243
  fs.rmSync(dest, { recursive: true, force: true });
244
244
  }
245
- fs.mkdirSync(dest, { recursive: true });
246
- fs.copyFileSync(path.join(pluginDirPath, "skills", skill.name, "SKILL.md"), path.join(dest, "SKILL.md"));
245
+ copyTreeNoSymlinks(path.join(pluginDirPath, "skills", skill.name), dest);
247
246
  fs.writeFileSync(path.join(dest, exports.CURSOR_MARKER), `${manifest.slug}\n`);
248
247
  }
249
248
  return notes;
@@ -295,7 +294,7 @@ function copyTreeNoSymlinks(source, dest, depth = 0) {
295
294
  if (stat.isDirectory())
296
295
  copyTreeNoSymlinks(from, to, depth + 1);
297
296
  else if (stat.isFile())
298
- fs.writeFileSync(to, fs.readFileSync(from));
297
+ fs.writeFileSync(to, fs.readFileSync(from), { mode: stat.mode & 0o777 });
299
298
  }
300
299
  }
301
300
  function installCursorPlugin(home, slug, sourceDir) {
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.codexPackHooksPath = codexPackHooksPath;
37
37
  exports.codexPackTrustEntries = codexPackTrustEntries;
38
38
  exports.installPacks = installPacks;
39
+ exports.pruneBlobs = pruneBlobs;
39
40
  exports.uninstallPacks = uninstallPacks;
40
41
  exports.formatUninstallLines = formatUninstallLines;
41
42
  exports.formatResultLines = formatResultLines;
@@ -47,6 +48,7 @@ const codexHookTrust_1 = require("../codexHookTrust");
47
48
  const harnesses_1 = require("./harnesses");
48
49
  const manifest_1 = require("./manifest");
49
50
  const paths_1 = require("./paths");
51
+ const files_1 = require("./files");
50
52
  const shim_1 = require("./shim");
51
53
  const store_1 = require("./store");
52
54
  function marketplaceEntries(registry) {
@@ -127,7 +129,7 @@ function installPacks(options) {
127
129
  for (const manifest of options.manifests) {
128
130
  const dir = (0, paths_1.pluginDir)(root, manifest.slug);
129
131
  const previous = registry.packs[manifest.slug];
130
- const files = Object.keys((0, store_1.pluginFiles)(manifest)).sort();
132
+ const files = [...Object.keys((0, store_1.pluginFiles)(manifest)), ...(manifest.files ?? []).map((f) => f.path)].sort();
131
133
  const results = [];
132
134
  if (options.dryRun) {
133
135
  for (const adapter of adapters) {
@@ -139,8 +141,9 @@ function installPacks(options) {
139
141
  output.push({ slug: manifest.slug, version: manifest.version, contentHash: manifest.contentHash, dir, files, results, harnesses: [] });
140
142
  continue;
141
143
  }
142
- (0, store_1.writePluginDir)(root, manifest);
143
- (0, store_1.writePluginDir)((0, paths_1.portableRoot)(root), manifest, { portable: true });
144
+ const blobDir = options.blobDir ?? (0, files_1.blobsDir)(root);
145
+ (0, store_1.writePluginDir)(root, manifest, { blobDir });
146
+ (0, store_1.writePluginDir)((0, paths_1.portableRoot)(root), manifest, { portable: true, blobDir });
144
147
  const pending = {
145
148
  packs: {
146
149
  ...registry.packs,
@@ -183,12 +186,14 @@ function installPacks(options) {
183
186
  description: manifest.description,
184
187
  ...(manifest.channel ? { channel: manifest.channel } : {}),
185
188
  ...(manifest.icon ? { icon: manifest.icon } : {}),
189
+ ...(manifest.files?.length ? { blobs: [...new Set(manifest.files.map((f) => f.sha256))] } : {}),
186
190
  };
187
191
  (0, store_1.writeRegistry)(root, registry);
188
192
  output.push({ slug: manifest.slug, ...(manifest.channel ? { channel: manifest.channel } : {}), version: manifest.version, contentHash: manifest.contentHash, dir, files, results, harnesses });
189
193
  }
190
194
  if (options.dryRun || options.manifests.length === 0)
191
195
  return output;
196
+ pruneBlobs(root, registry, options.blobDir);
192
197
  if (selected.has("codex") && detected.get("codex")) {
193
198
  const bySlug = regenerateCodexTrust(registry, env, home);
194
199
  for (const pack of output) {
@@ -210,6 +215,28 @@ function installPacks(options) {
210
215
  }
211
216
  return output;
212
217
  }
218
+ function pruneBlobs(root, registry, dir = (0, files_1.blobsDir)(root)) {
219
+ const keep = new Set(Object.values(registry.packs).flatMap((e) => e.blobs ?? []));
220
+ let removed = 0;
221
+ let entries = [];
222
+ try {
223
+ entries = fs.readdirSync(dir);
224
+ }
225
+ catch {
226
+ return 0;
227
+ }
228
+ for (const name of entries) {
229
+ if (keep.has(name))
230
+ continue;
231
+ try {
232
+ fs.rmSync(path.join(dir, name), { force: true });
233
+ removed += 1;
234
+ }
235
+ catch {
236
+ }
237
+ }
238
+ return removed;
239
+ }
213
240
  function regenerateCodexTrust(registry, env, home) {
214
241
  const codexDir = (0, harnessDirs_1.codexHome)(env, home);
215
242
  const bySlug = codexPackTrustEntries(registry, codexDir);
@@ -256,6 +283,7 @@ function uninstallPacks(options) {
256
283
  if (fs.existsSync(codexConfig))
257
284
  regenerateCodexTrust(registry, env, home);
258
285
  (0, shim_1.writeShim)((0, paths_1.shimPath)(root), shimInputs(registry));
286
+ pruneBlobs(root, registry);
259
287
  return output;
260
288
  }
261
289
  function formatUninstallLines(results) {
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.findLocalSkills = findLocalSkills;
37
+ exports.rewriteLocalSkills = rewriteLocalSkills;
38
+ exports.skillFolderEntries = skillFolderEntries;
39
+ const fs = __importStar(require("fs"));
40
+ const os = __importStar(require("os"));
41
+ const path = __importStar(require("path"));
42
+ const files_1 = require("./files");
43
+ const manifest_1 = require("./manifest");
44
+ const LOCAL = /^(\.{1,2}\/|~\/|~$|\/)/;
45
+ function pluginDirName(name) {
46
+ if (/^[A-Za-z0-9][A-Za-z0-9._ -]{0,127}$/.test(name) && !name.includes(".."))
47
+ return name;
48
+ const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48).replace(/-+$/g, "");
49
+ return slug || "pack";
50
+ }
51
+ function findLocalSkills(markdown, baseDir, home = os.homedir()) {
52
+ const lines = markdown.replace(/^/, "").split("\n").map((l) => l.replace(/\r$/, ""));
53
+ const out = [];
54
+ let fence = null;
55
+ let inSkills = false;
56
+ for (let i = 0; i < lines.length; i++) {
57
+ const line = lines[i];
58
+ const f = line.match(/^(`{3,})/);
59
+ if (f) {
60
+ if (!fence)
61
+ fence = f[1];
62
+ else if (f[1].length >= fence.length && /^`+\s*$/.test(line))
63
+ fence = null;
64
+ continue;
65
+ }
66
+ if (fence)
67
+ continue;
68
+ const h2 = line.match(/^##\s+(.+?)\s*$/);
69
+ if (h2 && !line.startsWith("###")) {
70
+ inSkills = h2[1].toLowerCase() === "skills";
71
+ continue;
72
+ }
73
+ if (!inSkills)
74
+ continue;
75
+ const m = line.match(/^\s*[-*]\s+(.+?)\s*$/);
76
+ if (!m)
77
+ continue;
78
+ const raw = m[1].replace(/^["']|["']$/g, "");
79
+ if (!LOCAL.test(raw))
80
+ continue;
81
+ const abs = path.resolve(baseDir, (0, files_1.expandHome)(raw, home));
82
+ let stat;
83
+ try {
84
+ stat = fs.statSync(abs);
85
+ }
86
+ catch {
87
+ throw new Error(`pack.md line ${i + 1}: ${raw} doesn't exist`);
88
+ }
89
+ const dir = stat.isDirectory() ? abs : null;
90
+ const skillFile = dir ? path.join(dir, "SKILL.md") : abs;
91
+ if (!fs.existsSync(skillFile))
92
+ throw new Error(`pack.md line ${i + 1}: no SKILL.md in ${raw}`);
93
+ const { data } = (0, manifest_1.parseFrontmatter)(fs.readFileSync(skillFile, "utf8"));
94
+ const name = (data.name ?? "").trim();
95
+ if (!name || !(data.description ?? "").trim()) {
96
+ throw new Error(`pack.md line ${i + 1}: ${path.relative(baseDir, skillFile) || skillFile} needs SKILL.md frontmatter with name: and description:`);
97
+ }
98
+ out.push({ line: i + 1, source: raw, skillFile, dir, name, pluginDir: `skills/${pluginDirName(name)}` });
99
+ }
100
+ const seen = new Map();
101
+ for (const skill of out) {
102
+ const prior = seen.get(skill.name.toLowerCase());
103
+ if (prior)
104
+ throw new Error(`pack.md lines ${prior.line} and ${skill.line} both add a skill named "${skill.name}"`);
105
+ seen.set(skill.name.toLowerCase(), skill);
106
+ }
107
+ return out;
108
+ }
109
+ function rewriteLocalSkills(markdown, keys) {
110
+ const lines = markdown.split("\n");
111
+ for (const [line, key] of keys) {
112
+ const cr = lines[line - 1].endsWith("\r") ? "\r" : "";
113
+ const indent = lines[line - 1].match(/^\s*/)?.[0] ?? "";
114
+ lines[line - 1] = `${indent}- ${key} @me${cr}`;
115
+ }
116
+ return lines.join("\n");
117
+ }
118
+ function skillFolderEntries(skills) {
119
+ return skills.filter((s) => s.dir).map((s) => ({ dest: s.pluginDir, source: s.dir, line: s.line }));
120
+ }
@@ -7,6 +7,7 @@ exports.parseManifest = parseManifest;
7
7
  exports.cleanLabel = cleanLabel;
8
8
  exports.displayName = displayName;
9
9
  exports.parseFrontmatter = parseFrontmatter;
10
+ const files_1 = require("./files");
10
11
  const SAFE_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
11
12
  function isSafeSegment(value) {
12
13
  return typeof value === "string" && SAFE_SEGMENT.test(value) && !value.includes("..");
@@ -41,6 +42,46 @@ function fileList(value, field, safe) {
41
42
  return { name: entry.name, content: entry.content, ...(artifactId ? { artifactId } : {}) };
42
43
  });
43
44
  }
45
+ function bundleFiles(value, reserved) {
46
+ if (value === undefined || value === null)
47
+ return [];
48
+ if (!Array.isArray(value))
49
+ throw new ManifestError("manifest.files must be an array");
50
+ if (value.length > files_1.PACK_MAX_FILES)
51
+ throw new ManifestError(`manifest.files has more than ${files_1.PACK_MAX_FILES} entries`);
52
+ const seen = new Set();
53
+ const dirs = new Set();
54
+ let total = 0;
55
+ const out = value.map((item, index) => {
56
+ const f = item;
57
+ if (!f || typeof f !== "object")
58
+ throw new ManifestError(`manifest.files[${index}] is not an object`);
59
+ const problem = (0, files_1.packFilePathProblem)(f.path);
60
+ if (problem)
61
+ throw new ManifestError(`manifest.files[${index}] has an unsafe path ${JSON.stringify(f.path)}: ${problem}`);
62
+ const p = f.path;
63
+ if (typeof f.sha256 !== "string" || !files_1.SHA256_HEX.test(f.sha256))
64
+ throw new ManifestError(`manifest.files[${index}].sha256 is not a sha256`);
65
+ const size = Number(f.size);
66
+ if (!Number.isInteger(size) || size < 0 || size > files_1.PACK_FILE_MAX_BYTES)
67
+ throw new ManifestError(`manifest.files[${index}].size is out of range`);
68
+ total += size;
69
+ const key = p.toLowerCase();
70
+ if (seen.has(key) || reserved.has(key))
71
+ throw new ManifestError(`manifest.files has a clashing path: ${p}`);
72
+ seen.add(key);
73
+ const parts = key.split("/");
74
+ for (let n = 1; n < parts.length; n++)
75
+ dirs.add(parts.slice(0, n).join("/"));
76
+ return { path: p, sha256: f.sha256, size, ...(f.executable === true ? { executable: true } : {}) };
77
+ });
78
+ for (const key of seen)
79
+ if (dirs.has(key))
80
+ throw new ManifestError(`manifest.files has a path that is also a folder: ${key}`);
81
+ if (total > files_1.PACK_FILES_MAX_TOTAL_BYTES)
82
+ throw new ManifestError("manifest.files is too large");
83
+ return out;
84
+ }
44
85
  function parseManifest(input) {
45
86
  let raw = input;
46
87
  if (typeof raw === "string") {
@@ -80,15 +121,23 @@ function parseManifest(input) {
80
121
  hooksJson = m.hooksJson;
81
122
  }
82
123
  }
124
+ const skills = fileList(m.skills, "skills", isSafeSkillName);
125
+ const agents = fileList(m.agents, "agents", isSafeSegment);
126
+ const reserved = new Set([
127
+ ...skills.map((s) => `skills/${s.name}/skill.md`.toLowerCase()),
128
+ ...agents.map((a) => `agents/${a.name}.md`.toLowerCase()),
129
+ ]);
130
+ const files = bundleFiles(m.files, reserved);
83
131
  return {
84
132
  slug: m.slug,
85
133
  name: typeof m.name === "string" && m.name ? m.name : m.slug,
86
134
  description: typeof m.description === "string" ? m.description : "",
87
135
  version,
88
136
  contentHash: m.contentHash,
89
- skills: fileList(m.skills, "skills", isSafeSkillName),
90
- agents: fileList(m.agents, "agents", isSafeSegment),
137
+ skills,
138
+ agents,
91
139
  hooksJson,
140
+ ...(files.length ? { files } : {}),
92
141
  ...(cleanLabel(m.channel, 16) ? { channel: cleanLabel(m.channel, 16) } : {}),
93
142
  ...(cleanLabel(m.icon, 16) ? { icon: cleanLabel(m.icon, 16) } : {}),
94
143
  };