skillspp 0.2.0 → 1.3.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.
@@ -0,0 +1,3895 @@
1
+ // ../../packages/core/src/runtime/background-tasks.ts
2
+ import fs13 from "node:fs";
3
+ import os7 from "node:os";
4
+ import path14 from "node:path";
5
+
6
+ // ../../packages/core/src/runtime/agents.ts
7
+ import fs from "node:fs";
8
+ import os from "node:os";
9
+ import path from "node:path";
10
+ var STANDARD_AGENTS = {
11
+ universal: {
12
+ displayName: "Universal",
13
+ projectSkillsDir: ".agents/skills",
14
+ globalSkillsDir: ".agents/skills",
15
+ installMarkers: [".agents"]
16
+ },
17
+ adal: {
18
+ displayName: "AdaL",
19
+ projectSkillsDir: ".adal/skills",
20
+ globalSkillsDir: ".adal/skills",
21
+ installMarkers: [".adal"]
22
+ },
23
+ antigravity: {
24
+ displayName: "Antigravity",
25
+ projectSkillsDir: ".agent/skills",
26
+ globalSkillsDir: ".gemini/antigravity/skills",
27
+ installMarkers: [".gemini/antigravity"]
28
+ },
29
+ augment: {
30
+ displayName: "Augment",
31
+ projectSkillsDir: ".augment/skills",
32
+ globalSkillsDir: ".augment/skills",
33
+ installMarkers: [".augment"]
34
+ },
35
+ "claude-code": {
36
+ displayName: "Claude Code",
37
+ projectSkillsDir: ".claude/skills",
38
+ globalSkillsDir: ".claude/skills",
39
+ installMarkers: [".claude"]
40
+ },
41
+ "cortex-code": {
42
+ displayName: "Cortex Code",
43
+ projectSkillsDir: ".cortex/skills",
44
+ globalSkillsDir: ".cortex/skills",
45
+ installMarkers: [".cortex"]
46
+ },
47
+ crush: {
48
+ displayName: "Crush",
49
+ projectSkillsDir: ".crush/skills",
50
+ globalSkillsDir: ".crush/skills",
51
+ installMarkers: [".crush"]
52
+ },
53
+ droid: {
54
+ displayName: "Droid",
55
+ projectSkillsDir: ".factory/skills",
56
+ globalSkillsDir: ".factory/skills",
57
+ installMarkers: [".factory"]
58
+ },
59
+ goose: {
60
+ displayName: "Goose",
61
+ projectSkillsDir: ".goose/skills",
62
+ globalSkillsDir: ".config/goose/skills",
63
+ installMarkers: [".config/goose"]
64
+ },
65
+ "iflow-cli": {
66
+ displayName: "iFlow CLI",
67
+ projectSkillsDir: ".iflow/skills",
68
+ globalSkillsDir: ".iflow/skills",
69
+ installMarkers: [".iflow"]
70
+ },
71
+ junie: {
72
+ displayName: "Junie",
73
+ projectSkillsDir: ".junie/skills",
74
+ globalSkillsDir: ".junie/skills",
75
+ installMarkers: [".junie"]
76
+ },
77
+ "kiro-cli": {
78
+ displayName: "Kiro CLI",
79
+ projectSkillsDir: ".kiro/skills",
80
+ globalSkillsDir: ".kiro/skills",
81
+ installMarkers: [".kiro"]
82
+ },
83
+ kode: {
84
+ displayName: "Kode",
85
+ projectSkillsDir: ".kode/skills",
86
+ globalSkillsDir: ".kode/skills",
87
+ installMarkers: [".kode"]
88
+ },
89
+ openclaw: {
90
+ displayName: "OpenClaw",
91
+ projectSkillsDir: "skills",
92
+ globalSkillsDir: ".openclaw/skills",
93
+ installMarkers: [".openclaw"]
94
+ },
95
+ openhands: {
96
+ displayName: "OpenHands",
97
+ projectSkillsDir: ".openhands/skills",
98
+ globalSkillsDir: ".openhands/skills",
99
+ installMarkers: [".openhands"]
100
+ },
101
+ "mistral-vibe": {
102
+ displayName: "Mistral Vibe",
103
+ projectSkillsDir: ".vibe/skills",
104
+ globalSkillsDir: ".vibe/skills",
105
+ installMarkers: [".vibe"]
106
+ },
107
+ neovate: {
108
+ displayName: "Neovate",
109
+ projectSkillsDir: ".neovate/skills",
110
+ globalSkillsDir: ".neovate/skills",
111
+ installMarkers: [".neovate"]
112
+ },
113
+ pochi: {
114
+ displayName: "Pochi",
115
+ projectSkillsDir: ".pochi/skills",
116
+ globalSkillsDir: ".pochi/skills",
117
+ installMarkers: [".pochi"]
118
+ },
119
+ qoder: {
120
+ displayName: "Qoder",
121
+ projectSkillsDir: ".qoder/skills",
122
+ globalSkillsDir: ".qoder/skills",
123
+ installMarkers: [".qoder"]
124
+ },
125
+ "qwen-code": {
126
+ displayName: "Qwen Code",
127
+ projectSkillsDir: ".qwen/skills",
128
+ globalSkillsDir: ".qwen/skills",
129
+ installMarkers: [".qwen"]
130
+ },
131
+ roo: {
132
+ displayName: "Roo Code",
133
+ projectSkillsDir: ".roo/skills",
134
+ globalSkillsDir: ".roo/skills",
135
+ installMarkers: [".roo"]
136
+ },
137
+ trae: {
138
+ displayName: "Trae",
139
+ projectSkillsDir: ".trae/skills",
140
+ globalSkillsDir: ".trae/skills",
141
+ installMarkers: [".trae"]
142
+ },
143
+ "trae-cn": {
144
+ displayName: "Trae CN",
145
+ projectSkillsDir: ".trae/skills",
146
+ globalSkillsDir: ".trae/skills",
147
+ installMarkers: [".trae"]
148
+ },
149
+ windsurf: {
150
+ displayName: "Windsurf",
151
+ projectSkillsDir: ".windsurf/skills",
152
+ globalSkillsDir: ".codeium/windsurf/skills",
153
+ installMarkers: [".windsurf", ".codeium/windsurf"]
154
+ },
155
+ zencoder: {
156
+ displayName: "Zencoder",
157
+ projectSkillsDir: ".zencoder/skills",
158
+ globalSkillsDir: ".zencoder/skills",
159
+ installMarkers: [".zencoder"]
160
+ },
161
+ continue: {
162
+ displayName: "Continue",
163
+ projectSkillsDir: ".continue/skills",
164
+ globalSkillsDir: ".continue/skills",
165
+ installMarkers: [".continue"]
166
+ },
167
+ codebuddy: {
168
+ displayName: "CodeBuddy",
169
+ projectSkillsDir: ".codebuddy/skills",
170
+ globalSkillsDir: ".codebuddy/skills",
171
+ installMarkers: [".codebuddy"]
172
+ },
173
+ "command-code": {
174
+ displayName: "Command Code",
175
+ projectSkillsDir: ".commandcode/skills",
176
+ globalSkillsDir: ".commandcode/skills",
177
+ installMarkers: [".commandcode"]
178
+ },
179
+ kilo: {
180
+ displayName: "Kilo Code",
181
+ projectSkillsDir: ".kilocode/skills",
182
+ globalSkillsDir: ".kilocode/skills",
183
+ installMarkers: [".kilocode"]
184
+ },
185
+ mcpjam: {
186
+ displayName: "MCPJam",
187
+ projectSkillsDir: ".mcpjam/skills",
188
+ globalSkillsDir: ".mcpjam/skills",
189
+ installMarkers: [".mcpjam"]
190
+ },
191
+ mux: {
192
+ displayName: "Mux",
193
+ projectSkillsDir: ".mux/skills",
194
+ globalSkillsDir: ".mux/skills",
195
+ installMarkers: [".mux"]
196
+ },
197
+ pi: {
198
+ displayName: "Pi",
199
+ projectSkillsDir: ".pi/skills",
200
+ globalSkillsDir: ".pi/agent/skills",
201
+ installMarkers: [".pi"]
202
+ },
203
+ replit: {
204
+ displayName: "Replit",
205
+ projectSkillsDir: ".agents/skills",
206
+ globalSkillsDir: ".config/agents/skills",
207
+ installMarkers: [".config/agents"]
208
+ }
209
+ };
210
+ var AGENTS = {
211
+ ...STANDARD_AGENTS,
212
+ codex: {
213
+ displayName: "Codex",
214
+ projectSkillsDir: ".agents/skills",
215
+ globalSkillsDir: ".codex/skills",
216
+ installMarkers: [".codex"]
217
+ },
218
+ cursor: {
219
+ displayName: "Cursor",
220
+ projectSkillsDir: ".agents/skills",
221
+ globalSkillsDir: ".cursor/skills",
222
+ installMarkers: [".cursor"]
223
+ },
224
+ "gemini-cli": {
225
+ displayName: "Gemini CLI",
226
+ projectSkillsDir: ".agents/skills",
227
+ globalSkillsDir: ".gemini/skills",
228
+ installMarkers: [".gemini"]
229
+ },
230
+ "github-copilot": {
231
+ displayName: "GitHub Copilot",
232
+ projectSkillsDir: ".agents/skills",
233
+ globalSkillsDir: ".copilot/skills",
234
+ installMarkers: [".copilot"]
235
+ },
236
+ amp: {
237
+ displayName: "Amp",
238
+ projectSkillsDir: ".agents/skills",
239
+ globalSkillsDir: ".config/agents/skills",
240
+ installMarkers: [".config/agents"]
241
+ },
242
+ opencode: {
243
+ displayName: "OpenCode",
244
+ projectSkillsDir: ".agents/skills",
245
+ globalSkillsDir: ".config/opencode/skills",
246
+ installMarkers: [".config/opencode"]
247
+ },
248
+ windsurf: {
249
+ displayName: "Windsurf",
250
+ projectSkillsDir: ".windsurf/skills",
251
+ globalSkillsDir: ".codeium/windsurf/skills",
252
+ installMarkers: [".windsurf", ".codeium/windsurf"]
253
+ },
254
+ cline: {
255
+ displayName: "Cline",
256
+ projectSkillsDir: ".cline/skills",
257
+ globalSkillsDir: ".cline/skills",
258
+ installMarkers: [".cline"]
259
+ }
260
+ };
261
+ var ALL_AGENTS = Object.keys(AGENTS);
262
+ function isAgent(value) {
263
+ return Object.prototype.hasOwnProperty.call(AGENTS, value);
264
+ }
265
+ function resolveAgents(input) {
266
+ if (!input || input.length === 0) {
267
+ const detected = detectInstalledAgents();
268
+ return detected.length > 0 ? detected : ["opencode", "codex"];
269
+ }
270
+ if (input.includes("*")) {
271
+ const detected = detectInstalledAgents();
272
+ return detected.length > 0 ? detected : ALL_AGENTS;
273
+ }
274
+ const out = [];
275
+ for (const value of input) {
276
+ if (!isAgent(value)) {
277
+ throw new Error(`Unknown agent: ${value}`);
278
+ }
279
+ if (!out.includes(value)) {
280
+ out.push(value);
281
+ }
282
+ }
283
+ return out;
284
+ }
285
+ function getAgentSkillsDir(agent, globalInstall, cwd) {
286
+ const relative = globalInstall ? AGENTS[agent].globalSkillsDir : AGENTS[agent].projectSkillsDir;
287
+ const base = globalInstall ? os.homedir() : cwd;
288
+ return path.join(base, relative);
289
+ }
290
+ function detectInstalledAgents(cwd = process.cwd()) {
291
+ const found = [];
292
+ for (const agent of Object.keys(AGENTS)) {
293
+ if (isAgentInstalled(agent, cwd)) {
294
+ found.push(agent);
295
+ }
296
+ }
297
+ return found;
298
+ }
299
+ function filterInstalledAgents(agents, cwd = process.cwd()) {
300
+ return agents.filter((agent) => isAgentInstalled(agent, cwd));
301
+ }
302
+ function isAgentInstalled(agent, cwd) {
303
+ const info = AGENTS[agent];
304
+ const home = os.homedir();
305
+ if (info.installMarkers) {
306
+ for (const marker of info.installMarkers) {
307
+ if (fs.existsSync(path.join(home, marker))) {
308
+ return true;
309
+ }
310
+ }
311
+ }
312
+ const projectSkillsDir = getAgentSkillsDir(agent, false, cwd);
313
+ if (info.projectSkillsDir !== ".agents/skills" && fs.existsSync(projectSkillsDir)) {
314
+ return true;
315
+ }
316
+ const globalSkillsDir = getAgentSkillsDir(agent, true, cwd);
317
+ if (info.globalSkillsDir !== ".config/agents/skills" && info.globalSkillsDir !== ".agents/skills" && fs.existsSync(globalSkillsDir)) {
318
+ return true;
319
+ }
320
+ return false;
321
+ }
322
+
323
+ // ../../packages/core/src/runtime/check-analysis.ts
324
+ import fs6 from "node:fs";
325
+ import path7 from "node:path";
326
+
327
+ // ../../packages/core/src/sources/skills.ts
328
+ import fs2 from "node:fs";
329
+ import os2 from "node:os";
330
+ import path2 from "node:path";
331
+ import matter from "gray-matter";
332
+ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", "__pycache__"]);
333
+ function resolveSourceLabel(parsedSource) {
334
+ switch (parsedSource.type) {
335
+ case "local":
336
+ return parsedSource.localPath;
337
+ case "github":
338
+ case "git":
339
+ return parsedSource.repoUrl;
340
+ case "well-known":
341
+ case "catalog":
342
+ return parsedSource.url;
343
+ default:
344
+ return "";
345
+ }
346
+ }
347
+ function stageRemoteSkillFilesToTempDir(files, options) {
348
+ const tmp = fs2.mkdtempSync(path2.join(os2.tmpdir(), options?.prefix || "skillspp-remote-"));
349
+ try {
350
+ for (const [relativePath, content] of files.entries()) {
351
+ const resolved = path2.resolve(tmp, relativePath);
352
+ const rel = path2.relative(tmp, resolved);
353
+ if (!rel || rel.startsWith("..") || path2.isAbsolute(rel)) {
354
+ throw new Error(`Unsafe remote skill file path: ${relativePath}`);
355
+ }
356
+ fs2.mkdirSync(path2.dirname(resolved), { recursive: true });
357
+ fs2.writeFileSync(resolved, content, "utf8");
358
+ }
359
+ } catch (error) {
360
+ fs2.rmSync(tmp, { recursive: true, force: true });
361
+ throw error;
362
+ }
363
+ return {
364
+ path: tmp,
365
+ cleanup: () => fs2.rmSync(tmp, { recursive: true, force: true })
366
+ };
367
+ }
368
+ function hasSkillMd(dir) {
369
+ const skillPath = path2.join(dir, "SKILL.md");
370
+ return fs2.existsSync(skillPath) && fs2.statSync(skillPath).isFile();
371
+ }
372
+ async function hasSkillMdAsync(dir) {
373
+ const skillPath = path2.join(dir, "SKILL.md");
374
+ try {
375
+ const stat = await fs2.promises.stat(skillPath);
376
+ return stat.isFile();
377
+ } catch {
378
+ return false;
379
+ }
380
+ }
381
+ function parseSkillMd(skillMdPath) {
382
+ try {
383
+ const raw = fs2.readFileSync(skillMdPath, "utf8");
384
+ const { data } = matter(raw);
385
+ if (typeof data.name !== "string" || typeof data.description !== "string") {
386
+ return null;
387
+ }
388
+ return {
389
+ name: data.name,
390
+ description: data.description,
391
+ path: path2.dirname(skillMdPath)
392
+ };
393
+ } catch {
394
+ return null;
395
+ }
396
+ }
397
+ async function parseSkillMdAsync(skillMdPath) {
398
+ try {
399
+ const raw = await fs2.promises.readFile(skillMdPath, "utf8");
400
+ const { data } = matter(raw);
401
+ if (typeof data.name !== "string" || typeof data.description !== "string") {
402
+ return null;
403
+ }
404
+ return {
405
+ name: data.name,
406
+ description: data.description,
407
+ path: path2.dirname(skillMdPath)
408
+ };
409
+ } catch {
410
+ return null;
411
+ }
412
+ }
413
+ function findSkillDirsRecursive(dir, depth, maxDepth, out) {
414
+ if (depth > maxDepth) {
415
+ return;
416
+ }
417
+ if (!fs2.existsSync(dir) || !fs2.statSync(dir).isDirectory()) {
418
+ return;
419
+ }
420
+ if (hasSkillMd(dir)) {
421
+ out.push(dir);
422
+ }
423
+ for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
424
+ if (!entry.isDirectory() || SKIP_DIRS.has(entry.name)) {
425
+ continue;
426
+ }
427
+ findSkillDirsRecursive(path2.join(dir, entry.name), depth + 1, maxDepth, out);
428
+ }
429
+ }
430
+ async function findSkillDirsRecursiveAsync(dir, depth, maxDepth, out) {
431
+ if (depth > maxDepth) {
432
+ return;
433
+ }
434
+ try {
435
+ const stat = await fs2.promises.stat(dir);
436
+ if (!stat.isDirectory()) {
437
+ return;
438
+ }
439
+ } catch {
440
+ return;
441
+ }
442
+ if (await hasSkillMdAsync(dir)) {
443
+ out.push(dir);
444
+ }
445
+ let entries = [];
446
+ try {
447
+ entries = await fs2.promises.readdir(dir, { withFileTypes: true });
448
+ } catch {
449
+ return;
450
+ }
451
+ for (const entry of entries) {
452
+ if (!entry.isDirectory() || SKIP_DIRS.has(entry.name)) {
453
+ continue;
454
+ }
455
+ await findSkillDirsRecursiveAsync(path2.join(dir, entry.name), depth + 1, maxDepth, out);
456
+ }
457
+ }
458
+ function discoverSkills(basePath) {
459
+ const dirsToSearch = [
460
+ basePath,
461
+ path2.join(basePath, "skills"),
462
+ path2.join(basePath, "skills", ".curated"),
463
+ path2.join(basePath, "skills", ".experimental"),
464
+ path2.join(basePath, "skills", ".system"),
465
+ path2.join(basePath, ".agents", "skills"),
466
+ path2.join(basePath, ".agent", "skills")
467
+ ];
468
+ const skillDirs = [];
469
+ for (const dir of dirsToSearch) {
470
+ if (!fs2.existsSync(dir) || !fs2.statSync(dir).isDirectory()) {
471
+ continue;
472
+ }
473
+ for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
474
+ if (!entry.isDirectory()) {
475
+ continue;
476
+ }
477
+ const candidate = path2.join(dir, entry.name);
478
+ if (hasSkillMd(candidate)) {
479
+ skillDirs.push(candidate);
480
+ }
481
+ }
482
+ }
483
+ if (hasSkillMd(basePath)) {
484
+ skillDirs.unshift(basePath);
485
+ }
486
+ if (skillDirs.length === 0) {
487
+ findSkillDirsRecursive(basePath, 0, 5, skillDirs);
488
+ }
489
+ const seen = /* @__PURE__ */ new Set();
490
+ const skills = [];
491
+ for (const dir of skillDirs) {
492
+ const parsed = parseSkillMd(path2.join(dir, "SKILL.md"));
493
+ if (!parsed) {
494
+ continue;
495
+ }
496
+ if (seen.has(parsed.name)) {
497
+ continue;
498
+ }
499
+ seen.add(parsed.name);
500
+ skills.push(parsed);
501
+ }
502
+ return skills;
503
+ }
504
+ async function discoverSkillsAsync(basePath) {
505
+ const dirsToSearch = [
506
+ basePath,
507
+ path2.join(basePath, "skills"),
508
+ path2.join(basePath, "skills", ".curated"),
509
+ path2.join(basePath, "skills", ".experimental"),
510
+ path2.join(basePath, "skills", ".system"),
511
+ path2.join(basePath, ".agents", "skills"),
512
+ path2.join(basePath, ".agent", "skills")
513
+ ];
514
+ const skillDirs = [];
515
+ for (const dir of dirsToSearch) {
516
+ let stat;
517
+ try {
518
+ stat = await fs2.promises.stat(dir);
519
+ } catch {
520
+ stat = void 0;
521
+ }
522
+ if (!stat || !stat.isDirectory()) {
523
+ continue;
524
+ }
525
+ let entries = [];
526
+ try {
527
+ entries = await fs2.promises.readdir(dir, { withFileTypes: true });
528
+ } catch {
529
+ entries = [];
530
+ }
531
+ for (const entry of entries) {
532
+ if (!entry.isDirectory()) {
533
+ continue;
534
+ }
535
+ const candidate = path2.join(dir, entry.name);
536
+ if (await hasSkillMdAsync(candidate)) {
537
+ skillDirs.push(candidate);
538
+ }
539
+ }
540
+ }
541
+ if (await hasSkillMdAsync(basePath)) {
542
+ skillDirs.unshift(basePath);
543
+ }
544
+ if (skillDirs.length === 0) {
545
+ await findSkillDirsRecursiveAsync(basePath, 0, 5, skillDirs);
546
+ }
547
+ const seen = /* @__PURE__ */ new Set();
548
+ const skills = [];
549
+ for (const dir of skillDirs) {
550
+ const parsed = await parseSkillMdAsync(path2.join(dir, "SKILL.md"));
551
+ if (!parsed) {
552
+ continue;
553
+ }
554
+ if (seen.has(parsed.name)) {
555
+ continue;
556
+ }
557
+ seen.add(parsed.name);
558
+ skills.push(parsed);
559
+ }
560
+ return skills;
561
+ }
562
+
563
+ // ../../packages/core/src/sources/source-parser.ts
564
+ import path3 from "node:path";
565
+ function isLocalPath(input) {
566
+ return path3.isAbsolute(input) || input === "." || input === ".." || input.startsWith("./") || input.startsWith("../") || /^[a-zA-Z]:[\\/]/.test(input);
567
+ }
568
+ function parseSource(input) {
569
+ if (input.startsWith("catalog+https://")) {
570
+ return { type: "catalog", url: input.slice("catalog+".length) };
571
+ }
572
+ if (isLocalPath(input)) {
573
+ return { type: "local", localPath: path3.resolve(input) };
574
+ }
575
+ const githubTreeWithPath = input.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)/);
576
+ if (githubTreeWithPath) {
577
+ const [, owner, repo, ref, subpath] = githubTreeWithPath;
578
+ return {
579
+ type: "github",
580
+ repoUrl: `https://github.com/${owner}/${repo.replace(/\.git$/, "")}.git`,
581
+ ref,
582
+ subpath
583
+ };
584
+ }
585
+ const githubTree = input.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)$/);
586
+ if (githubTree) {
587
+ const [, owner, repo, ref] = githubTree;
588
+ return {
589
+ type: "github",
590
+ repoUrl: `https://github.com/${owner}/${repo.replace(/\.git$/, "")}.git`,
591
+ ref
592
+ };
593
+ }
594
+ const githubRepo = input.match(/github\.com\/([^/]+)\/([^/]+)/);
595
+ if (githubRepo) {
596
+ const [, owner, repo] = githubRepo;
597
+ return {
598
+ type: "github",
599
+ repoUrl: `https://github.com/${owner}/${repo.replace(/\.git$/, "")}.git`
600
+ };
601
+ }
602
+ const gitlabRepo = input.match(/gitlab\.com\/([^/]+)\/([^/]+)/);
603
+ if (gitlabRepo) {
604
+ const [, owner, repo] = gitlabRepo;
605
+ return {
606
+ type: "git",
607
+ repoUrl: `https://gitlab.com/${owner}/${repo.replace(/\.git$/, "")}.git`
608
+ };
609
+ }
610
+ const shorthand = input.match(/^([^/]+)\/([^/]+)(?:\/(.+))?$/);
611
+ if (shorthand && !input.includes(":") && !input.startsWith(".")) {
612
+ const [, owner, repo, subpath] = shorthand;
613
+ return {
614
+ type: "github",
615
+ repoUrl: `https://github.com/${owner}/${repo.replace(/\.git$/, "")}.git`,
616
+ subpath
617
+ };
618
+ }
619
+ if (input.startsWith("http://") || input.startsWith("https://")) {
620
+ if (input.endsWith(".git")) {
621
+ return { type: "git", repoUrl: input };
622
+ }
623
+ return { type: "well-known", url: input };
624
+ }
625
+ return { type: "git", repoUrl: input };
626
+ }
627
+
628
+ // ../../packages/core/src/sources/git.ts
629
+ import fs3 from "node:fs";
630
+ import os3 from "node:os";
631
+ import path4 from "node:path";
632
+ import { spawn, spawnSync } from "node:child_process";
633
+ function runGit(args, cwd) {
634
+ const result = spawnSync("git", args, {
635
+ cwd,
636
+ encoding: "utf8",
637
+ stdio: "pipe"
638
+ });
639
+ if (result.status !== 0) {
640
+ const stderr = (result.stderr || "").trim();
641
+ const stdout = (result.stdout || "").trim();
642
+ const detail = stderr || stdout || "git command failed";
643
+ throw new Error(`${detail}`);
644
+ }
645
+ }
646
+ function runGitAsync(args, cwd) {
647
+ return new Promise((resolve, reject) => {
648
+ const child = spawn("git", args, {
649
+ cwd,
650
+ stdio: ["ignore", "pipe", "pipe"]
651
+ });
652
+ let stdout = "";
653
+ let stderr = "";
654
+ child.stdout.on("data", (chunk) => {
655
+ stdout += chunk.toString();
656
+ });
657
+ child.stderr.on("data", (chunk) => {
658
+ stderr += chunk.toString();
659
+ });
660
+ child.on("error", (error) => {
661
+ reject(error);
662
+ });
663
+ child.on("close", (code) => {
664
+ if (code === 0) {
665
+ resolve();
666
+ return;
667
+ }
668
+ const detail = stderr.trim() || stdout.trim() || "git command failed";
669
+ reject(new Error(detail));
670
+ });
671
+ });
672
+ }
673
+ function runGitOutputAsync(args, cwd) {
674
+ return new Promise((resolve, reject) => {
675
+ const child = spawn("git", args, {
676
+ cwd,
677
+ stdio: ["ignore", "pipe", "pipe"]
678
+ });
679
+ let stdout = "";
680
+ let stderr = "";
681
+ child.stdout.on("data", (chunk) => {
682
+ stdout += chunk.toString();
683
+ });
684
+ child.stderr.on("data", (chunk) => {
685
+ stderr += chunk.toString();
686
+ });
687
+ child.on("error", (error) => {
688
+ reject(error);
689
+ });
690
+ child.on("close", (code) => {
691
+ if (code === 0) {
692
+ resolve(stdout.trim());
693
+ return;
694
+ }
695
+ const detail = stderr.trim() || stdout.trim() || "git command failed";
696
+ reject(new Error(detail));
697
+ });
698
+ });
699
+ }
700
+ function applyCheckoutRefSync(repoDir, ref) {
701
+ if (!ref) {
702
+ return;
703
+ }
704
+ runGit(["fetch", "--depth", "1", "origin", ref], repoDir);
705
+ runGit(["checkout", ref], repoDir);
706
+ }
707
+ async function applyCheckoutRefAsync(repoDir, ref) {
708
+ if (!ref) {
709
+ return;
710
+ }
711
+ await runGitAsync(["fetch", "--depth", "1", "origin", ref], repoDir);
712
+ await runGitAsync(["checkout", ref], repoDir);
713
+ }
714
+ function prepareSourceDir(parsed) {
715
+ if (parsed.type === "local") {
716
+ if (!fs3.existsSync(parsed.localPath)) {
717
+ throw new Error(`Local source not found: ${parsed.localPath}`);
718
+ }
719
+ return { basePath: parsed.localPath };
720
+ }
721
+ const tmp = fs3.mkdtempSync(path4.join(os3.tmpdir(), "skillspp-cli-"));
722
+ runGit(["clone", "--depth", "1", parsed.repoUrl, tmp]);
723
+ const ref = parsed.type === "github" ? parsed.ref : void 0;
724
+ applyCheckoutRefSync(tmp, ref);
725
+ const basePath = parsed.type === "github" && parsed.subpath ? path4.join(tmp, parsed.subpath) : tmp;
726
+ return {
727
+ basePath,
728
+ cleanup: () => {
729
+ fs3.rmSync(tmp, { recursive: true, force: true });
730
+ }
731
+ };
732
+ }
733
+ async function prepareSourceDirAsync(parsed) {
734
+ if (parsed.type === "local") {
735
+ if (!fs3.existsSync(parsed.localPath)) {
736
+ throw new Error(`Local source not found: ${parsed.localPath}`);
737
+ }
738
+ return { basePath: parsed.localPath };
739
+ }
740
+ const tmp = fs3.mkdtempSync(path4.join(os3.tmpdir(), "skillspp-cli-"));
741
+ await runGitAsync(["clone", "--depth", "1", parsed.repoUrl, tmp]);
742
+ const ref = parsed.type === "github" ? parsed.ref : void 0;
743
+ await applyCheckoutRefAsync(tmp, ref);
744
+ const basePath = parsed.type === "github" && parsed.subpath ? path4.join(tmp, parsed.subpath) : tmp;
745
+ return {
746
+ basePath,
747
+ cleanup: () => {
748
+ fs3.rmSync(tmp, { recursive: true, force: true });
749
+ }
750
+ };
751
+ }
752
+ async function resolveGitHeadRefAsync(repoDir) {
753
+ return runGitOutputAsync(["rev-parse", "HEAD"], repoDir);
754
+ }
755
+
756
+ // ../../packages/core/src/providers/registry.ts
757
+ var ProviderRegistry = class {
758
+ providers = [];
759
+ register(provider) {
760
+ if (this.providers.some((item) => item.id === provider.id)) {
761
+ throw new Error(`Provider with id '${provider.id}' already registered`);
762
+ }
763
+ this.providers.push(provider);
764
+ }
765
+ findProvider(url) {
766
+ for (const provider of this.providers) {
767
+ if (provider.match(url).matches) {
768
+ return provider;
769
+ }
770
+ }
771
+ return null;
772
+ }
773
+ getProviders() {
774
+ return [...this.providers];
775
+ }
776
+ getProviderById(id) {
777
+ return this.providers.find((item) => item.id === id) || null;
778
+ }
779
+ };
780
+ var registry = new ProviderRegistry();
781
+ function registerProvider(provider) {
782
+ registry.register(provider);
783
+ }
784
+ function getProviderById(id) {
785
+ return registry.getProviderById(id);
786
+ }
787
+
788
+ // ../../packages/core/src/providers/wellknown.ts
789
+ import dns from "node:dns/promises";
790
+ import net from "node:net";
791
+ var DEFAULT_OPTIONS = {
792
+ maxDownloadBytes: 5 * 1024 * 1024,
793
+ timeoutMs: 1e4,
794
+ maxRedirects: 3,
795
+ maxFilesPerSkill: 128,
796
+ maxSkillFileBytes: 512 * 1024
797
+ };
798
+ var EXCLUDED_HOSTS = /* @__PURE__ */ new Set(["github.com", "gitlab.com", "raw.githubusercontent.com"]);
799
+ var SKILL_CONFIG = {
800
+ kind: "skills",
801
+ displayLabel: "well-known skills",
802
+ indexPath: "/.well-known/skills/index.json",
803
+ entryLabel: "skill",
804
+ requireDescription: true,
805
+ missingManifestMessage: (name) => `Well-known skill '${name}' is missing SKILL.md`,
806
+ validateName(name) {
807
+ if (!/^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$/.test(name)) {
808
+ throw new Error(`Invalid well-known skill name: ${name}`);
809
+ }
810
+ },
811
+ hasRequiredManifest(filePath) {
812
+ return filePath.toLowerCase() === "skill.md";
813
+ },
814
+ buildRemoteResult({ entry, files, sourceUrl }) {
815
+ return {
816
+ name: entry.name,
817
+ description: entry.description || "",
818
+ installName: entry.name,
819
+ sourceUrl,
820
+ sourceType: "well-known",
821
+ files
822
+ };
823
+ }
824
+ };
825
+ var SecureWellKnownProvider = class {
826
+ id = "well-known";
827
+ displayName = "Secure Well-Known Skills";
828
+ match(url) {
829
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
830
+ return { matches: false };
831
+ }
832
+ try {
833
+ const parsed = new URL(url);
834
+ if (EXCLUDED_HOSTS.has(parsed.hostname.toLowerCase())) {
835
+ return { matches: false };
836
+ }
837
+ return { matches: true, sourceIdentifier: this.getSourceIdentifier(url) };
838
+ } catch {
839
+ return { matches: false };
840
+ }
841
+ }
842
+ getSourceIdentifier(url) {
843
+ const parsed = new URL(url);
844
+ const pathname = parsed.pathname.replace(/\/$/, "");
845
+ return pathname && pathname !== "/" ? `wellknown/${parsed.hostname}${pathname}` : `wellknown/${parsed.hostname}`;
846
+ }
847
+ async fetchAllSkills(url, options = {}) {
848
+ return this.fetchAllResources(url, options, SKILL_CONFIG);
849
+ }
850
+ async fetchAllResources(url, options, config) {
851
+ const normalized = this.normalizeOptions(options);
852
+ const budget = { remaining: normalized.maxDownloadBytes };
853
+ const parsed = new URL(url);
854
+ if (parsed.protocol !== "https:") {
855
+ throw new Error("Well-known provider requires HTTPS URLs");
856
+ }
857
+ await this.assertHostAllowed(parsed.hostname, normalized);
858
+ const { index, resolvedBase } = await this.fetchIndex(parsed, normalized, budget, config);
859
+ const resources = [];
860
+ for (const entry of index) {
861
+ resources.push(
862
+ await this.fetchResourceByEntry(resolvedBase, entry, normalized, budget, config)
863
+ );
864
+ }
865
+ return resources;
866
+ }
867
+ normalizeOptions(options) {
868
+ return {
869
+ allowHosts: (options.allowHosts || []).map((value) => value.trim().toLowerCase()).filter(Boolean),
870
+ denyHosts: (options.denyHosts || []).map((value) => value.trim().toLowerCase()).filter(Boolean),
871
+ maxDownloadBytes: options.maxDownloadBytes ?? DEFAULT_OPTIONS.maxDownloadBytes,
872
+ timeoutMs: options.timeoutMs ?? DEFAULT_OPTIONS.timeoutMs,
873
+ maxRedirects: options.maxRedirects ?? DEFAULT_OPTIONS.maxRedirects,
874
+ maxFilesPerSkill: options.maxFilesPerSkill ?? DEFAULT_OPTIONS.maxFilesPerSkill,
875
+ maxSkillFileBytes: options.maxSkillFileBytes ?? DEFAULT_OPTIONS.maxSkillFileBytes
876
+ };
877
+ }
878
+ async fetchIndex(parsedUrl, options, budget, config) {
879
+ const candidates = this.buildBaseCandidates(parsedUrl, config.indexPath);
880
+ for (const base of candidates) {
881
+ const indexUrl = `${base}${config.indexPath}`;
882
+ try {
883
+ const jsonText = await this.fetchTextWithLimit(
884
+ indexUrl,
885
+ options.maxDownloadBytes,
886
+ options,
887
+ budget
888
+ );
889
+ const parsed = JSON.parse(jsonText);
890
+ const validated = this.validateIndex(parsed, options.maxFilesPerSkill, config);
891
+ return { index: validated, resolvedBase: base };
892
+ } catch {
893
+ continue;
894
+ }
895
+ }
896
+ throw new Error(`No valid ${config.displayLabel} index found at ${config.indexPath}`);
897
+ }
898
+ buildBaseCandidates(parsed, indexPath) {
899
+ const origin = parsed.origin;
900
+ const pathname = parsed.pathname.replace(/\/$/, "");
901
+ const marker = indexPath.replace(/\/index\.json$/, "");
902
+ const out = [];
903
+ if (pathname.includes(marker)) {
904
+ const prefix = pathname.slice(0, pathname.indexOf(marker));
905
+ out.push(`${origin}${prefix}`.replace(/\/$/, ""));
906
+ if (prefix !== "") {
907
+ out.push(origin);
908
+ }
909
+ } else {
910
+ out.push(`${origin}${pathname}`.replace(/\/$/, ""));
911
+ if (pathname !== "") {
912
+ out.push(origin);
913
+ }
914
+ }
915
+ return [
916
+ ...new Set(out.map((value) => value.endsWith("/") ? value.slice(0, -1) : value))
917
+ ].filter(Boolean);
918
+ }
919
+ validateIndex(raw, maxFilesPerSkill, config) {
920
+ if (!raw || typeof raw !== "object") {
921
+ throw new Error("Invalid well-known index: expected object");
922
+ }
923
+ const data = raw;
924
+ const rows = data[config.kind];
925
+ if (!Array.isArray(rows)) {
926
+ throw new Error(`Invalid well-known index: '${config.kind}' must be an array`);
927
+ }
928
+ return rows.map((item, idx) => {
929
+ if (!item || typeof item !== "object") {
930
+ throw new Error(`Invalid well-known index entry[${idx}]`);
931
+ }
932
+ const row = item;
933
+ const name = String(row.name || "").trim();
934
+ const description = typeof row.description === "string" ? row.description.trim() : void 0;
935
+ const files = Array.isArray(row.files) ? row.files.map((value) => String(value)) : [];
936
+ if (!name || files.length === 0) {
937
+ throw new Error(`Invalid well-known index entry[${idx}]: missing required fields`);
938
+ }
939
+ if (config.requireDescription && !description) {
940
+ throw new Error(`Invalid well-known index entry[${idx}]: missing required fields`);
941
+ }
942
+ config.validateName?.(name);
943
+ if (files.length > maxFilesPerSkill) {
944
+ throw new Error(`Too many files in well-known ${config.entryLabel} '${name}'`);
945
+ }
946
+ if (!files.some((filePath) => config.hasRequiredManifest(filePath))) {
947
+ throw new Error(config.missingManifestMessage(name));
948
+ }
949
+ for (const file of files) {
950
+ this.assertSafeRelativePath(file);
951
+ }
952
+ return { name, description, files };
953
+ });
954
+ }
955
+ assertSafeRelativePath(filePath) {
956
+ if (!filePath || filePath.startsWith("/") || filePath.startsWith("\\") || filePath.includes("..") || filePath.includes("\\")) {
957
+ throw new Error(`Unsafe well-known file path: ${filePath}`);
958
+ }
959
+ }
960
+ async fetchResourceByEntry(resolvedBase, entry, options, budget, config) {
961
+ const baseUrl = `${resolvedBase}/.well-known/${config.kind}/${entry.name}`;
962
+ const files = /* @__PURE__ */ new Map();
963
+ for (const filePath of entry.files) {
964
+ this.assertSafeRelativePath(filePath);
965
+ const fileUrl = `${baseUrl}/${filePath}`;
966
+ const text = await this.fetchTextWithLimit(
967
+ fileUrl,
968
+ options.maxSkillFileBytes,
969
+ options,
970
+ budget
971
+ );
972
+ if (text.includes("\0")) {
973
+ throw new Error(`Binary content is not allowed in well-known file: ${filePath}`);
974
+ }
975
+ files.set(filePath, text);
976
+ }
977
+ const manifestPath = this.pickPrimaryManifestPath(entry.files, config);
978
+ return config.buildRemoteResult({
979
+ entry,
980
+ files,
981
+ sourceUrl: `${baseUrl}/${manifestPath}`
982
+ });
983
+ }
984
+ pickPrimaryManifestPath(filePaths, config) {
985
+ const manifests = filePaths.filter((filePath) => config.hasRequiredManifest(filePath)).sort((left, right) => {
986
+ const leftDepth = left.split("/").length;
987
+ const rightDepth = right.split("/").length;
988
+ if (leftDepth !== rightDepth) {
989
+ return leftDepth - rightDepth;
990
+ }
991
+ return left.localeCompare(right);
992
+ });
993
+ const manifestPath = manifests[0];
994
+ if (!manifestPath) {
995
+ throw new Error("Missing required manifest in well-known index entry");
996
+ }
997
+ return manifestPath;
998
+ }
999
+ async fetchTextWithLimit(url, maxPerRequestBytes, options, budget) {
1000
+ let currentUrl = url;
1001
+ let redirects = 0;
1002
+ while (true) {
1003
+ const parsed = new URL(currentUrl);
1004
+ if (parsed.protocol !== "https:") {
1005
+ throw new Error("Well-known provider only allows HTTPS fetches");
1006
+ }
1007
+ await this.assertHostAllowed(parsed.hostname, options);
1008
+ const controller = new AbortController();
1009
+ const timeout = setTimeout(() => controller.abort(), options.timeoutMs);
1010
+ let response;
1011
+ try {
1012
+ response = await fetch(currentUrl, {
1013
+ redirect: "manual",
1014
+ signal: controller.signal
1015
+ });
1016
+ } finally {
1017
+ clearTimeout(timeout);
1018
+ }
1019
+ if (response.status >= 300 && response.status < 400) {
1020
+ const location = response.headers.get("location");
1021
+ if (!location) {
1022
+ throw new Error(`Redirect without location for ${currentUrl}`);
1023
+ }
1024
+ redirects += 1;
1025
+ if (redirects > options.maxRedirects) {
1026
+ throw new Error(`Too many redirects for ${url}`);
1027
+ }
1028
+ currentUrl = new URL(location, currentUrl).toString();
1029
+ continue;
1030
+ }
1031
+ if (!response.ok) {
1032
+ throw new Error(`Failed to fetch ${currentUrl}: ${response.status} ${response.statusText}`);
1033
+ }
1034
+ const bytes = new Uint8Array(await response.arrayBuffer());
1035
+ if (bytes.byteLength > maxPerRequestBytes) {
1036
+ throw new Error(`Exceeded per-file download limit for ${currentUrl}`);
1037
+ }
1038
+ budget.remaining -= bytes.byteLength;
1039
+ if (budget.remaining < 0) {
1040
+ throw new Error(`Exceeded total download budget while fetching ${url}`);
1041
+ }
1042
+ return new TextDecoder("utf8").decode(bytes);
1043
+ }
1044
+ }
1045
+ async assertHostAllowed(hostname, options) {
1046
+ const lowerHost = hostname.toLowerCase();
1047
+ if (options.denyHosts.includes(lowerHost)) {
1048
+ throw new Error(`Host '${hostname}' is explicitly denied`);
1049
+ }
1050
+ if (options.allowHosts.length > 0 && !options.allowHosts.includes(lowerHost)) {
1051
+ throw new Error(`Host '${hostname}' is not in allowed host list`);
1052
+ }
1053
+ if (isLocalHostname(lowerHost)) {
1054
+ throw new Error(`Local or loopback host '${hostname}' is not allowed`);
1055
+ }
1056
+ const ips = await resolveHostIps(hostname);
1057
+ if (ips.some((ip) => isPrivateOrLocalIp(ip))) {
1058
+ throw new Error(`Host '${hostname}' resolves to a private/local address`);
1059
+ }
1060
+ }
1061
+ };
1062
+ function isLocalHostname(hostname) {
1063
+ return hostname === "localhost" || hostname.endsWith(".localhost") || hostname.endsWith(".local");
1064
+ }
1065
+ async function resolveHostIps(hostname) {
1066
+ const out = /* @__PURE__ */ new Set();
1067
+ try {
1068
+ const entries = await dns.lookup(hostname, { all: true });
1069
+ for (const entry of entries) {
1070
+ out.add(entry.address);
1071
+ }
1072
+ } catch {
1073
+ }
1074
+ return [...out];
1075
+ }
1076
+ function isPrivateOrLocalIp(ip) {
1077
+ const family = net.isIP(ip);
1078
+ if (family === 4) {
1079
+ return ip.startsWith("10.") || ip.startsWith("127.") || ip.startsWith("169.254.") || ip.startsWith("192.168.") || /^172\.(1[6-9]|2\d|3[0-1])\./.test(ip);
1080
+ }
1081
+ if (family === 6) {
1082
+ const normalized = ip.toLowerCase();
1083
+ return normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe80:");
1084
+ }
1085
+ return false;
1086
+ }
1087
+ var wellKnownProvider = new SecureWellKnownProvider();
1088
+
1089
+ // ../../packages/core/src/providers/catalog.ts
1090
+ var DEFAULT_OPTIONS2 = {
1091
+ maxDownloadBytes: 5 * 1024 * 1024,
1092
+ timeoutMs: 1e4,
1093
+ maxFilesPerSkill: 128,
1094
+ maxSkillFileBytes: 512 * 1024
1095
+ };
1096
+ var HttpCatalogProvider = class {
1097
+ id = "catalog";
1098
+ displayName = "HTTP Catalog Skills";
1099
+ match(url) {
1100
+ try {
1101
+ const parsed = new URL(url);
1102
+ if (parsed.protocol !== "https:") {
1103
+ return { matches: false };
1104
+ }
1105
+ return { matches: true, sourceIdentifier: this.getSourceIdentifier(url) };
1106
+ } catch {
1107
+ return { matches: false };
1108
+ }
1109
+ }
1110
+ getSourceIdentifier(url) {
1111
+ const parsed = new URL(url);
1112
+ return `catalog/${parsed.host}${parsed.pathname.replace(/\/+$/, "")}`;
1113
+ }
1114
+ async fetchAllSkills(url, options = {}) {
1115
+ return this.fetchAllResources(url, options, {
1116
+ kind: "skills",
1117
+ indexLabel: "catalog skills",
1118
+ resolveIndexUrl(parsed) {
1119
+ return parsed.pathname.endsWith(".json") ? parsed.toString() : new URL(
1120
+ "index.json",
1121
+ parsed.toString().endsWith("/") ? parsed.toString() : `${parsed.toString()}/`
1122
+ ).toString();
1123
+ },
1124
+ requireDescription: true,
1125
+ missingManifestMessage: (name) => `Catalog skill '${name}' is missing SKILL.md`,
1126
+ hasRequiredManifest(filePath) {
1127
+ return filePath.toLowerCase() === "skill.md";
1128
+ },
1129
+ buildRemoteResult({ entry, files, sourceUrl }) {
1130
+ return {
1131
+ name: entry.name,
1132
+ description: entry.description || "",
1133
+ installName: entry.name,
1134
+ sourceUrl,
1135
+ sourceType: "catalog",
1136
+ files
1137
+ };
1138
+ }
1139
+ });
1140
+ }
1141
+ async fetchAllResources(url, options, config) {
1142
+ const parsed = new URL(url);
1143
+ if (parsed.protocol !== "https:") {
1144
+ throw new Error("Catalog provider requires HTTPS URLs");
1145
+ }
1146
+ const maxDownloadBytes = options.maxDownloadBytes ?? DEFAULT_OPTIONS2.maxDownloadBytes;
1147
+ const timeoutMs = options.timeoutMs ?? DEFAULT_OPTIONS2.timeoutMs;
1148
+ const maxFilesPerSkill = options.maxFilesPerSkill ?? DEFAULT_OPTIONS2.maxFilesPerSkill;
1149
+ const maxSkillFileBytes = options.maxSkillFileBytes ?? DEFAULT_OPTIONS2.maxSkillFileBytes;
1150
+ const indexUrl = config.resolveIndexUrl(parsed);
1151
+ const indexText = await this.fetchTextWithLimit(
1152
+ indexUrl,
1153
+ Math.min(maxDownloadBytes, maxSkillFileBytes),
1154
+ timeoutMs
1155
+ );
1156
+ const index = this.validateIndex(JSON.parse(indexText), maxFilesPerSkill, config);
1157
+ const out = [];
1158
+ let remaining = maxDownloadBytes - indexText.length;
1159
+ const indexBase = indexUrl.slice(0, indexUrl.lastIndexOf("/") + 1);
1160
+ for (const row of index) {
1161
+ const files = /* @__PURE__ */ new Map();
1162
+ for (const rel of row.files) {
1163
+ this.assertSafeRelativePath(rel);
1164
+ const fileUrl = new URL(`${row.name}/${rel}`, indexBase).toString();
1165
+ if (remaining <= 0) {
1166
+ throw new Error("Catalog download budget exhausted");
1167
+ }
1168
+ const text = await this.fetchTextWithLimit(
1169
+ fileUrl,
1170
+ Math.min(remaining, maxSkillFileBytes),
1171
+ timeoutMs
1172
+ );
1173
+ remaining -= text.length;
1174
+ files.set(rel, text);
1175
+ }
1176
+ out.push(
1177
+ config.buildRemoteResult({
1178
+ entry: row,
1179
+ files,
1180
+ sourceUrl: new URL(
1181
+ `${row.name}/${this.pickPrimaryManifestPath(row.files, config)}`,
1182
+ indexBase
1183
+ ).toString()
1184
+ })
1185
+ );
1186
+ }
1187
+ return out;
1188
+ }
1189
+ async fetchTextWithLimit(url, maxBytes, timeoutMs) {
1190
+ const controller = new AbortController();
1191
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
1192
+ try {
1193
+ const response = await fetch(url, { signal: controller.signal });
1194
+ if (!response.ok) {
1195
+ throw new Error(
1196
+ `Catalog fetch failed (${response.status} ${response.statusText}) for ${url}`
1197
+ );
1198
+ }
1199
+ const bytes = new Uint8Array(await response.arrayBuffer());
1200
+ if (bytes.byteLength > maxBytes) {
1201
+ throw new Error(`Catalog file exceeds size limit for ${url}`);
1202
+ }
1203
+ return new TextDecoder("utf8").decode(bytes);
1204
+ } finally {
1205
+ clearTimeout(timeout);
1206
+ }
1207
+ }
1208
+ validateIndex(raw, maxFilesPerSkill, config) {
1209
+ if (!raw || typeof raw !== "object") {
1210
+ throw new Error("Invalid catalog index: expected object");
1211
+ }
1212
+ const data = raw;
1213
+ const rows = data[config.kind];
1214
+ if (!Array.isArray(rows)) {
1215
+ throw new Error(`Invalid catalog index: '${config.kind}' must be an array`);
1216
+ }
1217
+ return rows.map((item, idx) => {
1218
+ if (!item || typeof item !== "object") {
1219
+ throw new Error(`Invalid catalog index entry[${idx}]`);
1220
+ }
1221
+ const row = item;
1222
+ const name = String(row.name || "").trim();
1223
+ const description = typeof row.description === "string" ? row.description.trim() : void 0;
1224
+ const files = Array.isArray(row.files) ? row.files.map((x) => String(x)) : [];
1225
+ if (!name || files.length === 0) {
1226
+ throw new Error(`Invalid catalog index entry[${idx}]: missing required fields`);
1227
+ }
1228
+ if (config.requireDescription && !description) {
1229
+ throw new Error(`Invalid catalog index entry[${idx}]: missing required fields`);
1230
+ }
1231
+ if (files.length > maxFilesPerSkill) {
1232
+ throw new Error(`Too many files in catalog ${config.kind.slice(0, -1)} '${name}'`);
1233
+ }
1234
+ if (!files.some((filePath) => config.hasRequiredManifest(filePath))) {
1235
+ throw new Error(config.missingManifestMessage(name));
1236
+ }
1237
+ for (const file of files) {
1238
+ this.assertSafeRelativePath(file);
1239
+ }
1240
+ return { name, description, files };
1241
+ });
1242
+ }
1243
+ pickPrimaryManifestPath(filePaths, config) {
1244
+ const manifests = filePaths.filter((filePath) => config.hasRequiredManifest(filePath)).sort((left, right) => {
1245
+ const leftDepth = left.split("/").length;
1246
+ const rightDepth = right.split("/").length;
1247
+ if (leftDepth !== rightDepth) {
1248
+ return leftDepth - rightDepth;
1249
+ }
1250
+ return left.localeCompare(right);
1251
+ });
1252
+ const manifestPath = manifests[0];
1253
+ if (!manifestPath) {
1254
+ throw new Error("Catalog entry is missing required manifest");
1255
+ }
1256
+ return manifestPath;
1257
+ }
1258
+ assertSafeRelativePath(filePath) {
1259
+ if (!filePath || filePath.startsWith("/") || filePath.startsWith("\\") || filePath.includes("..") || filePath.includes("\\")) {
1260
+ throw new Error(`Unsafe catalog file path: ${filePath}`);
1261
+ }
1262
+ }
1263
+ };
1264
+ var catalogProvider = new HttpCatalogProvider();
1265
+
1266
+ // ../../packages/core/src/providers/index.ts
1267
+ var initialized = false;
1268
+ function initializeProviders() {
1269
+ if (initialized) {
1270
+ return;
1271
+ }
1272
+ registerProvider(wellKnownProvider);
1273
+ registerProvider(catalogProvider);
1274
+ initialized = true;
1275
+ }
1276
+
1277
+ // ../../packages/core/src/application/experimental.ts
1278
+ function assertExperimentalFeatureEnabled(feature, enabled) {
1279
+ if (enabled) {
1280
+ return;
1281
+ }
1282
+ if (feature === "catalog") {
1283
+ throw new Error("Catalog source is experimental and requires explicit experimental mode.");
1284
+ }
1285
+ }
1286
+
1287
+ // ../../packages/core/src/sources/source-resolution.ts
1288
+ async function resolveWellKnownSkills(sourceUrl, options) {
1289
+ initializeProviders();
1290
+ const provider = getProviderById("well-known");
1291
+ if (!provider) {
1292
+ throw new Error("Well-known provider is not registered");
1293
+ }
1294
+ const wellKnown = provider;
1295
+ return wellKnown.fetchAllSkills(sourceUrl, {
1296
+ allowHosts: options.allowHost,
1297
+ denyHosts: options.denyHost,
1298
+ maxDownloadBytes: options.maxDownloadBytes
1299
+ });
1300
+ }
1301
+ async function resolveCatalogSkills(sourceUrl, options) {
1302
+ assertExperimentalFeatureEnabled("catalog", Boolean(options.experimental));
1303
+ initializeProviders();
1304
+ const provider = getProviderById("catalog");
1305
+ if (!provider) {
1306
+ throw new Error("Catalog provider is not registered");
1307
+ }
1308
+ const catalog = provider;
1309
+ return catalog.fetchAllSkills(sourceUrl, {
1310
+ allowHosts: options.allowHost,
1311
+ denyHosts: options.denyHost,
1312
+ maxDownloadBytes: options.maxDownloadBytes
1313
+ });
1314
+ }
1315
+
1316
+ // ../../packages/core/src/runtime/hash.ts
1317
+ import fs4 from "node:fs";
1318
+ import path5 from "node:path";
1319
+ import { createHash } from "node:crypto";
1320
+ var SKIP_DIRS2 = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", "__pycache__"]);
1321
+ var SKIP_FILES = /* @__PURE__ */ new Set(["skillspp-lock.json", "skillspp-lock.yaml"]);
1322
+ function walkDir(baseDir, dir, hash) {
1323
+ const entries = fs4.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
1324
+ for (const entry of entries) {
1325
+ if (entry.isDirectory() && SKIP_DIRS2.has(entry.name)) {
1326
+ continue;
1327
+ }
1328
+ const fullPath = path5.join(dir, entry.name);
1329
+ const relativePath = path5.relative(baseDir, fullPath).replace(/\\/g, "/");
1330
+ if (entry.isDirectory()) {
1331
+ hash.update(`dir:${relativePath}
1332
+ `);
1333
+ walkDir(baseDir, fullPath, hash);
1334
+ continue;
1335
+ }
1336
+ if (entry.isFile()) {
1337
+ if (SKIP_FILES.has(entry.name)) {
1338
+ continue;
1339
+ }
1340
+ const content = fs4.readFileSync(fullPath);
1341
+ hash.update(`file:${relativePath}
1342
+ `);
1343
+ hash.update(content);
1344
+ hash.update("\n");
1345
+ }
1346
+ }
1347
+ }
1348
+ function waitForNextTurn() {
1349
+ return new Promise((resolve) => {
1350
+ setImmediate(resolve);
1351
+ });
1352
+ }
1353
+ async function walkDirAsync(baseDir, dir, hash) {
1354
+ const entries = (await fs4.promises.readdir(dir, { withFileTypes: true })).sort(
1355
+ (a, b) => a.name.localeCompare(b.name)
1356
+ );
1357
+ for (const entry of entries) {
1358
+ await waitForNextTurn();
1359
+ if (entry.isDirectory() && SKIP_DIRS2.has(entry.name)) {
1360
+ continue;
1361
+ }
1362
+ const fullPath = path5.join(dir, entry.name);
1363
+ const relativePath = path5.relative(baseDir, fullPath).replace(/\\/g, "/");
1364
+ if (entry.isDirectory()) {
1365
+ hash.update(`dir:${relativePath}
1366
+ `);
1367
+ await walkDirAsync(baseDir, fullPath, hash);
1368
+ continue;
1369
+ }
1370
+ if (entry.isFile()) {
1371
+ if (SKIP_FILES.has(entry.name)) {
1372
+ continue;
1373
+ }
1374
+ const content = await fs4.promises.readFile(fullPath);
1375
+ hash.update(`file:${relativePath}
1376
+ `);
1377
+ hash.update(content);
1378
+ hash.update("\n");
1379
+ }
1380
+ }
1381
+ }
1382
+ function hashDirectory(dirPath) {
1383
+ const resolved = path5.resolve(dirPath);
1384
+ if (!fs4.existsSync(resolved) || !fs4.statSync(resolved).isDirectory()) {
1385
+ throw new Error(`Directory not found for hashing: ${resolved}`);
1386
+ }
1387
+ const hash = createHash("sha256");
1388
+ walkDir(resolved, resolved, hash);
1389
+ return hash.digest("hex");
1390
+ }
1391
+ async function hashDirectoryAsync(dirPath) {
1392
+ const resolved = path5.resolve(dirPath);
1393
+ const stats = await fs4.promises.stat(resolved).catch(() => null);
1394
+ if (!stats || !stats.isDirectory()) {
1395
+ throw new Error(`Directory not found for hashing: ${resolved}`);
1396
+ }
1397
+ const hash = createHash("sha256");
1398
+ await walkDirAsync(resolved, resolved, hash);
1399
+ return hash.digest("hex");
1400
+ }
1401
+
1402
+ // ../../packages/core/src/runtime/lockfile.ts
1403
+ import fs5 from "node:fs";
1404
+ import path6 from "node:path";
1405
+ import YAML from "yaml";
1406
+ function resolveSourceLoadInput(source) {
1407
+ return source.type === "local" && source.canonical ? source.canonical : source.input;
1408
+ }
1409
+ function buildSourceIdentityCacheKey(parsed) {
1410
+ if (parsed.type === "local") {
1411
+ return `local:${parsed.localPath}`;
1412
+ }
1413
+ if (parsed.type === "well-known") {
1414
+ return `well-known:${parsed.url}`;
1415
+ }
1416
+ if (parsed.type === "catalog") {
1417
+ return `catalog:${parsed.url}`;
1418
+ }
1419
+ if (parsed.type === "github") {
1420
+ return `github:${parsed.repoUrl}:${parsed.ref || ""}:${parsed.subpath || ""}`;
1421
+ }
1422
+ return `git:${parsed.repoUrl}`;
1423
+ }
1424
+ function buildSourceLoadCacheKey(source) {
1425
+ return buildSourceIdentityCacheKey(parseSource(resolveSourceLoadInput(source)));
1426
+ }
1427
+ function perSkillLockfilePath(canonicalDir, format = "json") {
1428
+ if (format === "yaml") {
1429
+ return path6.join(canonicalDir, "skillspp-lock.yaml");
1430
+ }
1431
+ return path6.join(canonicalDir, "skillspp-lock.json");
1432
+ }
1433
+ function parseLockPayload(text, format) {
1434
+ const raw = format === "json" ? JSON.parse(text) : YAML.parse(text);
1435
+ if (!raw || raw.version !== 1) {
1436
+ return null;
1437
+ }
1438
+ return raw;
1439
+ }
1440
+ function readPerSkillLockfile(canonicalDir) {
1441
+ const jsonPath = perSkillLockfilePath(canonicalDir, "json");
1442
+ const yamlPath = perSkillLockfilePath(canonicalDir, "yaml");
1443
+ const raw = fs5.existsSync(jsonPath) ? parseLockPayload(fs5.readFileSync(jsonPath, "utf8"), "json") : fs5.existsSync(yamlPath) ? parseLockPayload(fs5.readFileSync(yamlPath, "utf8"), "yaml") : null;
1444
+ if (!raw) {
1445
+ return null;
1446
+ }
1447
+ if (raw.entry && typeof raw.entry.skillName === "string") {
1448
+ return raw.entry;
1449
+ }
1450
+ if (Array.isArray(raw.entries) && raw.entries[0] && typeof raw.entries[0].skillName === "string") {
1451
+ return raw.entries[0];
1452
+ }
1453
+ return null;
1454
+ }
1455
+ function writePerSkillLockfile(canonicalDir, entry, format) {
1456
+ const jsonPath = perSkillLockfilePath(canonicalDir, "json");
1457
+ const yamlPath = perSkillLockfilePath(canonicalDir, "yaml");
1458
+ fs5.mkdirSync(canonicalDir, { recursive: true });
1459
+ if (format === "yaml") {
1460
+ fs5.writeFileSync(yamlPath, YAML.stringify({ version: 1, entry }), "utf8");
1461
+ if (fs5.existsSync(jsonPath)) {
1462
+ fs5.rmSync(jsonPath, { force: true });
1463
+ }
1464
+ return;
1465
+ }
1466
+ fs5.writeFileSync(jsonPath, `${JSON.stringify({ version: 1, entry }, null, 2)}
1467
+ `, "utf8");
1468
+ if (fs5.existsSync(yamlPath)) {
1469
+ fs5.rmSync(yamlPath, { force: true });
1470
+ }
1471
+ }
1472
+ function isSkillDirEntry(entry) {
1473
+ return entry.isDirectory() || entry.isSymbolicLink();
1474
+ }
1475
+ function listInstalledResourceDirs(_kind, globalInstall, cwd) {
1476
+ const out = /* @__PURE__ */ new Set();
1477
+ for (const agent of Object.keys(AGENTS)) {
1478
+ const resourceRoot = getAgentSkillsDir(agent, globalInstall, cwd);
1479
+ if (!fs5.existsSync(resourceRoot) || !fs5.statSync(resourceRoot).isDirectory()) {
1480
+ continue;
1481
+ }
1482
+ for (const entry of fs5.readdirSync(resourceRoot, { withFileTypes: true })) {
1483
+ if (!isSkillDirEntry(entry)) {
1484
+ continue;
1485
+ }
1486
+ out.add(path6.join(resourceRoot, entry.name));
1487
+ }
1488
+ }
1489
+ return [...out];
1490
+ }
1491
+ function lockEntrySortTime(entry) {
1492
+ const parsed = Date.parse(entry.updatedAt);
1493
+ return Number.isFinite(parsed) ? parsed : 0;
1494
+ }
1495
+ function readLockfile(globalInstall, cwd) {
1496
+ return readResourceLockfile("skill", globalInstall, cwd);
1497
+ }
1498
+ function readResourceLockfile(kind, globalInstall, cwd) {
1499
+ const entriesBySkill = /* @__PURE__ */ new Map();
1500
+ for (const skillDir of listInstalledResourceDirs(kind, globalInstall, cwd)) {
1501
+ const entry = readPerSkillLockfile(skillDir);
1502
+ if (!entry || typeof entry.skillName !== "string") {
1503
+ continue;
1504
+ }
1505
+ const existing = entriesBySkill.get(entry.skillName);
1506
+ if (!existing || lockEntrySortTime(entry) > lockEntrySortTime(existing)) {
1507
+ entriesBySkill.set(entry.skillName, entry);
1508
+ }
1509
+ }
1510
+ return {
1511
+ version: 1,
1512
+ entries: [...entriesBySkill.values()].sort((a, b) => a.skillName.localeCompare(b.skillName))
1513
+ };
1514
+ }
1515
+ function writeLockfile(globalInstall, cwd, lockfile, format = "json") {
1516
+ writeResourceLockfile("skill", globalInstall, cwd, lockfile, format);
1517
+ }
1518
+ function writeResourceLockfile(_kind, _globalInstall, _cwd, lockfile, format = "json") {
1519
+ const normalized = [...lockfile.entries].sort((a, b) => a.skillName.localeCompare(b.skillName));
1520
+ for (const entry of normalized) {
1521
+ writePerSkillLockfile(entry.canonicalDir, entry, format);
1522
+ }
1523
+ }
1524
+ function upsertLockEntry(lockfile, entry) {
1525
+ return upsertResourceLockEntry(lockfile, entry);
1526
+ }
1527
+ function upsertResourceLockEntry(lockfile, entry) {
1528
+ const next = lockfile.entries.filter((item) => item.skillName !== entry.skillName);
1529
+ next.push(entry);
1530
+ return { version: 1, entries: next };
1531
+ }
1532
+ function listCanonicalSkillDirs(globalInstall, cwd) {
1533
+ return listCanonicalResourceDirs("skill", globalInstall, cwd);
1534
+ }
1535
+ function listCanonicalResourceDirs(kind, globalInstall, cwd) {
1536
+ return [
1537
+ ...new Set(
1538
+ listInstalledResourceDirs(kind, globalInstall, cwd).map((dir) => path6.basename(dir))
1539
+ )
1540
+ ].sort((a, b) => a.localeCompare(b));
1541
+ }
1542
+
1543
+ // ../../packages/core/src/runtime/check-analysis.ts
1544
+ function includeBySkill(entry, selected) {
1545
+ if (!selected || selected.length === 0 || selected.includes("*")) {
1546
+ return true;
1547
+ }
1548
+ return selected.includes(entry.skillName);
1549
+ }
1550
+ function migrateHint(skillName) {
1551
+ return `skillspp update ${skillName} --migrate <new-skill-source>`;
1552
+ }
1553
+ function resolveSourceMetadataIssue(entry) {
1554
+ if (!entry.source.canonical) {
1555
+ return `source canonical metadata missing; run ${migrateHint(entry.skillName)}`;
1556
+ }
1557
+ if (entry.source.type === "local") {
1558
+ if (!entry.source.resolvedPath) {
1559
+ return `local source metadata missing; run ${migrateHint(entry.skillName)}`;
1560
+ }
1561
+ if (!fs6.existsSync(entry.source.canonical)) {
1562
+ return `local source path not found; run ${migrateHint(entry.skillName)}`;
1563
+ }
1564
+ return null;
1565
+ }
1566
+ if (!entry.source.pinnedRef) {
1567
+ return `remote pin metadata missing; run ${migrateHint(entry.skillName)}`;
1568
+ }
1569
+ return null;
1570
+ }
1571
+ function createRetainedCleanup(cleanup) {
1572
+ let cleaned = false;
1573
+ let refCount = 0;
1574
+ const runCleanup = () => {
1575
+ if (cleaned) {
1576
+ return;
1577
+ }
1578
+ cleaned = true;
1579
+ cleanup?.();
1580
+ };
1581
+ const cleanupNow = () => {
1582
+ if (refCount === 0) {
1583
+ runCleanup();
1584
+ }
1585
+ };
1586
+ const retainCleanup = () => {
1587
+ refCount += 1;
1588
+ let released = false;
1589
+ return () => {
1590
+ if (released) {
1591
+ return;
1592
+ }
1593
+ released = true;
1594
+ refCount -= 1;
1595
+ if (refCount === 0) {
1596
+ runCleanup();
1597
+ }
1598
+ };
1599
+ };
1600
+ return {
1601
+ cleanupNow,
1602
+ retainCleanup
1603
+ };
1604
+ }
1605
+ function toAsyncResult(promise) {
1606
+ return promise.then(
1607
+ (value) => ({ ok: true, value }),
1608
+ (error) => ({ ok: false, error })
1609
+ );
1610
+ }
1611
+ function unwrapAsyncResult(result) {
1612
+ if (result.ok) {
1613
+ return result.value;
1614
+ }
1615
+ throw result.error;
1616
+ }
1617
+ async function loadCachedSource(entry, options) {
1618
+ const sourceInput = resolveSourceLoadInput(entry.source);
1619
+ const parsed = parseSource(sourceInput);
1620
+ if (parsed.type === "well-known" || parsed.type === "catalog") {
1621
+ const remoteSkills = parsed.type === "well-known" ? await resolveWellKnownSkills(parsed.url, options) : await resolveCatalogSkills(parsed.url, options);
1622
+ return {
1623
+ kind: "remote",
1624
+ remoteSkills
1625
+ };
1626
+ }
1627
+ const prepared = await prepareSourceDirAsync(
1628
+ parsed
1629
+ );
1630
+ const retainedCleanup = createRetainedCleanup(prepared.cleanup);
1631
+ try {
1632
+ const skills = await discoverSkillsAsync(prepared.basePath);
1633
+ return {
1634
+ kind: "prepared",
1635
+ basePath: prepared.basePath,
1636
+ skills,
1637
+ cleanupNow: retainedCleanup.cleanupNow,
1638
+ retainCleanup: retainedCleanup.retainCleanup
1639
+ };
1640
+ } catch (error) {
1641
+ retainedCleanup.cleanupNow();
1642
+ throw error;
1643
+ }
1644
+ }
1645
+ function resolveSafeRealPath(inputPath) {
1646
+ try {
1647
+ return fs6.realpathSync(inputPath);
1648
+ } catch {
1649
+ return path7.resolve(inputPath);
1650
+ }
1651
+ }
1652
+ function isLocalSymlinkSource(localPath) {
1653
+ try {
1654
+ if (!fs6.existsSync(localPath)) {
1655
+ return false;
1656
+ }
1657
+ return fs6.lstatSync(localPath).isSymbolicLink();
1658
+ } catch {
1659
+ return false;
1660
+ }
1661
+ }
1662
+ async function buildRefreshedSourceMetadata(entry, resolved, cachedSource, sourceHash) {
1663
+ if (entry.source.type === "local") {
1664
+ const canonical2 = entry.source.canonical || entry.source.input;
1665
+ return {
1666
+ canonical: canonical2,
1667
+ resolvedPath: resolveSafeRealPath(resolved.skill.path),
1668
+ isSymlinkSource: isLocalSymlinkSource(canonical2),
1669
+ sourceSkillPath: resolved.sourceSkillPath
1670
+ };
1671
+ }
1672
+ if (entry.source.type === "git" || entry.source.type === "github") {
1673
+ const nextPinnedRef = cachedSource.kind === "prepared" ? await resolveGitHeadRefAsync(cachedSource.basePath) : entry.source.pinnedRef;
1674
+ return {
1675
+ canonical: entry.source.canonical,
1676
+ pinnedRef: nextPinnedRef,
1677
+ sourceSkillPath: resolved.sourceSkillPath
1678
+ };
1679
+ }
1680
+ const canonical = resolved.wellKnownSourceUrl || entry.source.canonical;
1681
+ return {
1682
+ canonical,
1683
+ pinnedRef: sourceHash,
1684
+ wellKnownSourceUrl: resolved.wellKnownSourceUrl
1685
+ };
1686
+ }
1687
+ function resolveCandidateFromCachedSource(entry, cachedSource, keepResolved) {
1688
+ if (cachedSource.kind === "remote") {
1689
+ const matched2 = cachedSource.remoteSkills.find(
1690
+ (item) => item.installName === entry.source.selector.skillName
1691
+ );
1692
+ if (!matched2) {
1693
+ throw new Error(`Skill '${entry.source.selector.skillName}' not found in well-known source`);
1694
+ }
1695
+ const staged = stageRemoteSkillFilesToTempDir(matched2.files);
1696
+ return {
1697
+ skill: {
1698
+ name: matched2.installName,
1699
+ description: matched2.description,
1700
+ path: staged.path
1701
+ },
1702
+ wellKnownSourceUrl: matched2.sourceUrl,
1703
+ cleanup: staged.cleanup
1704
+ };
1705
+ }
1706
+ const matched = entry.source.selector.relativePath ? cachedSource.skills.find(
1707
+ (item) => path7.resolve(item.path) === path7.resolve(path7.join(cachedSource.basePath, entry.source.selector.relativePath))
1708
+ ) : cachedSource.skills.find((item) => item.name === entry.source.selector.skillName);
1709
+ if (!matched) {
1710
+ throw new Error(`Skill '${entry.source.selector.skillName}' not found in source`);
1711
+ }
1712
+ return {
1713
+ skill: matched,
1714
+ sourceSkillPath: path7.relative(cachedSource.basePath, matched.path) || ".",
1715
+ cleanup: keepResolved ? cachedSource.retainCleanup() : void 0
1716
+ };
1717
+ }
1718
+ async function assessLockEntries(options, cwd, behavior = { keepResolved: false }) {
1719
+ const lock = readLockfile(Boolean(options.global), cwd);
1720
+ const entries = lock.entries.filter((entry) => includeBySkill(entry, options.skill));
1721
+ const drift = [];
1722
+ const assessments = [];
1723
+ const sourceOptions = {
1724
+ global: options.global,
1725
+ allowHost: options.allowHost,
1726
+ denyHost: options.denyHost,
1727
+ maxDownloadBytes: options.maxDownloadBytes,
1728
+ policyMode: options.policyMode,
1729
+ experimental: options.experimental
1730
+ };
1731
+ const sourceCache = /* @__PURE__ */ new Map();
1732
+ const getCachedSource = (entry) => {
1733
+ const key = buildSourceLoadCacheKey(entry.source);
1734
+ const existing = sourceCache.get(key);
1735
+ if (existing) {
1736
+ return existing;
1737
+ }
1738
+ const created = loadCachedSource(entry, sourceOptions);
1739
+ sourceCache.set(key, created);
1740
+ return created;
1741
+ };
1742
+ try {
1743
+ for (const entry of entries) {
1744
+ const assessment = {
1745
+ entry,
1746
+ drift: []
1747
+ };
1748
+ if (!fs6.existsSync(entry.canonicalDir) || !fs6.statSync(entry.canonicalDir).isDirectory()) {
1749
+ const row = {
1750
+ skillName: entry.skillName,
1751
+ kind: "local-modified",
1752
+ detail: "canonical directory is missing"
1753
+ };
1754
+ drift.push(row);
1755
+ assessment.drift.push(row);
1756
+ assessments.push(assessment);
1757
+ continue;
1758
+ }
1759
+ const installedHash = unwrapAsyncResult(
1760
+ await toAsyncResult(hashDirectoryAsync(entry.canonicalDir))
1761
+ );
1762
+ if (installedHash !== entry.installedHash) {
1763
+ const row = {
1764
+ skillName: entry.skillName,
1765
+ kind: "local-modified",
1766
+ detail: "installed content differs from lockfile hash"
1767
+ };
1768
+ drift.push(row);
1769
+ assessment.drift.push(row);
1770
+ }
1771
+ const metadataIssue = resolveSourceMetadataIssue(entry);
1772
+ if (metadataIssue) {
1773
+ const row = {
1774
+ skillName: entry.skillName,
1775
+ kind: "migrate-required",
1776
+ detail: metadataIssue
1777
+ };
1778
+ drift.push(row);
1779
+ assessment.drift.push(row);
1780
+ assessments.push(assessment);
1781
+ continue;
1782
+ }
1783
+ let resolved;
1784
+ try {
1785
+ const cachedSource = unwrapAsyncResult(await toAsyncResult(getCachedSource(entry)));
1786
+ resolved = resolveCandidateFromCachedSource(entry, cachedSource, behavior.keepResolved);
1787
+ if (entry.source.type === "local") {
1788
+ const currentResolvedPath = fs6.realpathSync(resolved.skill.path);
1789
+ if (currentResolvedPath !== path7.resolve(entry.source.resolvedPath)) {
1790
+ const row = {
1791
+ skillName: entry.skillName,
1792
+ kind: "migrate-required",
1793
+ detail: `local source identity changed; run ${migrateHint(entry.skillName)}`
1794
+ };
1795
+ drift.push(row);
1796
+ assessment.drift.push(row);
1797
+ if (resolved.cleanup && !behavior.keepResolved) {
1798
+ resolved.cleanup();
1799
+ resolved = void 0;
1800
+ }
1801
+ assessments.push(assessment);
1802
+ continue;
1803
+ }
1804
+ }
1805
+ const sourceHash = await hashDirectoryAsync(resolved.skill.path);
1806
+ assessment.sourceHash = sourceHash;
1807
+ assessment.refreshedSource = await buildRefreshedSourceMetadata(
1808
+ entry,
1809
+ resolved,
1810
+ cachedSource,
1811
+ sourceHash
1812
+ );
1813
+ if (sourceHash !== entry.sourceHash) {
1814
+ const row = {
1815
+ skillName: entry.skillName,
1816
+ kind: "changed-source",
1817
+ detail: "source hash changed"
1818
+ };
1819
+ drift.push(row);
1820
+ assessment.drift.push(row);
1821
+ }
1822
+ if (behavior.keepResolved) {
1823
+ assessment.resolved = resolved;
1824
+ }
1825
+ } catch (error) {
1826
+ const asText = error instanceof Error ? error.message : String(error);
1827
+ const migrateRequired = entry.source.type === "local" && (asText.includes("Local source not found") || asText.includes("not found in source"));
1828
+ const row = {
1829
+ skillName: entry.skillName,
1830
+ kind: migrateRequired ? "migrate-required" : "missing-source",
1831
+ detail: migrateRequired ? `local source identity changed; run ${migrateHint(entry.skillName)}` : asText
1832
+ };
1833
+ drift.push(row);
1834
+ assessment.drift.push(row);
1835
+ } finally {
1836
+ if (resolved?.cleanup && !behavior.keepResolved) {
1837
+ resolved.cleanup();
1838
+ }
1839
+ }
1840
+ assessments.push(assessment);
1841
+ }
1842
+ const canonical = listCanonicalSkillDirs(Boolean(options.global), cwd);
1843
+ const lockNames = new Set(lock.entries.map((item) => item.skillName));
1844
+ for (const skillName of canonical) {
1845
+ if (!lockNames.has(skillName) && includeBySkill({ skillName }, options.skill)) {
1846
+ drift.push({
1847
+ skillName,
1848
+ kind: "lock-missing",
1849
+ detail: "installed skill is not tracked in lockfile"
1850
+ });
1851
+ }
1852
+ }
1853
+ return { drift, checked: entries.length, assessments };
1854
+ } finally {
1855
+ if (!behavior.keepResolved) {
1856
+ for (const cachedSourcePromise of sourceCache.values()) {
1857
+ const cachedSource = await cachedSourcePromise.catch(() => null);
1858
+ if (cachedSource?.kind === "prepared") {
1859
+ cachedSource.cleanupNow();
1860
+ }
1861
+ }
1862
+ }
1863
+ }
1864
+ }
1865
+
1866
+ // ../../packages/core/src/runtime/validate-analysis.ts
1867
+ import fs9 from "node:fs";
1868
+ import path10 from "node:path";
1869
+ import os5 from "node:os";
1870
+ import matter2 from "gray-matter";
1871
+
1872
+ // ../../packages/core/src/runtime/skill-installer.ts
1873
+ import { spawnSync as spawnSync2 } from "node:child_process";
1874
+ import fs8 from "node:fs";
1875
+ import os4 from "node:os";
1876
+ import path9 from "node:path";
1877
+ import YAML2 from "yaml";
1878
+ import { z } from "zod";
1879
+
1880
+ // ../../packages/core/src/runtime/installer-security.ts
1881
+ import fs7 from "node:fs";
1882
+ import path8 from "node:path";
1883
+ function isInsideRoot(rootDir, candidatePath) {
1884
+ const relative = path8.relative(rootDir, candidatePath);
1885
+ return Boolean(relative) && !relative.startsWith("..") && !path8.isAbsolute(relative);
1886
+ }
1887
+ function toEscapeViolation(source) {
1888
+ return {
1889
+ rule: "installer-local-dependency-path-escape",
1890
+ message: `local dependency escapes source root: ${source}`,
1891
+ severity: "error",
1892
+ blocking: true
1893
+ };
1894
+ }
1895
+ function evaluateInstallerLocalDependency(input, _options = {}) {
1896
+ if (path8.isAbsolute(input.source)) {
1897
+ return {
1898
+ ok: false,
1899
+ violation: {
1900
+ rule: "installer-local-dependency-absolute-path",
1901
+ message: `absolute local dependency paths are not allowed: ${input.source}`,
1902
+ severity: "error",
1903
+ blocking: true
1904
+ }
1905
+ };
1906
+ }
1907
+ const resolvedRoot = path8.resolve(input.sourceRoot);
1908
+ const resolvedSourcePath = path8.resolve(resolvedRoot, input.source);
1909
+ if (!isInsideRoot(resolvedRoot, resolvedSourcePath)) {
1910
+ return {
1911
+ ok: false,
1912
+ violation: toEscapeViolation(input.source)
1913
+ };
1914
+ }
1915
+ if (fs7.existsSync(resolvedSourcePath)) {
1916
+ const realRoot = fs7.realpathSync.native ? fs7.realpathSync.native(resolvedRoot) : fs7.realpathSync(resolvedRoot);
1917
+ const realSource = fs7.realpathSync.native ? fs7.realpathSync.native(resolvedSourcePath) : fs7.realpathSync(resolvedSourcePath);
1918
+ if (!isInsideRoot(realRoot, realSource)) {
1919
+ return {
1920
+ ok: false,
1921
+ violation: toEscapeViolation(input.source)
1922
+ };
1923
+ }
1924
+ }
1925
+ return {
1926
+ ok: true,
1927
+ resolvedPath: resolvedSourcePath
1928
+ };
1929
+ }
1930
+
1931
+ // ../../packages/core/src/runtime/policy.ts
1932
+ function applyMode(result, mode) {
1933
+ if (mode === "enforce" || result.ok) {
1934
+ return result;
1935
+ }
1936
+ const violation = "violation" in result ? result.violation : void 0;
1937
+ if (!violation) {
1938
+ return result;
1939
+ }
1940
+ return {
1941
+ ok: false,
1942
+ violation: {
1943
+ ...violation,
1944
+ severity: "warning",
1945
+ blocking: false
1946
+ }
1947
+ };
1948
+ }
1949
+ function evaluateInstallerLocalDependencyPolicy(input, mode) {
1950
+ return applyMode(evaluateInstallerLocalDependency(input, { policyMode: "fixed" }), mode);
1951
+ }
1952
+ function evaluateHookTrustPolicy(input) {
1953
+ if (input.sourceType !== "well-known") {
1954
+ return { allowed: true };
1955
+ }
1956
+ if (input.trustWellKnown) {
1957
+ return { allowed: true };
1958
+ }
1959
+ if (input.mode === "warn") {
1960
+ return {
1961
+ allowed: true,
1962
+ violation: {
1963
+ rule: "hook-trust-required",
1964
+ message: "Well-known hook commands are untrusted. Proceeding due to warning policy mode.",
1965
+ severity: "warning",
1966
+ blocking: false
1967
+ }
1968
+ };
1969
+ }
1970
+ return {
1971
+ allowed: false,
1972
+ violation: {
1973
+ rule: "hook-trust-required",
1974
+ message: "Blocked skill-installer hook commands for well-known source. Trust this source explicitly or use warning policy mode.",
1975
+ severity: "error",
1976
+ blocking: true
1977
+ }
1978
+ };
1979
+ }
1980
+
1981
+ // ../../packages/core/src/runtime/skill-installer.ts
1982
+ var InstallerSecurityError = class extends Error {
1983
+ violation;
1984
+ constructor(violation) {
1985
+ super(violation.message);
1986
+ this.name = "InstallerSecurityError";
1987
+ this.violation = violation;
1988
+ }
1989
+ };
1990
+ function isInstallerSecurityError(error) {
1991
+ return error instanceof InstallerSecurityError;
1992
+ }
1993
+ var InstallerPolicyError = class extends Error {
1994
+ violation;
1995
+ constructor(violation) {
1996
+ super(violation.message);
1997
+ this.name = "InstallerPolicyError";
1998
+ this.violation = violation;
1999
+ }
2000
+ };
2001
+ function isInstallerPolicyError(error) {
2002
+ return error instanceof InstallerPolicyError;
2003
+ }
2004
+ var dependencyObjectSchema = z.object({
2005
+ source: z.string().min(1),
2006
+ path: z.string().min(1)
2007
+ }).strict();
2008
+ var installerConfigSchema = z.object({
2009
+ schemaVersion: z.literal(1),
2010
+ "pre-install": z.array(z.string().min(1)).optional().default([]),
2011
+ dependencies: z.array(z.union([z.string().min(1), dependencyObjectSchema])).optional().default([]),
2012
+ "post-install": z.array(z.string().min(1)).optional().default([])
2013
+ }).strict();
2014
+ function ensureInsideRoot(rootDir, relativeTarget) {
2015
+ const resolved = path9.resolve(rootDir, relativeTarget);
2016
+ const relative = path9.relative(rootDir, resolved);
2017
+ if (!relative || relative.startsWith("..") || path9.isAbsolute(relative)) {
2018
+ throw new Error(`Unsafe destination path: ${relativeTarget}`);
2019
+ }
2020
+ return resolved;
2021
+ }
2022
+ function sourceLooksLikeUrl(source) {
2023
+ try {
2024
+ const parsed = new URL(source);
2025
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
2026
+ } catch {
2027
+ return false;
2028
+ }
2029
+ }
2030
+ function sourceLooksLikeRepoShorthand(source) {
2031
+ const trimmed = source.trim().replace(/^https?:\/\//, "");
2032
+ return /^(github\.com|gitlab\.com)\/[^/]+\/[^/]+(?:\.git)?\/?$/.test(trimmed);
2033
+ }
2034
+ function parseRepoSource(source) {
2035
+ const withoutProtocol = source.trim().replace(/^https?:\/\//, "").replace(/\/+$/, "");
2036
+ const match = withoutProtocol.match(/^(github\.com|gitlab\.com)\/([^/]+)\/([^/]+?)(?:\.git)?$/);
2037
+ if (!match) {
2038
+ throw new Error(`Unsupported repository dependency source: ${source}`);
2039
+ }
2040
+ const host = match[1];
2041
+ const owner = match[2];
2042
+ const repo = match[3];
2043
+ return {
2044
+ repoUrl: `https://${host}/${owner}/${repo}.git`,
2045
+ repoName: repo
2046
+ };
2047
+ }
2048
+ function deriveDestinationNameFromSource(source) {
2049
+ if (sourceLooksLikeUrl(source)) {
2050
+ const parsed = new URL(source);
2051
+ const parts = parsed.pathname.split("/").filter(Boolean);
2052
+ const leaf2 = parts[parts.length - 1];
2053
+ if (!leaf2) {
2054
+ throw new Error(`Cannot derive dependency name from URL source: ${source}`);
2055
+ }
2056
+ return leaf2;
2057
+ }
2058
+ if (sourceLooksLikeRepoShorthand(source)) {
2059
+ return parseRepoSource(source).repoName;
2060
+ }
2061
+ const leaf = path9.basename(source);
2062
+ if (!leaf || leaf === "." || leaf === path9.sep) {
2063
+ throw new Error(`Cannot derive dependency name from source: ${source}`);
2064
+ }
2065
+ return leaf;
2066
+ }
2067
+ function classifyDependencySource(source) {
2068
+ if (sourceLooksLikeUrl(source)) {
2069
+ return "url";
2070
+ }
2071
+ if (sourceLooksLikeRepoShorthand(source)) {
2072
+ return "repo";
2073
+ }
2074
+ return "local";
2075
+ }
2076
+ function parseConfigObject(raw) {
2077
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
2078
+ throw new Error("Invalid skill-installer config: expected an object with top-level keys");
2079
+ }
2080
+ const parsed = raw;
2081
+ if (typeof parsed["skill-installer"] !== "undefined") {
2082
+ throw new Error(
2083
+ "Invalid skill-installer config: do not nest under 'skill-installer:'. Use top-level 'pre-install', 'dependencies', and 'post-install'."
2084
+ );
2085
+ }
2086
+ if (Array.isArray(parsed.dependencies)) {
2087
+ const hasLegacyDependency = parsed.dependencies.some((entry) => {
2088
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
2089
+ return false;
2090
+ }
2091
+ const row = entry;
2092
+ return typeof row.type !== "undefined" || typeof row.url !== "undefined" || typeof row.name !== "undefined";
2093
+ });
2094
+ if (hasLegacyDependency) {
2095
+ throw new Error(
2096
+ "Invalid skill-installer config: legacy dependency format detected. Use schemaVersion: 1 and the lean dependencies format from docs/proposed-skill-format.md."
2097
+ );
2098
+ }
2099
+ }
2100
+ const validated = installerConfigSchema.safeParse(parsed);
2101
+ if (!validated.success) {
2102
+ const reason = validated.error.issues.map((issue) => issue.message).join("; ");
2103
+ throw new Error(
2104
+ `Invalid skill-installer config: ${reason}. Use docs/proposed-skill-format.md as the source-of-truth for schemaVersion, dependencies, pre-install, and post-install.`
2105
+ );
2106
+ }
2107
+ const normalized = validated.data;
2108
+ return {
2109
+ schemaVersion: 1,
2110
+ preInstall: normalized["pre-install"],
2111
+ dependencies: normalized.dependencies.map(
2112
+ (dep) => typeof dep === "string" ? dep : { source: dep.source, path: dep.path }
2113
+ ),
2114
+ postInstall: normalized["post-install"]
2115
+ };
2116
+ }
2117
+ function assertNoLegacyInstallerBlock(skillDir) {
2118
+ const openAiYamlPath = path9.join(skillDir, "agents", "openai.yaml");
2119
+ if (!fs8.existsSync(openAiYamlPath) || !fs8.statSync(openAiYamlPath).isFile()) {
2120
+ return;
2121
+ }
2122
+ const parsed = YAML2.parse(fs8.readFileSync(openAiYamlPath, "utf8"));
2123
+ if (parsed && typeof parsed === "object" && typeof parsed["skill-installer"] !== "undefined") {
2124
+ throw new Error(
2125
+ "Legacy skill-installer config detected in agents/openai.yaml. Move it to skill-installer.yaml or skill-installer.json."
2126
+ );
2127
+ }
2128
+ }
2129
+ function loadInstallerConfig(skillDir) {
2130
+ assertNoLegacyInstallerBlock(skillDir);
2131
+ const yamlPath = path9.join(skillDir, "skill-installer.yaml");
2132
+ const jsonPath = path9.join(skillDir, "skill-installer.json");
2133
+ const hasYaml = fs8.existsSync(yamlPath) && fs8.statSync(yamlPath).isFile();
2134
+ const hasJson = fs8.existsSync(jsonPath) && fs8.statSync(jsonPath).isFile();
2135
+ if (hasYaml && hasJson) {
2136
+ throw new Error(
2137
+ "Both skill-installer.yaml and skill-installer.json exist. Keep only one installer config file."
2138
+ );
2139
+ }
2140
+ if (!hasYaml && !hasJson) {
2141
+ return {
2142
+ schemaVersion: 1,
2143
+ preInstall: [],
2144
+ dependencies: [],
2145
+ postInstall: []
2146
+ };
2147
+ }
2148
+ if (hasYaml) {
2149
+ const parsed = YAML2.parse(fs8.readFileSync(yamlPath, "utf8"));
2150
+ return parseConfigObject(parsed);
2151
+ }
2152
+ const raw = JSON.parse(fs8.readFileSync(jsonPath, "utf8"));
2153
+ return parseConfigObject(raw);
2154
+ }
2155
+ function runHookPhase(phase, commands, cwd, events) {
2156
+ for (const command of commands) {
2157
+ events.push({
2158
+ level: "info",
2159
+ message: `[skills] ${phase}: ${command}`
2160
+ });
2161
+ const result = spawnSync2("sh", ["-c", command], {
2162
+ cwd,
2163
+ encoding: "utf8",
2164
+ stdio: "pipe"
2165
+ });
2166
+ if (result.status !== 0) {
2167
+ const message = (result.stderr || result.stdout || "command failed").trim().split(/\r?\n/)[0] || "command failed";
2168
+ throw new Error(`${phase} command failed: ${command} (${message})`);
2169
+ }
2170
+ }
2171
+ }
2172
+ function resolveDependencySourceAndTarget(dep) {
2173
+ if (typeof dep === "string") {
2174
+ return {
2175
+ source: dep,
2176
+ targetPath: deriveDestinationNameFromSource(dep)
2177
+ };
2178
+ }
2179
+ return {
2180
+ source: dep.source,
2181
+ targetPath: dep.path
2182
+ };
2183
+ }
2184
+ function runGitClone(repoUrl, targetDir) {
2185
+ const result = spawnSync2("git", ["clone", "--depth", "1", repoUrl, targetDir], {
2186
+ encoding: "utf8",
2187
+ stdio: "pipe"
2188
+ });
2189
+ if (result.status !== 0) {
2190
+ const message = (result.stderr || result.stdout || "git clone failed").trim().split(/\r?\n/)[0] || "git clone failed";
2191
+ throw new Error(`Repository dependency clone failed: ${repoUrl} (${message})`);
2192
+ }
2193
+ }
2194
+ async function prepareDependency(source, targetPath, index, sourceCwd, stagingDir, policyMode, events) {
2195
+ const sourceKind = classifyDependencySource(source);
2196
+ if (sourceKind === "local") {
2197
+ const evaluation = evaluateInstallerLocalDependencyPolicy(
2198
+ {
2199
+ source,
2200
+ sourceRoot: sourceCwd
2201
+ },
2202
+ policyMode
2203
+ );
2204
+ if (!evaluation.ok) {
2205
+ const violation = "violation" in evaluation ? evaluation.violation : void 0;
2206
+ if (!violation) {
2207
+ throw new Error(`Dependency[${index}] policy evaluation failed for source: ${source}`);
2208
+ }
2209
+ if (violation.blocking) {
2210
+ throw new InstallerSecurityError(violation);
2211
+ }
2212
+ events.push({
2213
+ level: "warning",
2214
+ message: `[skills] ${violation.message}`
2215
+ });
2216
+ }
2217
+ const sourcePath = evaluation.ok ? evaluation.resolvedPath : path9.resolve(sourceCwd, source);
2218
+ if (!fs8.existsSync(sourcePath)) {
2219
+ throw new Error(`Dependency[${index}] (local) source not found: ${source}`);
2220
+ }
2221
+ fs8.accessSync(sourcePath, fs8.constants.R_OK);
2222
+ events.push({
2223
+ level: "info",
2224
+ message: `[skills] dependency[${index}] preflight-validated: ${source}`
2225
+ });
2226
+ return {
2227
+ kind: "local",
2228
+ index,
2229
+ targetPath,
2230
+ sourcePath,
2231
+ sourceLabel: source
2232
+ };
2233
+ }
2234
+ if (sourceKind === "repo") {
2235
+ const stagePath = ensureInsideRoot(
2236
+ stagingDir,
2237
+ `repo-${index}-${path9.basename(targetPath).replace(/[^a-zA-Z0-9._-]/g, "_")}`
2238
+ );
2239
+ const { repoUrl } = parseRepoSource(source);
2240
+ runGitClone(repoUrl, stagePath);
2241
+ events.push({
2242
+ level: "info",
2243
+ message: `[skills] dependency[${index}] staged: ${source}`
2244
+ });
2245
+ return {
2246
+ kind: "repo-staged",
2247
+ index,
2248
+ targetPath,
2249
+ repoStagePath: stagePath,
2250
+ sourceLabel: source
2251
+ };
2252
+ }
2253
+ const target = new URL(source);
2254
+ if (target.protocol !== "http:" && target.protocol !== "https:") {
2255
+ throw new Error(`Dependency[${index}] (remote) unsupported protocol: ${target.protocol}`);
2256
+ }
2257
+ const response = await fetch(target.toString());
2258
+ if (!response.ok) {
2259
+ throw new Error(
2260
+ `Dependency[${index}] (remote) download failed: ${response.status} ${response.statusText}`
2261
+ );
2262
+ }
2263
+ const stagedFilePath = ensureInsideRoot(
2264
+ stagingDir,
2265
+ `remote-${index}-${path9.basename(targetPath)}`
2266
+ );
2267
+ fs8.mkdirSync(path9.dirname(stagedFilePath), { recursive: true });
2268
+ fs8.writeFileSync(stagedFilePath, Buffer.from(await response.arrayBuffer()));
2269
+ events.push({
2270
+ level: "info",
2271
+ message: `[skills] dependency[${index}] staged: ${source}`
2272
+ });
2273
+ return {
2274
+ kind: "remote-bytes",
2275
+ index,
2276
+ targetPath,
2277
+ remoteBufferPath: stagedFilePath,
2278
+ sourceLabel: source
2279
+ };
2280
+ }
2281
+ async function prepareInstallerArtifacts(skillDir, sourceCwd, options = {}) {
2282
+ const config = loadInstallerConfig(skillDir);
2283
+ const sourceType = options.sourceType ?? "local";
2284
+ const allowHookCommands = options.allowHookCommands;
2285
+ const policyMode = options.policyMode ?? "enforce";
2286
+ const events = [];
2287
+ if (config.preInstall.length > 0 || config.postInstall.length > 0) {
2288
+ if (allowHookCommands === false) {
2289
+ throw new Error(
2290
+ "Blocked skill-installer hook commands by caller policy for pre/post install commands."
2291
+ );
2292
+ }
2293
+ const trustDecision = evaluateHookTrustPolicy({
2294
+ sourceType,
2295
+ trustWellKnown: Boolean(options.trustWellKnown),
2296
+ mode: policyMode
2297
+ });
2298
+ if (!trustDecision.allowed && trustDecision.violation) {
2299
+ throw new InstallerPolicyError(trustDecision.violation);
2300
+ }
2301
+ if (trustDecision.allowed && trustDecision.violation?.severity === "warning") {
2302
+ events.push({
2303
+ level: "warning",
2304
+ message: `[skills] ${trustDecision.violation.message}`
2305
+ });
2306
+ }
2307
+ }
2308
+ if (config.preInstall.length === 0 && config.dependencies.length === 0 && config.postInstall.length === 0) {
2309
+ return {
2310
+ preInstall: [],
2311
+ postInstall: [],
2312
+ preparedDependencies: [],
2313
+ stagingDir: "",
2314
+ events
2315
+ };
2316
+ }
2317
+ const stagingDir = fs8.mkdtempSync(path9.join(os4.tmpdir(), "skillspp-installer-stage-"));
2318
+ const preparedDependencies = [];
2319
+ const seenTargetPaths = /* @__PURE__ */ new Set();
2320
+ try {
2321
+ for (let i = 0; i < config.dependencies.length; i += 1) {
2322
+ const dep = config.dependencies[i];
2323
+ const { source, targetPath } = resolveDependencySourceAndTarget(dep);
2324
+ const normalizedTarget = targetPath.replace(/\\/g, "/");
2325
+ if (seenTargetPaths.has(normalizedTarget)) {
2326
+ throw new Error(`Dependency[${i}] duplicate target path: ${targetPath}`);
2327
+ }
2328
+ seenTargetPaths.add(normalizedTarget);
2329
+ const destinationInSkill = ensureInsideRoot(skillDir, targetPath);
2330
+ if (fs8.existsSync(destinationInSkill)) {
2331
+ throw new Error(
2332
+ `Dependency[${i}] destination already exists: ${path9.relative(
2333
+ skillDir,
2334
+ destinationInSkill
2335
+ )}`
2336
+ );
2337
+ }
2338
+ const preparedDep = await prepareDependency(
2339
+ source,
2340
+ targetPath,
2341
+ i,
2342
+ sourceCwd,
2343
+ stagingDir,
2344
+ policyMode,
2345
+ events
2346
+ );
2347
+ preparedDependencies.push(preparedDep);
2348
+ }
2349
+ return {
2350
+ preInstall: config.preInstall,
2351
+ postInstall: config.postInstall,
2352
+ preparedDependencies,
2353
+ stagingDir,
2354
+ events
2355
+ };
2356
+ } catch (error) {
2357
+ fs8.rmSync(stagingDir, { recursive: true, force: true });
2358
+ throw error;
2359
+ }
2360
+ }
2361
+ function cleanupPreparedInstallerArtifacts(prepared) {
2362
+ if (prepared.stagingDir) {
2363
+ fs8.rmSync(prepared.stagingDir, { recursive: true, force: true });
2364
+ }
2365
+ }
2366
+ async function applyInstallerArtifacts(installedSkillDir, prepared) {
2367
+ if (prepared.preInstall.length === 0 && prepared.postInstall.length === 0 && prepared.preparedDependencies.length === 0) {
2368
+ return;
2369
+ }
2370
+ runHookPhase("pre-install", prepared.preInstall, installedSkillDir, prepared.events);
2371
+ for (const dep of prepared.preparedDependencies) {
2372
+ const destinationPath = ensureInsideRoot(installedSkillDir, dep.targetPath);
2373
+ if (fs8.existsSync(destinationPath)) {
2374
+ throw new Error(
2375
+ `Dependency[${dep.index}] destination already exists: ${path9.relative(
2376
+ installedSkillDir,
2377
+ destinationPath
2378
+ )}`
2379
+ );
2380
+ }
2381
+ fs8.mkdirSync(path9.dirname(destinationPath), { recursive: true });
2382
+ if (dep.kind === "local") {
2383
+ const stat = fs8.statSync(dep.sourcePath);
2384
+ if (stat.isDirectory()) {
2385
+ fs8.cpSync(dep.sourcePath, destinationPath, {
2386
+ recursive: true,
2387
+ force: false,
2388
+ errorOnExist: true
2389
+ });
2390
+ } else {
2391
+ fs8.copyFileSync(dep.sourcePath, destinationPath);
2392
+ }
2393
+ } else if (dep.kind === "remote-bytes") {
2394
+ fs8.copyFileSync(dep.remoteBufferPath, destinationPath);
2395
+ } else {
2396
+ try {
2397
+ fs8.renameSync(dep.repoStagePath, destinationPath);
2398
+ } catch (error) {
2399
+ const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "";
2400
+ if (code !== "EXDEV") {
2401
+ throw error;
2402
+ }
2403
+ fs8.cpSync(dep.repoStagePath, destinationPath, {
2404
+ recursive: true,
2405
+ force: false,
2406
+ errorOnExist: true
2407
+ });
2408
+ }
2409
+ }
2410
+ prepared.events.push({
2411
+ level: "info",
2412
+ message: `[skills] dependency[${dep.index}] installed: ${dep.targetPath}`
2413
+ });
2414
+ }
2415
+ runHookPhase("post-install", prepared.postInstall, installedSkillDir, prepared.events);
2416
+ }
2417
+
2418
+ // ../../packages/core/src/contracts/errors/core-error.ts
2419
+ var CoreError = class extends Error {
2420
+ code;
2421
+ details;
2422
+ cause;
2423
+ constructor(input) {
2424
+ super(input.message);
2425
+ this.name = "CoreError";
2426
+ this.code = input.code;
2427
+ this.details = input.details;
2428
+ this.cause = input.cause;
2429
+ }
2430
+ };
2431
+
2432
+ // ../../packages/core/src/runtime/validate-analysis.ts
2433
+ var DEFAULT_MAX_LINES = 500;
2434
+ var DEFAULT_MAX_DESCRIPTION = 1024;
2435
+ function resolveThresholds(options) {
2436
+ return {
2437
+ maxLines: options.maxLines || DEFAULT_MAX_LINES,
2438
+ maxDescriptionChars: options.maxDescriptionChars || DEFAULT_MAX_DESCRIPTION
2439
+ };
2440
+ }
2441
+ function addDiagnostic(list, diagnostic) {
2442
+ list.push(diagnostic);
2443
+ }
2444
+ function discoverMarkdownReferences(content) {
2445
+ const refs = /* @__PURE__ */ new Set();
2446
+ const markdownLinkPattern = /\[[^\]]+\]\(([^)]+)\)/g;
2447
+ let match;
2448
+ while ((match = markdownLinkPattern.exec(content)) !== null) {
2449
+ const value = match[1].trim();
2450
+ if (!value || value.startsWith("http://") || value.startsWith("https://") || value.startsWith("#")) {
2451
+ continue;
2452
+ }
2453
+ refs.add(value);
2454
+ }
2455
+ return [...refs];
2456
+ }
2457
+ var typeRules = [
2458
+ {
2459
+ id: "framework-references-dir",
2460
+ when: (frontmatter) => frontmatter.type === "framework",
2461
+ validate: ({ skillDir, skillName, diagnostics }) => {
2462
+ const referencesDir = path10.join(skillDir, "references");
2463
+ if (!fs9.existsSync(referencesDir) || !fs9.statSync(referencesDir).isDirectory()) {
2464
+ addDiagnostic(diagnostics, {
2465
+ severity: "error",
2466
+ skill: skillName,
2467
+ file: referencesDir,
2468
+ rule: "framework-references-required",
2469
+ message: "framework skills must contain a references directory"
2470
+ });
2471
+ }
2472
+ }
2473
+ }
2474
+ ];
2475
+ async function validateInstallerDependencies(skillDir, sourceRoot, diagnostics, skillName) {
2476
+ const installerConfigPath = fs9.existsSync(path10.join(skillDir, "skill-installer.yaml")) ? path10.join(skillDir, "skill-installer.yaml") : path10.join(skillDir, "skill-installer.json");
2477
+ try {
2478
+ const installer = loadInstallerConfig(skillDir);
2479
+ const fallbackRoots = Array.from(
2480
+ /* @__PURE__ */ new Set([path10.resolve(sourceRoot), path10.resolve(process.cwd())])
2481
+ );
2482
+ let hasSecurityViolation = false;
2483
+ for (const dep of installer.dependencies) {
2484
+ const source = typeof dep === "string" ? dep : dep.source;
2485
+ const kind = classifyDependencySource(source);
2486
+ if (kind !== "local") {
2487
+ continue;
2488
+ }
2489
+ let accepted = false;
2490
+ let securityViolation;
2491
+ for (const root of fallbackRoots) {
2492
+ const evaluated = evaluateInstallerLocalDependencyPolicy(
2493
+ {
2494
+ source,
2495
+ sourceRoot: root
2496
+ },
2497
+ "enforce"
2498
+ );
2499
+ if (evaluated.ok) {
2500
+ accepted = true;
2501
+ break;
2502
+ } else {
2503
+ securityViolation = "violation" in evaluated ? evaluated.violation : void 0;
2504
+ }
2505
+ }
2506
+ if (!accepted && securityViolation) {
2507
+ hasSecurityViolation = true;
2508
+ addDiagnostic(diagnostics, {
2509
+ severity: securityViolation.severity,
2510
+ skill: skillName,
2511
+ file: installerConfigPath,
2512
+ rule: securityViolation.rule,
2513
+ message: securityViolation.message
2514
+ });
2515
+ }
2516
+ }
2517
+ if (hasSecurityViolation) {
2518
+ return;
2519
+ }
2520
+ const tempSkillDir = fs9.mkdtempSync(path10.join(os5.tmpdir(), "skillspp-validate-installer-"));
2521
+ try {
2522
+ const filesToCopy = ["SKILL.md", "skill-installer.yaml", "skill-installer.json"];
2523
+ for (const fileName of filesToCopy) {
2524
+ const src = path10.join(skillDir, fileName);
2525
+ if (fs9.existsSync(src) && fs9.statSync(src).isFile()) {
2526
+ fs9.copyFileSync(src, path10.join(tempSkillDir, fileName));
2527
+ }
2528
+ }
2529
+ const agentsDir = path10.join(skillDir, "agents");
2530
+ if (fs9.existsSync(agentsDir) && fs9.statSync(agentsDir).isDirectory()) {
2531
+ fs9.cpSync(agentsDir, path10.join(tempSkillDir, "agents"), {
2532
+ recursive: true,
2533
+ force: true
2534
+ });
2535
+ }
2536
+ let preparedSuccess = false;
2537
+ let lastMissingSourceError;
2538
+ let securityError;
2539
+ for (const root of fallbackRoots) {
2540
+ try {
2541
+ const prepared = await prepareInstallerArtifacts(tempSkillDir, root, {
2542
+ sourceType: "local",
2543
+ allowHookCommands: false,
2544
+ policyMode: "enforce"
2545
+ });
2546
+ cleanupPreparedInstallerArtifacts(prepared);
2547
+ preparedSuccess = true;
2548
+ break;
2549
+ } catch (error) {
2550
+ if (isInstallerSecurityError(error)) {
2551
+ securityError = error;
2552
+ break;
2553
+ }
2554
+ if (isInstallerPolicyError(error)) {
2555
+ securityError = error;
2556
+ break;
2557
+ }
2558
+ const message = error instanceof Error ? error.message : String(error);
2559
+ if (message.includes("(local) source not found")) {
2560
+ lastMissingSourceError = error;
2561
+ continue;
2562
+ }
2563
+ throw error;
2564
+ }
2565
+ }
2566
+ if (!preparedSuccess && securityError) {
2567
+ throw securityError;
2568
+ }
2569
+ if (!preparedSuccess && lastMissingSourceError) {
2570
+ throw lastMissingSourceError;
2571
+ }
2572
+ } finally {
2573
+ fs9.rmSync(tempSkillDir, { recursive: true, force: true });
2574
+ }
2575
+ } catch (error) {
2576
+ if (isInstallerSecurityError(error)) {
2577
+ addDiagnostic(diagnostics, {
2578
+ severity: error.violation.severity,
2579
+ skill: skillName,
2580
+ file: installerConfigPath,
2581
+ rule: error.violation.rule,
2582
+ message: error.violation.message
2583
+ });
2584
+ return;
2585
+ }
2586
+ if (isInstallerPolicyError(error)) {
2587
+ addDiagnostic(diagnostics, {
2588
+ severity: "error",
2589
+ skill: skillName,
2590
+ file: installerConfigPath,
2591
+ rule: error.violation.rule,
2592
+ message: error.violation.message
2593
+ });
2594
+ return;
2595
+ }
2596
+ addDiagnostic(diagnostics, {
2597
+ severity: "warning",
2598
+ skill: skillName,
2599
+ file: installerConfigPath,
2600
+ rule: "missing-installer-local-dependency",
2601
+ message: error instanceof Error ? error.message : String(error)
2602
+ });
2603
+ }
2604
+ }
2605
+ async function validateSkillDir(skillDir, sourceRoot, diagnostics, strict, thresholds) {
2606
+ const skillMd = path10.join(skillDir, "SKILL.md");
2607
+ const skillName = path10.basename(skillDir);
2608
+ if (!fs9.existsSync(skillMd)) {
2609
+ addDiagnostic(diagnostics, {
2610
+ severity: "error",
2611
+ skill: skillName,
2612
+ file: skillMd,
2613
+ rule: "missing-skill-md",
2614
+ message: "SKILL.md is required"
2615
+ });
2616
+ return;
2617
+ }
2618
+ const content = fs9.readFileSync(skillMd, "utf8");
2619
+ const lines = content.split(/\r?\n/);
2620
+ if (lines.length > thresholds.maxLines) {
2621
+ addDiagnostic(diagnostics, {
2622
+ severity: strict ? "error" : "warning",
2623
+ skill: skillName,
2624
+ file: skillMd,
2625
+ rule: "line-budget",
2626
+ message: `SKILL.md has ${lines.length} lines (limit ${thresholds.maxLines})`
2627
+ });
2628
+ }
2629
+ let parsed;
2630
+ try {
2631
+ parsed = matter2(content);
2632
+ } catch (error) {
2633
+ addDiagnostic(diagnostics, {
2634
+ severity: "error",
2635
+ skill: skillName,
2636
+ file: skillMd,
2637
+ rule: "invalid-frontmatter",
2638
+ message: error instanceof Error ? error.message : "frontmatter parsing failed"
2639
+ });
2640
+ return;
2641
+ }
2642
+ const data = parsed.data || {};
2643
+ const name = data.name;
2644
+ const description = data.description;
2645
+ if (typeof name !== "string" || !name.trim()) {
2646
+ addDiagnostic(diagnostics, {
2647
+ severity: "error",
2648
+ skill: skillName,
2649
+ file: skillMd,
2650
+ rule: "missing-name",
2651
+ message: "frontmatter field 'name' is required"
2652
+ });
2653
+ }
2654
+ if (typeof description !== "string" || !description.trim()) {
2655
+ addDiagnostic(diagnostics, {
2656
+ severity: "error",
2657
+ skill: skillName,
2658
+ file: skillMd,
2659
+ rule: "missing-description",
2660
+ message: "frontmatter field 'description' is required"
2661
+ });
2662
+ } else if (description.length > thresholds.maxDescriptionChars) {
2663
+ addDiagnostic(diagnostics, {
2664
+ severity: strict ? "error" : "warning",
2665
+ skill: skillName,
2666
+ file: skillMd,
2667
+ rule: "description-budget",
2668
+ message: `description has ${description.length} chars (limit ${thresholds.maxDescriptionChars})`
2669
+ });
2670
+ }
2671
+ if (typeof name === "string" && name.trim()) {
2672
+ const normalized = name.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[.-]+|[.-]+$/g, "");
2673
+ if (normalized && normalized !== skillName.toLowerCase()) {
2674
+ addDiagnostic(diagnostics, {
2675
+ severity: "error",
2676
+ skill: skillName,
2677
+ file: skillMd,
2678
+ rule: "name-path-mismatch",
2679
+ message: `frontmatter name '${name}' does not match directory '${skillName}'`
2680
+ });
2681
+ }
2682
+ }
2683
+ for (const ref of discoverMarkdownReferences(content)) {
2684
+ const resolved = path10.resolve(skillDir, ref);
2685
+ const rel = path10.relative(skillDir, resolved);
2686
+ if (!rel || rel.startsWith("..") || path10.isAbsolute(rel)) {
2687
+ continue;
2688
+ }
2689
+ if (!fs9.existsSync(resolved)) {
2690
+ addDiagnostic(diagnostics, {
2691
+ severity: "error",
2692
+ skill: skillName,
2693
+ file: skillMd,
2694
+ rule: "missing-reference",
2695
+ message: `referenced path not found: ${ref}`
2696
+ });
2697
+ }
2698
+ }
2699
+ for (const rule of typeRules) {
2700
+ if (rule.when(data)) {
2701
+ rule.validate({ skillDir, skillName, diagnostics });
2702
+ }
2703
+ }
2704
+ await validateInstallerDependencies(skillDir, sourceRoot, diagnostics, skillName);
2705
+ }
2706
+ async function stageAndValidateLocalRoot(rootPath, dependencyRoot, seenSkillPaths, diagnostics, strict, thresholds, emitProgress) {
2707
+ const resolvedRoot = path10.resolve(rootPath);
2708
+ if (!fs9.existsSync(resolvedRoot) || !fs9.statSync(resolvedRoot).isDirectory()) {
2709
+ addDiagnostic(diagnostics, {
2710
+ severity: "error",
2711
+ skill: path10.basename(resolvedRoot),
2712
+ file: resolvedRoot,
2713
+ rule: "missing-root",
2714
+ message: "validation root does not exist"
2715
+ });
2716
+ return;
2717
+ }
2718
+ await emitProgress?.("discovering candidate skills");
2719
+ const skills = discoverSkills(resolvedRoot);
2720
+ if (skills.length === 0) {
2721
+ addDiagnostic(diagnostics, {
2722
+ severity: "error",
2723
+ skill: path10.basename(resolvedRoot),
2724
+ file: resolvedRoot,
2725
+ rule: "no-skills-discovered",
2726
+ message: "no SKILL.md files discovered"
2727
+ });
2728
+ return;
2729
+ }
2730
+ for (const skill of skills) {
2731
+ const resolvedSkillPath = path10.resolve(skill.path);
2732
+ if (seenSkillPaths.has(resolvedSkillPath)) {
2733
+ continue;
2734
+ }
2735
+ seenSkillPaths.add(resolvedSkillPath);
2736
+ await emitProgress?.(`validating ${skill.name}`);
2737
+ await validateSkillDir(skill.path, dependencyRoot, diagnostics, strict, thresholds);
2738
+ }
2739
+ }
2740
+ async function stageAndValidateSource(options, diagnostics, thresholds, emitProgress) {
2741
+ if (!options.source) {
2742
+ throw new CoreError({
2743
+ code: "VALIDATION_MISSING_SOURCE",
2744
+ message: "validate requires a source unless CI mode is enabled"
2745
+ });
2746
+ }
2747
+ await emitProgress?.("parsing source");
2748
+ const parsed = parseSource(options.source);
2749
+ if (parsed.type === "well-known" || parsed.type === "catalog") {
2750
+ await emitProgress?.("discovering candidate skills");
2751
+ const remote = parsed.type === "well-known" ? await resolveWellKnownSkills(parsed.url, {
2752
+ allowHost: options.allowHost,
2753
+ denyHost: options.denyHost,
2754
+ maxDownloadBytes: options.maxDownloadBytes,
2755
+ experimental: options.experimental
2756
+ }) : await resolveCatalogSkills(parsed.url, {
2757
+ allowHost: options.allowHost,
2758
+ denyHost: options.denyHost,
2759
+ maxDownloadBytes: options.maxDownloadBytes,
2760
+ experimental: options.experimental
2761
+ });
2762
+ if (remote.length === 0) {
2763
+ addDiagnostic(diagnostics, {
2764
+ severity: "error",
2765
+ skill: parsed.url,
2766
+ file: parsed.url,
2767
+ rule: "no-skills-discovered",
2768
+ message: "no SKILL.md files discovered"
2769
+ });
2770
+ return;
2771
+ }
2772
+ const stagedRoots = [];
2773
+ try {
2774
+ for (const remoteSkill of remote) {
2775
+ const staged2 = stageRemoteSkillFilesToTempDir(remoteSkill.files, {
2776
+ prefix: "skillspp-validate-"
2777
+ });
2778
+ stagedRoots.push(staged2);
2779
+ await emitProgress?.(`validating ${remoteSkill.installName}`);
2780
+ await validateSkillDir(
2781
+ staged2.path,
2782
+ staged2.path,
2783
+ diagnostics,
2784
+ Boolean(options.strict),
2785
+ thresholds
2786
+ );
2787
+ }
2788
+ } finally {
2789
+ for (const staged2 of stagedRoots) {
2790
+ staged2.cleanup();
2791
+ }
2792
+ }
2793
+ return;
2794
+ }
2795
+ const staged = prepareSourceDir(parsed);
2796
+ try {
2797
+ await emitProgress?.("staging source");
2798
+ await stageAndValidateLocalRoot(
2799
+ staged.basePath,
2800
+ staged.basePath,
2801
+ /* @__PURE__ */ new Set(),
2802
+ diagnostics,
2803
+ Boolean(options.strict),
2804
+ thresholds,
2805
+ emitProgress
2806
+ );
2807
+ } finally {
2808
+ if (staged.cleanup) {
2809
+ staged.cleanup();
2810
+ }
2811
+ }
2812
+ }
2813
+ async function runValidateAnalysis(options, emitProgress) {
2814
+ const diagnostics = [];
2815
+ const thresholds = resolveThresholds(options);
2816
+ const seenSkillPaths = /* @__PURE__ */ new Set();
2817
+ if (options.ci) {
2818
+ await emitProgress?.("staging source");
2819
+ const roots = options.roots && options.roots.length > 0 ? options.roots : [process.cwd()];
2820
+ if (roots.length === 0) {
2821
+ throw new Error("No CI roots found to validate");
2822
+ }
2823
+ await emitProgress?.("discovering candidate skills");
2824
+ for (const root of roots) {
2825
+ await stageAndValidateLocalRoot(
2826
+ root,
2827
+ process.cwd(),
2828
+ seenSkillPaths,
2829
+ diagnostics,
2830
+ Boolean(options.strict),
2831
+ thresholds,
2832
+ emitProgress
2833
+ );
2834
+ }
2835
+ } else {
2836
+ await stageAndValidateSource(options, diagnostics, thresholds, emitProgress);
2837
+ }
2838
+ await emitProgress?.("collecting diagnostics");
2839
+ return { diagnostics };
2840
+ }
2841
+
2842
+ // ../../packages/core/src/runtime/installer.ts
2843
+ import fs10 from "node:fs";
2844
+ import path11 from "node:path";
2845
+ function sanitizeSkillName(name) {
2846
+ const sanitized = name.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[.-]+|[.-]+$/g, "");
2847
+ return sanitized || "unnamed-skill";
2848
+ }
2849
+ function ensureSafeInside(baseDir, target) {
2850
+ const resolvedBase = path11.resolve(baseDir);
2851
+ const resolvedTarget = path11.resolve(target);
2852
+ if (!(resolvedTarget === resolvedBase || resolvedTarget.startsWith(`${resolvedBase}${path11.sep}`))) {
2853
+ throw new Error(`Unsafe path detected: ${target}`);
2854
+ }
2855
+ }
2856
+ function symlinkRelative(target, linkPath) {
2857
+ const parent = path11.dirname(linkPath);
2858
+ const relative = path11.relative(parent, target);
2859
+ const symlinkType = process.platform === "win32" ? "junction" : void 0;
2860
+ fs10.symlinkSync(relative, linkPath, symlinkType);
2861
+ }
2862
+ function installToAgent(itemName, canonicalDir, agent, mode, globalInstall, cwd, resolveAgentBaseDir) {
2863
+ const agentBase = resolveAgentBaseDir(agent, globalInstall, cwd);
2864
+ const agentDir = path11.join(agentBase, itemName);
2865
+ fs10.mkdirSync(agentBase, { recursive: true });
2866
+ ensureSafeInside(agentBase, agentDir);
2867
+ if (path11.resolve(agentDir) === path11.resolve(canonicalDir)) {
2868
+ return { agent, path: canonicalDir, mode };
2869
+ }
2870
+ if (mode === "copy") {
2871
+ fs10.rmSync(agentDir, { recursive: true, force: true });
2872
+ fs10.cpSync(canonicalDir, agentDir, { recursive: true, force: true });
2873
+ return { agent, path: agentDir, mode };
2874
+ }
2875
+ try {
2876
+ fs10.rmSync(agentDir, { recursive: true, force: true });
2877
+ symlinkRelative(canonicalDir, agentDir);
2878
+ return { agent, path: agentDir, mode: "symlink" };
2879
+ } catch {
2880
+ fs10.rmSync(agentDir, { recursive: true, force: true });
2881
+ fs10.cpSync(canonicalDir, agentDir, { recursive: true, force: true });
2882
+ return { agent, path: agentDir, mode: "copy" };
2883
+ }
2884
+ }
2885
+ function installSkillToAgent(skillName, canonicalDir, agent, mode, globalInstall, cwd) {
2886
+ return installToAgent(
2887
+ skillName,
2888
+ canonicalDir,
2889
+ agent,
2890
+ mode,
2891
+ globalInstall,
2892
+ cwd,
2893
+ getAgentSkillsDir
2894
+ );
2895
+ }
2896
+ function installToCanonicalDir(sourcePath, itemName, canonicalBase) {
2897
+ const canonicalDir = path11.join(canonicalBase, itemName);
2898
+ fs10.mkdirSync(canonicalBase, { recursive: true });
2899
+ ensureSafeInside(canonicalBase, canonicalDir);
2900
+ fs10.rmSync(canonicalDir, { recursive: true, force: true });
2901
+ fs10.cpSync(sourcePath, canonicalDir, { recursive: true, force: true });
2902
+ return canonicalDir;
2903
+ }
2904
+ function installSkill(skill, agents, options) {
2905
+ if (agents.length === 0) {
2906
+ throw new Error("At least one target agent is required for installation.");
2907
+ }
2908
+ const uniqueAgents = Array.from(new Set(agents));
2909
+ const skillName = sanitizeSkillName(skill.name);
2910
+ const canonicalAgent = uniqueAgents[0];
2911
+ const canonicalBase = getAgentSkillsDir(canonicalAgent, options.globalInstall, options.cwd);
2912
+ const canonicalDir = installToCanonicalDir(skill.path, skillName, canonicalBase);
2913
+ const installedTo = [
2914
+ { agent: canonicalAgent, path: canonicalDir, mode: options.mode },
2915
+ ...uniqueAgents.slice(1).map(
2916
+ (agent) => installSkillToAgent(
2917
+ skillName,
2918
+ canonicalDir,
2919
+ agent,
2920
+ options.mode,
2921
+ options.globalInstall,
2922
+ options.cwd
2923
+ )
2924
+ )
2925
+ ];
2926
+ return {
2927
+ skillName,
2928
+ canonicalDir,
2929
+ installedTo
2930
+ };
2931
+ }
2932
+
2933
+ // ../../packages/core/src/sources/scanner.ts
2934
+ import fs11 from "node:fs";
2935
+ import os6 from "node:os";
2936
+ import path12 from "node:path";
2937
+ function listDirs(dir) {
2938
+ if (!fs11.existsSync(dir) || !fs11.statSync(dir).isDirectory()) {
2939
+ return [];
2940
+ }
2941
+ return fs11.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
2942
+ }
2943
+ function detectLocalGlobalConflicts(cwd) {
2944
+ const localDir = path12.join(cwd, ".agents", "skills");
2945
+ const globalDir = path12.join(os6.homedir(), ".config", "agents", "skills");
2946
+ const local = new Set(listDirs(localDir));
2947
+ const global = new Set(listDirs(globalDir));
2948
+ const overlap = [...local].filter((name) => global.has(name));
2949
+ return overlap.sort((a, b) => a.localeCompare(b)).map((skillName) => ({
2950
+ skillName,
2951
+ winner: "local"
2952
+ }));
2953
+ }
2954
+ function readPackageMeta(packageDir) {
2955
+ const packageJson = path12.join(packageDir, "package.json");
2956
+ if (!fs11.existsSync(packageJson) || !fs11.statSync(packageJson).isFile()) {
2957
+ return {
2958
+ name: path12.basename(packageDir),
2959
+ version: "0.0.0"
2960
+ };
2961
+ }
2962
+ const parsed = JSON.parse(fs11.readFileSync(packageJson, "utf8"));
2963
+ return {
2964
+ name: parsed.name || path12.basename(packageDir),
2965
+ version: parsed.version || "0.0.0"
2966
+ };
2967
+ }
2968
+ function collectSkillsFromPackage(packageDir, depth, out) {
2969
+ const meta = readPackageMeta(packageDir);
2970
+ const skillsDir = path12.join(packageDir, "skills");
2971
+ if (fs11.existsSync(skillsDir) && fs11.statSync(skillsDir).isDirectory()) {
2972
+ for (const entry of fs11.readdirSync(skillsDir, { withFileTypes: true })) {
2973
+ if (!entry.isDirectory()) {
2974
+ continue;
2975
+ }
2976
+ const skillDir = path12.join(skillsDir, entry.name);
2977
+ const skillMd = path12.join(skillDir, "SKILL.md");
2978
+ if (!fs11.existsSync(skillMd) || !fs11.statSync(skillMd).isFile()) {
2979
+ continue;
2980
+ }
2981
+ out.push({
2982
+ skillName: entry.name,
2983
+ skillDir,
2984
+ packageName: meta.name,
2985
+ packageVersion: meta.version,
2986
+ depth
2987
+ });
2988
+ }
2989
+ }
2990
+ }
2991
+ function walkNodeModules(nodeModulesDir, depth, maxDepth, seen, out) {
2992
+ const resolvedNodeModules = path12.resolve(nodeModulesDir);
2993
+ if (seen.has(resolvedNodeModules) || depth > maxDepth) {
2994
+ return;
2995
+ }
2996
+ seen.add(resolvedNodeModules);
2997
+ if (!fs11.existsSync(resolvedNodeModules) || !fs11.statSync(resolvedNodeModules).isDirectory()) {
2998
+ return;
2999
+ }
3000
+ for (const entry of fs11.readdirSync(resolvedNodeModules, {
3001
+ withFileTypes: true
3002
+ })) {
3003
+ if (!entry.isDirectory()) {
3004
+ continue;
3005
+ }
3006
+ if (entry.name.startsWith("@")) {
3007
+ const scopeDir = path12.join(resolvedNodeModules, entry.name);
3008
+ for (const scoped of fs11.readdirSync(scopeDir, { withFileTypes: true })) {
3009
+ if (!scoped.isDirectory()) {
3010
+ continue;
3011
+ }
3012
+ const packageDir2 = path12.join(scopeDir, scoped.name);
3013
+ collectSkillsFromPackage(packageDir2, depth, out);
3014
+ walkNodeModules(path12.join(packageDir2, "node_modules"), depth + 1, maxDepth, seen, out);
3015
+ }
3016
+ continue;
3017
+ }
3018
+ const packageDir = path12.join(resolvedNodeModules, entry.name);
3019
+ collectSkillsFromPackage(packageDir, depth, out);
3020
+ walkNodeModules(path12.join(packageDir, "node_modules"), depth + 1, maxDepth, seen, out);
3021
+ }
3022
+ }
3023
+ function discoverTransitiveSkillCandidates(cwd, options = {}) {
3024
+ const maxDepth = options.maxDepth ?? 8;
3025
+ const out = [];
3026
+ const seen = /* @__PURE__ */ new Set();
3027
+ walkNodeModules(path12.join(cwd, "node_modules"), 0, maxDepth, seen, out);
3028
+ return out.sort((a, b) => {
3029
+ if (a.skillName !== b.skillName) {
3030
+ return a.skillName.localeCompare(b.skillName);
3031
+ }
3032
+ if (a.depth !== b.depth) {
3033
+ return a.depth - b.depth;
3034
+ }
3035
+ if (a.packageName !== b.packageName) {
3036
+ return a.packageName.localeCompare(b.packageName);
3037
+ }
3038
+ if (a.packageVersion !== b.packageVersion) {
3039
+ return b.packageVersion.localeCompare(a.packageVersion);
3040
+ }
3041
+ return a.skillDir.localeCompare(b.skillDir);
3042
+ });
3043
+ }
3044
+ function detectTransitiveSkillConflicts(candidates) {
3045
+ const grouped = /* @__PURE__ */ new Map();
3046
+ for (const candidate of candidates) {
3047
+ const rows = grouped.get(candidate.skillName) || [];
3048
+ rows.push(candidate);
3049
+ grouped.set(candidate.skillName, rows);
3050
+ }
3051
+ const conflicts = [];
3052
+ for (const [skillName, rows] of grouped) {
3053
+ if (rows.length <= 1) {
3054
+ continue;
3055
+ }
3056
+ const sorted = [...rows].sort((a, b) => {
3057
+ if (a.depth !== b.depth) {
3058
+ return a.depth - b.depth;
3059
+ }
3060
+ if (a.packageName !== b.packageName) {
3061
+ return a.packageName.localeCompare(b.packageName);
3062
+ }
3063
+ if (a.packageVersion !== b.packageVersion) {
3064
+ return b.packageVersion.localeCompare(a.packageVersion);
3065
+ }
3066
+ return a.skillDir.localeCompare(b.skillDir);
3067
+ });
3068
+ conflicts.push({
3069
+ skillName,
3070
+ winner: sorted[0],
3071
+ losers: sorted.slice(1)
3072
+ });
3073
+ }
3074
+ return conflicts.sort((a, b) => a.skillName.localeCompare(b.skillName));
3075
+ }
3076
+
3077
+ // ../../packages/core/src/runtime/installer-scaffold.ts
3078
+ import fs12 from "node:fs";
3079
+ import path13 from "node:path";
3080
+ import YAML3 from "yaml";
3081
+ function installerConfigSkeleton() {
3082
+ return {
3083
+ schemaVersion: 1,
3084
+ dependencies: [],
3085
+ "pre-install": [],
3086
+ "post-install": []
3087
+ };
3088
+ }
3089
+ function isFile(filePath) {
3090
+ return fs12.existsSync(filePath) && fs12.statSync(filePath).isFile();
3091
+ }
3092
+ function getInstallerConfigState(skillDir) {
3093
+ const yamlPath = path13.join(skillDir, "skill-installer.yaml");
3094
+ const jsonPath = path13.join(skillDir, "skill-installer.json");
3095
+ const hasYaml = isFile(yamlPath);
3096
+ const hasJson = isFile(jsonPath);
3097
+ if (hasYaml && hasJson) {
3098
+ throw new Error(
3099
+ "Both skill-installer.yaml and skill-installer.json exist. Keep only one installer config file."
3100
+ );
3101
+ }
3102
+ return {
3103
+ yamlPath,
3104
+ jsonPath,
3105
+ hasYaml,
3106
+ hasJson,
3107
+ missing: !hasYaml && !hasJson
3108
+ };
3109
+ }
3110
+ function listSkillsMissingInstallerConfig(skillDirs) {
3111
+ return skillDirs.filter((skillDir) => getInstallerConfigState(skillDir).missing);
3112
+ }
3113
+ function scaffoldInstallerConfigFile(skillDir, format) {
3114
+ const state = getInstallerConfigState(skillDir);
3115
+ if (!state.missing) {
3116
+ return { created: false };
3117
+ }
3118
+ const content = format === "yaml" ? YAML3.stringify(installerConfigSkeleton()) : JSON.stringify(installerConfigSkeleton(), null, 2);
3119
+ const destinationPath = format === "yaml" ? state.yamlPath : state.jsonPath;
3120
+ fs12.writeFileSync(destinationPath, `${content}
3121
+ `, "utf8");
3122
+ return { created: true, filePath: destinationPath };
3123
+ }
3124
+ function scaffoldInstallerConfigForSkills(skillDirs, format) {
3125
+ const created = [];
3126
+ for (const skillDir of skillDirs) {
3127
+ const result = scaffoldInstallerConfigFile(skillDir, format);
3128
+ if (result.created && result.filePath) {
3129
+ created.push(result.filePath);
3130
+ }
3131
+ }
3132
+ return created;
3133
+ }
3134
+
3135
+ // ../../packages/core/src/runtime/background-tasks.ts
3136
+ function serializeAssessments(assessments) {
3137
+ return assessments.map((assessment) => ({
3138
+ entry: assessment.entry,
3139
+ drift: assessment.drift
3140
+ }));
3141
+ }
3142
+ function resolveAddGlobalInstall(options) {
3143
+ if (options.globalFlagProvided) {
3144
+ return Boolean(options.global);
3145
+ }
3146
+ return false;
3147
+ }
3148
+ function resolveAddInstallMode(options) {
3149
+ if (options.symlinkFlagProvided) {
3150
+ return "symlink";
3151
+ }
3152
+ return "copy";
3153
+ }
3154
+ function resolveAddInstallerScaffoldFormat(options, missingCount) {
3155
+ if (missingCount === 0) {
3156
+ return void 0;
3157
+ }
3158
+ return options.yaml ? "yaml" : "json";
3159
+ }
3160
+ function sourceHashForInstalledSkill(options) {
3161
+ if (options.parsedSource.type === "local") {
3162
+ return hashDirectory(options.skillPath);
3163
+ }
3164
+ return options.beforeHash || hashDirectory(options.skillPath);
3165
+ }
3166
+ function canonicalSourceIdentity(options) {
3167
+ if (options.parsedSource.type === "local") {
3168
+ return options.parsedSource.localPath;
3169
+ }
3170
+ if (options.parsedSource.type === "well-known") {
3171
+ return options.wellKnownSourceUrl || options.parsedSource.url;
3172
+ }
3173
+ if (options.parsedSource.type === "catalog") {
3174
+ return options.wellKnownSourceUrl || options.parsedSource.url;
3175
+ }
3176
+ if (options.parsedSource.type === "github") {
3177
+ const suffix = options.parsedSource.subpath ? `#${options.parsedSource.subpath}` : "";
3178
+ return `${options.parsedSource.repoUrl}${suffix}`;
3179
+ }
3180
+ return options.parsedSource.repoUrl;
3181
+ }
3182
+ function resolveSafeRealPath2(inputPath) {
3183
+ try {
3184
+ return fs13.realpathSync(inputPath);
3185
+ } catch {
3186
+ return path14.resolve(inputPath);
3187
+ }
3188
+ }
3189
+ function isLocalSymlinkSource2(localPath) {
3190
+ try {
3191
+ if (!fs13.existsSync(localPath)) {
3192
+ return false;
3193
+ }
3194
+ return fs13.lstatSync(localPath).isSymbolicLink();
3195
+ } catch {
3196
+ return false;
3197
+ }
3198
+ }
3199
+ function lockfileNameForFormat(format) {
3200
+ return format === "yaml" ? "skillspp-lock.yaml" : "skillspp-lock.json";
3201
+ }
3202
+ function staleLockfileNameForFormat(format) {
3203
+ return format === "yaml" ? "skillspp-lock.json" : "skillspp-lock.yaml";
3204
+ }
3205
+ function propagateLockfileVisibility(options) {
3206
+ const lockName = lockfileNameForFormat(options.lockFormat);
3207
+ const staleLockName = staleLockfileNameForFormat(options.lockFormat);
3208
+ const canonicalLockPath = path14.join(options.canonicalDir, lockName);
3209
+ const canonicalRealPath = fs13.existsSync(options.canonicalDir) ? fs13.realpathSync(options.canonicalDir) : path14.resolve(options.canonicalDir);
3210
+ for (const targetDir of options.targetDirs) {
3211
+ const targetRealPath = fs13.existsSync(targetDir) ? fs13.realpathSync(targetDir) : path14.resolve(targetDir);
3212
+ if (targetRealPath === canonicalRealPath) {
3213
+ continue;
3214
+ }
3215
+ const targetLockPath = path14.join(targetDir, lockName);
3216
+ const staleTargetPath = path14.join(targetDir, staleLockName);
3217
+ fs13.copyFileSync(canonicalLockPath, targetLockPath);
3218
+ if (fs13.existsSync(staleTargetPath)) {
3219
+ fs13.rmSync(staleTargetPath, { force: true });
3220
+ }
3221
+ }
3222
+ }
3223
+ function writeLockEntryAfterInstall(options) {
3224
+ const installedHash = hashDirectory(options.outcome.canonicalDir);
3225
+ const resourceKind = options.resourceKind || "skill";
3226
+ const lock = readResourceLockfile(resourceKind, options.globalInstall, options.cwd);
3227
+ const entry = {
3228
+ skillName: options.outcome.skillName,
3229
+ global: options.globalInstall,
3230
+ installMode: options.mode,
3231
+ agents: options.outcome.installedTo.map((row) => row.agent),
3232
+ canonicalDir: options.outcome.canonicalDir,
3233
+ source: {
3234
+ input: options.sourceInput,
3235
+ type: options.sourceType,
3236
+ canonical: options.sourceCanonical,
3237
+ pinnedRef: options.sourcePinnedRef,
3238
+ resolvedPath: options.sourceResolvedPath,
3239
+ isSymlinkSource: options.sourceIsSymlink,
3240
+ selector: {
3241
+ skillName: options.sourceSkillName,
3242
+ relativePath: options.sourceSkillPath,
3243
+ wellKnownSourceUrl: options.wellKnownSourceUrl
3244
+ }
3245
+ },
3246
+ sourceHash: options.sourceHash,
3247
+ installedHash,
3248
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3249
+ };
3250
+ const next = upsertResourceLockEntry(lock, entry);
3251
+ writeResourceLockfile(
3252
+ resourceKind,
3253
+ options.globalInstall,
3254
+ options.cwd,
3255
+ next,
3256
+ options.lockFormat || "json"
3257
+ );
3258
+ const selectedFormat = options.lockFormat || "json";
3259
+ propagateLockfileVisibility({
3260
+ canonicalDir: options.outcome.canonicalDir,
3261
+ targetDirs: options.outcome.installedTo.map((destination) => destination.path),
3262
+ lockFormat: selectedFormat
3263
+ });
3264
+ const staleLockPath = path14.join(
3265
+ options.outcome.canonicalDir,
3266
+ staleLockfileNameForFormat(selectedFormat)
3267
+ );
3268
+ if (fs13.existsSync(staleLockPath)) {
3269
+ fs13.rmSync(staleLockPath, { force: true });
3270
+ }
3271
+ }
3272
+ function buildRemoteSkill(remote) {
3273
+ const staged = stageRemoteSkillFilesToTempDir(remote.files);
3274
+ return {
3275
+ skill: {
3276
+ name: remote.installName,
3277
+ description: remote.description,
3278
+ path: staged.path
3279
+ },
3280
+ cleanup: staged.cleanup
3281
+ };
3282
+ }
3283
+ async function runCheckScanTask(cwd, options, emitProgress) {
3284
+ await emitProgress("checking drift");
3285
+ const assessed = await assessLockEntries(options, cwd, {
3286
+ keepResolved: false
3287
+ });
3288
+ await emitProgress("checking local/global conflicts");
3289
+ const conflicts = detectLocalGlobalConflicts(cwd);
3290
+ await emitProgress("checking transitive conflicts");
3291
+ const transitiveCandidates = discoverTransitiveSkillCandidates(cwd);
3292
+ const transitiveConflicts = detectTransitiveSkillConflicts(transitiveCandidates);
3293
+ return {
3294
+ drift: assessed.drift,
3295
+ checked: assessed.checked,
3296
+ conflicts,
3297
+ transitiveConflicts
3298
+ };
3299
+ }
3300
+ async function runUpdateAssessTask(cwd, options, emitProgress) {
3301
+ await emitProgress("assessing drift");
3302
+ const assessed = await assessLockEntries(options, cwd, {
3303
+ keepResolved: false
3304
+ });
3305
+ return {
3306
+ assessments: serializeAssessments(assessed.assessments)
3307
+ };
3308
+ }
3309
+ function createBackupDir(skillName, sourceDir) {
3310
+ const backupRoot = fs13.mkdtempSync(path14.join(os7.tmpdir(), "skillspp-update-backup-"));
3311
+ const backupDir = path14.join(backupRoot, skillName);
3312
+ fs13.mkdirSync(backupDir, { recursive: true });
3313
+ fs13.cpSync(sourceDir, backupDir, { recursive: true, force: true });
3314
+ return backupDir;
3315
+ }
3316
+ async function applyEntryUpdate(assessment, options, cwd) {
3317
+ const { entry } = assessment;
3318
+ if (!assessment.resolved || !assessment.sourceHash) {
3319
+ throw new Error(`No resolved source available for ${entry.skillName}`);
3320
+ }
3321
+ const resolved = assessment.resolved;
3322
+ const sourceHash = assessment.sourceHash;
3323
+ const backupDir = createBackupDir(entry.skillName, entry.canonicalDir);
3324
+ try {
3325
+ const preparedInstaller = await prepareInstallerArtifacts(resolved.skill.path, cwd, {
3326
+ sourceType: entry.source.type,
3327
+ policyMode: options.policyMode || "enforce",
3328
+ trustWellKnown: Boolean(options.trustWellKnown)
3329
+ });
3330
+ const outcome = installSkill(resolved.skill, entry.agents, {
3331
+ mode: entry.installMode,
3332
+ globalInstall: entry.global,
3333
+ cwd
3334
+ });
3335
+ try {
3336
+ await applyInstallerArtifacts(outcome.canonicalDir, preparedInstaller);
3337
+ } finally {
3338
+ cleanupPreparedInstallerArtifacts(preparedInstaller);
3339
+ }
3340
+ const installedHash = await hashDirectoryAsync(outcome.canonicalDir);
3341
+ return {
3342
+ ...entry,
3343
+ source: {
3344
+ ...entry.source,
3345
+ canonical: assessment.refreshedSource?.canonical ?? entry.source.canonical,
3346
+ pinnedRef: assessment.refreshedSource?.pinnedRef ?? entry.source.pinnedRef,
3347
+ resolvedPath: assessment.refreshedSource?.resolvedPath ?? entry.source.resolvedPath,
3348
+ isSymlinkSource: assessment.refreshedSource?.isSymlinkSource ?? entry.source.isSymlinkSource,
3349
+ selector: {
3350
+ ...entry.source.selector,
3351
+ relativePath: assessment.refreshedSource?.sourceSkillPath ?? entry.source.selector.relativePath,
3352
+ wellKnownSourceUrl: assessment.refreshedSource?.wellKnownSourceUrl ?? entry.source.selector.wellKnownSourceUrl
3353
+ }
3354
+ },
3355
+ sourceHash,
3356
+ installedHash,
3357
+ canonicalDir: outcome.canonicalDir,
3358
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3359
+ };
3360
+ } catch (error) {
3361
+ const rollbackSkill = {
3362
+ name: entry.skillName,
3363
+ description: `Rollback for ${entry.skillName}`,
3364
+ path: backupDir
3365
+ };
3366
+ installSkill(rollbackSkill, entry.agents, {
3367
+ mode: entry.installMode,
3368
+ globalInstall: entry.global,
3369
+ cwd
3370
+ });
3371
+ throw error;
3372
+ } finally {
3373
+ fs13.rmSync(path14.dirname(backupDir), { recursive: true, force: true });
3374
+ if (resolved.cleanup) {
3375
+ resolved.cleanup();
3376
+ }
3377
+ }
3378
+ }
3379
+ async function runUpdateApplyTask(payload, emitProgress) {
3380
+ const selectedOptions = {
3381
+ ...payload.options,
3382
+ skill: payload.selectedSkillNames
3383
+ };
3384
+ await emitProgress("assessing selected skills");
3385
+ const assessed = await assessLockEntries(selectedOptions, payload.cwd, {
3386
+ keepResolved: true
3387
+ });
3388
+ const candidateAssessments = assessed.assessments.filter(
3389
+ (assessment) => !assessment.drift.some((item) => item.kind === "migrate-required") && assessment.drift.some(
3390
+ (item) => item.kind === "changed-source" || item.kind === "local-modified"
3391
+ )
3392
+ );
3393
+ let nextLock = readLockfile(Boolean(payload.options.global), payload.cwd);
3394
+ const updatedEntries = [];
3395
+ const ordered = [...candidateAssessments].sort(
3396
+ (a, b) => a.entry.skillName.localeCompare(b.entry.skillName)
3397
+ );
3398
+ try {
3399
+ for (const assessment of ordered) {
3400
+ await emitProgress(`updating ${assessment.entry.skillName}`);
3401
+ const updated = await applyEntryUpdate(assessment, payload.options, payload.cwd);
3402
+ updatedEntries.push(updated);
3403
+ nextLock = upsertLockEntry(nextLock, updated);
3404
+ }
3405
+ await emitProgress("writing lockfile");
3406
+ writeLockfile(Boolean(payload.options.global), payload.cwd, nextLock, payload.lockFormat);
3407
+ for (const updated of updatedEntries) {
3408
+ const targetDirs = Array.from(
3409
+ new Set(
3410
+ updated.agents.map(
3411
+ (agent) => path14.join(getAgentSkillsDir(agent, updated.global, payload.cwd), updated.skillName)
3412
+ )
3413
+ )
3414
+ );
3415
+ propagateLockfileVisibility({
3416
+ canonicalDir: updated.canonicalDir,
3417
+ targetDirs,
3418
+ lockFormat: payload.lockFormat
3419
+ });
3420
+ }
3421
+ return {
3422
+ updatedSkillNames: ordered.map((assessment) => assessment.entry.skillName)
3423
+ };
3424
+ } finally {
3425
+ for (const assessment of assessed.assessments) {
3426
+ if (assessment.resolved?.cleanup) {
3427
+ assessment.resolved.cleanup();
3428
+ }
3429
+ }
3430
+ }
3431
+ }
3432
+ async function resolveMigrationSource(options) {
3433
+ const parsedSource = parseSource(options.sourceInput);
3434
+ if (parsedSource.type === "well-known" || parsedSource.type === "catalog") {
3435
+ const remoteSkills = parsedSource.type === "well-known" ? await resolveWellKnownSkills(parsedSource.url, options.addOptions) : await resolveCatalogSkills(parsedSource.url, options.addOptions);
3436
+ const remote = remoteSkills.find((item) => item.installName === options.skillName);
3437
+ if (!remote) {
3438
+ throw new Error(`Skill '${options.skillName}' not found in migration source`);
3439
+ }
3440
+ const staged = buildRemoteSkill(remote);
3441
+ const sourceHash = hashDirectory(staged.skill.path);
3442
+ return {
3443
+ parsedSource,
3444
+ skill: staged.skill,
3445
+ sourceHash,
3446
+ sourceCanonical: canonicalSourceIdentity({
3447
+ parsedSource,
3448
+ wellKnownSourceUrl: remote.sourceUrl
3449
+ }),
3450
+ sourcePinnedRef: sourceHash,
3451
+ wellKnownSourceUrl: remote.sourceUrl,
3452
+ cleanup: staged.cleanup
3453
+ };
3454
+ }
3455
+ const prepared = await prepareSourceDirAsync(
3456
+ parsedSource
3457
+ );
3458
+ try {
3459
+ const skills = await discoverSkillsAsync(prepared.basePath);
3460
+ const skill = skills.find((item) => item.name === options.skillName);
3461
+ if (!skill) {
3462
+ throw new Error(`Skill '${options.skillName}' not found in migration source`);
3463
+ }
3464
+ const sourceHash = sourceHashForInstalledSkill({
3465
+ parsedSource,
3466
+ skillPath: skill.path
3467
+ });
3468
+ const sourcePinnedRef = parsedSource.type === "github" || parsedSource.type === "git" ? await resolveGitHeadRefAsync(prepared.basePath) : void 0;
3469
+ return {
3470
+ parsedSource,
3471
+ skill,
3472
+ sourceSkillPath: path14.relative(prepared.basePath, skill.path) || ".",
3473
+ sourceHash,
3474
+ sourceCanonical: canonicalSourceIdentity({ parsedSource }),
3475
+ sourcePinnedRef,
3476
+ sourceResolvedPath: parsedSource.type === "local" ? resolveSafeRealPath2(skill.path) : void 0,
3477
+ sourceIsSymlink: parsedSource.type === "local" ? isLocalSymlinkSource2(parsedSource.localPath) : void 0,
3478
+ cleanup: prepared.cleanup
3479
+ };
3480
+ } catch (error) {
3481
+ if (prepared.cleanup) {
3482
+ prepared.cleanup();
3483
+ }
3484
+ throw error;
3485
+ }
3486
+ }
3487
+ async function runUpdateMigrateTask(payload, emitProgress) {
3488
+ await emitProgress("resolving migration source");
3489
+ const lock = readLockfile(Boolean(payload.options.global), payload.cwd);
3490
+ const entry = lock.entries.find((item) => item.skillName === payload.skillName);
3491
+ if (!entry) {
3492
+ throw new Error(`Unknown skill for migration: ${payload.skillName}`);
3493
+ }
3494
+ const source = await resolveMigrationSource({
3495
+ sourceInput: payload.sourceInput,
3496
+ skillName: payload.skillName,
3497
+ addOptions: payload.options
3498
+ });
3499
+ const backupDir = createBackupDir(entry.skillName, entry.canonicalDir);
3500
+ try {
3501
+ await emitProgress(`migrating ${entry.skillName}`);
3502
+ const preparedInstaller = await prepareInstallerArtifacts(source.skill.path, payload.cwd, {
3503
+ sourceType: source.parsedSource.type,
3504
+ policyMode: payload.options.policyMode || "enforce",
3505
+ trustWellKnown: Boolean(payload.options.trustWellKnown)
3506
+ });
3507
+ const outcome = installSkill(source.skill, entry.agents, {
3508
+ mode: entry.installMode,
3509
+ globalInstall: entry.global,
3510
+ cwd: payload.cwd
3511
+ });
3512
+ try {
3513
+ await applyInstallerArtifacts(outcome.canonicalDir, preparedInstaller);
3514
+ await emitProgress("writing lockfile");
3515
+ writeLockEntryAfterInstall({
3516
+ globalInstall: entry.global,
3517
+ cwd: payload.cwd,
3518
+ sourceInput: payload.sourceInput,
3519
+ sourceType: source.parsedSource.type,
3520
+ sourceCanonical: source.sourceCanonical,
3521
+ sourcePinnedRef: source.sourcePinnedRef,
3522
+ sourceResolvedPath: source.sourceResolvedPath,
3523
+ sourceIsSymlink: source.sourceIsSymlink,
3524
+ sourceSkillName: source.skill.name,
3525
+ sourceSkillPath: source.sourceSkillPath,
3526
+ wellKnownSourceUrl: source.wellKnownSourceUrl,
3527
+ sourceHash: source.sourceHash,
3528
+ outcome,
3529
+ mode: entry.installMode,
3530
+ lockFormat: payload.lockFormat
3531
+ });
3532
+ } finally {
3533
+ cleanupPreparedInstallerArtifacts(preparedInstaller);
3534
+ }
3535
+ } catch (error) {
3536
+ const rollbackSkill = {
3537
+ name: entry.skillName,
3538
+ description: `Rollback for ${entry.skillName}`,
3539
+ path: backupDir
3540
+ };
3541
+ installSkill(rollbackSkill, entry.agents, {
3542
+ mode: entry.installMode,
3543
+ globalInstall: entry.global,
3544
+ cwd: payload.cwd
3545
+ });
3546
+ throw error;
3547
+ } finally {
3548
+ fs13.rmSync(path14.dirname(backupDir), { recursive: true, force: true });
3549
+ if (source.cleanup) {
3550
+ source.cleanup();
3551
+ }
3552
+ }
3553
+ return { skillName: entry.skillName };
3554
+ }
3555
+ async function runListDetectAgentsTask(cwd, options, emitProgress) {
3556
+ await emitProgress("detecting installed agents");
3557
+ const agents = options.agent ? filterInstalledAgents(resolveAgents(options.agent), cwd) : detectInstalledAgents(cwd);
3558
+ return { agents };
3559
+ }
3560
+ async function runListScanInventoryTask(payload, emitProgress) {
3561
+ const grouped = /* @__PURE__ */ new Map();
3562
+ for (const agent of payload.agents) {
3563
+ await emitProgress(`scanning installed skills (${AGENTS[agent].displayName})`);
3564
+ const dir = getAgentSkillsDir(agent, payload.globalInstall, payload.cwd);
3565
+ if (!fs13.existsSync(dir) || !fs13.statSync(dir).isDirectory()) {
3566
+ continue;
3567
+ }
3568
+ for (const entry of fs13.readdirSync(dir, { withFileTypes: true })) {
3569
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) {
3570
+ continue;
3571
+ }
3572
+ const fullPath = path14.join(dir, entry.name);
3573
+ let resolvedPath = fullPath;
3574
+ try {
3575
+ resolvedPath = fs13.realpathSync(fullPath);
3576
+ } catch {
3577
+ resolvedPath = fullPath;
3578
+ }
3579
+ const key = `${entry.name}:${resolvedPath}`;
3580
+ const existing = grouped.get(key);
3581
+ if (existing) {
3582
+ existing.agents.add(AGENTS[agent].displayName);
3583
+ } else {
3584
+ grouped.set(key, {
3585
+ name: entry.name,
3586
+ resolvedPath,
3587
+ agents: /* @__PURE__ */ new Set([AGENTS[agent].displayName])
3588
+ });
3589
+ }
3590
+ }
3591
+ }
3592
+ const rows = Array.from(grouped.values()).map((row) => ({
3593
+ name: row.name,
3594
+ resolvedPath: row.resolvedPath,
3595
+ agents: Array.from(row.agents).sort((a, b) => a.localeCompare(b))
3596
+ })).sort((a, b) => a.name.localeCompare(b.name));
3597
+ return { rows };
3598
+ }
3599
+ async function resolveAddSourceSkills(sourceInput, options, emitProgress) {
3600
+ const parsed = parseSource(sourceInput);
3601
+ if (parsed.type === "well-known" || parsed.type === "catalog") {
3602
+ await emitProgress("fetching skill index");
3603
+ const remoteSkills = parsed.type === "well-known" ? await resolveWellKnownSkills(parsed.url, options) : await resolveCatalogSkills(parsed.url, options);
3604
+ if (remoteSkills.length === 0) {
3605
+ throw new Error("No skills found at remote endpoint");
3606
+ }
3607
+ return {
3608
+ skills: remoteSkills.map((skill) => ({
3609
+ name: skill.installName,
3610
+ description: skill.description
3611
+ }))
3612
+ };
3613
+ }
3614
+ await emitProgress("loading source");
3615
+ const prepared = await prepareSourceDirAsync(
3616
+ parsed
3617
+ );
3618
+ try {
3619
+ const skills = await discoverSkillsAsync(prepared.basePath);
3620
+ return {
3621
+ skills: skills.map((skill) => ({
3622
+ name: skill.name,
3623
+ description: skill.description
3624
+ }))
3625
+ };
3626
+ } finally {
3627
+ if (prepared.cleanup) {
3628
+ prepared.cleanup();
3629
+ }
3630
+ }
3631
+ }
3632
+ async function installSelectedAddSkills(payload, emitProgress) {
3633
+ const parsedSource = parseSource(payload.sourceInput);
3634
+ const globalInstall = resolveAddGlobalInstall(payload.options);
3635
+ const mode = resolveAddInstallMode(payload.options);
3636
+ if (parsedSource.type === "well-known" || parsedSource.type === "catalog") {
3637
+ await emitProgress("fetching selected skills");
3638
+ const remoteSkills = parsedSource.type === "well-known" ? await resolveWellKnownSkills(parsedSource.url, payload.options) : await resolveCatalogSkills(parsedSource.url, payload.options);
3639
+ const remoteByName = new Map(remoteSkills.map((remote) => [remote.installName, remote]));
3640
+ const tempCleanups = [];
3641
+ const stagedSelected = [];
3642
+ const sourceHashesBefore = /* @__PURE__ */ new Map();
3643
+ try {
3644
+ for (const skillName of payload.selectedSkillNames) {
3645
+ const remote = remoteByName.get(skillName);
3646
+ if (!remote) {
3647
+ throw new Error(`No matching well-known skills found in source`);
3648
+ }
3649
+ const staged = buildRemoteSkill(remote);
3650
+ tempCleanups.push(staged.cleanup);
3651
+ stagedSelected.push(staged.skill);
3652
+ sourceHashesBefore.set(staged.skill.path, hashDirectory(staged.skill.path));
3653
+ }
3654
+ const missingInstallerSkillDirs = listSkillsMissingInstallerConfig(
3655
+ stagedSelected.map((item) => item.path)
3656
+ );
3657
+ const scaffoldFormat = resolveAddInstallerScaffoldFormat(
3658
+ payload.options,
3659
+ missingInstallerSkillDirs.length
3660
+ );
3661
+ if (scaffoldFormat) {
3662
+ scaffoldInstallerConfigForSkills(missingInstallerSkillDirs, scaffoldFormat);
3663
+ }
3664
+ for (const localSkill of stagedSelected) {
3665
+ const remote = remoteByName.get(localSkill.name);
3666
+ if (!remote) {
3667
+ throw new Error(
3668
+ `Could not resolve remote metadata for selected skill '${localSkill.name}'`
3669
+ );
3670
+ }
3671
+ const sourceHash = sourceHashesBefore.get(localSkill.path) || hashDirectory(localSkill.path);
3672
+ const sourceCanonical = canonicalSourceIdentity({
3673
+ parsedSource,
3674
+ wellKnownSourceUrl: remote.sourceUrl
3675
+ });
3676
+ await emitProgress(`installing ${localSkill.name}`);
3677
+ const preparedInstaller = await prepareInstallerArtifacts(localSkill.path, payload.cwd, {
3678
+ sourceType: parsedSource.type,
3679
+ policyMode: payload.options.policyMode || "enforce",
3680
+ trustWellKnown: Boolean(payload.options.trustWellKnown)
3681
+ });
3682
+ const outcome = installSkill(localSkill, payload.agents, {
3683
+ mode,
3684
+ globalInstall,
3685
+ cwd: payload.cwd
3686
+ });
3687
+ try {
3688
+ await applyInstallerArtifacts(outcome.canonicalDir, preparedInstaller);
3689
+ writeLockEntryAfterInstall({
3690
+ globalInstall,
3691
+ cwd: payload.cwd,
3692
+ sourceInput: payload.sourceInput,
3693
+ sourceType: parsedSource.type,
3694
+ sourceCanonical,
3695
+ sourcePinnedRef: sourceHash,
3696
+ sourceSkillName: remote.installName,
3697
+ sourceHash,
3698
+ wellKnownSourceUrl: remote.sourceUrl,
3699
+ outcome,
3700
+ mode,
3701
+ lockFormat: payload.options.lockFormat
3702
+ });
3703
+ } finally {
3704
+ cleanupPreparedInstallerArtifacts(preparedInstaller);
3705
+ }
3706
+ }
3707
+ } finally {
3708
+ for (const cleanup of tempCleanups) {
3709
+ cleanup();
3710
+ }
3711
+ }
3712
+ return {
3713
+ installedSkillNames: [...payload.selectedSkillNames],
3714
+ agentCount: payload.agents.length
3715
+ };
3716
+ }
3717
+ await emitProgress("loading source");
3718
+ const prepared = await prepareSourceDirAsync(
3719
+ parsedSource
3720
+ );
3721
+ try {
3722
+ const sourceCanonical = canonicalSourceIdentity({ parsedSource });
3723
+ const sourcePinnedRef = parsedSource.type === "github" || parsedSource.type === "git" ? await resolveGitHeadRefAsync(prepared.basePath) : void 0;
3724
+ const sourceIsSymlink = parsedSource.type === "local" ? isLocalSymlinkSource2(parsedSource.localPath) : void 0;
3725
+ const skills = await discoverSkillsAsync(prepared.basePath);
3726
+ const selected = skills.filter((skill) => payload.selectedSkillNames.includes(skill.name));
3727
+ if (selected.length === 0) {
3728
+ throw new Error("No matching skills found in source");
3729
+ }
3730
+ const sourceHashesBefore = new Map(
3731
+ selected.map((skill) => [skill.path, hashDirectory(skill.path)])
3732
+ );
3733
+ const missingInstallerSkillDirs = listSkillsMissingInstallerConfig(
3734
+ selected.map((skill) => skill.path)
3735
+ );
3736
+ const scaffoldFormat = resolveAddInstallerScaffoldFormat(
3737
+ payload.options,
3738
+ missingInstallerSkillDirs.length
3739
+ );
3740
+ if (scaffoldFormat) {
3741
+ scaffoldInstallerConfigForSkills(missingInstallerSkillDirs, scaffoldFormat);
3742
+ }
3743
+ for (const skill of selected) {
3744
+ const sourceResolvedPath = parsedSource.type === "local" ? resolveSafeRealPath2(skill.path) : void 0;
3745
+ await emitProgress(`installing ${skill.name}`);
3746
+ const sourceSkillPath = path14.relative(prepared.basePath, skill.path) || ".";
3747
+ const preparedInstaller = await prepareInstallerArtifacts(skill.path, payload.cwd, {
3748
+ sourceType: parsedSource.type,
3749
+ policyMode: payload.options.policyMode || "enforce",
3750
+ trustWellKnown: Boolean(payload.options.trustWellKnown)
3751
+ });
3752
+ const outcome = installSkill(skill, payload.agents, {
3753
+ mode,
3754
+ globalInstall,
3755
+ cwd: payload.cwd
3756
+ });
3757
+ try {
3758
+ await applyInstallerArtifacts(outcome.canonicalDir, preparedInstaller);
3759
+ writeLockEntryAfterInstall({
3760
+ globalInstall,
3761
+ cwd: payload.cwd,
3762
+ sourceInput: payload.sourceInput,
3763
+ sourceType: parsedSource.type,
3764
+ sourceCanonical,
3765
+ sourcePinnedRef,
3766
+ sourceResolvedPath,
3767
+ sourceIsSymlink,
3768
+ sourceSkillName: skill.name,
3769
+ sourceSkillPath,
3770
+ sourceHash: sourceHashForInstalledSkill({
3771
+ parsedSource,
3772
+ skillPath: skill.path,
3773
+ beforeHash: sourceHashesBefore.get(skill.path)
3774
+ }),
3775
+ outcome,
3776
+ mode,
3777
+ lockFormat: payload.options.lockFormat
3778
+ });
3779
+ } finally {
3780
+ cleanupPreparedInstallerArtifacts(preparedInstaller);
3781
+ }
3782
+ }
3783
+ return {
3784
+ installedSkillNames: selected.map((skill) => skill.name),
3785
+ agentCount: payload.agents.length
3786
+ };
3787
+ } finally {
3788
+ if (prepared.cleanup) {
3789
+ prepared.cleanup();
3790
+ }
3791
+ }
3792
+ }
3793
+ async function runFindFetchInventoryTask(sourceInput, options, emitProgress) {
3794
+ await emitProgress("parsing source");
3795
+ const parsedSource = parseSource(sourceInput);
3796
+ const sourceLabel = resolveSourceLabel(parsedSource);
3797
+ await emitProgress("fetching skill inventory");
3798
+ if (parsedSource.type === "well-known" || parsedSource.type === "catalog") {
3799
+ if (parsedSource.type === "catalog") {
3800
+ assertExperimentalFeatureEnabled("catalog", Boolean(options.experimental));
3801
+ }
3802
+ const remoteSkills = parsedSource.type === "well-known" ? await resolveWellKnownSkills(parsedSource.url, {
3803
+ allowHost: options.allowHost,
3804
+ denyHost: options.denyHost,
3805
+ maxDownloadBytes: options.maxDownloadBytes,
3806
+ experimental: options.experimental
3807
+ }) : await resolveCatalogSkills(parsedSource.url, {
3808
+ allowHost: options.allowHost,
3809
+ denyHost: options.denyHost,
3810
+ maxDownloadBytes: options.maxDownloadBytes,
3811
+ experimental: options.experimental
3812
+ });
3813
+ return {
3814
+ sourceType: parsedSource.type,
3815
+ sourceLabel,
3816
+ skills: remoteSkills.map((item) => ({
3817
+ name: item.installName,
3818
+ description: item.description
3819
+ }))
3820
+ };
3821
+ }
3822
+ const prepared = await prepareSourceDirAsync(parsedSource);
3823
+ try {
3824
+ const discovered = await discoverSkillsAsync(prepared.basePath);
3825
+ return {
3826
+ sourceType: parsedSource.type,
3827
+ sourceLabel,
3828
+ skills: discovered.map((item) => ({
3829
+ name: item.name,
3830
+ description: item.description
3831
+ }))
3832
+ };
3833
+ } finally {
3834
+ if (prepared.cleanup) {
3835
+ prepared.cleanup();
3836
+ }
3837
+ }
3838
+ }
3839
+ async function runValidateRunTask(options, emitProgress) {
3840
+ return await runValidateAnalysis(options, emitProgress);
3841
+ }
3842
+ function runBlockingTask(payload) {
3843
+ const end = Date.now() + payload.durationMs;
3844
+ while (Date.now() < end) {
3845
+ Math.sqrt(Math.random() * Number.MAX_SAFE_INTEGER);
3846
+ }
3847
+ return { durationMs: payload.durationMs };
3848
+ }
3849
+ async function executeBackgroundTask(request, emitProgress) {
3850
+ switch (request.kind) {
3851
+ case "check.scan":
3852
+ return await runCheckScanTask(request.payload.cwd, request.payload.options, emitProgress);
3853
+ case "update.assess":
3854
+ return await runUpdateAssessTask(request.payload.cwd, request.payload.options, emitProgress);
3855
+ case "update.apply":
3856
+ return await runUpdateApplyTask(request.payload, emitProgress);
3857
+ case "update.migrate":
3858
+ return await runUpdateMigrateTask(request.payload, emitProgress);
3859
+ case "list.detectAgents":
3860
+ return await runListDetectAgentsTask(
3861
+ request.payload.cwd,
3862
+ request.payload.options,
3863
+ emitProgress
3864
+ );
3865
+ case "list.scanInventory":
3866
+ return await runListScanInventoryTask(request.payload, emitProgress);
3867
+ case "add.fetchOrDiscover":
3868
+ return await resolveAddSourceSkills(
3869
+ request.payload.sourceInput,
3870
+ request.payload.options,
3871
+ emitProgress
3872
+ );
3873
+ case "add.install":
3874
+ return await installSelectedAddSkills(request.payload, emitProgress);
3875
+ case "find.fetchInventory":
3876
+ return await runFindFetchInventoryTask(
3877
+ request.payload.sourceInput,
3878
+ request.payload.options,
3879
+ emitProgress
3880
+ );
3881
+ case "validate.run":
3882
+ return await runValidateRunTask(request.payload.options, emitProgress);
3883
+ case "test.blocking":
3884
+ if (request.payload.progressLabel) {
3885
+ await emitProgress(request.payload.progressLabel);
3886
+ }
3887
+ return runBlockingTask(request.payload);
3888
+ default:
3889
+ throw new Error(`Unsupported background task: ${String(request)}`);
3890
+ }
3891
+ }
3892
+ export {
3893
+ executeBackgroundTask
3894
+ };
3895
+ //# sourceMappingURL=background-executor.js.map