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.
- package/dist/cli.js +63 -18
- package/dist/codie.js +19 -2
- package/dist/codieLauncher.js +20 -1
- package/dist/commands/agents.js +18 -1
- package/dist/commands/agentsSdk.generated.js +3 -3
- package/dist/commands/inbox.js +9 -0
- package/dist/commands/pack.js +258 -12
- package/dist/commands/ping.js +35 -10
- package/dist/commands/pingFiles.js +145 -0
- package/dist/commands/push.js +33 -4
- package/dist/commands/reply.js +49 -0
- package/dist/commands/scrub.js +1 -2
- package/dist/commands/send.js +11 -51
- package/dist/commands/shared.js +51 -0
- package/dist/commands/work.js +761 -0
- package/dist/connect.js +3 -0
- package/dist/hook.js +50 -5
- package/dist/inboxFormat.js +17 -1
- package/dist/launchContext.js +56 -0
- package/dist/launcher.js +24 -1
- package/dist/localScrub.js +6 -0
- package/dist/localScrubReport.js +4 -2
- package/dist/managedPi.js +1 -0
- package/dist/mcp.js +15 -77
- package/dist/packs/files.js +291 -0
- package/dist/packs/harnesses.js +2 -3
- package/dist/packs/install.js +31 -3
- package/dist/packs/localSkills.js +120 -0
- package/dist/packs/manifest.js +51 -2
- package/dist/packs/skillFiles.js +106 -0
- package/dist/packs/store.js +21 -3
- package/dist/promptGuard.js +223 -0
- package/dist/scrub/rules.js +260 -21
- package/dist/teamSkillContext.js +1 -1
- package/dist/unreadNotice.js +72 -0
- package/dist/utils.js +120 -25
- package/package.json +2 -2
- package/dist/commands/ask.js +0 -55
|
@@ -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
|
+
}
|
package/dist/packs/harnesses.js
CHANGED
|
@@ -242,8 +242,7 @@ function copyCursorSkills(home, manifest, pluginDirPath) {
|
|
|
242
242
|
}
|
|
243
243
|
fs.rmSync(dest, { recursive: true, force: true });
|
|
244
244
|
}
|
|
245
|
-
|
|
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) {
|
package/dist/packs/install.js
CHANGED
|
@@ -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,
|
|
143
|
-
(0, store_1.writePluginDir)(
|
|
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
|
+
}
|
package/dist/packs/manifest.js
CHANGED
|
@@ -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
|
|
90
|
-
agents
|
|
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
|
};
|