@channlize/skills 0.0.0-stage → 0.5.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/.claude-plugin/plugin.json +12 -0
- package/CHANGELOG.md +45 -0
- package/README.md +113 -2
- package/dist/catalog.json +289 -0
- package/dist/cli.js +841 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.mts +139 -0
- package/dist/index.d.ts +139 -0
- package/dist/index.js +541 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +507 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +64 -3
- package/prompts.json +113 -0
- package/skills/channlize-agents/SKILL.md +155 -0
- package/skills/channlize-connections/SKILL.md +63 -0
- package/skills/channlize-connections/references/automation-contract.md +51 -0
- package/skills/channlize-connections/references/presets.md +30 -0
- package/skills/channlize-log-tracking/SKILL.md +94 -0
- package/skills/channlize-mcp-setup/SKILL.md +113 -0
- package/skills/channlize-notify-events/SKILL.md +85 -0
- package/skills/channlize-react-widget/SKILL.md +133 -0
- package/skills/channlize-support-triage/SKILL.md +53 -0
- package/skills/channlize-troubleshooting/SKILL.md +74 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ALL_TARGETS: () => ALL_TARGETS,
|
|
24
|
+
TARGETS: () => TARGETS,
|
|
25
|
+
buildCatalog: () => buildCatalog,
|
|
26
|
+
bundledSkillsDir: () => bundledSkillsDir,
|
|
27
|
+
fillPrompt: () => fillPrompt,
|
|
28
|
+
installSkills: () => installSkills,
|
|
29
|
+
loadPrompts: () => loadPrompts,
|
|
30
|
+
loadSkills: () => loadSkills,
|
|
31
|
+
packageVersion: () => packageVersion,
|
|
32
|
+
placeholdersOf: () => placeholdersOf,
|
|
33
|
+
targetDir: () => targetDir,
|
|
34
|
+
validateMarketplace: () => validateMarketplace,
|
|
35
|
+
validatePrompts: () => validatePrompts,
|
|
36
|
+
validateSkill: () => validateSkill,
|
|
37
|
+
validateSkillsDir: () => validateSkillsDir
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(index_exports);
|
|
40
|
+
|
|
41
|
+
// src/catalog.ts
|
|
42
|
+
var import_node_fs = require("fs");
|
|
43
|
+
var import_node_path = require("path");
|
|
44
|
+
|
|
45
|
+
// src/frontmatter.ts
|
|
46
|
+
function unquote(raw) {
|
|
47
|
+
const v = raw.trim();
|
|
48
|
+
if (v.length >= 2 && v.startsWith('"') && v.endsWith('"')) {
|
|
49
|
+
return v.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
50
|
+
}
|
|
51
|
+
if (v.length >= 2 && v.startsWith("'") && v.endsWith("'")) {
|
|
52
|
+
return v.slice(1, -1).replace(/''/g, "'");
|
|
53
|
+
}
|
|
54
|
+
return v;
|
|
55
|
+
}
|
|
56
|
+
function parseSkillFile(text) {
|
|
57
|
+
const src = text.replace(/^\uFEFF/, "").replace(/\r\n/g, "\n");
|
|
58
|
+
if (!src.startsWith("---\n")) {
|
|
59
|
+
return { data: null, body: src, errors: [] };
|
|
60
|
+
}
|
|
61
|
+
const end = src.indexOf("\n---", 4);
|
|
62
|
+
if (end === -1) {
|
|
63
|
+
return {
|
|
64
|
+
data: null,
|
|
65
|
+
body: src,
|
|
66
|
+
errors: ["frontmatter is not closed with ---"]
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const block = src.slice(4, end);
|
|
70
|
+
const afterFence = src.indexOf("\n", end + 1);
|
|
71
|
+
const body = afterFence === -1 ? "" : src.slice(afterFence + 1);
|
|
72
|
+
const data = {};
|
|
73
|
+
const errors = [];
|
|
74
|
+
let currentMap = null;
|
|
75
|
+
block.split("\n").forEach((line, i) => {
|
|
76
|
+
const lineNo = i + 2;
|
|
77
|
+
if (!line.trim() || line.trimStart().startsWith("#")) return;
|
|
78
|
+
const nested = /^\s+([A-Za-z0-9_.-]+):\s*(.*)$/.exec(line);
|
|
79
|
+
if (nested && currentMap) {
|
|
80
|
+
currentMap[nested[1]] = unquote(nested[2]);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const top = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line);
|
|
84
|
+
if (!top) {
|
|
85
|
+
errors.push(`line ${lineNo}: expected "key: value"`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const [, key, rawValue] = top;
|
|
89
|
+
if (key in data) errors.push(`line ${lineNo}: duplicate key "${key}"`);
|
|
90
|
+
if (rawValue === "") {
|
|
91
|
+
currentMap = {};
|
|
92
|
+
data[key] = currentMap;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
currentMap = null;
|
|
96
|
+
if (/^[>|][+-]?$/.test(rawValue.trim())) {
|
|
97
|
+
errors.push(
|
|
98
|
+
`line ${lineNo}: "${key}" uses a block scalar; write it on one line`
|
|
99
|
+
);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (rawValue.trim().startsWith("[") || rawValue.trim().startsWith("{")) {
|
|
103
|
+
errors.push(`line ${lineNo}: "${key}" must be a plain string`);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
data[key] = unquote(rawValue);
|
|
107
|
+
});
|
|
108
|
+
return { data, body, errors };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/catalog.ts
|
|
112
|
+
function packageRoot() {
|
|
113
|
+
return (0, import_node_path.resolve)(__dirname, "..");
|
|
114
|
+
}
|
|
115
|
+
function bundledSkillsDir() {
|
|
116
|
+
return (0, import_node_path.join)(packageRoot(), "skills");
|
|
117
|
+
}
|
|
118
|
+
function packageVersion() {
|
|
119
|
+
const pkg = JSON.parse(
|
|
120
|
+
(0, import_node_fs.readFileSync)((0, import_node_path.join)(packageRoot(), "package.json"), "utf8")
|
|
121
|
+
);
|
|
122
|
+
return pkg.version;
|
|
123
|
+
}
|
|
124
|
+
function listSkillFiles(dir) {
|
|
125
|
+
const out = [];
|
|
126
|
+
const walk = (d) => {
|
|
127
|
+
for (const entry of (0, import_node_fs.readdirSync)(d).sort()) {
|
|
128
|
+
if (entry.startsWith(".")) continue;
|
|
129
|
+
const full = (0, import_node_path.join)(d, entry);
|
|
130
|
+
if ((0, import_node_fs.statSync)(full).isDirectory()) walk(full);
|
|
131
|
+
else out.push((0, import_node_path.relative)(dir, full).split(import_node_path.sep).join("/"));
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
walk(dir);
|
|
135
|
+
return out.sort(
|
|
136
|
+
(a, b) => a === "SKILL.md" ? -1 : b === "SKILL.md" ? 1 : a.localeCompare(b)
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
function skillFolders(root) {
|
|
140
|
+
return (0, import_node_fs.readdirSync)(root).filter((e) => !e.startsWith(".") && (0, import_node_fs.statSync)((0, import_node_path.join)(root, e)).isDirectory()).sort();
|
|
141
|
+
}
|
|
142
|
+
function loadSkills(root = bundledSkillsDir()) {
|
|
143
|
+
return skillFolders(root).flatMap((folder) => {
|
|
144
|
+
const dir = (0, import_node_path.join)(root, folder);
|
|
145
|
+
let text;
|
|
146
|
+
try {
|
|
147
|
+
text = (0, import_node_fs.readFileSync)((0, import_node_path.join)(dir, "SKILL.md"), "utf8");
|
|
148
|
+
} catch {
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
const { data } = parseSkillFile(text);
|
|
152
|
+
const name = typeof data?.name === "string" ? data.name : folder;
|
|
153
|
+
const description = typeof data?.description === "string" ? data.description : "";
|
|
154
|
+
const metadata = data && typeof data.metadata === "object" ? data.metadata : {};
|
|
155
|
+
const files = listSkillFiles(dir).map((path) => ({
|
|
156
|
+
path,
|
|
157
|
+
content: (0, import_node_fs.readFileSync)((0, import_node_path.join)(dir, path), "utf8")
|
|
158
|
+
}));
|
|
159
|
+
return [{ name, description, dir, metadata, files }];
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function loadPrompts(file = (0, import_node_path.join)(packageRoot(), "prompts.json")) {
|
|
163
|
+
return JSON.parse((0, import_node_fs.readFileSync)(file, "utf8"));
|
|
164
|
+
}
|
|
165
|
+
var PROMPT_PLACEHOLDERS = [
|
|
166
|
+
"projectId",
|
|
167
|
+
"widgetKey",
|
|
168
|
+
"react",
|
|
169
|
+
"core",
|
|
170
|
+
"mcp"
|
|
171
|
+
];
|
|
172
|
+
function placeholdersOf(template) {
|
|
173
|
+
return [...template.matchAll(/\{\{\s*([A-Za-z0-9_]+)\s*\}\}/g)].map(
|
|
174
|
+
(m) => m[1]
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
function fillPrompt(template, library, values = {}) {
|
|
178
|
+
const map = {
|
|
179
|
+
projectId: values.projectId || "<your-project-id>",
|
|
180
|
+
widgetKey: values.widgetKey || "<your-widget-key (wk_\u2026)>",
|
|
181
|
+
...library.versions
|
|
182
|
+
};
|
|
183
|
+
return template.replace(
|
|
184
|
+
/\{\{\s*([A-Za-z0-9_]+)\s*\}\}/g,
|
|
185
|
+
(whole, key) => map[key] ?? whole
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
function buildCatalog() {
|
|
189
|
+
return {
|
|
190
|
+
package: "@channlize/skills",
|
|
191
|
+
version: packageVersion(),
|
|
192
|
+
skills: loadSkills().map(({ dir: _dir, ...rest }) => rest),
|
|
193
|
+
prompts: loadPrompts()
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/install.ts
|
|
198
|
+
var import_node_crypto = require("crypto");
|
|
199
|
+
var import_node_fs2 = require("fs");
|
|
200
|
+
var import_node_os = require("os");
|
|
201
|
+
var import_node_path2 = require("path");
|
|
202
|
+
var TARGETS = {
|
|
203
|
+
claude: {
|
|
204
|
+
label: "Claude Code",
|
|
205
|
+
project: ".claude/skills",
|
|
206
|
+
user: ".claude/skills"
|
|
207
|
+
},
|
|
208
|
+
cursor: {
|
|
209
|
+
label: "Cursor",
|
|
210
|
+
project: ".cursor/skills",
|
|
211
|
+
user: ".cursor/skills"
|
|
212
|
+
},
|
|
213
|
+
codex: {
|
|
214
|
+
label: "Codex (and other .agents readers)",
|
|
215
|
+
project: ".agents/skills",
|
|
216
|
+
user: ".agents/skills"
|
|
217
|
+
},
|
|
218
|
+
copilot: {
|
|
219
|
+
label: "VS Code / GitHub Copilot",
|
|
220
|
+
project: ".github/skills",
|
|
221
|
+
user: ".copilot/skills"
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
var TARGET_IDS = Object.keys(TARGETS);
|
|
225
|
+
var ALL_TARGETS = ["claude", "codex", "copilot"];
|
|
226
|
+
var MANIFEST = ".channlize-skill.json";
|
|
227
|
+
function targetDir(target, scope, cwd = process.cwd(), home = (0, import_node_os.homedir)()) {
|
|
228
|
+
const t = TARGETS[target];
|
|
229
|
+
return scope === "user" ? (0, import_node_path2.join)(home, t.user) : (0, import_node_path2.resolve)(cwd, t.project);
|
|
230
|
+
}
|
|
231
|
+
var sha256 = (s) => (0, import_node_crypto.createHash)("sha256").update(s).digest("hex");
|
|
232
|
+
function readManifest(skillDir) {
|
|
233
|
+
try {
|
|
234
|
+
return JSON.parse(
|
|
235
|
+
(0, import_node_fs2.readFileSync)((0, import_node_path2.join)(skillDir, MANIFEST), "utf8")
|
|
236
|
+
);
|
|
237
|
+
} catch {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async function installSkills(opts) {
|
|
242
|
+
const results = [];
|
|
243
|
+
for (const skill of opts.skills) {
|
|
244
|
+
const skillDir = (0, import_node_path2.join)(opts.dir, skill.name);
|
|
245
|
+
const previous = readManifest(skillDir);
|
|
246
|
+
const manifest = {
|
|
247
|
+
package: "@channlize/skills",
|
|
248
|
+
version: opts.version,
|
|
249
|
+
files: {}
|
|
250
|
+
};
|
|
251
|
+
for (const file of skill.files) {
|
|
252
|
+
const dest = (0, import_node_path2.join)(skillDir, file.path);
|
|
253
|
+
const next = sha256(file.content);
|
|
254
|
+
const result = {
|
|
255
|
+
skill: skill.name,
|
|
256
|
+
path: file.path,
|
|
257
|
+
dest,
|
|
258
|
+
action: "created"
|
|
259
|
+
};
|
|
260
|
+
if ((0, import_node_fs2.existsSync)(dest)) {
|
|
261
|
+
const current = sha256((0, import_node_fs2.readFileSync)(dest, "utf8"));
|
|
262
|
+
const recorded = previous?.files[file.path];
|
|
263
|
+
if (current === next) {
|
|
264
|
+
result.action = "unchanged";
|
|
265
|
+
} else if (recorded && current === recorded) {
|
|
266
|
+
result.action = "updated";
|
|
267
|
+
} else if (opts.force || opts.confirm && await opts.confirm({ ...result, action: "overwritten" })) {
|
|
268
|
+
result.action = "overwritten";
|
|
269
|
+
} else {
|
|
270
|
+
result.action = "kept";
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (!opts.dryRun && (result.action === "created" || result.action === "updated" || result.action === "overwritten")) {
|
|
274
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(dest), { recursive: true });
|
|
275
|
+
(0, import_node_fs2.writeFileSync)(dest, file.content);
|
|
276
|
+
}
|
|
277
|
+
manifest.files[file.path] = result.action === "kept" ? previous?.files[file.path] ?? "" : next;
|
|
278
|
+
results.push(result);
|
|
279
|
+
}
|
|
280
|
+
if (!opts.dryRun) {
|
|
281
|
+
(0, import_node_fs2.mkdirSync)(skillDir, { recursive: true });
|
|
282
|
+
(0, import_node_fs2.writeFileSync)(
|
|
283
|
+
(0, import_node_path2.join)(skillDir, MANIFEST),
|
|
284
|
+
JSON.stringify(manifest, null, 2) + "\n"
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return results;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// src/validate.ts
|
|
292
|
+
var import_node_fs3 = require("fs");
|
|
293
|
+
var import_node_path3 = require("path");
|
|
294
|
+
var NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
295
|
+
var NAME_MAX = 64;
|
|
296
|
+
var DESCRIPTION_MAX = 1024;
|
|
297
|
+
var COMPATIBILITY_MAX = 500;
|
|
298
|
+
var BODY_MAX_LINES = 500;
|
|
299
|
+
var SPEC_KEYS = [
|
|
300
|
+
"name",
|
|
301
|
+
"description",
|
|
302
|
+
"license",
|
|
303
|
+
"compatibility",
|
|
304
|
+
"metadata",
|
|
305
|
+
"allowed-tools"
|
|
306
|
+
];
|
|
307
|
+
function displayPath(p) {
|
|
308
|
+
const rel = (0, import_node_path3.relative)(process.cwd(), p);
|
|
309
|
+
return rel && !rel.startsWith("..") ? rel : p;
|
|
310
|
+
}
|
|
311
|
+
function extractLinks(markdown) {
|
|
312
|
+
const prose = markdown.replace(/^[ \t]*(```|~~~)[^\n]*\n[\s\S]*?^[ \t]*\1[^\n]*$/gm, "").replace(/`[^`\n]*`/g, "");
|
|
313
|
+
const links = /* @__PURE__ */ new Set();
|
|
314
|
+
for (const m of prose.matchAll(
|
|
315
|
+
/\[[^\]]*\]\(\s*<?([^)\s>]+)>?(?:\s+"[^"]*")?\s*\)/g
|
|
316
|
+
))
|
|
317
|
+
links.add(m[1]);
|
|
318
|
+
for (const m of prose.matchAll(/https?:\/\/[^\s<>)\]"'`]+/g))
|
|
319
|
+
links.add(m[0].replace(/[.,;:!?]+$/, ""));
|
|
320
|
+
return [...links];
|
|
321
|
+
}
|
|
322
|
+
function validateSkill(dir) {
|
|
323
|
+
const folder = dir.split(import_node_path3.sep).pop() ?? dir;
|
|
324
|
+
const issues = [];
|
|
325
|
+
const urls = /* @__PURE__ */ new Map();
|
|
326
|
+
const skillFile = (0, import_node_path3.join)(dir, "SKILL.md");
|
|
327
|
+
const at = (file) => displayPath((0, import_node_path3.join)(dir, file));
|
|
328
|
+
if (!(0, import_node_fs3.existsSync)(skillFile)) {
|
|
329
|
+
issues.push({ file: at("SKILL.md"), message: "missing SKILL.md" });
|
|
330
|
+
return { issues, urls };
|
|
331
|
+
}
|
|
332
|
+
const { data, body, errors } = parseSkillFile(
|
|
333
|
+
(0, import_node_fs3.readFileSync)(skillFile, "utf8")
|
|
334
|
+
);
|
|
335
|
+
for (const e of errors)
|
|
336
|
+
issues.push({ file: at("SKILL.md"), message: `frontmatter ${e}` });
|
|
337
|
+
if (!data) {
|
|
338
|
+
issues.push({
|
|
339
|
+
file: at("SKILL.md"),
|
|
340
|
+
message: "no YAML frontmatter (the file must start with ---)"
|
|
341
|
+
});
|
|
342
|
+
} else {
|
|
343
|
+
const { name, description, compatibility, metadata } = data;
|
|
344
|
+
if (typeof name !== "string" || !name) {
|
|
345
|
+
issues.push({ file: at("SKILL.md"), message: '"name" is required' });
|
|
346
|
+
} else {
|
|
347
|
+
if (name.length > NAME_MAX)
|
|
348
|
+
issues.push({
|
|
349
|
+
file: at("SKILL.md"),
|
|
350
|
+
message: `"name" is over ${NAME_MAX} characters`
|
|
351
|
+
});
|
|
352
|
+
if (!NAME_RE.test(name)) {
|
|
353
|
+
issues.push({
|
|
354
|
+
file: at("SKILL.md"),
|
|
355
|
+
message: `"name" must be lowercase letters, digits and single hyphens: "${name}"`
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
if (name !== folder)
|
|
359
|
+
issues.push({
|
|
360
|
+
file: at("SKILL.md"),
|
|
361
|
+
message: `"name" (${name}) must match its folder (${folder})`
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
if (typeof description !== "string" || !description.trim()) {
|
|
365
|
+
issues.push({
|
|
366
|
+
file: at("SKILL.md"),
|
|
367
|
+
message: '"description" is required'
|
|
368
|
+
});
|
|
369
|
+
} else if (description.length > DESCRIPTION_MAX) {
|
|
370
|
+
issues.push({
|
|
371
|
+
file: at("SKILL.md"),
|
|
372
|
+
message: `"description" is ${description.length} characters; the limit is ${DESCRIPTION_MAX}`
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
if (compatibility !== void 0 && (typeof compatibility !== "string" || compatibility.length > COMPATIBILITY_MAX)) {
|
|
376
|
+
issues.push({
|
|
377
|
+
file: at("SKILL.md"),
|
|
378
|
+
message: `"compatibility" must be a string of at most ${COMPATIBILITY_MAX} characters`
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
if (metadata !== void 0 && typeof metadata !== "object") {
|
|
382
|
+
issues.push({
|
|
383
|
+
file: at("SKILL.md"),
|
|
384
|
+
message: '"metadata" must be a map of strings'
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
for (const key of Object.keys(data)) {
|
|
388
|
+
if (!SPEC_KEYS.includes(key)) {
|
|
389
|
+
issues.push({
|
|
390
|
+
file: at("SKILL.md"),
|
|
391
|
+
message: `unknown frontmatter key "${key}" (allowed: ${SPEC_KEYS.join(", ")})`
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (!body.trim())
|
|
397
|
+
issues.push({ file: at("SKILL.md"), message: "the body is empty" });
|
|
398
|
+
const lines = body.split("\n").length;
|
|
399
|
+
if (lines > BODY_MAX_LINES)
|
|
400
|
+
issues.push({
|
|
401
|
+
file: at("SKILL.md"),
|
|
402
|
+
message: `the body has ${lines} lines; keep it under ${BODY_MAX_LINES}`
|
|
403
|
+
});
|
|
404
|
+
for (const file of listSkillFiles(dir).filter((f) => f.endsWith(".md"))) {
|
|
405
|
+
const text = (0, import_node_fs3.readFileSync)((0, import_node_path3.join)(dir, file), "utf8");
|
|
406
|
+
for (const link of extractLinks(text)) {
|
|
407
|
+
if (/^https?:\/\//.test(link)) {
|
|
408
|
+
if (!urls.has(link)) urls.set(link, at(file));
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
if (/^(mailto:|#)/.test(link)) continue;
|
|
412
|
+
const target = (0, import_node_path3.normalize)(
|
|
413
|
+
(0, import_node_path3.join)((0, import_node_path3.dirname)((0, import_node_path3.join)(dir, file)), decodeURI(link.split("#")[0]))
|
|
414
|
+
);
|
|
415
|
+
if (!target.startsWith((0, import_node_path3.resolve)(dir) + import_node_path3.sep)) {
|
|
416
|
+
issues.push({
|
|
417
|
+
file: at(file),
|
|
418
|
+
message: `link "${link}" points outside the skill folder`
|
|
419
|
+
});
|
|
420
|
+
} else if (!(0, import_node_fs3.existsSync)(target)) {
|
|
421
|
+
issues.push({ file: at(file), message: `broken link "${link}"` });
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return { issues, urls };
|
|
426
|
+
}
|
|
427
|
+
function validatePrompts(library, skillNames, file = "prompts.json") {
|
|
428
|
+
const issues = [];
|
|
429
|
+
const ids = /* @__PURE__ */ new Set();
|
|
430
|
+
for (const [i, p] of library.prompts.entries()) {
|
|
431
|
+
const where = `${file} prompts[${i}] (${p.id ?? "?"})`;
|
|
432
|
+
const add = (message) => issues.push({ file: where, message });
|
|
433
|
+
if (!p.id || !NAME_RE.test(p.id)) add('"id" must be kebab-case');
|
|
434
|
+
if (ids.has(p.id)) add("duplicate id");
|
|
435
|
+
ids.add(p.id);
|
|
436
|
+
if (!p.title?.trim()) add('"title" is required');
|
|
437
|
+
if (!library.categories[p.category])
|
|
438
|
+
add(`unknown category "${p.category}"`);
|
|
439
|
+
if (!Array.isArray(p.frameworks)) add('"frameworks" must be an array');
|
|
440
|
+
if (!Array.isArray(p.skills) || p.skills.length === 0)
|
|
441
|
+
add('"skills" must list at least one skill');
|
|
442
|
+
for (const s of p.skills ?? [])
|
|
443
|
+
if (!skillNames.includes(s)) add(`unknown skill "${s}"`);
|
|
444
|
+
for (const ph of placeholdersOf(p.prompt ?? "")) {
|
|
445
|
+
if (!PROMPT_PLACEHOLDERS.includes(ph))
|
|
446
|
+
add(`unknown placeholder {{${ph}}}`);
|
|
447
|
+
}
|
|
448
|
+
const text = p.prompt ?? "";
|
|
449
|
+
for (const s of p.skills ?? [])
|
|
450
|
+
if (!text.includes(s))
|
|
451
|
+
add(`the prompt should tell the assistant to read the ${s} skill`);
|
|
452
|
+
if (!/\bverify\b/i.test(text))
|
|
453
|
+
add('the prompt should end with a verification step ("Verify: \u2026")');
|
|
454
|
+
if (/ch_live_[A-Za-z0-9]{6,}/.test(text))
|
|
455
|
+
add("the prompt contains what looks like a real server key");
|
|
456
|
+
}
|
|
457
|
+
for (const key of PROMPT_PLACEHOLDERS.slice(2)) {
|
|
458
|
+
if (!library.versions?.[key])
|
|
459
|
+
issues.push({ file, message: `versions.${key} is missing` });
|
|
460
|
+
}
|
|
461
|
+
return issues;
|
|
462
|
+
}
|
|
463
|
+
function validateMarketplace(root) {
|
|
464
|
+
const file = (0, import_node_path3.join)(root, ".claude-plugin", "marketplace.json");
|
|
465
|
+
const issues = [];
|
|
466
|
+
const add = (message) => issues.push({ file: displayPath(file), message });
|
|
467
|
+
let m;
|
|
468
|
+
try {
|
|
469
|
+
m = JSON.parse((0, import_node_fs3.readFileSync)(file, "utf8"));
|
|
470
|
+
} catch (e) {
|
|
471
|
+
add(`cannot read: ${e.message}`);
|
|
472
|
+
return issues;
|
|
473
|
+
}
|
|
474
|
+
const idPart = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
475
|
+
if (typeof m.name !== "string" || !idPart.test(m.name) || m.name.includes(".."))
|
|
476
|
+
add('"name" is missing or invalid');
|
|
477
|
+
if (typeof m.owner?.name !== "string" || !m.owner.name)
|
|
478
|
+
add('"owner.name" is required');
|
|
479
|
+
if (!Array.isArray(m.plugins) || m.plugins.length === 0)
|
|
480
|
+
add('"plugins" must list at least one plugin');
|
|
481
|
+
for (const [i, p] of (m.plugins ?? []).entries()) {
|
|
482
|
+
if (typeof p.name !== "string" || !idPart.test(p.name))
|
|
483
|
+
add(`plugins[${i}].name is invalid`);
|
|
484
|
+
if (typeof p.source === "string") {
|
|
485
|
+
if (!p.source.startsWith("./") && p.source !== ".")
|
|
486
|
+
add(`plugins[${i}].source must start with "./"`);
|
|
487
|
+
if (p.source.split("/").includes(".."))
|
|
488
|
+
add(`plugins[${i}].source must not contain ".."`);
|
|
489
|
+
const dir = (0, import_node_path3.resolve)(root, p.source);
|
|
490
|
+
const manifest = (0, import_node_path3.join)(dir, ".claude-plugin", "plugin.json");
|
|
491
|
+
if (!(0, import_node_fs3.existsSync)(dir) || !(0, import_node_fs3.statSync)(dir).isDirectory()) {
|
|
492
|
+
add(`plugins[${i}].source ${p.source} does not exist`);
|
|
493
|
+
} else if ((0, import_node_fs3.existsSync)(manifest)) {
|
|
494
|
+
const pj = JSON.parse((0, import_node_fs3.readFileSync)(manifest, "utf8"));
|
|
495
|
+
if (pj.name !== p.name)
|
|
496
|
+
add(
|
|
497
|
+
`plugins[${i}].name (${String(p.name)}) differs from plugin.json name (${pj.name})`
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
} else if (!p.source || typeof p.source !== "object" || typeof p.source.source !== "string") {
|
|
501
|
+
add(`plugins[${i}].source must be a "./" path or a source object`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return issues;
|
|
505
|
+
}
|
|
506
|
+
function validateSkillsDir(root) {
|
|
507
|
+
const issues = [];
|
|
508
|
+
const urls = /* @__PURE__ */ new Map();
|
|
509
|
+
const folders = skillFolders(root);
|
|
510
|
+
const names = /* @__PURE__ */ new Set();
|
|
511
|
+
for (const folder of folders) {
|
|
512
|
+
const r = validateSkill((0, import_node_path3.join)(root, folder));
|
|
513
|
+
issues.push(...r.issues);
|
|
514
|
+
for (const [u, f] of r.urls) if (!urls.has(u)) urls.set(u, f);
|
|
515
|
+
if (names.has(folder))
|
|
516
|
+
issues.push({ file: folder, message: "duplicate skill name" });
|
|
517
|
+
names.add(folder);
|
|
518
|
+
}
|
|
519
|
+
if (folders.length === 0)
|
|
520
|
+
issues.push({ file: root, message: "no skill folders found" });
|
|
521
|
+
return { issues, urls, count: folders.length };
|
|
522
|
+
}
|
|
523
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
524
|
+
0 && (module.exports = {
|
|
525
|
+
ALL_TARGETS,
|
|
526
|
+
TARGETS,
|
|
527
|
+
buildCatalog,
|
|
528
|
+
bundledSkillsDir,
|
|
529
|
+
fillPrompt,
|
|
530
|
+
installSkills,
|
|
531
|
+
loadPrompts,
|
|
532
|
+
loadSkills,
|
|
533
|
+
packageVersion,
|
|
534
|
+
placeholdersOf,
|
|
535
|
+
targetDir,
|
|
536
|
+
validateMarketplace,
|
|
537
|
+
validatePrompts,
|
|
538
|
+
validateSkill,
|
|
539
|
+
validateSkillsDir
|
|
540
|
+
});
|
|
541
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/catalog.ts","../src/frontmatter.ts","../src/install.ts","../src/validate.ts"],"sourcesContent":["/**\n * @channlize/skills\n * Agent Skills that teach AI coding agents (Claude Code, Cursor, Codex,\n * VS Code / Copilot) to integrate Channlize, plus the prompt library shown\n * in the dashboard. The CLI is `npx @channlize/skills`.\n */\n\nexport {\n buildCatalog,\n bundledSkillsDir,\n fillPrompt,\n loadPrompts,\n loadSkills,\n packageVersion,\n placeholdersOf,\n} from './catalog';\nexport type {\n Catalog,\n PromptEntry,\n PromptLibrary,\n PromptValues,\n Skill,\n SkillFile,\n} from './catalog';\nexport { ALL_TARGETS, installSkills, TARGETS, targetDir } from './install';\nexport type { FileResult, InstallOptions, Scope, TargetId } from './install';\nexport {\n validateMarketplace,\n validatePrompts,\n validateSkill,\n validateSkillsDir,\n} from './validate';\nexport type { Issue } from './validate';\n","import { readdirSync, readFileSync, statSync } from 'node:fs';\nimport { join, relative, resolve, sep } from 'node:path';\nimport { parseSkillFile } from './frontmatter';\n\n/** The package root (one level above dist/ or src/). */\nexport function packageRoot(): string {\n return resolve(__dirname, '..');\n}\n\nexport function bundledSkillsDir(): string {\n return join(packageRoot(), 'skills');\n}\n\nexport function packageVersion(): string {\n const pkg = JSON.parse(\n readFileSync(join(packageRoot(), 'package.json'), 'utf8')\n ) as { version: string };\n return pkg.version;\n}\n\nexport interface SkillFile {\n /** Path inside the skill folder, with forward slashes (`SKILL.md`, `references/x.md`). */\n path: string;\n content: string;\n}\n\nexport interface Skill {\n name: string;\n description: string;\n /** Absolute path of the skill folder. */\n dir: string;\n metadata: Record<string, string>;\n files: SkillFile[];\n}\n\n/** Every file of a skill folder, recursively, SKILL.md first. Dotfiles are skipped. */\nexport function listSkillFiles(dir: string): string[] {\n const out: string[] = [];\n const walk = (d: string) => {\n for (const entry of readdirSync(d).sort()) {\n if (entry.startsWith('.')) continue;\n const full = join(d, entry);\n if (statSync(full).isDirectory()) walk(full);\n else out.push(relative(dir, full).split(sep).join('/'));\n }\n };\n walk(dir);\n return out.sort((a, b) =>\n a === 'SKILL.md' ? -1 : b === 'SKILL.md' ? 1 : a.localeCompare(b)\n );\n}\n\n/** Folders of `root` that contain a SKILL.md. */\nexport function skillFolders(root: string): string[] {\n return readdirSync(root)\n .filter(e => !e.startsWith('.') && statSync(join(root, e)).isDirectory())\n .sort();\n}\n\nexport function loadSkills(root: string = bundledSkillsDir()): Skill[] {\n return skillFolders(root).flatMap(folder => {\n const dir = join(root, folder);\n let text: string;\n try {\n text = readFileSync(join(dir, 'SKILL.md'), 'utf8');\n } catch {\n return [];\n }\n const { data } = parseSkillFile(text);\n const name = typeof data?.name === 'string' ? data.name : folder;\n const description =\n typeof data?.description === 'string' ? data.description : '';\n const metadata =\n data && typeof data.metadata === 'object' ? data.metadata : {};\n const files = listSkillFiles(dir).map(path => ({\n path,\n content: readFileSync(join(dir, path), 'utf8'),\n }));\n return [{ name, description, dir, metadata, files }];\n });\n}\n\n// ---------- prompts ----------\n\nexport interface PromptEntry {\n id: string;\n title: string;\n category: string;\n frameworks: string[];\n skills: string[];\n prompt: string;\n}\n\nexport interface PromptLibrary {\n versions: Record<string, string>;\n categories: Record<string, string>;\n prompts: PromptEntry[];\n}\n\nexport function loadPrompts(\n file: string = join(packageRoot(), 'prompts.json')\n): PromptLibrary {\n return JSON.parse(readFileSync(file, 'utf8')) as PromptLibrary;\n}\n\nexport interface PromptValues {\n projectId?: string;\n widgetKey?: string;\n}\n\nexport const PROMPT_PLACEHOLDERS = [\n 'projectId',\n 'widgetKey',\n 'react',\n 'core',\n 'mcp',\n] as const;\n\n/** Placeholders (`{{name}}`) used in a template. */\nexport function placeholdersOf(template: string): string[] {\n return [...template.matchAll(/\\{\\{\\s*([A-Za-z0-9_]+)\\s*\\}\\}/g)].map(\n m => m[1]!\n );\n}\n\n/**\n * Fills a prompt template. Public values only: the project ID and widget key\n * fall back to visible placeholders; server keys are never part of a prompt.\n */\nexport function fillPrompt(\n template: string,\n library: Pick<PromptLibrary, 'versions'>,\n values: PromptValues = {}\n): string {\n const map: Record<string, string> = {\n projectId: values.projectId || '<your-project-id>',\n widgetKey: values.widgetKey || '<your-widget-key (wk_…)>',\n ...library.versions,\n };\n return template.replace(\n /\\{\\{\\s*([A-Za-z0-9_]+)\\s*\\}\\}/g,\n (whole, key: string) => map[key] ?? whole\n );\n}\n\n// ---------- catalog (for the dashboard) ----------\n\nexport interface Catalog {\n package: string;\n version: string;\n skills: Array<Omit<Skill, 'dir'>>;\n prompts: PromptLibrary;\n}\n\nexport function buildCatalog(): Catalog {\n return {\n package: '@channlize/skills',\n version: packageVersion(),\n skills: loadSkills().map(({ dir: _dir, ...rest }) => rest),\n prompts: loadPrompts(),\n };\n}\n","/**\n * A deliberately small YAML-frontmatter reader for SKILL.md files: single-line\n * `key: value` scalars (optionally quoted) and one level of nested string maps\n * (`metadata:`). That is everything the Agent Skills spec uses, and anything\n * fancier (block scalars, lists, anchors) is reported instead of guessed.\n */\n\nexport type FrontmatterValue = string | Record<string, string>;\n\nexport interface ParsedSkillFile {\n /** Null when the file doesn't start with a `---` frontmatter block. */\n data: Record<string, FrontmatterValue> | null;\n body: string;\n errors: string[];\n}\n\nfunction unquote(raw: string): string {\n const v = raw.trim();\n if (v.length >= 2 && v.startsWith('\"') && v.endsWith('\"')) {\n return v.slice(1, -1).replace(/\\\\\"/g, '\"').replace(/\\\\\\\\/g, '\\\\');\n }\n if (v.length >= 2 && v.startsWith(\"'\") && v.endsWith(\"'\")) {\n return v.slice(1, -1).replace(/''/g, \"'\");\n }\n return v;\n}\n\nexport function parseSkillFile(text: string): ParsedSkillFile {\n const src = text.replace(/^\\uFEFF/, '').replace(/\\r\\n/g, '\\n');\n if (!src.startsWith('---\\n')) {\n return { data: null, body: src, errors: [] };\n }\n const end = src.indexOf('\\n---', 4);\n if (end === -1) {\n return {\n data: null,\n body: src,\n errors: ['frontmatter is not closed with ---'],\n };\n }\n const block = src.slice(4, end);\n const afterFence = src.indexOf('\\n', end + 1);\n const body = afterFence === -1 ? '' : src.slice(afterFence + 1);\n\n const data: Record<string, FrontmatterValue> = {};\n const errors: string[] = [];\n let currentMap: Record<string, string> | null = null;\n\n block.split('\\n').forEach((line, i) => {\n const lineNo = i + 2;\n if (!line.trim() || line.trimStart().startsWith('#')) return;\n\n const nested = /^\\s+([A-Za-z0-9_.-]+):\\s*(.*)$/.exec(line);\n if (nested && currentMap) {\n currentMap[nested[1]!] = unquote(nested[2]!);\n return;\n }\n\n const top = /^([A-Za-z0-9_-]+):\\s*(.*)$/.exec(line);\n if (!top) {\n errors.push(`line ${lineNo}: expected \"key: value\"`);\n return;\n }\n const [, key, rawValue] = top as unknown as [string, string, string];\n if (key in data) errors.push(`line ${lineNo}: duplicate key \"${key}\"`);\n if (rawValue === '') {\n currentMap = {};\n data[key] = currentMap;\n return;\n }\n currentMap = null;\n if (/^[>|][+-]?$/.test(rawValue.trim())) {\n errors.push(\n `line ${lineNo}: \"${key}\" uses a block scalar; write it on one line`\n );\n return;\n }\n if (rawValue.trim().startsWith('[') || rawValue.trim().startsWith('{')) {\n errors.push(`line ${lineNo}: \"${key}\" must be a plain string`);\n return;\n }\n data[key] = unquote(rawValue);\n });\n\n return { data, body, errors };\n}\n","import { createHash } from 'node:crypto';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join, resolve } from 'node:path';\nimport type { Skill } from './catalog';\n\n/**\n * Where each agent reads Agent Skills (folders with a SKILL.md), per its docs:\n * - Claude Code: `.claude/skills`, `~/.claude/skills`\n * - Cursor: `.cursor/skills`, `~/.cursor/skills` (it also reads `.agents` and `.claude`)\n * - Codex: `.agents/skills`, `~/.agents/skills`\n * - VS Code / GitHub Copilot: `.github/skills`, `~/.copilot/skills`\n */\nexport const TARGETS = {\n claude: {\n label: 'Claude Code',\n project: '.claude/skills',\n user: '.claude/skills',\n },\n cursor: {\n label: 'Cursor',\n project: '.cursor/skills',\n user: '.cursor/skills',\n },\n codex: {\n label: 'Codex (and other .agents readers)',\n project: '.agents/skills',\n user: '.agents/skills',\n },\n copilot: {\n label: 'VS Code / GitHub Copilot',\n project: '.github/skills',\n user: '.copilot/skills',\n },\n} as const;\n\nexport type TargetId = keyof typeof TARGETS;\nexport const TARGET_IDS = Object.keys(TARGETS) as TargetId[];\n/**\n * `--all`: Claude Code, `.agents` (Codex; Cursor reads it too) and Copilot.\n * `.cursor/skills` is left out because Cursor already reads `.agents/skills`\n * and `.claude/skills`, and a third copy would only list each skill again.\n */\nexport const ALL_TARGETS: TargetId[] = ['claude', 'codex', 'copilot'];\n\nexport const MANIFEST = '.channlize-skill.json';\n\nexport type Scope = 'project' | 'user';\n\nexport function targetDir(\n target: TargetId,\n scope: Scope,\n cwd = process.cwd(),\n home = homedir()\n): string {\n const t = TARGETS[target];\n return scope === 'user' ? join(home, t.user) : resolve(cwd, t.project);\n}\n\nconst sha256 = (s: string) => createHash('sha256').update(s).digest('hex');\n\ninterface Manifest {\n package: string;\n version: string;\n files: Record<string, string>;\n}\n\nfunction readManifest(skillDir: string): Manifest | null {\n try {\n return JSON.parse(\n readFileSync(join(skillDir, MANIFEST), 'utf8')\n ) as Manifest;\n } catch {\n return null;\n }\n}\n\nexport type FileAction =\n 'created' | 'updated' | 'unchanged' | 'overwritten' | 'kept';\n\nexport interface FileResult {\n skill: string;\n path: string;\n dest: string;\n action: FileAction;\n}\n\n/**\n * Asked before replacing a file the user changed since it was installed (or\n * one that was not installed by this package). Return true to overwrite.\n */\nexport type ConfirmOverwrite = (file: FileResult) => Promise<boolean> | boolean;\n\nexport interface InstallOptions {\n skills: Skill[];\n /** Absolute skills directory, e.g. `<repo>/.claude/skills`. */\n dir: string;\n version: string;\n force?: boolean;\n dryRun?: boolean;\n confirm?: ConfirmOverwrite;\n}\n\n/**\n * Copies skills into `dir/<skill>/`, never silently replacing a file the user\n * edited: an unchanged previous install (same hash as recorded in\n * `.channlize-skill.json`) is updated; anything else needs `force` or a yes\n * from `confirm`, and is kept otherwise.\n */\nexport async function installSkills(\n opts: InstallOptions\n): Promise<FileResult[]> {\n const results: FileResult[] = [];\n for (const skill of opts.skills) {\n const skillDir = join(opts.dir, skill.name);\n const previous = readManifest(skillDir);\n const manifest: Manifest = {\n package: '@channlize/skills',\n version: opts.version,\n files: {},\n };\n\n for (const file of skill.files) {\n const dest = join(skillDir, file.path);\n const next = sha256(file.content);\n const result: FileResult = {\n skill: skill.name,\n path: file.path,\n dest,\n action: 'created',\n };\n\n if (existsSync(dest)) {\n const current = sha256(readFileSync(dest, 'utf8'));\n const recorded = previous?.files[file.path];\n if (current === next) {\n result.action = 'unchanged';\n } else if (recorded && current === recorded) {\n result.action = 'updated';\n } else if (\n opts.force ||\n (opts.confirm &&\n (await opts.confirm({ ...result, action: 'overwritten' })))\n ) {\n result.action = 'overwritten';\n } else {\n result.action = 'kept';\n }\n }\n\n if (\n !opts.dryRun &&\n (result.action === 'created' ||\n result.action === 'updated' ||\n result.action === 'overwritten')\n ) {\n mkdirSync(dirname(dest), { recursive: true });\n writeFileSync(dest, file.content);\n }\n // A kept file keeps its old recorded hash, so it still counts as edited next time.\n manifest.files[file.path] =\n result.action === 'kept' ? (previous?.files[file.path] ?? '') : next;\n results.push(result);\n }\n\n if (!opts.dryRun) {\n mkdirSync(skillDir, { recursive: true });\n writeFileSync(\n join(skillDir, MANIFEST),\n JSON.stringify(manifest, null, 2) + '\\n'\n );\n }\n }\n return results;\n}\n","import { existsSync, readFileSync, statSync } from 'node:fs';\nimport { dirname, join, normalize, relative, resolve, sep } from 'node:path';\nimport {\n listSkillFiles,\n placeholdersOf,\n PROMPT_PLACEHOLDERS,\n skillFolders,\n type PromptLibrary,\n} from './catalog';\nimport { parseSkillFile } from './frontmatter';\n\n/**\n * Checks for the Agent Skills format (https://agentskills.io/specification),\n * plus the stricter rules claude.ai uploads apply: only the spec's\n * frontmatter keys are accepted.\n */\nexport const NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;\nexport const NAME_MAX = 64;\nexport const DESCRIPTION_MAX = 1024;\nexport const COMPATIBILITY_MAX = 500;\nexport const BODY_MAX_LINES = 500;\nexport const SPEC_KEYS = [\n 'name',\n 'description',\n 'license',\n 'compatibility',\n 'metadata',\n 'allowed-tools',\n];\n\n/** Relative to the working directory when inside it, absolute otherwise. */\nexport function displayPath(p: string): string {\n const rel = relative(process.cwd(), p);\n return rel && !rel.startsWith('..') ? rel : p;\n}\n\nexport interface Issue {\n file: string;\n message: string;\n}\n\n/** Markdown links and bare URLs outside code (fenced blocks and inline code are skipped). */\nexport function extractLinks(markdown: string): string[] {\n const prose = markdown\n .replace(/^[ \\t]*(```|~~~)[^\\n]*\\n[\\s\\S]*?^[ \\t]*\\1[^\\n]*$/gm, '')\n .replace(/`[^`\\n]*`/g, '');\n const links = new Set<string>();\n for (const m of prose.matchAll(\n /\\[[^\\]]*\\]\\(\\s*<?([^)\\s>]+)>?(?:\\s+\"[^\"]*\")?\\s*\\)/g\n ))\n links.add(m[1]!);\n for (const m of prose.matchAll(/https?:\\/\\/[^\\s<>)\\]\"'`]+/g))\n links.add(m[0].replace(/[.,;:!?]+$/, ''));\n return [...links];\n}\n\nexport function validateSkill(dir: string): {\n issues: Issue[];\n urls: Map<string, string>;\n} {\n const folder = dir.split(sep).pop() ?? dir;\n const issues: Issue[] = [];\n const urls = new Map<string, string>();\n const skillFile = join(dir, 'SKILL.md');\n const at = (file: string) => displayPath(join(dir, file));\n\n if (!existsSync(skillFile)) {\n issues.push({ file: at('SKILL.md'), message: 'missing SKILL.md' });\n return { issues, urls };\n }\n\n const { data, body, errors } = parseSkillFile(\n readFileSync(skillFile, 'utf8')\n );\n for (const e of errors)\n issues.push({ file: at('SKILL.md'), message: `frontmatter ${e}` });\n if (!data) {\n issues.push({\n file: at('SKILL.md'),\n message: 'no YAML frontmatter (the file must start with ---)',\n });\n } else {\n const { name, description, compatibility, metadata } = data;\n if (typeof name !== 'string' || !name) {\n issues.push({ file: at('SKILL.md'), message: '\"name\" is required' });\n } else {\n if (name.length > NAME_MAX)\n issues.push({\n file: at('SKILL.md'),\n message: `\"name\" is over ${NAME_MAX} characters`,\n });\n if (!NAME_RE.test(name)) {\n issues.push({\n file: at('SKILL.md'),\n message: `\"name\" must be lowercase letters, digits and single hyphens: \"${name}\"`,\n });\n }\n if (name !== folder)\n issues.push({\n file: at('SKILL.md'),\n message: `\"name\" (${name}) must match its folder (${folder})`,\n });\n }\n if (typeof description !== 'string' || !description.trim()) {\n issues.push({\n file: at('SKILL.md'),\n message: '\"description\" is required',\n });\n } else if (description.length > DESCRIPTION_MAX) {\n issues.push({\n file: at('SKILL.md'),\n message: `\"description\" is ${description.length} characters; the limit is ${DESCRIPTION_MAX}`,\n });\n }\n if (\n compatibility !== undefined &&\n (typeof compatibility !== 'string' ||\n compatibility.length > COMPATIBILITY_MAX)\n ) {\n issues.push({\n file: at('SKILL.md'),\n message: `\"compatibility\" must be a string of at most ${COMPATIBILITY_MAX} characters`,\n });\n }\n if (metadata !== undefined && typeof metadata !== 'object') {\n issues.push({\n file: at('SKILL.md'),\n message: '\"metadata\" must be a map of strings',\n });\n }\n for (const key of Object.keys(data)) {\n if (!SPEC_KEYS.includes(key)) {\n issues.push({\n file: at('SKILL.md'),\n message: `unknown frontmatter key \"${key}\" (allowed: ${SPEC_KEYS.join(', ')})`,\n });\n }\n }\n }\n if (!body.trim())\n issues.push({ file: at('SKILL.md'), message: 'the body is empty' });\n const lines = body.split('\\n').length;\n if (lines > BODY_MAX_LINES)\n issues.push({\n file: at('SKILL.md'),\n message: `the body has ${lines} lines; keep it under ${BODY_MAX_LINES}`,\n });\n\n for (const file of listSkillFiles(dir).filter(f => f.endsWith('.md'))) {\n const text = readFileSync(join(dir, file), 'utf8');\n for (const link of extractLinks(text)) {\n if (/^https?:\\/\\//.test(link)) {\n if (!urls.has(link)) urls.set(link, at(file));\n continue;\n }\n if (/^(mailto:|#)/.test(link)) continue;\n const target = normalize(\n join(dirname(join(dir, file)), decodeURI(link.split('#')[0]!))\n );\n if (!target.startsWith(resolve(dir) + sep)) {\n issues.push({\n file: at(file),\n message: `link \"${link}\" points outside the skill folder`,\n });\n } else if (!existsSync(target)) {\n issues.push({ file: at(file), message: `broken link \"${link}\"` });\n }\n }\n }\n return { issues, urls };\n}\n\nexport function validatePrompts(\n library: PromptLibrary,\n skillNames: string[],\n file = 'prompts.json'\n): Issue[] {\n const issues: Issue[] = [];\n const ids = new Set<string>();\n for (const [i, p] of library.prompts.entries()) {\n const where = `${file} prompts[${i}] (${p.id ?? '?'})`;\n const add = (message: string) => issues.push({ file: where, message });\n if (!p.id || !NAME_RE.test(p.id)) add('\"id\" must be kebab-case');\n if (ids.has(p.id)) add('duplicate id');\n ids.add(p.id);\n if (!p.title?.trim()) add('\"title\" is required');\n if (!library.categories[p.category])\n add(`unknown category \"${p.category}\"`);\n if (!Array.isArray(p.frameworks)) add('\"frameworks\" must be an array');\n if (!Array.isArray(p.skills) || p.skills.length === 0)\n add('\"skills\" must list at least one skill');\n for (const s of p.skills ?? [])\n if (!skillNames.includes(s)) add(`unknown skill \"${s}\"`);\n for (const ph of placeholdersOf(p.prompt ?? '')) {\n if (!(PROMPT_PLACEHOLDERS as readonly string[]).includes(ph))\n add(`unknown placeholder {{${ph}}}`);\n }\n const text = p.prompt ?? '';\n for (const s of p.skills ?? [])\n if (!text.includes(s))\n add(`the prompt should tell the assistant to read the ${s} skill`);\n if (!/\\bverify\\b/i.test(text))\n add('the prompt should end with a verification step (\"Verify: …\")');\n if (/ch_live_[A-Za-z0-9]{6,}/.test(text))\n add('the prompt contains what looks like a real server key');\n }\n for (const key of PROMPT_PLACEHOLDERS.slice(2)) {\n if (!library.versions?.[key])\n issues.push({ file, message: `versions.${key} is missing` });\n }\n return issues;\n}\n\n/**\n * A Claude Code marketplace file (`.claude-plugin/marketplace.json`):\n * required fields, and relative plugin sources that resolve to a plugin\n * whose `plugin.json` name matches the entry.\n */\nexport function validateMarketplace(root: string): Issue[] {\n const file = join(root, '.claude-plugin', 'marketplace.json');\n const issues: Issue[] = [];\n const add = (message: string) =>\n issues.push({ file: displayPath(file), message });\n let m: {\n name?: unknown;\n owner?: { name?: unknown };\n plugins?: Array<{ name?: unknown; source?: unknown }>;\n };\n try {\n m = JSON.parse(readFileSync(file, 'utf8'));\n } catch (e) {\n add(`cannot read: ${(e as Error).message}`);\n return issues;\n }\n const idPart = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;\n if (\n typeof m.name !== 'string' ||\n !idPart.test(m.name) ||\n m.name.includes('..')\n )\n add('\"name\" is missing or invalid');\n if (typeof m.owner?.name !== 'string' || !m.owner.name)\n add('\"owner.name\" is required');\n if (!Array.isArray(m.plugins) || m.plugins.length === 0)\n add('\"plugins\" must list at least one plugin');\n for (const [i, p] of (m.plugins ?? []).entries()) {\n if (typeof p.name !== 'string' || !idPart.test(p.name))\n add(`plugins[${i}].name is invalid`);\n if (typeof p.source === 'string') {\n if (!p.source.startsWith('./') && p.source !== '.')\n add(`plugins[${i}].source must start with \"./\"`);\n if (p.source.split('/').includes('..'))\n add(`plugins[${i}].source must not contain \"..\"`);\n const dir = resolve(root, p.source);\n const manifest = join(dir, '.claude-plugin', 'plugin.json');\n if (!existsSync(dir) || !statSync(dir).isDirectory()) {\n add(`plugins[${i}].source ${p.source} does not exist`);\n } else if (existsSync(manifest)) {\n const pj = JSON.parse(readFileSync(manifest, 'utf8')) as {\n name?: string;\n };\n if (pj.name !== p.name)\n add(\n `plugins[${i}].name (${String(p.name)}) differs from plugin.json name (${pj.name})`\n );\n }\n } else if (\n !p.source ||\n typeof p.source !== 'object' ||\n typeof (p.source as { source?: unknown }).source !== 'string'\n ) {\n add(`plugins[${i}].source must be a \"./\" path or a source object`);\n }\n }\n return issues;\n}\n\n/** Checks external URLs; GET after a failed HEAD, with retries for flaky hosts. */\nexport async function checkUrls(\n urls: Map<string, string>,\n { retries = 2, timeoutMs = 15000 } = {}\n): Promise<Issue[]> {\n const issues: Issue[] = [];\n const probe = async (url: string): Promise<number> => {\n // npmjs.com answers bots with 403; the registry has the same package.\n const npm = /^https:\\/\\/www\\.npmjs\\.com\\/package\\/(.+)$/.exec(url);\n if (npm) url = `https://registry.npmjs.org/${npm[1]!.replace('/', '%2F')}`;\n for (const method of ['HEAD', 'GET']) {\n const res = await fetch(url, {\n method,\n redirect: 'follow',\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (res.status < 400) return res.status;\n if (method === 'GET') return res.status;\n }\n return 0;\n };\n await Promise.all(\n [...urls].map(async ([url, file]) => {\n let last = '';\n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n const status = await probe(url);\n if (status < 400) return;\n last = `HTTP ${status}`;\n if (status < 500 && status !== 429) break;\n } catch (e) {\n last = (e as Error).message;\n }\n await new Promise(r => setTimeout(r, 1000 * (attempt + 1)));\n }\n issues.push({ file, message: `broken URL ${url} (${last})` });\n })\n );\n return issues;\n}\n\nexport function validateSkillsDir(root: string): {\n issues: Issue[];\n urls: Map<string, string>;\n count: number;\n} {\n const issues: Issue[] = [];\n const urls = new Map<string, string>();\n const folders = skillFolders(root);\n const names = new Set<string>();\n for (const folder of folders) {\n const r = validateSkill(join(root, folder));\n issues.push(...r.issues);\n for (const [u, f] of r.urls) if (!urls.has(u)) urls.set(u, f);\n if (names.has(folder))\n issues.push({ file: folder, message: 'duplicate skill name' });\n names.add(folder);\n }\n if (folders.length === 0)\n issues.push({ file: root, message: 'no skill folders found' });\n return { issues, urls, count: folders.length };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,qBAAoD;AACpD,uBAA6C;;;ACe7C,SAAS,QAAQ,KAAqB;AACpC,QAAM,IAAI,IAAI,KAAK;AACnB,MAAI,EAAE,UAAU,KAAK,EAAE,WAAW,GAAG,KAAK,EAAE,SAAS,GAAG,GAAG;AACzD,WAAO,EAAE,MAAM,GAAG,EAAE,EAAE,QAAQ,QAAQ,GAAG,EAAE,QAAQ,SAAS,IAAI;AAAA,EAClE;AACA,MAAI,EAAE,UAAU,KAAK,EAAE,WAAW,GAAG,KAAK,EAAE,SAAS,GAAG,GAAG;AACzD,WAAO,EAAE,MAAM,GAAG,EAAE,EAAE,QAAQ,OAAO,GAAG;AAAA,EAC1C;AACA,SAAO;AACT;AAEO,SAAS,eAAe,MAA+B;AAC5D,QAAM,MAAM,KAAK,QAAQ,WAAW,EAAE,EAAE,QAAQ,SAAS,IAAI;AAC7D,MAAI,CAAC,IAAI,WAAW,OAAO,GAAG;AAC5B,WAAO,EAAE,MAAM,MAAM,MAAM,KAAK,QAAQ,CAAC,EAAE;AAAA,EAC7C;AACA,QAAM,MAAM,IAAI,QAAQ,SAAS,CAAC;AAClC,MAAI,QAAQ,IAAI;AACd,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,MACN,QAAQ,CAAC,oCAAoC;AAAA,IAC/C;AAAA,EACF;AACA,QAAM,QAAQ,IAAI,MAAM,GAAG,GAAG;AAC9B,QAAM,aAAa,IAAI,QAAQ,MAAM,MAAM,CAAC;AAC5C,QAAM,OAAO,eAAe,KAAK,KAAK,IAAI,MAAM,aAAa,CAAC;AAE9D,QAAM,OAAyC,CAAC;AAChD,QAAM,SAAmB,CAAC;AAC1B,MAAI,aAA4C;AAEhD,QAAM,MAAM,IAAI,EAAE,QAAQ,CAAC,MAAM,MAAM;AACrC,UAAM,SAAS,IAAI;AACnB,QAAI,CAAC,KAAK,KAAK,KAAK,KAAK,UAAU,EAAE,WAAW,GAAG,EAAG;AAEtD,UAAM,SAAS,iCAAiC,KAAK,IAAI;AACzD,QAAI,UAAU,YAAY;AACxB,iBAAW,OAAO,CAAC,CAAE,IAAI,QAAQ,OAAO,CAAC,CAAE;AAC3C;AAAA,IACF;AAEA,UAAM,MAAM,6BAA6B,KAAK,IAAI;AAClD,QAAI,CAAC,KAAK;AACR,aAAO,KAAK,QAAQ,MAAM,yBAAyB;AACnD;AAAA,IACF;AACA,UAAM,CAAC,EAAE,KAAK,QAAQ,IAAI;AAC1B,QAAI,OAAO,KAAM,QAAO,KAAK,QAAQ,MAAM,oBAAoB,GAAG,GAAG;AACrE,QAAI,aAAa,IAAI;AACnB,mBAAa,CAAC;AACd,WAAK,GAAG,IAAI;AACZ;AAAA,IACF;AACA,iBAAa;AACb,QAAI,cAAc,KAAK,SAAS,KAAK,CAAC,GAAG;AACvC,aAAO;AAAA,QACL,QAAQ,MAAM,MAAM,GAAG;AAAA,MACzB;AACA;AAAA,IACF;AACA,QAAI,SAAS,KAAK,EAAE,WAAW,GAAG,KAAK,SAAS,KAAK,EAAE,WAAW,GAAG,GAAG;AACtE,aAAO,KAAK,QAAQ,MAAM,MAAM,GAAG,0BAA0B;AAC7D;AAAA,IACF;AACA,SAAK,GAAG,IAAI,QAAQ,QAAQ;AAAA,EAC9B,CAAC;AAED,SAAO,EAAE,MAAM,MAAM,OAAO;AAC9B;;;ADhFO,SAAS,cAAsB;AACpC,aAAO,0BAAQ,WAAW,IAAI;AAChC;AAEO,SAAS,mBAA2B;AACzC,aAAO,uBAAK,YAAY,GAAG,QAAQ;AACrC;AAEO,SAAS,iBAAyB;AACvC,QAAM,MAAM,KAAK;AAAA,QACf,iCAAa,uBAAK,YAAY,GAAG,cAAc,GAAG,MAAM;AAAA,EAC1D;AACA,SAAO,IAAI;AACb;AAkBO,SAAS,eAAe,KAAuB;AACpD,QAAM,MAAgB,CAAC;AACvB,QAAM,OAAO,CAAC,MAAc;AAC1B,eAAW,aAAS,4BAAY,CAAC,EAAE,KAAK,GAAG;AACzC,UAAI,MAAM,WAAW,GAAG,EAAG;AAC3B,YAAM,WAAO,uBAAK,GAAG,KAAK;AAC1B,cAAI,yBAAS,IAAI,EAAE,YAAY,EAAG,MAAK,IAAI;AAAA,UACtC,KAAI,SAAK,2BAAS,KAAK,IAAI,EAAE,MAAM,oBAAG,EAAE,KAAK,GAAG,CAAC;AAAA,IACxD;AAAA,EACF;AACA,OAAK,GAAG;AACR,SAAO,IAAI;AAAA,IAAK,CAAC,GAAG,MAClB,MAAM,aAAa,KAAK,MAAM,aAAa,IAAI,EAAE,cAAc,CAAC;AAAA,EAClE;AACF;AAGO,SAAS,aAAa,MAAwB;AACnD,aAAO,4BAAY,IAAI,EACpB,OAAO,OAAK,CAAC,EAAE,WAAW,GAAG,SAAK,6BAAS,uBAAK,MAAM,CAAC,CAAC,EAAE,YAAY,CAAC,EACvE,KAAK;AACV;AAEO,SAAS,WAAW,OAAe,iBAAiB,GAAY;AACrE,SAAO,aAAa,IAAI,EAAE,QAAQ,YAAU;AAC1C,UAAM,UAAM,uBAAK,MAAM,MAAM;AAC7B,QAAI;AACJ,QAAI;AACF,iBAAO,iCAAa,uBAAK,KAAK,UAAU,GAAG,MAAM;AAAA,IACnD,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AACA,UAAM,EAAE,KAAK,IAAI,eAAe,IAAI;AACpC,UAAM,OAAO,OAAO,MAAM,SAAS,WAAW,KAAK,OAAO;AAC1D,UAAM,cACJ,OAAO,MAAM,gBAAgB,WAAW,KAAK,cAAc;AAC7D,UAAM,WACJ,QAAQ,OAAO,KAAK,aAAa,WAAW,KAAK,WAAW,CAAC;AAC/D,UAAM,QAAQ,eAAe,GAAG,EAAE,IAAI,WAAS;AAAA,MAC7C;AAAA,MACA,aAAS,iCAAa,uBAAK,KAAK,IAAI,GAAG,MAAM;AAAA,IAC/C,EAAE;AACF,WAAO,CAAC,EAAE,MAAM,aAAa,KAAK,UAAU,MAAM,CAAC;AAAA,EACrD,CAAC;AACH;AAmBO,SAAS,YACd,WAAe,uBAAK,YAAY,GAAG,cAAc,GAClC;AACf,SAAO,KAAK,UAAM,6BAAa,MAAM,MAAM,CAAC;AAC9C;AAOO,IAAM,sBAAsB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,SAAS,eAAe,UAA4B;AACzD,SAAO,CAAC,GAAG,SAAS,SAAS,gCAAgC,CAAC,EAAE;AAAA,IAC9D,OAAK,EAAE,CAAC;AAAA,EACV;AACF;AAMO,SAAS,WACd,UACA,SACA,SAAuB,CAAC,GAChB;AACR,QAAM,MAA8B;AAAA,IAClC,WAAW,OAAO,aAAa;AAAA,IAC/B,WAAW,OAAO,aAAa;AAAA,IAC/B,GAAG,QAAQ;AAAA,EACb;AACA,SAAO,SAAS;AAAA,IACd;AAAA,IACA,CAAC,OAAO,QAAgB,IAAI,GAAG,KAAK;AAAA,EACtC;AACF;AAWO,SAAS,eAAwB;AACtC,SAAO;AAAA,IACL,SAAS;AAAA,IACT,SAAS,eAAe;AAAA,IACxB,QAAQ,WAAW,EAAE,IAAI,CAAC,EAAE,KAAK,MAAM,GAAG,KAAK,MAAM,IAAI;AAAA,IACzD,SAAS,YAAY;AAAA,EACvB;AACF;;;AEjKA,yBAA2B;AAC3B,IAAAA,kBAAmE;AACnE,qBAAwB;AACxB,IAAAC,oBAAuC;AAUhC,IAAM,UAAU;AAAA,EACrB,QAAQ;AAAA,IACN,OAAO;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,EACR;AAAA,EACA,QAAQ;AAAA,IACN,OAAO;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,EACR;AAAA,EACA,OAAO;AAAA,IACL,OAAO;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,EACR;AAAA,EACA,SAAS;AAAA,IACP,OAAO;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,EACR;AACF;AAGO,IAAM,aAAa,OAAO,KAAK,OAAO;AAMtC,IAAM,cAA0B,CAAC,UAAU,SAAS,SAAS;AAE7D,IAAM,WAAW;AAIjB,SAAS,UACd,QACA,OACA,MAAM,QAAQ,IAAI,GAClB,WAAO,wBAAQ,GACP;AACR,QAAM,IAAI,QAAQ,MAAM;AACxB,SAAO,UAAU,aAAS,wBAAK,MAAM,EAAE,IAAI,QAAI,2BAAQ,KAAK,EAAE,OAAO;AACvE;AAEA,IAAM,SAAS,CAAC,UAAc,+BAAW,QAAQ,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK;AAQzE,SAAS,aAAa,UAAmC;AACvD,MAAI;AACF,WAAO,KAAK;AAAA,UACV,kCAAa,wBAAK,UAAU,QAAQ,GAAG,MAAM;AAAA,IAC/C;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAkCA,eAAsB,cACpB,MACuB;AACvB,QAAM,UAAwB,CAAC;AAC/B,aAAW,SAAS,KAAK,QAAQ;AAC/B,UAAM,eAAW,wBAAK,KAAK,KAAK,MAAM,IAAI;AAC1C,UAAM,WAAW,aAAa,QAAQ;AACtC,UAAM,WAAqB;AAAA,MACzB,SAAS;AAAA,MACT,SAAS,KAAK;AAAA,MACd,OAAO,CAAC;AAAA,IACV;AAEA,eAAW,QAAQ,MAAM,OAAO;AAC9B,YAAM,WAAO,wBAAK,UAAU,KAAK,IAAI;AACrC,YAAM,OAAO,OAAO,KAAK,OAAO;AAChC,YAAM,SAAqB;AAAA,QACzB,OAAO,MAAM;AAAA,QACb,MAAM,KAAK;AAAA,QACX;AAAA,QACA,QAAQ;AAAA,MACV;AAEA,cAAI,4BAAW,IAAI,GAAG;AACpB,cAAM,UAAU,WAAO,8BAAa,MAAM,MAAM,CAAC;AACjD,cAAM,WAAW,UAAU,MAAM,KAAK,IAAI;AAC1C,YAAI,YAAY,MAAM;AACpB,iBAAO,SAAS;AAAA,QAClB,WAAW,YAAY,YAAY,UAAU;AAC3C,iBAAO,SAAS;AAAA,QAClB,WACE,KAAK,SACJ,KAAK,WACH,MAAM,KAAK,QAAQ,EAAE,GAAG,QAAQ,QAAQ,cAAc,CAAC,GAC1D;AACA,iBAAO,SAAS;AAAA,QAClB,OAAO;AACL,iBAAO,SAAS;AAAA,QAClB;AAAA,MACF;AAEA,UACE,CAAC,KAAK,WACL,OAAO,WAAW,aACjB,OAAO,WAAW,aAClB,OAAO,WAAW,gBACpB;AACA,2CAAU,2BAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,2CAAc,MAAM,KAAK,OAAO;AAAA,MAClC;AAEA,eAAS,MAAM,KAAK,IAAI,IACtB,OAAO,WAAW,SAAU,UAAU,MAAM,KAAK,IAAI,KAAK,KAAM;AAClE,cAAQ,KAAK,MAAM;AAAA,IACrB;AAEA,QAAI,CAAC,KAAK,QAAQ;AAChB,qCAAU,UAAU,EAAE,WAAW,KAAK,CAAC;AACvC;AAAA,YACE,wBAAK,UAAU,QAAQ;AAAA,QACvB,KAAK,UAAU,UAAU,MAAM,CAAC,IAAI;AAAA,MACtC;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;;;AC9KA,IAAAC,kBAAmD;AACnD,IAAAC,oBAAiE;AAe1D,IAAM,UAAU;AAChB,IAAM,WAAW;AACjB,IAAM,kBAAkB;AACxB,IAAM,oBAAoB;AAC1B,IAAM,iBAAiB;AACvB,IAAM,YAAY;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,SAAS,YAAY,GAAmB;AAC7C,QAAM,UAAM,4BAAS,QAAQ,IAAI,GAAG,CAAC;AACrC,SAAO,OAAO,CAAC,IAAI,WAAW,IAAI,IAAI,MAAM;AAC9C;AAQO,SAAS,aAAa,UAA4B;AACvD,QAAM,QAAQ,SACX,QAAQ,sDAAsD,EAAE,EAChE,QAAQ,cAAc,EAAE;AAC3B,QAAM,QAAQ,oBAAI,IAAY;AAC9B,aAAW,KAAK,MAAM;AAAA,IACpB;AAAA,EACF;AACE,UAAM,IAAI,EAAE,CAAC,CAAE;AACjB,aAAW,KAAK,MAAM,SAAS,4BAA4B;AACzD,UAAM,IAAI,EAAE,CAAC,EAAE,QAAQ,cAAc,EAAE,CAAC;AAC1C,SAAO,CAAC,GAAG,KAAK;AAClB;AAEO,SAAS,cAAc,KAG5B;AACA,QAAM,SAAS,IAAI,MAAM,qBAAG,EAAE,IAAI,KAAK;AACvC,QAAM,SAAkB,CAAC;AACzB,QAAM,OAAO,oBAAI,IAAoB;AACrC,QAAM,gBAAY,wBAAK,KAAK,UAAU;AACtC,QAAM,KAAK,CAAC,SAAiB,gBAAY,wBAAK,KAAK,IAAI,CAAC;AAExD,MAAI,KAAC,4BAAW,SAAS,GAAG;AAC1B,WAAO,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,mBAAmB,CAAC;AACjE,WAAO,EAAE,QAAQ,KAAK;AAAA,EACxB;AAEA,QAAM,EAAE,MAAM,MAAM,OAAO,IAAI;AAAA,QAC7B,8BAAa,WAAW,MAAM;AAAA,EAChC;AACA,aAAW,KAAK;AACd,WAAO,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,eAAe,CAAC,GAAG,CAAC;AACnE,MAAI,CAAC,MAAM;AACT,WAAO,KAAK;AAAA,MACV,MAAM,GAAG,UAAU;AAAA,MACnB,SAAS;AAAA,IACX,CAAC;AAAA,EACH,OAAO;AACL,UAAM,EAAE,MAAM,aAAa,eAAe,SAAS,IAAI;AACvD,QAAI,OAAO,SAAS,YAAY,CAAC,MAAM;AACrC,aAAO,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,qBAAqB,CAAC;AAAA,IACrE,OAAO;AACL,UAAI,KAAK,SAAS;AAChB,eAAO,KAAK;AAAA,UACV,MAAM,GAAG,UAAU;AAAA,UACnB,SAAS,kBAAkB,QAAQ;AAAA,QACrC,CAAC;AACH,UAAI,CAAC,QAAQ,KAAK,IAAI,GAAG;AACvB,eAAO,KAAK;AAAA,UACV,MAAM,GAAG,UAAU;AAAA,UACnB,SAAS,iEAAiE,IAAI;AAAA,QAChF,CAAC;AAAA,MACH;AACA,UAAI,SAAS;AACX,eAAO,KAAK;AAAA,UACV,MAAM,GAAG,UAAU;AAAA,UACnB,SAAS,WAAW,IAAI,4BAA4B,MAAM;AAAA,QAC5D,CAAC;AAAA,IACL;AACA,QAAI,OAAO,gBAAgB,YAAY,CAAC,YAAY,KAAK,GAAG;AAC1D,aAAO,KAAK;AAAA,QACV,MAAM,GAAG,UAAU;AAAA,QACnB,SAAS;AAAA,MACX,CAAC;AAAA,IACH,WAAW,YAAY,SAAS,iBAAiB;AAC/C,aAAO,KAAK;AAAA,QACV,MAAM,GAAG,UAAU;AAAA,QACnB,SAAS,oBAAoB,YAAY,MAAM,6BAA6B,eAAe;AAAA,MAC7F,CAAC;AAAA,IACH;AACA,QACE,kBAAkB,WACjB,OAAO,kBAAkB,YACxB,cAAc,SAAS,oBACzB;AACA,aAAO,KAAK;AAAA,QACV,MAAM,GAAG,UAAU;AAAA,QACnB,SAAS,+CAA+C,iBAAiB;AAAA,MAC3E,CAAC;AAAA,IACH;AACA,QAAI,aAAa,UAAa,OAAO,aAAa,UAAU;AAC1D,aAAO,KAAK;AAAA,QACV,MAAM,GAAG,UAAU;AAAA,QACnB,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AACA,eAAW,OAAO,OAAO,KAAK,IAAI,GAAG;AACnC,UAAI,CAAC,UAAU,SAAS,GAAG,GAAG;AAC5B,eAAO,KAAK;AAAA,UACV,MAAM,GAAG,UAAU;AAAA,UACnB,SAAS,4BAA4B,GAAG,eAAe,UAAU,KAAK,IAAI,CAAC;AAAA,QAC7E,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,KAAK,KAAK;AACb,WAAO,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,oBAAoB,CAAC;AACpE,QAAM,QAAQ,KAAK,MAAM,IAAI,EAAE;AAC/B,MAAI,QAAQ;AACV,WAAO,KAAK;AAAA,MACV,MAAM,GAAG,UAAU;AAAA,MACnB,SAAS,gBAAgB,KAAK,yBAAyB,cAAc;AAAA,IACvE,CAAC;AAEH,aAAW,QAAQ,eAAe,GAAG,EAAE,OAAO,OAAK,EAAE,SAAS,KAAK,CAAC,GAAG;AACrE,UAAM,WAAO,kCAAa,wBAAK,KAAK,IAAI,GAAG,MAAM;AACjD,eAAW,QAAQ,aAAa,IAAI,GAAG;AACrC,UAAI,eAAe,KAAK,IAAI,GAAG;AAC7B,YAAI,CAAC,KAAK,IAAI,IAAI,EAAG,MAAK,IAAI,MAAM,GAAG,IAAI,CAAC;AAC5C;AAAA,MACF;AACA,UAAI,eAAe,KAAK,IAAI,EAAG;AAC/B,YAAM,aAAS;AAAA,YACb,4BAAK,+BAAQ,wBAAK,KAAK,IAAI,CAAC,GAAG,UAAU,KAAK,MAAM,GAAG,EAAE,CAAC,CAAE,CAAC;AAAA,MAC/D;AACA,UAAI,CAAC,OAAO,eAAW,2BAAQ,GAAG,IAAI,qBAAG,GAAG;AAC1C,eAAO,KAAK;AAAA,UACV,MAAM,GAAG,IAAI;AAAA,UACb,SAAS,SAAS,IAAI;AAAA,QACxB,CAAC;AAAA,MACH,WAAW,KAAC,4BAAW,MAAM,GAAG;AAC9B,eAAO,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,gBAAgB,IAAI,IAAI,CAAC;AAAA,MAClE;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,QAAQ,KAAK;AACxB;AAEO,SAAS,gBACd,SACA,YACA,OAAO,gBACE;AACT,QAAM,SAAkB,CAAC;AACzB,QAAM,MAAM,oBAAI,IAAY;AAC5B,aAAW,CAAC,GAAG,CAAC,KAAK,QAAQ,QAAQ,QAAQ,GAAG;AAC9C,UAAM,QAAQ,GAAG,IAAI,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG;AACnD,UAAM,MAAM,CAAC,YAAoB,OAAO,KAAK,EAAE,MAAM,OAAO,QAAQ,CAAC;AACrE,QAAI,CAAC,EAAE,MAAM,CAAC,QAAQ,KAAK,EAAE,EAAE,EAAG,KAAI,yBAAyB;AAC/D,QAAI,IAAI,IAAI,EAAE,EAAE,EAAG,KAAI,cAAc;AACrC,QAAI,IAAI,EAAE,EAAE;AACZ,QAAI,CAAC,EAAE,OAAO,KAAK,EAAG,KAAI,qBAAqB;AAC/C,QAAI,CAAC,QAAQ,WAAW,EAAE,QAAQ;AAChC,UAAI,qBAAqB,EAAE,QAAQ,GAAG;AACxC,QAAI,CAAC,MAAM,QAAQ,EAAE,UAAU,EAAG,KAAI,+BAA+B;AACrE,QAAI,CAAC,MAAM,QAAQ,EAAE,MAAM,KAAK,EAAE,OAAO,WAAW;AAClD,UAAI,uCAAuC;AAC7C,eAAW,KAAK,EAAE,UAAU,CAAC;AAC3B,UAAI,CAAC,WAAW,SAAS,CAAC,EAAG,KAAI,kBAAkB,CAAC,GAAG;AACzD,eAAW,MAAM,eAAe,EAAE,UAAU,EAAE,GAAG;AAC/C,UAAI,CAAE,oBAA0C,SAAS,EAAE;AACzD,YAAI,yBAAyB,EAAE,IAAI;AAAA,IACvC;AACA,UAAM,OAAO,EAAE,UAAU;AACzB,eAAW,KAAK,EAAE,UAAU,CAAC;AAC3B,UAAI,CAAC,KAAK,SAAS,CAAC;AAClB,YAAI,oDAAoD,CAAC,QAAQ;AACrE,QAAI,CAAC,cAAc,KAAK,IAAI;AAC1B,UAAI,mEAA8D;AACpE,QAAI,0BAA0B,KAAK,IAAI;AACrC,UAAI,uDAAuD;AAAA,EAC/D;AACA,aAAW,OAAO,oBAAoB,MAAM,CAAC,GAAG;AAC9C,QAAI,CAAC,QAAQ,WAAW,GAAG;AACzB,aAAO,KAAK,EAAE,MAAM,SAAS,YAAY,GAAG,cAAc,CAAC;AAAA,EAC/D;AACA,SAAO;AACT;AAOO,SAAS,oBAAoB,MAAuB;AACzD,QAAM,WAAO,wBAAK,MAAM,kBAAkB,kBAAkB;AAC5D,QAAM,SAAkB,CAAC;AACzB,QAAM,MAAM,CAAC,YACX,OAAO,KAAK,EAAE,MAAM,YAAY,IAAI,GAAG,QAAQ,CAAC;AAClD,MAAI;AAKJ,MAAI;AACF,QAAI,KAAK,UAAM,8BAAa,MAAM,MAAM,CAAC;AAAA,EAC3C,SAAS,GAAG;AACV,QAAI,gBAAiB,EAAY,OAAO,EAAE;AAC1C,WAAO;AAAA,EACT;AACA,QAAM,SAAS;AACf,MACE,OAAO,EAAE,SAAS,YAClB,CAAC,OAAO,KAAK,EAAE,IAAI,KACnB,EAAE,KAAK,SAAS,IAAI;AAEpB,QAAI,8BAA8B;AACpC,MAAI,OAAO,EAAE,OAAO,SAAS,YAAY,CAAC,EAAE,MAAM;AAChD,QAAI,0BAA0B;AAChC,MAAI,CAAC,MAAM,QAAQ,EAAE,OAAO,KAAK,EAAE,QAAQ,WAAW;AACpD,QAAI,yCAAyC;AAC/C,aAAW,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,QAAQ,GAAG;AAChD,QAAI,OAAO,EAAE,SAAS,YAAY,CAAC,OAAO,KAAK,EAAE,IAAI;AACnD,UAAI,WAAW,CAAC,mBAAmB;AACrC,QAAI,OAAO,EAAE,WAAW,UAAU;AAChC,UAAI,CAAC,EAAE,OAAO,WAAW,IAAI,KAAK,EAAE,WAAW;AAC7C,YAAI,WAAW,CAAC,+BAA+B;AACjD,UAAI,EAAE,OAAO,MAAM,GAAG,EAAE,SAAS,IAAI;AACnC,YAAI,WAAW,CAAC,gCAAgC;AAClD,YAAM,UAAM,2BAAQ,MAAM,EAAE,MAAM;AAClC,YAAM,eAAW,wBAAK,KAAK,kBAAkB,aAAa;AAC1D,UAAI,KAAC,4BAAW,GAAG,KAAK,KAAC,0BAAS,GAAG,EAAE,YAAY,GAAG;AACpD,YAAI,WAAW,CAAC,YAAY,EAAE,MAAM,iBAAiB;AAAA,MACvD,eAAW,4BAAW,QAAQ,GAAG;AAC/B,cAAM,KAAK,KAAK,UAAM,8BAAa,UAAU,MAAM,CAAC;AAGpD,YAAI,GAAG,SAAS,EAAE;AAChB;AAAA,YACE,WAAW,CAAC,WAAW,OAAO,EAAE,IAAI,CAAC,oCAAoC,GAAG,IAAI;AAAA,UAClF;AAAA,MACJ;AAAA,IACF,WACE,CAAC,EAAE,UACH,OAAO,EAAE,WAAW,YACpB,OAAQ,EAAE,OAAgC,WAAW,UACrD;AACA,UAAI,WAAW,CAAC,iDAAiD;AAAA,IACnE;AAAA,EACF;AACA,SAAO;AACT;AA2CO,SAAS,kBAAkB,MAIhC;AACA,QAAM,SAAkB,CAAC;AACzB,QAAM,OAAO,oBAAI,IAAoB;AACrC,QAAM,UAAU,aAAa,IAAI;AACjC,QAAM,QAAQ,oBAAI,IAAY;AAC9B,aAAW,UAAU,SAAS;AAC5B,UAAM,IAAI,kBAAc,wBAAK,MAAM,MAAM,CAAC;AAC1C,WAAO,KAAK,GAAG,EAAE,MAAM;AACvB,eAAW,CAAC,GAAG,CAAC,KAAK,EAAE,KAAM,KAAI,CAAC,KAAK,IAAI,CAAC,EAAG,MAAK,IAAI,GAAG,CAAC;AAC5D,QAAI,MAAM,IAAI,MAAM;AAClB,aAAO,KAAK,EAAE,MAAM,QAAQ,SAAS,uBAAuB,CAAC;AAC/D,UAAM,IAAI,MAAM;AAAA,EAClB;AACA,MAAI,QAAQ,WAAW;AACrB,WAAO,KAAK,EAAE,MAAM,MAAM,SAAS,yBAAyB,CAAC;AAC/D,SAAO,EAAE,QAAQ,MAAM,OAAO,QAAQ,OAAO;AAC/C;","names":["import_node_fs","import_node_path","import_node_fs","import_node_path"]}
|