aweskill 0.1.8 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,13 +5,13 @@ import { Command } from "commander";
5
5
  import { homedir as homedir2 } from "os";
6
6
 
7
7
  // src/commands/backup.ts
8
- import path3 from "path";
8
+ import path4 from "path";
9
9
 
10
10
  // src/lib/backup.ts
11
- import { mkdtemp, mkdir, stat } from "fs/promises";
11
+ import { gunzipSync, gzipSync } from "zlib";
12
+ import { cp, mkdtemp, mkdir, readdir as readdir2, readFile as readFile2, stat, writeFile } from "fs/promises";
12
13
  import { tmpdir } from "os";
13
- import path2 from "path";
14
- import { spawn } from "child_process";
14
+ import path3 from "path";
15
15
 
16
16
  // src/lib/fs.ts
17
17
  import { access } from "fs/promises";
@@ -24,30 +24,116 @@ async function pathExists(targetPath) {
24
24
  }
25
25
  }
26
26
 
27
+ // src/lib/hygiene.ts
28
+ import { lstat, readFile, readdir } from "fs/promises";
29
+ import path from "path";
30
+ import { parse } from "yaml";
31
+ function relativeLabel(rootDir, targetPath) {
32
+ return path.relative(rootDir, targetPath).split(path.sep).join("/");
33
+ }
34
+ async function scanStoreHygiene(options) {
35
+ const includeSkills = options.includeSkills ?? true;
36
+ const includeBundles = options.includeBundles ?? true;
37
+ const findings = [];
38
+ const validSkills = [];
39
+ const validBundles = [];
40
+ if (includeSkills && await pathExists(options.skillsDir)) {
41
+ const entries = await readdir(options.skillsDir, { withFileTypes: true });
42
+ for (const entry of entries) {
43
+ const entryPath = path.join(options.skillsDir, entry.name);
44
+ const stats = await lstat(entryPath);
45
+ if (stats.isDirectory() || stats.isSymbolicLink()) {
46
+ const hasSKILLMd = await pathExists(path.join(entryPath, "SKILL.md"));
47
+ if (hasSKILLMd) {
48
+ validSkills.push({ name: entry.name, path: entryPath, hasSKILLMd: true });
49
+ } else {
50
+ findings.push({
51
+ kind: "missing-skill-md",
52
+ path: entryPath,
53
+ relativePath: relativeLabel(options.rootDir, entryPath)
54
+ });
55
+ }
56
+ continue;
57
+ }
58
+ findings.push({
59
+ kind: "unexpected-skill-entry",
60
+ path: entryPath,
61
+ relativePath: relativeLabel(options.rootDir, entryPath)
62
+ });
63
+ }
64
+ }
65
+ if (includeBundles && await pathExists(options.bundlesDir)) {
66
+ const entries = await readdir(options.bundlesDir, { withFileTypes: true });
67
+ for (const entry of entries) {
68
+ const entryPath = path.join(options.bundlesDir, entry.name);
69
+ if (!entry.isFile() || !entry.name.endsWith(".yaml")) {
70
+ findings.push({
71
+ kind: "unexpected-bundle-entry",
72
+ path: entryPath,
73
+ relativePath: relativeLabel(options.rootDir, entryPath)
74
+ });
75
+ continue;
76
+ }
77
+ try {
78
+ const parsed = parse(await readFile(entryPath, "utf8"));
79
+ validBundles.push({
80
+ name: String(parsed?.name ?? entry.name.replace(/\.yaml$/, "")).trim().toLowerCase(),
81
+ skills: Array.isArray(parsed?.skills) ? parsed.skills.map((skill) => String(skill).trim().toLowerCase()).filter(Boolean).sort() : []
82
+ });
83
+ } catch {
84
+ findings.push({
85
+ kind: "invalid-bundle-yaml",
86
+ path: entryPath,
87
+ relativePath: relativeLabel(options.rootDir, entryPath)
88
+ });
89
+ }
90
+ }
91
+ }
92
+ validSkills.sort((left, right) => left.name.localeCompare(right.name));
93
+ validBundles.sort((left, right) => left.name.localeCompare(right.name));
94
+ findings.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
95
+ return { validSkills, validBundles, findings };
96
+ }
97
+ function formatHygieneHint(findings) {
98
+ if (findings.length === 0) {
99
+ return [];
100
+ }
101
+ return [
102
+ `Suspicious store entries detected: ${findings.length}`,
103
+ `Run "aweskill doctor clean" to inspect them, or "aweskill doctor clean --apply" to remove them.`
104
+ ];
105
+ }
106
+
27
107
  // src/lib/path.ts
28
108
  import { homedir } from "os";
29
- import path from "path";
109
+ import path2 from "path";
30
110
  function sanitizeName(input) {
31
111
  return input.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+/, "").replace(/-+$/, "").slice(0, 80);
32
112
  }
113
+ function stripVersionSuffix(input) {
114
+ return input.replace(/-\d+(?:\.\d+)*$/, "");
115
+ }
116
+ function getDuplicateMatchKey(input) {
117
+ return stripVersionSuffix(sanitizeName(input)).replace(/[^a-z0-9]+/g, "");
118
+ }
33
119
  function expandHomePath(targetPath, homeDir = homedir()) {
34
120
  if (targetPath === "~") {
35
121
  return homeDir;
36
122
  }
37
123
  if (targetPath.startsWith("~/")) {
38
- return path.join(homeDir, targetPath.slice(2));
124
+ return path2.join(homeDir, targetPath.slice(2));
39
125
  }
40
126
  return targetPath;
41
127
  }
42
128
  function getAweskillPaths(homeDir) {
43
- const rootDir = path.join(homeDir, ".aweskill");
129
+ const rootDir = path2.join(homeDir, ".aweskill");
44
130
  return {
45
131
  homeDir,
46
132
  rootDir,
47
- skillsDir: path.join(rootDir, "skills"),
48
- dupSkillsDir: path.join(rootDir, "dup_skills"),
49
- backupDir: path.join(rootDir, "backup"),
50
- bundlesDir: path.join(rootDir, "bundles")
133
+ skillsDir: path2.join(rootDir, "skills"),
134
+ dupSkillsDir: path2.join(rootDir, "dup_skills"),
135
+ backupDir: path2.join(rootDir, "backup"),
136
+ bundlesDir: path2.join(rootDir, "bundles")
51
137
  };
52
138
  }
53
139
  function uniqueSorted(items) {
@@ -58,44 +144,212 @@ function splitCommaValues(input) {
58
144
  }
59
145
 
60
146
  // src/lib/backup.ts
147
+ var TAR_BLOCK_SIZE = 512;
61
148
  function formatTimestamp(date) {
62
149
  return date.toISOString().replace(/:/g, "-").replace(/\.\d{3}Z$/, "Z");
63
150
  }
64
- async function runTar(args) {
65
- await new Promise((resolve, reject) => {
66
- const child = spawn("tar", args, { stdio: "ignore" });
67
- child.on("error", reject);
68
- child.on("exit", (code) => {
69
- if (code === 0) {
70
- resolve();
71
- return;
151
+ function formatBackupLabel(includeBundles) {
152
+ return includeBundles ? "skills and bundles" : "skills";
153
+ }
154
+ function normalizeTarPath(entryPath) {
155
+ return entryPath.split(path3.sep).join("/");
156
+ }
157
+ function encodeOctal(value, length) {
158
+ const encoded = value.toString(8).padStart(length - 1, "0");
159
+ return Buffer.from(`${encoded}\0`, "ascii");
160
+ }
161
+ function writeString(field, value) {
162
+ field.fill(0);
163
+ field.write(value, 0, Math.min(Buffer.byteLength(value, "utf8"), field.length), "utf8");
164
+ }
165
+ function splitTarName(name) {
166
+ if (Buffer.byteLength(name, "utf8") <= 100) {
167
+ return { name, prefix: "" };
168
+ }
169
+ const parts = name.split("/");
170
+ while (parts.length > 1) {
171
+ const candidatePrefix = parts.slice(0, -1).join("/");
172
+ const candidateName = parts[parts.length - 1] ?? "";
173
+ if (Buffer.byteLength(candidateName, "utf8") <= 100 && Buffer.byteLength(candidatePrefix, "utf8") <= 155) {
174
+ return { name: candidateName, prefix: candidatePrefix };
175
+ }
176
+ parts.shift();
177
+ }
178
+ throw new Error(`Path is too long for tar header: ${name}`);
179
+ }
180
+ function createTarHeader(entry) {
181
+ const header = Buffer.alloc(TAR_BLOCK_SIZE, 0);
182
+ const entryName = entry.type === "directory" && !entry.name.endsWith("/") ? `${entry.name}/` : entry.name;
183
+ const { name, prefix } = splitTarName(entryName);
184
+ writeString(header.subarray(0, 100), name);
185
+ encodeOctal(entry.mode, 8).copy(header, 100);
186
+ encodeOctal(0, 8).copy(header, 108);
187
+ encodeOctal(0, 8).copy(header, 116);
188
+ encodeOctal(entry.type === "file" ? entry.data?.length ?? 0 : 0, 12).copy(header, 124);
189
+ encodeOctal(Math.floor(Date.now() / 1e3), 12).copy(header, 136);
190
+ header.fill(32, 148, 156);
191
+ header[156] = entry.type === "directory" ? "5".charCodeAt(0) : "0".charCodeAt(0);
192
+ writeString(header.subarray(257, 263), "ustar");
193
+ writeString(header.subarray(263, 265), "00");
194
+ writeString(header.subarray(345, 500), prefix);
195
+ let checksum = 0;
196
+ for (const byte of header) {
197
+ checksum += byte;
198
+ }
199
+ Buffer.from(checksum.toString(8).padStart(6, "0") + "\0 ", "ascii").copy(header, 148);
200
+ return header;
201
+ }
202
+ function buildTarArchive(entries) {
203
+ const chunks = [];
204
+ for (const entry of entries) {
205
+ chunks.push(createTarHeader(entry));
206
+ if (entry.type === "file") {
207
+ const data = entry.data ?? Buffer.alloc(0);
208
+ chunks.push(data);
209
+ const remainder = data.length % TAR_BLOCK_SIZE;
210
+ if (remainder !== 0) {
211
+ chunks.push(Buffer.alloc(TAR_BLOCK_SIZE - remainder, 0));
72
212
  }
73
- reject(new Error(`tar exited with code ${code ?? "unknown"}`));
213
+ }
214
+ }
215
+ chunks.push(Buffer.alloc(TAR_BLOCK_SIZE * 2, 0));
216
+ return Buffer.concat(chunks);
217
+ }
218
+ function isZeroBlock(block) {
219
+ for (const byte of block) {
220
+ if (byte !== 0) {
221
+ return false;
222
+ }
223
+ }
224
+ return true;
225
+ }
226
+ function readTarString(block, start, end) {
227
+ const value = block.subarray(start, end).toString("utf8");
228
+ return value.replace(/\0.*$/, "");
229
+ }
230
+ function readTarNumber(block, start, end) {
231
+ const value = readTarString(block, start, end).trim();
232
+ return value === "" ? 0 : Number.parseInt(value, 8);
233
+ }
234
+ function safeExtractPath(baseDir, entryName) {
235
+ const normalized = entryName.replace(/\\/g, "/");
236
+ if (normalized.startsWith("/") || normalized.split("/").includes("..")) {
237
+ throw new Error(`Archive contains unsafe path: ${entryName}`);
238
+ }
239
+ return path3.join(baseDir, ...normalized.split("/").filter(Boolean));
240
+ }
241
+ async function extractTarArchive(buffer, destinationDir) {
242
+ let offset = 0;
243
+ while (offset + TAR_BLOCK_SIZE <= buffer.length) {
244
+ const header = buffer.subarray(offset, offset + TAR_BLOCK_SIZE);
245
+ offset += TAR_BLOCK_SIZE;
246
+ if (isZeroBlock(header)) {
247
+ break;
248
+ }
249
+ const name = readTarString(header, 0, 100);
250
+ const prefix = readTarString(header, 345, 500);
251
+ const entryName = prefix ? `${prefix}/${name}` : name;
252
+ const typeFlag = String.fromCharCode(header[156] || "0".charCodeAt(0));
253
+ const size = readTarNumber(header, 124, 136);
254
+ const outputPath = safeExtractPath(destinationDir, entryName);
255
+ if (typeFlag === "5") {
256
+ await mkdir(outputPath, { recursive: true });
257
+ continue;
258
+ }
259
+ const fileData = buffer.subarray(offset, offset + size);
260
+ offset += Math.ceil(size / TAR_BLOCK_SIZE) * TAR_BLOCK_SIZE;
261
+ await mkdir(path3.dirname(outputPath), { recursive: true });
262
+ await writeFile(outputPath, fileData);
263
+ }
264
+ }
265
+ async function collectTarEntries(rootDir, relativeDir) {
266
+ const entries = [
267
+ {
268
+ name: normalizeTarPath(relativeDir),
269
+ type: "directory",
270
+ mode: 493
271
+ }
272
+ ];
273
+ const absoluteDir = path3.join(rootDir, relativeDir);
274
+ const directoryEntries = await readdir2(absoluteDir, { withFileTypes: true });
275
+ for (const entry of directoryEntries) {
276
+ const absolutePath = path3.join(absoluteDir, entry.name);
277
+ const archivePath = normalizeTarPath(path3.join(relativeDir, entry.name));
278
+ if (entry.isDirectory()) {
279
+ entries.push(...await collectTarEntries(rootDir, path3.join(relativeDir, entry.name)));
280
+ continue;
281
+ }
282
+ if (!entry.isFile()) {
283
+ continue;
284
+ }
285
+ const entryStats = await stat(absolutePath);
286
+ entries.push({
287
+ name: archivePath,
288
+ type: "file",
289
+ mode: entryStats.mode & 511,
290
+ data: await readFile2(absolutePath)
74
291
  });
75
- });
292
+ }
293
+ return entries;
76
294
  }
77
- function formatBackupLabel(includeBundles) {
78
- return includeBundles ? "skills and bundles" : "skills";
295
+ async function collectTarEntriesForFile(rootDir, relativeFilePath) {
296
+ const absolutePath = path3.join(rootDir, relativeFilePath);
297
+ const entryStats = await stat(absolutePath);
298
+ return [{
299
+ name: normalizeTarPath(relativeFilePath),
300
+ type: "file",
301
+ mode: entryStats.mode & 511,
302
+ data: await readFile2(absolutePath)
303
+ }];
79
304
  }
80
- function archiveEntries(homeDir, includeBundles) {
305
+ async function archiveEntries(homeDir, includeBundles) {
81
306
  const { rootDir, skillsDir, bundlesDir } = getAweskillPaths(homeDir);
82
- return includeBundles ? [path2.relative(rootDir, skillsDir), path2.relative(rootDir, bundlesDir)] : [path2.relative(rootDir, skillsDir)];
307
+ const entries = [];
308
+ const hygiene = await scanStoreHygiene({ rootDir, skillsDir, bundlesDir, includeBundles });
309
+ if (hygiene.validSkills.length > 0) {
310
+ entries.push({ name: "skills", type: "directory", mode: 493 });
311
+ for (const skill of hygiene.validSkills) {
312
+ entries.push(...await collectTarEntries(rootDir, path3.relative(rootDir, skill.path)));
313
+ }
314
+ }
315
+ if (includeBundles && hygiene.validBundles.length > 0) {
316
+ entries.push({ name: "bundles", type: "directory", mode: 493 });
317
+ for (const bundle of hygiene.validBundles) {
318
+ entries.push(...await collectTarEntriesForFile(rootDir, path3.join("bundles", `${bundle.name}.yaml`)));
319
+ }
320
+ }
321
+ return entries;
83
322
  }
84
323
  async function createSkillsBackupArchive(homeDir, options = {}) {
85
- const { rootDir, backupDir } = getAweskillPaths(homeDir);
324
+ const { backupDir } = getAweskillPaths(homeDir);
86
325
  const includeBundles = options.includeBundles ?? false;
87
326
  const archivePath = await resolveBackupArchivePath(backupDir, options.archivePath);
88
- await mkdir(path2.dirname(archivePath), { recursive: true });
89
- await runTar(["-czf", archivePath, "-C", rootDir, ...archiveEntries(homeDir, includeBundles)]);
327
+ await mkdir(path3.dirname(archivePath), { recursive: true });
328
+ const tarArchive = buildTarArchive(await archiveEntries(homeDir, includeBundles));
329
+ await writeFile(archivePath, gzipSync(tarArchive));
90
330
  return archivePath;
91
331
  }
92
332
  async function extractSkillsArchive(archivePath) {
93
- const tempDir = await mkdtemp(path2.join(tmpdir(), "aweskill-restore-"));
94
- await runTar(["-xzf", archivePath, "-C", tempDir]);
333
+ const tempDir = await mkdtemp(path3.join(tmpdir(), "aweskill-restore-"));
334
+ const sourceStats = await stat(archivePath);
335
+ if (sourceStats.isDirectory()) {
336
+ const sourceSkillsDir = path3.join(archivePath, "skills");
337
+ const sourceBundlesDir = path3.join(archivePath, "bundles");
338
+ if (!await pathExists(sourceSkillsDir)) {
339
+ throw new Error(`Restore source does not contain a skills/ directory: ${archivePath}`);
340
+ }
341
+ await cp(sourceSkillsDir, path3.join(tempDir, "skills"), { recursive: true });
342
+ if (await pathExists(sourceBundlesDir)) {
343
+ await cp(sourceBundlesDir, path3.join(tempDir, "bundles"), { recursive: true });
344
+ }
345
+ } else {
346
+ const archiveBuffer = await readFile2(archivePath);
347
+ await extractTarArchive(gunzipSync(archiveBuffer), tempDir);
348
+ }
95
349
  return {
96
350
  tempDir,
97
- extractedSkillsDir: path2.join(tempDir, "skills"),
98
- extractedBundlesDir: path2.join(tempDir, "bundles")
351
+ extractedSkillsDir: path3.join(tempDir, "skills"),
352
+ extractedBundlesDir: path3.join(tempDir, "bundles")
99
353
  };
100
354
  }
101
355
  async function resolveBackupArchivePath(backupDir, requestedPath) {
@@ -113,13 +367,13 @@ async function resolveBackupArchivePath(backupDir, requestedPath) {
113
367
  }
114
368
  async function nextBackupArchivePath(backupDir) {
115
369
  const base = `skills-${formatTimestamp(/* @__PURE__ */ new Date())}`;
116
- const primary = path2.join(backupDir, `${base}.tar.gz`);
370
+ const primary = path3.join(backupDir, `${base}.tar.gz`);
117
371
  if (!await pathExists(primary)) {
118
372
  return primary;
119
373
  }
120
374
  let index = 1;
121
375
  while (true) {
122
- const candidate = path2.join(backupDir, `${base}-${index}.tar.gz`);
376
+ const candidate = path3.join(backupDir, `${base}-${index}.tar.gz`);
123
377
  if (!await pathExists(candidate)) {
124
378
  return candidate;
125
379
  }
@@ -129,22 +383,28 @@ async function nextBackupArchivePath(backupDir) {
129
383
 
130
384
  // src/commands/backup.ts
131
385
  async function runBackup(context, options = {}) {
386
+ const includeBundles = options.includeBundles ?? true;
387
+ const { rootDir, skillsDir, bundlesDir } = getAweskillPaths(context.homeDir);
388
+ const { findings } = await scanStoreHygiene({ rootDir, skillsDir, bundlesDir, includeBundles });
132
389
  const archivePath = await createSkillsBackupArchive(context.homeDir, {
133
- archivePath: options.archivePath ? path3.resolve(context.cwd, expandHomePath(options.archivePath, context.homeDir)) : void 0,
134
- includeBundles: options.includeBundles
390
+ archivePath: options.archivePath ? path4.resolve(context.cwd, expandHomePath(options.archivePath, context.homeDir)) : void 0,
391
+ includeBundles
135
392
  });
136
- context.write(`Backed up ${formatBackupLabel(options.includeBundles ?? false)} to ${archivePath}`);
393
+ context.write(`Backed up ${formatBackupLabel(includeBundles)} to ${archivePath}`);
394
+ if (findings.length > 0) {
395
+ context.write(`Skipped suspicious store entries during backup: ${findings.map((finding) => finding.relativePath).join(", ")}`);
396
+ }
137
397
  return { archivePath };
138
398
  }
139
399
 
140
400
  // src/lib/bundles.ts
141
- import { mkdir as mkdir3, readFile, readdir as readdir2, rm, writeFile } from "fs/promises";
142
- import path5 from "path";
143
- import { parse, stringify } from "yaml";
401
+ import { mkdir as mkdir3, readFile as readFile3, readdir as readdir4, rm, writeFile as writeFile2 } from "fs/promises";
402
+ import path6 from "path";
403
+ import { parse as parse2, stringify } from "yaml";
144
404
 
145
405
  // src/lib/skills.ts
146
- import { mkdir as mkdir2, readdir } from "fs/promises";
147
- import path4 from "path";
406
+ import { mkdir as mkdir2, readdir as readdir3 } from "fs/promises";
407
+ import path5 from "path";
148
408
  async function ensureHomeLayout(homeDir) {
149
409
  const paths = getAweskillPaths(homeDir);
150
410
  await mkdir2(paths.rootDir, { recursive: true });
@@ -154,7 +414,7 @@ async function ensureHomeLayout(homeDir) {
154
414
  await mkdir2(paths.bundlesDir, { recursive: true });
155
415
  }
156
416
  function getSkillPath(homeDir, skillName) {
157
- return path4.join(getAweskillPaths(homeDir).skillsDir, sanitizeName(skillName));
417
+ return path5.join(getAweskillPaths(homeDir).skillsDir, sanitizeName(skillName));
158
418
  }
159
419
  async function listSkills(homeDir) {
160
420
  const skillsDir = getAweskillPaths(homeDir).skillsDir;
@@ -164,21 +424,21 @@ async function listSkillEntriesInDirectory(skillsDir) {
164
424
  if (!await pathExists(skillsDir)) {
165
425
  return [];
166
426
  }
167
- const entries = await readdir(skillsDir, { withFileTypes: true });
427
+ const entries = await readdir3(skillsDir, { withFileTypes: true });
168
428
  const skills = await Promise.all(
169
429
  entries.filter((entry) => entry.isDirectory() || entry.isSymbolicLink()).map(async (entry) => {
170
- const skillPath = path4.join(skillsDir, entry.name);
430
+ const skillPath = path5.join(skillsDir, entry.name);
171
431
  return {
172
432
  name: entry.name,
173
433
  path: skillPath,
174
- hasSKILLMd: await pathExists(path4.join(skillPath, "SKILL.md"))
434
+ hasSKILLMd: await pathExists(path5.join(skillPath, "SKILL.md"))
175
435
  };
176
436
  })
177
437
  );
178
438
  return skills.sort((left, right) => left.name.localeCompare(right.name));
179
439
  }
180
440
  async function assertSkillSource(sourcePath) {
181
- const skillReadme = path4.join(sourcePath, "SKILL.md");
441
+ const skillReadme = path5.join(sourcePath, "SKILL.md");
182
442
  if (!await pathExists(skillReadme)) {
183
443
  throw new Error(`Skill source must contain SKILL.md: ${sourcePath}`);
184
444
  }
@@ -186,13 +446,22 @@ async function assertSkillSource(sourcePath) {
186
446
  async function skillExists(homeDir, skillName) {
187
447
  return pathExists(getSkillPath(homeDir, skillName));
188
448
  }
449
+ function getSkillSuspicionReason(skill) {
450
+ if (!skill.hasSKILLMd) {
451
+ return "missing-skill-md";
452
+ }
453
+ if (skill.name.startsWith(".")) {
454
+ return "reserved-name";
455
+ }
456
+ return null;
457
+ }
189
458
 
190
459
  // src/lib/bundles.ts
191
460
  function bundleFilePath(homeDir, bundleName) {
192
- return path5.join(getAweskillPaths(homeDir).bundlesDir, `${sanitizeName(bundleName)}.yaml`);
461
+ return path6.join(getAweskillPaths(homeDir).bundlesDir, `${sanitizeName(bundleName)}.yaml`);
193
462
  }
194
463
  function bundleFilePathInDirectory(bundlesDir, bundleName) {
195
- return path5.join(bundlesDir, `${sanitizeName(bundleName)}.yaml`);
464
+ return path6.join(bundlesDir, `${sanitizeName(bundleName)}.yaml`);
196
465
  }
197
466
  function normalizeBundle(raw, fallbackName) {
198
467
  const data = raw ?? {};
@@ -207,7 +476,7 @@ async function listBundles(homeDir) {
207
476
  }
208
477
  async function listBundlesInDirectory(bundlesDir) {
209
478
  await mkdir3(bundlesDir, { recursive: true });
210
- const entries = await readdir2(bundlesDir, { withFileTypes: true });
479
+ const entries = await readdir4(bundlesDir, { withFileTypes: true });
211
480
  const bundles = await Promise.all(
212
481
  entries.filter((entry) => entry.isFile() && entry.name.endsWith(".yaml")).map(async (entry) => readBundleFromDirectory(bundlesDir, entry.name.replace(/\.yaml$/, "")))
213
482
  );
@@ -215,19 +484,19 @@ async function listBundlesInDirectory(bundlesDir) {
215
484
  }
216
485
  async function readBundle(homeDir, bundleName) {
217
486
  const filePath = bundleFilePath(homeDir, bundleName);
218
- const content = await readFile(filePath, "utf8");
219
- return normalizeBundle(parse(content), bundleName);
487
+ const content = await readFile3(filePath, "utf8");
488
+ return normalizeBundle(parse2(content), bundleName);
220
489
  }
221
490
  async function readBundleFromDirectory(bundlesDir, bundleName) {
222
491
  const filePath = bundleFilePathInDirectory(bundlesDir, bundleName);
223
- const content = await readFile(filePath, "utf8");
224
- return normalizeBundle(parse(content), bundleName);
492
+ const content = await readFile3(filePath, "utf8");
493
+ return normalizeBundle(parse2(content), bundleName);
225
494
  }
226
495
  async function writeBundle(homeDir, bundle) {
227
496
  const normalized = normalizeBundle(bundle, bundle.name);
228
497
  const filePath = bundleFilePath(homeDir, normalized.name);
229
- await mkdir3(path5.dirname(filePath), { recursive: true });
230
- await writeFile(filePath, stringify(normalized), "utf8");
498
+ await mkdir3(path6.dirname(filePath), { recursive: true });
499
+ await writeFile2(filePath, stringify(normalized), "utf8");
231
500
  return normalized;
232
501
  }
233
502
  async function createBundle(homeDir, bundleName) {
@@ -259,13 +528,13 @@ async function deleteBundle(homeDir, bundleName) {
259
528
  }
260
529
 
261
530
  // src/lib/templates.ts
262
- import path6 from "path";
531
+ import path7 from "path";
263
532
  import { fileURLToPath } from "url";
264
533
  async function getTemplateBundlesDir() {
265
- const moduleDir = path6.dirname(fileURLToPath(import.meta.url));
534
+ const moduleDir = path7.dirname(fileURLToPath(import.meta.url));
266
535
  const candidates = [
267
- path6.resolve(moduleDir, "..", "..", "resources", "bundle_templates"),
268
- path6.resolve(moduleDir, "..", "resources", "bundle_templates")
536
+ path7.resolve(moduleDir, "..", "..", "resources", "bundle_templates"),
537
+ path7.resolve(moduleDir, "..", "resources", "bundle_templates")
269
538
  ];
270
539
  for (const candidate of candidates) {
271
540
  if (await pathExists(candidate)) {
@@ -352,8 +621,236 @@ async function runBundleAddTemplate(context, bundleName) {
352
621
  }
353
622
 
354
623
  // src/lib/import.ts
355
- import { cp, lstat, mkdir as mkdir4, readlink, readdir as readdir3, rename, rm as rm2, stat as stat2 } from "fs/promises";
356
- import path7 from "path";
624
+ import { cp as cp3, lstat as lstat3, mkdir as mkdir5, readlink as readlink2, readdir as readdir6, stat as stat2 } from "fs/promises";
625
+ import path9 from "path";
626
+
627
+ // src/lib/symlink.ts
628
+ import { cp as cp2, lstat as lstat2, mkdir as mkdir4, readFile as readFile4, readdir as readdir5, readlink, rm as rm2, symlink, unlink, writeFile as writeFile3 } from "fs/promises";
629
+ import path8 from "path";
630
+ var COPY_MARKER = ".aweskill-projection.json";
631
+ async function tryLstat(targetPath) {
632
+ try {
633
+ return await lstat2(targetPath);
634
+ } catch {
635
+ return null;
636
+ }
637
+ }
638
+ async function readCopyMarker(targetPath) {
639
+ try {
640
+ const content = await readFile4(path8.join(targetPath, COPY_MARKER), "utf8");
641
+ const parsed = JSON.parse(content);
642
+ return parsed.managedBy === "aweskill" ? parsed : null;
643
+ } catch {
644
+ return null;
645
+ }
646
+ }
647
+ function getDirectoryLinkTypeForPlatform(platform = process.platform) {
648
+ return platform === "win32" ? "junction" : "dir";
649
+ }
650
+ async function defaultDirectoryLinkCreator(sourcePath, targetPath) {
651
+ const linkTarget = path8.relative(path8.dirname(targetPath), sourcePath) || ".";
652
+ await symlink(linkTarget, targetPath, getDirectoryLinkTypeForPlatform());
653
+ }
654
+ var directoryLinkCreator = defaultDirectoryLinkCreator;
655
+ function shouldFallbackToCopy(error, platform = process.platform) {
656
+ if (platform !== "win32") {
657
+ return false;
658
+ }
659
+ const code = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
660
+ return code === "EPERM" || code === "EACCES" || code === "EINVAL" || code === "UNKNOWN";
661
+ }
662
+ async function inspectProjectionTarget(targetPath, options = {}) {
663
+ const existing = await tryLstat(targetPath);
664
+ if (!existing) {
665
+ return { kind: "missing" };
666
+ }
667
+ const centralRoot = options.centralSkillsDir ? path8.resolve(options.centralSkillsDir) : void 0;
668
+ const expectedSource = options.sourcePath ? path8.resolve(options.sourcePath) : void 0;
669
+ if (existing.isSymbolicLink()) {
670
+ const currentTarget = await readlink(targetPath);
671
+ const resolvedCurrent = path8.resolve(path8.dirname(targetPath), currentTarget);
672
+ if (centralRoot && resolvedCurrent.startsWith(centralRoot)) {
673
+ return {
674
+ kind: "managed_symlink",
675
+ sourcePath: resolvedCurrent,
676
+ matchesSource: expectedSource ? resolvedCurrent === expectedSource : true
677
+ };
678
+ }
679
+ return { kind: "foreign_symlink", sourcePath: resolvedCurrent };
680
+ }
681
+ if (existing.isDirectory()) {
682
+ const marker = await readCopyMarker(targetPath);
683
+ if (marker) {
684
+ return {
685
+ kind: "managed_copy",
686
+ sourcePath: marker.sourcePath,
687
+ matchesSource: expectedSource ? marker.sourcePath === expectedSource : true
688
+ };
689
+ }
690
+ return { kind: "directory" };
691
+ }
692
+ return { kind: "file" };
693
+ }
694
+ async function createSkillSymlink(sourcePath, targetPath, options = {}) {
695
+ await mkdir4(path8.dirname(targetPath), { recursive: true });
696
+ const existing = await tryLstat(targetPath);
697
+ if (existing?.isSymbolicLink()) {
698
+ const currentTarget = await readlink(targetPath);
699
+ const resolvedCurrent = path8.resolve(path8.dirname(targetPath), currentTarget);
700
+ if (resolvedCurrent === path8.resolve(sourcePath)) {
701
+ return { status: "skipped", mode: "symlink" };
702
+ }
703
+ await unlink(targetPath);
704
+ } else if (existing) {
705
+ if (existing.isDirectory()) {
706
+ const marker = await readCopyMarker(targetPath);
707
+ if (marker?.sourcePath === path8.resolve(sourcePath)) {
708
+ return { status: "skipped", mode: "copy" };
709
+ }
710
+ }
711
+ if (!options.allowReplaceExisting) {
712
+ throw new Error(`Refusing to overwrite non-symlink target: ${targetPath}`);
713
+ }
714
+ await rm2(targetPath, { force: true, recursive: true });
715
+ }
716
+ try {
717
+ await directoryLinkCreator(sourcePath, targetPath);
718
+ return { status: "created", mode: "symlink" };
719
+ } catch (error) {
720
+ if (!shouldFallbackToCopy(error)) {
721
+ throw error;
722
+ }
723
+ return createSkillCopy(sourcePath, targetPath, options);
724
+ }
725
+ }
726
+ async function createSkillCopy(sourcePath, targetPath, options = {}) {
727
+ await mkdir4(path8.dirname(targetPath), { recursive: true });
728
+ const existing = await tryLstat(targetPath);
729
+ if (existing?.isSymbolicLink()) {
730
+ await unlink(targetPath);
731
+ } else if (existing) {
732
+ const marker2 = await readCopyMarker(targetPath);
733
+ if (marker2?.sourcePath === path8.resolve(sourcePath)) {
734
+ return { status: "skipped", mode: "copy" };
735
+ }
736
+ if (!marker2 && !options.allowReplaceExisting) {
737
+ throw new Error(`Refusing to overwrite unmanaged directory: ${targetPath}`);
738
+ }
739
+ await rm2(targetPath, { force: true, recursive: true });
740
+ }
741
+ await cp2(sourcePath, targetPath, { recursive: true });
742
+ const marker = { managedBy: "aweskill", sourcePath: path8.resolve(sourcePath) };
743
+ await writeFile3(path8.join(targetPath, COPY_MARKER), JSON.stringify(marker, null, 2), "utf8");
744
+ return { status: "created", mode: "copy" };
745
+ }
746
+ async function removeManagedProjection(targetPath) {
747
+ const existing = await tryLstat(targetPath);
748
+ if (!existing) {
749
+ return false;
750
+ }
751
+ if (existing.isSymbolicLink()) {
752
+ await unlink(targetPath);
753
+ return true;
754
+ }
755
+ if (existing.isDirectory()) {
756
+ const marker = await readCopyMarker(targetPath);
757
+ if (marker) {
758
+ await rm2(targetPath, { force: true, recursive: true });
759
+ return true;
760
+ }
761
+ }
762
+ return false;
763
+ }
764
+ async function removeProjectionTarget(targetPath, options = {}) {
765
+ const status = await inspectProjectionTarget(targetPath, { centralSkillsDir: options.centralSkillsDir });
766
+ if (status.kind === "missing") {
767
+ return false;
768
+ }
769
+ if (status.kind === "managed_symlink") {
770
+ await unlink(targetPath);
771
+ return true;
772
+ }
773
+ if (status.kind === "managed_copy" || status.kind === "directory") {
774
+ if (status.kind === "directory" && !options.force) {
775
+ return false;
776
+ }
777
+ await rm2(targetPath, { force: true, recursive: true });
778
+ return true;
779
+ }
780
+ if (status.kind === "foreign_symlink") {
781
+ if (!options.force) {
782
+ return false;
783
+ }
784
+ await unlink(targetPath);
785
+ return true;
786
+ }
787
+ if (status.kind === "file") {
788
+ if (!options.force) {
789
+ return false;
790
+ }
791
+ await rm2(targetPath, { force: true });
792
+ return true;
793
+ }
794
+ return false;
795
+ }
796
+ async function listManagedSkillNames(skillsDir, centralSkillsDir) {
797
+ const result = /* @__PURE__ */ new Map();
798
+ try {
799
+ const entries = await readdir5(skillsDir, { withFileTypes: true });
800
+ for (const entry of entries) {
801
+ const targetPath = path8.join(skillsDir, entry.name);
802
+ const stats = await tryLstat(targetPath);
803
+ if (stats?.isSymbolicLink()) {
804
+ try {
805
+ const currentTarget = await readlink(targetPath);
806
+ const resolvedCurrent = path8.resolve(path8.dirname(targetPath), currentTarget);
807
+ if (resolvedCurrent.startsWith(path8.resolve(centralSkillsDir))) {
808
+ result.set(entry.name, "symlink");
809
+ }
810
+ } catch {
811
+ result.set(entry.name, "symlink");
812
+ }
813
+ continue;
814
+ }
815
+ if (stats?.isDirectory()) {
816
+ const marker = await readCopyMarker(targetPath);
817
+ if (marker) {
818
+ result.set(entry.name, "copy");
819
+ }
820
+ }
821
+ }
822
+ } catch {
823
+ return result;
824
+ }
825
+ return result;
826
+ }
827
+ async function listBrokenSymlinkNames(skillsDir) {
828
+ const result = /* @__PURE__ */ new Set();
829
+ try {
830
+ const entries = await readdir5(skillsDir, { withFileTypes: true });
831
+ for (const entry of entries) {
832
+ const targetPath = path8.join(skillsDir, entry.name);
833
+ const stats = await tryLstat(targetPath);
834
+ if (!stats?.isSymbolicLink()) {
835
+ continue;
836
+ }
837
+ try {
838
+ const currentTarget = await readlink(targetPath);
839
+ const resolvedCurrent = path8.resolve(path8.dirname(targetPath), currentTarget);
840
+ if (!await tryLstat(resolvedCurrent)) {
841
+ result.add(entry.name);
842
+ }
843
+ } catch {
844
+ result.add(entry.name);
845
+ }
846
+ }
847
+ } catch {
848
+ return result;
849
+ }
850
+ return result;
851
+ }
852
+
853
+ // src/lib/import.ts
357
854
  var MissingSymlinkSourceError = class extends Error {
358
855
  sourcePath;
359
856
  resolvedSourcePath;
@@ -365,15 +862,15 @@ var MissingSymlinkSourceError = class extends Error {
365
862
  }
366
863
  };
367
864
  async function resolveImportSource(sourcePath) {
368
- const statResult = await lstat(sourcePath);
865
+ const statResult = await lstat3(sourcePath);
369
866
  if (!statResult.isSymbolicLink()) {
370
867
  return {
371
868
  effectiveSourcePath: sourcePath,
372
869
  isSymlinkSource: false
373
870
  };
374
871
  }
375
- const linkTarget = await readlink(sourcePath);
376
- const resolvedSourcePath = path7.resolve(path7.dirname(sourcePath), linkTarget);
872
+ const linkTarget = await readlink2(sourcePath);
873
+ const resolvedSourcePath = path9.resolve(path9.dirname(sourcePath), linkTarget);
377
874
  if (!await pathExists(resolvedSourcePath)) {
378
875
  throw new MissingSymlinkSourceError(sourcePath, resolvedSourcePath);
379
876
  }
@@ -385,37 +882,32 @@ async function resolveImportSource(sourcePath) {
385
882
  async function mergeMissingEntries(sourcePath, destinationPath) {
386
883
  const sourceStat = await stat2(sourcePath);
387
884
  if (sourceStat.isDirectory()) {
388
- await mkdir4(destinationPath, { recursive: true });
389
- const entries = await readdir3(sourcePath, { withFileTypes: true });
885
+ await mkdir5(destinationPath, { recursive: true });
886
+ const entries = await readdir6(sourcePath, { withFileTypes: true });
390
887
  for (const entry of entries) {
391
- await mergeMissingEntries(path7.join(sourcePath, entry.name), path7.join(destinationPath, entry.name));
888
+ await mergeMissingEntries(path9.join(sourcePath, entry.name), path9.join(destinationPath, entry.name));
392
889
  }
393
890
  return;
394
891
  }
395
892
  if (await pathExists(destinationPath)) {
396
893
  return;
397
894
  }
398
- await mkdir4(path7.dirname(destinationPath), { recursive: true });
399
- await cp(sourcePath, destinationPath, { recursive: false, errorOnExist: true, force: false });
895
+ await mkdir5(path9.dirname(destinationPath), { recursive: true });
896
+ await cp3(sourcePath, destinationPath, { recursive: false, errorOnExist: true, force: false });
400
897
  }
401
898
  async function copyIntoDestination(sourcePath, destination, override) {
402
899
  if (!override && await pathExists(destination)) {
403
900
  await mergeMissingEntries(sourcePath, destination);
404
901
  return;
405
902
  }
406
- await cp(sourcePath, destination, { recursive: true, errorOnExist: false, force: override });
903
+ await cp3(sourcePath, destination, { recursive: true, errorOnExist: false, force: override });
407
904
  }
408
- async function moveIntoDestination(sourcePath, destination, override) {
409
- if (!await pathExists(destination)) {
410
- await rename(sourcePath, destination);
411
- return;
412
- }
413
- if (override) {
414
- await cp(sourcePath, destination, { recursive: true, errorOnExist: false, force: true });
415
- await rm2(sourcePath, { recursive: true, force: true });
416
- return;
905
+ async function relinkImportedSource(sourcePath, destination) {
906
+ if (path9.resolve(sourcePath) === path9.resolve(destination)) {
907
+ return void 0;
417
908
  }
418
- await mergeMissingEntries(sourcePath, destination);
909
+ await createSkillSymlink(destination, sourcePath, { allowReplaceExisting: true });
910
+ return sourcePath;
419
911
  }
420
912
  async function importBatchSources(options) {
421
913
  const seen = /* @__PURE__ */ new Set();
@@ -424,6 +916,7 @@ async function importBatchSources(options) {
424
916
  const overwritten = [];
425
917
  const warnings = [];
426
918
  const errors = [];
919
+ const linkedSources = [];
427
920
  let missingSources = 0;
428
921
  for (const source of options.sources) {
429
922
  if (seen.has(source.name)) {
@@ -432,22 +925,31 @@ async function importBatchSources(options) {
432
925
  }
433
926
  seen.add(source.name);
434
927
  const alreadyExists = await skillExists(options.homeDir, source.name);
435
- if (alreadyExists && !options.override) {
436
- skipped.push(source.name);
437
- continue;
438
- }
439
928
  try {
440
- const result = await importSkill({
441
- homeDir: options.homeDir,
442
- sourcePath: source.path,
443
- mode: options.mode,
444
- override: options.override
445
- });
446
- warnings.push(...result.warnings);
447
- if (alreadyExists) {
448
- overwritten.push(source.name);
929
+ if (!alreadyExists || options.override) {
930
+ const result = await importSkill({
931
+ homeDir: options.homeDir,
932
+ sourcePath: source.path,
933
+ override: options.override,
934
+ linkSource: options.linkSource
935
+ });
936
+ warnings.push(...result.warnings);
937
+ if (result.linkedSourcePath) {
938
+ linkedSources.push(result.name);
939
+ }
940
+ if (alreadyExists) {
941
+ overwritten.push(source.name);
942
+ } else {
943
+ imported.push(source.name);
944
+ }
449
945
  } else {
450
- imported.push(source.name);
946
+ if (options.linkSource) {
947
+ const linkedPath = await relinkImportedSource(source.path, getSkillPath(options.homeDir, source.name));
948
+ if (linkedPath) {
949
+ linkedSources.push(source.name);
950
+ }
951
+ }
952
+ skipped.push(source.name);
451
953
  }
452
954
  } catch (error) {
453
955
  if (error instanceof MissingSymlinkSourceError) {
@@ -462,17 +964,17 @@ async function importBatchSources(options) {
462
964
  throw error;
463
965
  }
464
966
  }
465
- return { imported, skipped, overwritten, warnings, errors, missingSources };
967
+ return { imported, skipped, overwritten, warnings, errors, missingSources, linkedSources };
466
968
  }
467
969
  async function listImportableChildren(sourceRoot) {
468
- const entries = await readdir3(sourceRoot, { withFileTypes: true });
970
+ const entries = await readdir6(sourceRoot, { withFileTypes: true });
469
971
  const sources = [];
470
972
  for (const entry of entries) {
471
973
  if (!(entry.isDirectory() || entry.isSymbolicLink())) {
472
974
  continue;
473
975
  }
474
- const childPath = path7.join(sourceRoot, entry.name);
475
- if (await pathExists(path7.join(childPath, "SKILL.md"))) {
976
+ const childPath = path9.join(sourceRoot, entry.name);
977
+ if (await pathExists(path9.join(childPath, "SKILL.md"))) {
476
978
  sources.push({
477
979
  name: sanitizeName(entry.name),
478
980
  path: childPath
@@ -491,22 +993,19 @@ async function listImportableChildren(sourceRoot) {
491
993
  async function importSkill(options) {
492
994
  const { effectiveSourcePath, isSymlinkSource } = await resolveImportSource(options.sourcePath);
493
995
  await assertSkillSource(effectiveSourcePath);
494
- const skillName = sanitizeName(path7.basename(options.sourcePath));
996
+ const skillName = sanitizeName(path9.basename(options.sourcePath));
495
997
  if (!skillName) {
496
998
  throw new Error(`Unable to infer skill name from path: ${options.sourcePath}`);
497
999
  }
498
1000
  const destination = getSkillPath(options.homeDir, skillName);
499
- await mkdir4(path7.dirname(destination), { recursive: true });
1001
+ await mkdir5(path9.dirname(destination), { recursive: true });
500
1002
  const warnings = [];
501
1003
  if (isSymlinkSource) {
502
1004
  warnings.push(`Source ${options.sourcePath} is a symlink; copied from ${effectiveSourcePath} to ${destination}`);
503
1005
  }
504
- if (options.mode === "mv" && !isSymlinkSource) {
505
- await moveIntoDestination(effectiveSourcePath, destination, options.override ?? false);
506
- } else {
507
- await copyIntoDestination(effectiveSourcePath, destination, options.override ?? false);
508
- }
509
- return { name: skillName, destination, warnings };
1006
+ await copyIntoDestination(effectiveSourcePath, destination, options.override ?? false);
1007
+ const linkedSourcePath = options.linkSource ? await relinkImportedSource(options.sourcePath, destination) : void 0;
1008
+ return { name: skillName, destination, warnings, linkedSourcePath };
510
1009
  }
511
1010
  async function importScannedSkills(options) {
512
1011
  return importBatchSources({
@@ -515,21 +1014,23 @@ async function importScannedSkills(options) {
515
1014
  name: candidate.name,
516
1015
  path: candidate.path
517
1016
  })),
518
- mode: options.mode,
519
- override: options.override
1017
+ override: options.override,
1018
+ linkSource: options.linkSource
520
1019
  });
521
1020
  }
522
1021
  async function importPath(options) {
523
- if (await pathExists(path7.join(options.sourcePath, "SKILL.md"))) {
524
- const skillName = sanitizeName(path7.basename(options.sourcePath));
1022
+ if (await pathExists(path9.join(options.sourcePath, "SKILL.md"))) {
1023
+ const skillName = sanitizeName(path9.basename(options.sourcePath));
525
1024
  const alreadyExisted = skillName ? await skillExists(options.homeDir, skillName) : false;
526
1025
  if (alreadyExisted && !options.override) {
1026
+ const linkedSourcePath = options.linkSource ? await relinkImportedSource(options.sourcePath, getSkillPath(options.homeDir, skillName)) : void 0;
527
1027
  return {
528
1028
  kind: "single",
529
1029
  alreadyExisted: true,
530
1030
  name: skillName,
531
1031
  destination: getSkillPath(options.homeDir, skillName),
532
- warnings: []
1032
+ warnings: [],
1033
+ linkedSourcePath
533
1034
  };
534
1035
  }
535
1036
  const result = await importSkill(options);
@@ -542,18 +1043,18 @@ async function importPath(options) {
542
1043
  const batchResult = await importBatchSources({
543
1044
  homeDir: options.homeDir,
544
1045
  sources,
545
- mode: options.mode,
546
- override: options.override
1046
+ override: options.override,
1047
+ linkSource: options.linkSource
547
1048
  });
548
1049
  return { kind: "batch", ...batchResult };
549
1050
  }
550
1051
 
551
1052
  // src/lib/scanner.ts
552
- import { access as access2, lstat as lstat2, readdir as readdir4, readlink as readlink2 } from "fs/promises";
553
- import path9 from "path";
1053
+ import { access as access2, lstat as lstat4, readdir as readdir7, readlink as readlink3 } from "fs/promises";
1054
+ import path11 from "path";
554
1055
 
555
1056
  // src/lib/agents.ts
556
- import path8 from "path";
1057
+ import path10 from "path";
557
1058
  function defineAgent(id, displayName, options) {
558
1059
  return {
559
1060
  id,
@@ -568,238 +1069,238 @@ function defineAgent(id, displayName, options) {
568
1069
  }
569
1070
  var AGENTS = {
570
1071
  adal: defineAgent("adal", "AdaL", {
571
- rootDir: (homeDir) => path8.join(homeDir, ".adal"),
572
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".adal", "skills"),
573
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".adal", "skills")
1072
+ rootDir: (homeDir) => path10.join(homeDir, ".adal"),
1073
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".adal", "skills"),
1074
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".adal", "skills")
574
1075
  }),
575
1076
  amp: defineAgent("amp", "Amp", {
576
- rootDir: (homeDir) => path8.join(homeDir, ".agents"),
577
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".agents", "skills"),
578
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".agents", "skills")
1077
+ rootDir: (homeDir) => path10.join(homeDir, ".agents"),
1078
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".agents", "skills"),
1079
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".agents", "skills")
579
1080
  }),
580
1081
  antigravity: defineAgent("antigravity", "Antigravity", {
581
- rootDir: (homeDir) => path8.join(homeDir, ".gemini", "antigravity"),
582
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".gemini", "antigravity", "skills"),
583
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".gemini", "antigravity", "skills")
1082
+ rootDir: (homeDir) => path10.join(homeDir, ".gemini", "antigravity"),
1083
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".gemini", "antigravity", "skills"),
1084
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".gemini", "antigravity", "skills")
584
1085
  }),
585
1086
  augment: defineAgent("augment", "Augment", {
586
- rootDir: (homeDir) => path8.join(homeDir, ".augment"),
587
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".augment", "skills"),
588
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".augment", "skills")
1087
+ rootDir: (homeDir) => path10.join(homeDir, ".augment"),
1088
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".augment", "skills"),
1089
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".augment", "skills")
589
1090
  }),
590
1091
  bob: defineAgent("bob", "IBM Bob", {
591
- rootDir: (homeDir) => path8.join(homeDir, ".bob"),
592
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".bob", "skills"),
593
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".bob", "skills")
1092
+ rootDir: (homeDir) => path10.join(homeDir, ".bob"),
1093
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".bob", "skills"),
1094
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".bob", "skills")
594
1095
  }),
595
1096
  "claude-code": defineAgent("claude-code", "Claude Code", {
596
- rootDir: (homeDir) => path8.join(homeDir, ".claude"),
597
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".claude", "skills"),
598
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".claude", "skills")
1097
+ rootDir: (homeDir) => path10.join(homeDir, ".claude"),
1098
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".claude", "skills"),
1099
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".claude", "skills")
599
1100
  }),
600
1101
  cline: defineAgent("cline", "Cline", {
601
- rootDir: (homeDir) => path8.join(homeDir, ".cline"),
602
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".cline", "skills"),
603
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".cline", "skills")
1102
+ rootDir: (homeDir) => path10.join(homeDir, ".cline"),
1103
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".cline", "skills"),
1104
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".cline", "skills")
604
1105
  }),
605
1106
  codebuddy: defineAgent("codebuddy", "CodeBuddy", {
606
- rootDir: (homeDir) => path8.join(homeDir, ".codebuddy"),
607
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".codebuddy", "skills"),
608
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".codebuddy", "skills")
1107
+ rootDir: (homeDir) => path10.join(homeDir, ".codebuddy"),
1108
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".codebuddy", "skills"),
1109
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".codebuddy", "skills")
609
1110
  }),
610
1111
  "command-code": defineAgent("command-code", "Command Code", {
611
- rootDir: (homeDir) => path8.join(homeDir, ".commandcode"),
612
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".commandcode", "skills"),
613
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".commandcode", "skills")
1112
+ rootDir: (homeDir) => path10.join(homeDir, ".commandcode"),
1113
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".commandcode", "skills"),
1114
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".commandcode", "skills")
614
1115
  }),
615
1116
  continue: defineAgent("continue", "Continue", {
616
- rootDir: (homeDir) => path8.join(homeDir, ".continue"),
617
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".continue", "skills"),
618
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".continue", "skills")
1117
+ rootDir: (homeDir) => path10.join(homeDir, ".continue"),
1118
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".continue", "skills"),
1119
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".continue", "skills")
619
1120
  }),
620
1121
  codex: defineAgent("codex", "Codex", {
621
- rootDir: (homeDir) => path8.join(homeDir, ".codex"),
622
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".codex", "skills"),
623
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".codex", "skills")
1122
+ rootDir: (homeDir) => path10.join(homeDir, ".codex"),
1123
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".codex", "skills"),
1124
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".codex", "skills")
624
1125
  }),
625
1126
  copilot: defineAgent("copilot", "GitHub Copilot", {
626
- rootDir: (homeDir) => path8.join(homeDir, ".copilot"),
627
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".copilot", "skills"),
628
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".copilot", "skills")
1127
+ rootDir: (homeDir) => path10.join(homeDir, ".copilot"),
1128
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".copilot", "skills"),
1129
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".copilot", "skills")
629
1130
  }),
630
1131
  cortex: defineAgent("cortex", "Cortex Code", {
631
- rootDir: (homeDir) => path8.join(homeDir, ".snowflake", "cortex"),
632
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".snowflake", "cortex", "skills"),
633
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".cortex", "skills")
1132
+ rootDir: (homeDir) => path10.join(homeDir, ".snowflake", "cortex"),
1133
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".snowflake", "cortex", "skills"),
1134
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".cortex", "skills")
634
1135
  }),
635
1136
  crush: defineAgent("crush", "Crush", {
636
- rootDir: (homeDir) => path8.join(homeDir, ".config", "crush"),
637
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".config", "crush", "skills"),
638
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".config", "crush", "skills")
1137
+ rootDir: (homeDir) => path10.join(homeDir, ".config", "crush"),
1138
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".config", "crush", "skills"),
1139
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".config", "crush", "skills")
639
1140
  }),
640
1141
  cursor: defineAgent("cursor", "Cursor", {
641
- rootDir: (homeDir) => path8.join(homeDir, ".cursor"),
642
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".cursor", "skills"),
643
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".cursor", "skills")
1142
+ rootDir: (homeDir) => path10.join(homeDir, ".cursor"),
1143
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".cursor", "skills"),
1144
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".cursor", "skills")
644
1145
  }),
645
1146
  deepagents: defineAgent("deepagents", "Deep Agents", {
646
- rootDir: (homeDir) => path8.join(homeDir, ".deepagents"),
647
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".deepagents", "agent", "skills"),
648
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".deepagents", "agent", "skills")
1147
+ rootDir: (homeDir) => path10.join(homeDir, ".deepagents"),
1148
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".deepagents", "agent", "skills"),
1149
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".deepagents", "agent", "skills")
649
1150
  }),
650
1151
  droid: defineAgent("droid", "Droid", {
651
- rootDir: (homeDir) => path8.join(homeDir, ".factory"),
652
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".factory", "skills"),
653
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".factory", "skills")
1152
+ rootDir: (homeDir) => path10.join(homeDir, ".factory"),
1153
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".factory", "skills"),
1154
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".factory", "skills")
654
1155
  }),
655
1156
  firebender: defineAgent("firebender", "Firebender", {
656
- rootDir: (homeDir) => path8.join(homeDir, ".firebender"),
657
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".firebender", "skills"),
658
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".firebender", "skills")
1157
+ rootDir: (homeDir) => path10.join(homeDir, ".firebender"),
1158
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".firebender", "skills"),
1159
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".firebender", "skills")
659
1160
  }),
660
1161
  "gemini-cli": defineAgent("gemini-cli", "Gemini CLI", {
661
- rootDir: (homeDir) => path8.join(homeDir, ".gemini"),
662
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".gemini", "skills"),
663
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".gemini", "skills")
1162
+ rootDir: (homeDir) => path10.join(homeDir, ".gemini"),
1163
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".gemini", "skills"),
1164
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".gemini", "skills")
664
1165
  }),
665
1166
  "github-copilot": defineAgent("github-copilot", "GitHub Copilot", {
666
- rootDir: (homeDir) => path8.join(homeDir, ".copilot"),
667
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".copilot", "skills"),
668
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".copilot", "skills")
1167
+ rootDir: (homeDir) => path10.join(homeDir, ".copilot"),
1168
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".copilot", "skills"),
1169
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".copilot", "skills")
669
1170
  }),
670
1171
  goose: defineAgent("goose", "Goose", {
671
- rootDir: (homeDir) => path8.join(homeDir, ".goose"),
672
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".goose", "skills"),
673
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".goose", "skills")
1172
+ rootDir: (homeDir) => path10.join(homeDir, ".goose"),
1173
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".goose", "skills"),
1174
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".goose", "skills")
674
1175
  }),
675
1176
  "iflow-cli": defineAgent("iflow-cli", "iFlow CLI", {
676
- rootDir: (homeDir) => path8.join(homeDir, ".iflow"),
677
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".iflow", "skills"),
678
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".iflow", "skills")
1177
+ rootDir: (homeDir) => path10.join(homeDir, ".iflow"),
1178
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".iflow", "skills"),
1179
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".iflow", "skills")
679
1180
  }),
680
1181
  junie: defineAgent("junie", "Junie", {
681
- rootDir: (homeDir) => path8.join(homeDir, ".junie"),
682
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".junie", "skills"),
683
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".junie", "skills")
1182
+ rootDir: (homeDir) => path10.join(homeDir, ".junie"),
1183
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".junie", "skills"),
1184
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".junie", "skills")
684
1185
  }),
685
1186
  kilo: defineAgent("kilo", "Kilo Code", {
686
- rootDir: (homeDir) => path8.join(homeDir, ".kilocode"),
687
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".kilocode", "skills"),
688
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".kilocode", "skills")
1187
+ rootDir: (homeDir) => path10.join(homeDir, ".kilocode"),
1188
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".kilocode", "skills"),
1189
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".kilocode", "skills")
689
1190
  }),
690
1191
  "kiro-cli": defineAgent("kiro-cli", "Kiro CLI", {
691
- rootDir: (homeDir) => path8.join(homeDir, ".kiro"),
692
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".kiro", "skills"),
693
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".kiro", "skills")
1192
+ rootDir: (homeDir) => path10.join(homeDir, ".kiro"),
1193
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".kiro", "skills"),
1194
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".kiro", "skills")
694
1195
  }),
695
1196
  "kilo-code": defineAgent("kilo-code", "Kilo Code", {
696
- rootDir: (homeDir) => path8.join(homeDir, ".kilocode"),
697
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".kilocode", "skills"),
698
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".kilocode", "skills")
1197
+ rootDir: (homeDir) => path10.join(homeDir, ".kilocode"),
1198
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".kilocode", "skills"),
1199
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".kilocode", "skills")
699
1200
  }),
700
1201
  "kimi-cli": defineAgent("kimi-cli", "Kimi Code CLI", {
701
- rootDir: (homeDir) => path8.join(homeDir, ".kimi"),
702
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".kimi", "skills"),
703
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".kimi", "skills")
1202
+ rootDir: (homeDir) => path10.join(homeDir, ".kimi"),
1203
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".kimi", "skills"),
1204
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".kimi", "skills")
704
1205
  }),
705
1206
  kode: defineAgent("kode", "Kode", {
706
- rootDir: (homeDir) => path8.join(homeDir, ".kode"),
707
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".kode", "skills"),
708
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".kode", "skills")
1207
+ rootDir: (homeDir) => path10.join(homeDir, ".kode"),
1208
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".kode", "skills"),
1209
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".kode", "skills")
709
1210
  }),
710
1211
  mcpjam: defineAgent("mcpjam", "MCPJam", {
711
- rootDir: (homeDir) => path8.join(homeDir, ".mcpjam"),
712
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".mcpjam", "skills"),
713
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".mcpjam", "skills")
1212
+ rootDir: (homeDir) => path10.join(homeDir, ".mcpjam"),
1213
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".mcpjam", "skills"),
1214
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".mcpjam", "skills")
714
1215
  }),
715
1216
  "mistral-vibe": defineAgent("mistral-vibe", "Mistral Vibe", {
716
- rootDir: (homeDir) => path8.join(homeDir, ".vibe"),
717
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".vibe", "skills"),
718
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".vibe", "skills")
1217
+ rootDir: (homeDir) => path10.join(homeDir, ".vibe"),
1218
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".vibe", "skills"),
1219
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".vibe", "skills")
719
1220
  }),
720
1221
  mux: defineAgent("mux", "Mux", {
721
- rootDir: (homeDir) => path8.join(homeDir, ".mux"),
722
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".mux", "skills"),
723
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".mux", "skills")
1222
+ rootDir: (homeDir) => path10.join(homeDir, ".mux"),
1223
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".mux", "skills"),
1224
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".mux", "skills")
724
1225
  }),
725
1226
  neovate: defineAgent("neovate", "Neovate", {
726
- rootDir: (homeDir) => path8.join(homeDir, ".neovate"),
727
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".neovate", "skills"),
728
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".neovate", "skills")
1227
+ rootDir: (homeDir) => path10.join(homeDir, ".neovate"),
1228
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".neovate", "skills"),
1229
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".neovate", "skills")
729
1230
  }),
730
1231
  openclaw: defineAgent("openclaw", "OpenClaw", {
731
- rootDir: (homeDir) => path8.join(homeDir, ".openclaw"),
732
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".openclaw", "skills"),
733
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".openclaw", "skills")
1232
+ rootDir: (homeDir) => path10.join(homeDir, ".openclaw"),
1233
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".openclaw", "skills"),
1234
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".openclaw", "skills")
734
1235
  }),
735
1236
  "openclaude-ide": defineAgent("openclaude-ide", "OpenClaude IDE", {
736
- rootDir: (homeDir) => path8.join(homeDir, ".openclaude"),
737
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".openclaude", "skills"),
738
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".openclaude", "skills")
1237
+ rootDir: (homeDir) => path10.join(homeDir, ".openclaude"),
1238
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".openclaude", "skills"),
1239
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".openclaude", "skills")
739
1240
  }),
740
1241
  openhands: defineAgent("openhands", "OpenHands", {
741
- rootDir: (homeDir) => path8.join(homeDir, ".openhands"),
742
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".openhands", "skills"),
743
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".openhands", "skills")
1242
+ rootDir: (homeDir) => path10.join(homeDir, ".openhands"),
1243
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".openhands", "skills"),
1244
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".openhands", "skills")
744
1245
  }),
745
1246
  opencode: defineAgent("opencode", "OpenCode", {
746
- rootDir: (homeDir) => path8.join(homeDir, ".opencode"),
747
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".opencode", "skills"),
748
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".opencode", "skills")
1247
+ rootDir: (homeDir) => path10.join(homeDir, ".opencode"),
1248
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".opencode", "skills"),
1249
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".opencode", "skills")
749
1250
  }),
750
1251
  pi: defineAgent("pi", "Pi", {
751
- rootDir: (homeDir) => path8.join(homeDir, ".pi", "agent"),
752
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".pi", "agent", "skills"),
753
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".pi", "agent", "skills")
1252
+ rootDir: (homeDir) => path10.join(homeDir, ".pi", "agent"),
1253
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".pi", "agent", "skills"),
1254
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".pi", "agent", "skills")
754
1255
  }),
755
1256
  pochi: defineAgent("pochi", "Pochi", {
756
- rootDir: (homeDir) => path8.join(homeDir, ".pochi"),
757
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".pochi", "skills"),
758
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".pochi", "skills")
1257
+ rootDir: (homeDir) => path10.join(homeDir, ".pochi"),
1258
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".pochi", "skills"),
1259
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".pochi", "skills")
759
1260
  }),
760
1261
  qoder: defineAgent("qoder", "Qoder", {
761
- rootDir: (homeDir) => path8.join(homeDir, ".qoder"),
762
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".qoder", "skills"),
763
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".qoder", "skills")
1262
+ rootDir: (homeDir) => path10.join(homeDir, ".qoder"),
1263
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".qoder", "skills"),
1264
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".qoder", "skills")
764
1265
  }),
765
1266
  "qwen-code": defineAgent("qwen-code", "Qwen Code", {
766
- rootDir: (homeDir) => path8.join(homeDir, ".qwen"),
767
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".qwen", "skills"),
768
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".qwen", "skills")
1267
+ rootDir: (homeDir) => path10.join(homeDir, ".qwen"),
1268
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".qwen", "skills"),
1269
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".qwen", "skills")
769
1270
  }),
770
1271
  replit: defineAgent("replit", "Replit", {
771
- rootDir: (homeDir) => path8.join(homeDir, ".config", "replit"),
772
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".agent", "skills")
1272
+ rootDir: (homeDir) => path10.join(homeDir, ".config", "replit"),
1273
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".agent", "skills")
773
1274
  }),
774
1275
  roo: defineAgent("roo", "Roo Code", {
775
- rootDir: (homeDir) => path8.join(homeDir, ".roo"),
776
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".roo", "skills"),
777
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".roo", "skills")
1276
+ rootDir: (homeDir) => path10.join(homeDir, ".roo"),
1277
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".roo", "skills"),
1278
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".roo", "skills")
778
1279
  }),
779
1280
  trae: defineAgent("trae", "Trae", {
780
- rootDir: (homeDir) => path8.join(homeDir, ".trae"),
781
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".trae", "skills"),
782
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".trae", "skills")
1281
+ rootDir: (homeDir) => path10.join(homeDir, ".trae"),
1282
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".trae", "skills"),
1283
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".trae", "skills")
783
1284
  }),
784
1285
  "trae-cn": defineAgent("trae-cn", "Trae CN", {
785
- rootDir: (homeDir) => path8.join(homeDir, ".trae-cn"),
786
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".trae-cn", "skills"),
787
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".trae-cn", "skills")
1286
+ rootDir: (homeDir) => path10.join(homeDir, ".trae-cn"),
1287
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".trae-cn", "skills"),
1288
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".trae-cn", "skills")
788
1289
  }),
789
1290
  warp: defineAgent("warp", "Warp", {
790
- rootDir: (homeDir) => path8.join(homeDir, ".warp"),
791
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".warp", "skills"),
792
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".warp", "skills")
1291
+ rootDir: (homeDir) => path10.join(homeDir, ".warp"),
1292
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".warp", "skills"),
1293
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".warp", "skills")
793
1294
  }),
794
1295
  windsurf: defineAgent("windsurf", "Windsurf", {
795
- rootDir: (homeDir) => path8.join(homeDir, ".codeium", "windsurf"),
796
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".codeium", "windsurf", "skills"),
797
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".codeium", "windsurf", "skills")
1296
+ rootDir: (homeDir) => path10.join(homeDir, ".codeium", "windsurf"),
1297
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".codeium", "windsurf", "skills"),
1298
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".codeium", "windsurf", "skills")
798
1299
  }),
799
1300
  zencoder: defineAgent("zencoder", "Zencoder", {
800
- rootDir: (homeDir) => path8.join(homeDir, ".zencoder"),
801
- globalSkillsDir: (homeDir) => path8.join(homeDir, ".zencoder", "skills"),
802
- projectSkillsDir: (projectDir) => path8.join(projectDir, ".zencoder", "skills")
1301
+ rootDir: (homeDir) => path10.join(homeDir, ".zencoder"),
1302
+ globalSkillsDir: (homeDir) => path10.join(homeDir, ".zencoder", "skills"),
1303
+ projectSkillsDir: (projectDir) => path10.join(projectDir, ".zencoder", "skills")
803
1304
  })
804
1305
  };
805
1306
  function listSupportedAgents() {
@@ -808,6 +1309,13 @@ function listSupportedAgents() {
808
1309
  function listSupportedAgentIds() {
809
1310
  return listSupportedAgents().map((agent) => agent.id);
810
1311
  }
1312
+ function formatDetectedAgentsForScope(scope, agents, projectDir) {
1313
+ const countLabel = `${agents.length} agent${agents.length === 1 ? "" : "s"}`;
1314
+ if (scope === "global") {
1315
+ return `Detected ${countLabel} for global scope: ${agents.join(", ")}`;
1316
+ }
1317
+ return `Detected ${countLabel} for project scope at ${projectDir}: ${agents.join(", ")}`;
1318
+ }
811
1319
  function supportsScope(agentId, scope) {
812
1320
  const definition = getAgentDefinition(agentId);
813
1321
  return scope === "global" ? definition.supportsGlobal : definition.supportsProject;
@@ -844,27 +1352,96 @@ async function detectInstalledAgents(options) {
844
1352
  }
845
1353
  return installed;
846
1354
  }
847
-
848
- // src/lib/scanner.ts
849
- async function hasSkillReadme(skillDir) {
850
- try {
851
- await access2(path9.join(skillDir, "SKILL.md"));
852
- return true;
853
- } catch {
854
- return false;
855
- }
856
- }
857
- async function isSymlinkPath(targetPath) {
858
- try {
859
- return (await lstat2(targetPath)).isSymbolicLink();
860
- } catch {
861
- return false;
1355
+ async function listSupportedAgentsWithGlobalStatus(homeDir) {
1356
+ const results = [];
1357
+ for (const agent of listSupportedAgents()) {
1358
+ if (!agent.supportsGlobal || !agent.globalSkillsDir) {
1359
+ results.push({
1360
+ id: agent.id,
1361
+ displayName: agent.displayName,
1362
+ installed: false
1363
+ });
1364
+ continue;
1365
+ }
1366
+ const skillsDir = agent.globalSkillsDir(homeDir);
1367
+ results.push({
1368
+ id: agent.id,
1369
+ displayName: agent.displayName,
1370
+ installed: await pathExists(agent.rootDir(homeDir)),
1371
+ skillsDir
1372
+ });
1373
+ }
1374
+ return results;
1375
+ }
1376
+ async function detectAgentsForListingScope(homeDir, scope, projectDir) {
1377
+ const installed = [];
1378
+ for (const agent of listSupportedAgents()) {
1379
+ if (scope === "global") {
1380
+ if (!agent.supportsGlobal) {
1381
+ continue;
1382
+ }
1383
+ const rootPath = agent.rootDir(homeDir);
1384
+ if (await pathExists(rootPath)) {
1385
+ installed.push(agent.id);
1386
+ }
1387
+ } else {
1388
+ if (!agent.supportsProject || !projectDir) {
1389
+ continue;
1390
+ }
1391
+ const skillsPath = resolveAgentSkillsDir(agent.id, "project", projectDir);
1392
+ if (await pathExists(skillsPath)) {
1393
+ installed.push(agent.id);
1394
+ }
1395
+ }
1396
+ }
1397
+ return installed;
1398
+ }
1399
+ async function resolveAgentsForListingOrSync(options) {
1400
+ const wantsAll = options.requestedAgents.length === 0 || options.requestedAgents.includes("all");
1401
+ if (!wantsAll) {
1402
+ const agents2 = uniqueSorted(
1403
+ options.requestedAgents.map((agent) => {
1404
+ if (!isAgentId(agent)) {
1405
+ throw new Error(`Unsupported agent: ${agent}`);
1406
+ }
1407
+ if (!supportsScope(agent, options.scope)) {
1408
+ throw new Error(`Agent ${agent} does not support ${options.scope} scope.`);
1409
+ }
1410
+ return agent;
1411
+ })
1412
+ );
1413
+ return { agents: agents2, explicit: true };
1414
+ }
1415
+ const agents = await detectAgentsForListingScope(options.homeDir, options.scope, options.projectDir);
1416
+ return { agents, explicit: false };
1417
+ }
1418
+ function formatNoAgentsDetectedForScope(scope, projectDir) {
1419
+ if (scope === "global") {
1420
+ return "No agents detected for global scope (no supported agent installation directories were found). Install an agent or pass --agent <id> to inspect a specific agent.";
1421
+ }
1422
+ return `No agents detected for project scope at ${projectDir}. Add project-local agent skill directories or pass --agent <id> to inspect a specific agent.`;
1423
+ }
1424
+
1425
+ // src/lib/scanner.ts
1426
+ async function hasSkillReadme(skillDir) {
1427
+ try {
1428
+ await access2(path11.join(skillDir, "SKILL.md"));
1429
+ return true;
1430
+ } catch {
1431
+ return false;
1432
+ }
1433
+ }
1434
+ async function isSymlinkPath(targetPath) {
1435
+ try {
1436
+ return (await lstat4(targetPath)).isSymbolicLink();
1437
+ } catch {
1438
+ return false;
862
1439
  }
863
1440
  }
864
1441
  async function resolveSymlinkSource(targetPath) {
865
1442
  try {
866
- const linkTarget = await readlink2(targetPath);
867
- const sourcePath = path9.resolve(path9.dirname(targetPath), linkTarget);
1443
+ const linkTarget = await readlink3(targetPath);
1444
+ const sourcePath = path11.resolve(path11.dirname(targetPath), linkTarget);
868
1445
  return {
869
1446
  sourcePath,
870
1447
  isBroken: !await pathExists(sourcePath)
@@ -877,13 +1454,13 @@ async function resolveSymlinkSource(targetPath) {
877
1454
  }
878
1455
  async function scanDirectory(baseDir, agentId, scope, projectDir) {
879
1456
  try {
880
- const entries = await readdir4(baseDir, { withFileTypes: true });
1457
+ const entries = await readdir7(baseDir, { withFileTypes: true });
881
1458
  const candidates = [];
882
1459
  for (const entry of entries) {
883
1460
  if (!entry.isDirectory() && !entry.isSymbolicLink()) {
884
1461
  continue;
885
1462
  }
886
- const fullPath = path9.join(baseDir, entry.name);
1463
+ const fullPath = path11.join(baseDir, entry.name);
887
1464
  const isSymlink = await isSymlinkPath(fullPath);
888
1465
  const symlinkInfo = isSymlink ? await resolveSymlinkSource(fullPath) : { isBroken: false };
889
1466
  if (!await hasSkillReadme(fullPath) && !symlinkInfo.isBroken) {
@@ -905,33 +1482,54 @@ async function scanDirectory(baseDir, agentId, scope, projectDir) {
905
1482
  return [];
906
1483
  }
907
1484
  }
1485
+ function uniqueSorted2(items) {
1486
+ return [...new Set(items)].sort((left, right) => left.localeCompare(right));
1487
+ }
1488
+ function resolveRequestedAgents(requestedAgents, scope) {
1489
+ if (requestedAgents.length === 0 || requestedAgents.includes("all")) {
1490
+ return listSupportedAgentIds().filter((agentId) => supportsScope(agentId, scope));
1491
+ }
1492
+ return uniqueSorted2(
1493
+ requestedAgents.map((agent) => {
1494
+ if (!isAgentId(agent)) {
1495
+ throw new Error(`Unsupported agent: ${agent}`);
1496
+ }
1497
+ if (!supportsScope(agent, scope)) {
1498
+ throw new Error(`Agent ${agent} does not support ${scope} scope.`);
1499
+ }
1500
+ return agent;
1501
+ })
1502
+ );
1503
+ }
908
1504
  async function scanSkills(options) {
909
1505
  const results = [];
910
- for (const agent of listSupportedAgents()) {
911
- if (supportsScope(agent.id, "global")) {
912
- results.push(...await scanDirectory(agent.globalSkillsDir(options.homeDir), agent.id, "global"));
913
- }
914
- for (const projectDir of options.projectDirs ?? []) {
915
- if (supportsScope(agent.id, "project")) {
916
- results.push(...await scanDirectory(agent.projectSkillsDir(projectDir), agent.id, "project", projectDir));
917
- }
918
- }
1506
+ const agents = resolveRequestedAgents(options.agents ?? [], options.scope);
1507
+ for (const agentId of agents) {
1508
+ const baseDir = options.scope === "global" ? options.homeDir : options.projectDir;
1509
+ const skillsDir = resolveAgentSkillsDir(agentId, options.scope, baseDir);
1510
+ results.push(...await scanDirectory(skillsDir, agentId, options.scope, options.projectDir));
919
1511
  }
920
1512
  return results.sort((left, right) => left.path.localeCompare(right.path));
921
1513
  }
922
1514
 
923
1515
  // src/commands/import.ts
924
1516
  async function runImport(context, options) {
1517
+ if (options.keepSource && options.linkSource) {
1518
+ throw new Error("Choose either --keep-source or --link-source, not both.");
1519
+ }
1520
+ const linkSource = options.scan ? !options.keepSource : Boolean(options.linkSource);
925
1521
  if (options.scan) {
926
1522
  const candidates = await scanSkills({
927
1523
  homeDir: context.homeDir,
928
- projectDirs: [context.cwd]
1524
+ scope: options.scope ?? "global",
1525
+ agents: options.agents,
1526
+ projectDir: (options.scope ?? "global") === "project" ? options.projectDir ?? context.cwd : void 0
929
1527
  });
930
1528
  const result2 = await importScannedSkills({
931
1529
  homeDir: context.homeDir,
932
1530
  candidates,
933
- mode: options.mode,
934
- override: options.override
1531
+ override: options.override,
1532
+ linkSource
935
1533
  });
936
1534
  for (const warning of result2.warnings) {
937
1535
  context.write(`Warning: ${warning}`);
@@ -949,6 +1547,11 @@ async function runImport(context, options) {
949
1547
  if (result2.missingSources > 0) {
950
1548
  context.write(`Missing source files: ${result2.missingSources}`);
951
1549
  }
1550
+ if (linkSource) {
1551
+ context.write(`Replaced ${result2.linkedSources.length} scanned source paths with aweskill-managed projections.`);
1552
+ } else {
1553
+ context.write("Source paths were kept in place. Re-run without --keep-source to replace scanned agent skills with aweskill-managed projections.");
1554
+ }
952
1555
  return result2;
953
1556
  }
954
1557
  if (!options.sourcePath) {
@@ -957,8 +1560,8 @@ async function runImport(context, options) {
957
1560
  const result = await importPath({
958
1561
  homeDir: context.homeDir,
959
1562
  sourcePath: options.sourcePath,
960
- mode: options.mode,
961
- override: options.override
1563
+ override: options.override,
1564
+ linkSource
962
1565
  });
963
1566
  if (result.kind === "single") {
964
1567
  for (const warning of result.warnings) {
@@ -971,6 +1574,11 @@ async function runImport(context, options) {
971
1574
  } else {
972
1575
  context.write(`Imported ${result.name}`);
973
1576
  }
1577
+ if (result.linkedSourcePath) {
1578
+ context.write(`Replaced source path with an aweskill-managed projection: ${result.linkedSourcePath}`);
1579
+ } else {
1580
+ context.write("Source was kept in place. Re-run with --link-source to replace it with an aweskill-managed projection.");
1581
+ }
974
1582
  return result;
975
1583
  }
976
1584
  for (const warning of result.warnings) {
@@ -989,150 +1597,16 @@ async function runImport(context, options) {
989
1597
  if (result.missingSources > 0) {
990
1598
  context.write(`Missing source files: ${result.missingSources}`);
991
1599
  }
992
- return result;
993
- }
994
-
995
- // src/lib/symlink.ts
996
- import { cp as cp2, lstat as lstat3, mkdir as mkdir5, readFile as readFile2, readdir as readdir5, readlink as readlink3, rm as rm3, symlink, unlink, writeFile as writeFile2 } from "fs/promises";
997
- import path10 from "path";
998
- var COPY_MARKER = ".aweskill-projection.json";
999
- async function tryLstat(targetPath) {
1000
- try {
1001
- return await lstat3(targetPath);
1002
- } catch {
1003
- return null;
1004
- }
1005
- }
1006
- async function readCopyMarker(targetPath) {
1007
- try {
1008
- const content = await readFile2(path10.join(targetPath, COPY_MARKER), "utf8");
1009
- const parsed = JSON.parse(content);
1010
- return parsed.managedBy === "aweskill" ? parsed : null;
1011
- } catch {
1012
- return null;
1013
- }
1014
- }
1015
- async function assertProjectionTargetSafe(mode, sourcePath, targetPath, options = {}) {
1016
- const existing = await tryLstat(targetPath);
1017
- if (!existing) {
1018
- return;
1019
- }
1020
- if (mode === "symlink") {
1021
- if (!existing.isSymbolicLink()) {
1022
- if (options.allowReplaceExisting) {
1023
- return;
1024
- }
1025
- throw new Error(`Refusing to overwrite non-symlink target: ${targetPath}`);
1026
- }
1027
- const currentTarget = await readlink3(targetPath);
1028
- const resolvedCurrent = path10.resolve(path10.dirname(targetPath), currentTarget);
1029
- if (resolvedCurrent === path10.resolve(sourcePath)) {
1030
- return;
1031
- }
1032
- return;
1033
- }
1034
- if (existing.isSymbolicLink()) {
1035
- return;
1036
- }
1037
- const marker = await readCopyMarker(targetPath);
1038
- if (!marker) {
1039
- if (options.allowReplaceExisting) {
1040
- return;
1041
- }
1042
- throw new Error(`Refusing to overwrite unmanaged directory: ${targetPath}`);
1043
- }
1044
- }
1045
- async function createSkillSymlink(sourcePath, targetPath, options = {}) {
1046
- await mkdir5(path10.dirname(targetPath), { recursive: true });
1047
- const existing = await tryLstat(targetPath);
1048
- if (existing?.isSymbolicLink()) {
1049
- const currentTarget = await readlink3(targetPath);
1050
- const resolvedCurrent = path10.resolve(path10.dirname(targetPath), currentTarget);
1051
- if (resolvedCurrent === path10.resolve(sourcePath)) {
1052
- return "skipped";
1053
- }
1054
- await unlink(targetPath);
1055
- } else if (existing) {
1056
- if (options.allowReplaceExisting) {
1057
- await rm3(targetPath, { force: true, recursive: true });
1058
- } else {
1059
- throw new Error(`Refusing to overwrite non-symlink target: ${targetPath}`);
1060
- }
1061
- }
1062
- const linkTarget = path10.relative(path10.dirname(targetPath), sourcePath) || ".";
1063
- await symlink(linkTarget, targetPath, "dir");
1064
- return "created";
1065
- }
1066
- async function createSkillCopy(sourcePath, targetPath, options = {}) {
1067
- await mkdir5(path10.dirname(targetPath), { recursive: true });
1068
- const existing = await tryLstat(targetPath);
1069
- if (existing?.isSymbolicLink()) {
1070
- await unlink(targetPath);
1071
- } else if (existing) {
1072
- const marker2 = await readCopyMarker(targetPath);
1073
- if (!marker2) {
1074
- if (!options.allowReplaceExisting) {
1075
- throw new Error(`Refusing to overwrite unmanaged directory: ${targetPath}`);
1076
- }
1077
- }
1078
- await rm3(targetPath, { force: true, recursive: true });
1079
- }
1080
- await cp2(sourcePath, targetPath, { recursive: true });
1081
- const marker = { managedBy: "aweskill", sourcePath: path10.resolve(sourcePath) };
1082
- await writeFile2(path10.join(targetPath, COPY_MARKER), JSON.stringify(marker, null, 2), "utf8");
1083
- return "created";
1084
- }
1085
- async function removeManagedProjection(targetPath) {
1086
- const existing = await tryLstat(targetPath);
1087
- if (!existing) {
1088
- return false;
1089
- }
1090
- if (existing.isSymbolicLink()) {
1091
- await unlink(targetPath);
1092
- return true;
1093
- }
1094
- if (existing.isDirectory()) {
1095
- const marker = await readCopyMarker(targetPath);
1096
- if (marker) {
1097
- await rm3(targetPath, { force: true, recursive: true });
1098
- return true;
1099
- }
1100
- }
1101
- return false;
1102
- }
1103
- async function listManagedSkillNames(skillsDir, centralSkillsDir) {
1104
- const result = /* @__PURE__ */ new Map();
1105
- try {
1106
- const entries = await readdir5(skillsDir, { withFileTypes: true });
1107
- for (const entry of entries) {
1108
- const targetPath = path10.join(skillsDir, entry.name);
1109
- if (entry.isSymbolicLink()) {
1110
- try {
1111
- const currentTarget = await readlink3(targetPath);
1112
- const resolvedCurrent = path10.resolve(path10.dirname(targetPath), currentTarget);
1113
- if (resolvedCurrent.startsWith(path10.resolve(centralSkillsDir))) {
1114
- result.set(entry.name, "symlink");
1115
- }
1116
- } catch {
1117
- result.set(entry.name, "symlink");
1118
- }
1119
- continue;
1120
- }
1121
- if (entry.isDirectory()) {
1122
- const marker = await readCopyMarker(targetPath);
1123
- if (marker) {
1124
- result.set(entry.name, "copy");
1125
- }
1126
- }
1127
- }
1128
- } catch {
1129
- return result;
1600
+ if (linkSource) {
1601
+ context.write(`Replaced ${result.linkedSources.length} source paths with aweskill-managed projections.`);
1602
+ } else {
1603
+ context.write("Source paths were kept in place. Re-run with --link-source to replace them with aweskill-managed projections.");
1130
1604
  }
1131
1605
  return result;
1132
1606
  }
1133
1607
 
1134
- // src/commands/check.ts
1135
- var DEFAULT_PREVIEW_COUNT = 5;
1608
+ // src/commands/disable.ts
1609
+ import path12 from "path";
1136
1610
  function getProjectDir(context, explicitProjectDir) {
1137
1611
  return explicitProjectDir ?? context.cwd;
1138
1612
  }
@@ -1157,125 +1631,6 @@ async function resolveAgentsForScope(context, requestedAgents, scope, projectDir
1157
1631
  })
1158
1632
  );
1159
1633
  }
1160
- function formatSkillBlockWithSummary(title, skills, verbose = false) {
1161
- if (skills.length === 0) {
1162
- return [`No skills found for ${title.replace(/:$/, "").toLowerCase()}.`];
1163
- }
1164
- const lines = [title];
1165
- const categories = [
1166
- { title: " linked", marker: "\u2713", key: "linked" },
1167
- { title: " duplicate", marker: "!", key: "duplicate" },
1168
- { title: " new", marker: "+", key: "new" }
1169
- ];
1170
- for (const category of categories) {
1171
- const entries = skills.filter((skill) => skill.category === category.key);
1172
- lines.push(`${category.title}: ${entries.length}`);
1173
- const preview = verbose ? entries : entries.slice(0, DEFAULT_PREVIEW_COUNT);
1174
- for (const skill of preview) {
1175
- lines.push(` ${category.marker} ${skill.name} ${skill.path}`);
1176
- }
1177
- if (!verbose && entries.length > preview.length) {
1178
- lines.push(` ... and ${entries.length - preview.length} more (use --verbose to show all)`);
1179
- }
1180
- }
1181
- return lines;
1182
- }
1183
- async function runCheck(context, options) {
1184
- const lines = [];
1185
- const centralSkillEntries = await listSkills(context.homeDir);
1186
- const centralSkills = new Set(centralSkillEntries.map((skill) => skill.name));
1187
- const projectDir = options.scope === "project" ? getProjectDir(context, options.projectDir) : void 0;
1188
- const agents = await resolveAgentsForScope(context, options.agents, options.scope, projectDir);
1189
- const updated = [];
1190
- const skipped = [];
1191
- let importedCount = 0;
1192
- for (const agentId of agents) {
1193
- const baseDir = options.scope === "global" ? context.homeDir : projectDir;
1194
- const skillsDir = resolveAgentSkillsDir(agentId, options.scope, baseDir);
1195
- const managed = await listManagedSkillNames(skillsDir, getAweskillPaths(context.homeDir).skillsDir);
1196
- const skills = await listSkillEntriesInDirectory(skillsDir);
1197
- const checked = skills.map((skill) => {
1198
- let category = "new";
1199
- if (managed.has(skill.name)) {
1200
- category = "linked";
1201
- } else if (centralSkills.has(skill.name)) {
1202
- category = "duplicate";
1203
- }
1204
- return {
1205
- name: skill.name,
1206
- path: skill.path,
1207
- category,
1208
- hasSKILLMd: skill.hasSKILLMd
1209
- };
1210
- });
1211
- if (options.update) {
1212
- for (const skill of checked) {
1213
- if (skill.category === "linked") {
1214
- continue;
1215
- }
1216
- if (!skill.hasSKILLMd) {
1217
- context.write(`Warning: Skipping ${agentId}:${skill.name}; missing SKILL.md in ${skill.path}`);
1218
- skipped.push(`${agentId}:${skill.name}`);
1219
- continue;
1220
- }
1221
- if (skill.category === "new") {
1222
- await importSkill({
1223
- homeDir: context.homeDir,
1224
- sourcePath: skill.path,
1225
- mode: "mv"
1226
- });
1227
- centralSkills.add(skill.name);
1228
- importedCount += 1;
1229
- }
1230
- await createSkillSymlink(getSkillPath(context.homeDir, skill.name), skill.path, {
1231
- allowReplaceExisting: true
1232
- });
1233
- updated.push(`${agentId}:${skill.name}`);
1234
- }
1235
- }
1236
- const title = options.scope === "global" ? `Global skills for ${agentId}:` : `Project skills for ${agentId} (${projectDir}):`;
1237
- lines.push("");
1238
- lines.push(...formatSkillBlockWithSummary(title, checked, options.verbose));
1239
- }
1240
- if (options.update) {
1241
- lines.push("");
1242
- lines.push(`Updated ${updated.length} skills`);
1243
- if (importedCount > 0) {
1244
- lines.push(`Imported ${importedCount} new skills into the central repo`);
1245
- }
1246
- if (skipped.length > 0) {
1247
- lines.push(`Skipped ${skipped.length} entries: ${skipped.join(", ")}`);
1248
- }
1249
- }
1250
- context.write(lines.join("\n").trim());
1251
- return { agents, updated, importedCount, skipped };
1252
- }
1253
-
1254
- // src/commands/disable.ts
1255
- function getProjectDir2(context, explicitProjectDir) {
1256
- return explicitProjectDir ?? context.cwd;
1257
- }
1258
- async function resolveAgentsForScope2(context, requestedAgents, scope, projectDir) {
1259
- if (requestedAgents.length === 0 || requestedAgents.includes("all")) {
1260
- const detected = await detectInstalledAgents({
1261
- homeDir: context.homeDir,
1262
- projectDir: scope === "project" ? projectDir : void 0
1263
- });
1264
- const candidates = detected.length > 0 ? detected : listSupportedAgentIds();
1265
- return candidates.filter((agentId) => supportsScope(agentId, scope));
1266
- }
1267
- return uniqueSorted(
1268
- requestedAgents.map((agent) => {
1269
- if (!isAgentId(agent)) {
1270
- throw new Error(`Unsupported agent: ${agent}`);
1271
- }
1272
- if (!supportsScope(agent, scope)) {
1273
- throw new Error(`Agent ${agent} does not support ${scope} scope.`);
1274
- }
1275
- return agent;
1276
- })
1277
- );
1278
- }
1279
1634
  async function resolveSkillNames(context, type, names) {
1280
1635
  const normalizedNames = uniqueSorted(splitCommaValues(names).map((name) => sanitizeName(name)));
1281
1636
  if (normalizedNames.includes("all")) {
@@ -1337,12 +1692,12 @@ async function bundlesWithCoEnabledSiblings(options) {
1337
1692
  return [...hit].sort();
1338
1693
  }
1339
1694
  async function runDisable(context, options) {
1340
- const projectDir = options.scope === "project" ? getProjectDir2(context, options.projectDir) : void 0;
1341
- const agents = await resolveAgentsForScope2(context, options.agents, options.scope, projectDir);
1695
+ const projectDir = options.scope === "project" ? getProjectDir(context, options.projectDir) : void 0;
1696
+ const agents = await resolveAgentsForScope(context, options.agents, options.scope, projectDir);
1342
1697
  const baseDir = options.scope === "global" ? context.homeDir : projectDir ?? context.cwd;
1343
1698
  const skillNames = await resolveSkillNames(context, options.type, options.name);
1699
+ const { skillsDir: centralSkillsDir } = getAweskillPaths(context.homeDir);
1344
1700
  if (splitCommaValues(options.name).map((name) => sanitizeName(name)).includes("all") && options.type === "skill") {
1345
- const { skillsDir: centralSkillsDir } = getAweskillPaths(context.homeDir);
1346
1701
  const scopedManaged = await Promise.all(
1347
1702
  agents.map(async (agentId) => {
1348
1703
  const agentSkillsDir = resolveAgentSkillsDir(agentId, options.scope, baseDir);
@@ -1368,12 +1723,36 @@ async function runDisable(context, options) {
1368
1723
  );
1369
1724
  }
1370
1725
  }
1726
+ for (const agentId of agents) {
1727
+ const skillsDir = resolveAgentSkillsDir(agentId, options.scope, baseDir);
1728
+ for (const skillName of skillNames) {
1729
+ const targetPath = path12.join(skillsDir, skillName);
1730
+ const status = await inspectProjectionTarget(targetPath, { centralSkillsDir });
1731
+ if (status.kind === "missing" || status.kind === "managed_symlink" || status.kind === "managed_copy") {
1732
+ continue;
1733
+ }
1734
+ if (status.kind === "foreign_symlink" && !options.force) {
1735
+ throw new Error(
1736
+ `Target path is a symlink that is not managed by aweskill: ${targetPath}. Re-run with --force to remove it.`
1737
+ );
1738
+ }
1739
+ if (status.kind === "directory" && !options.force) {
1740
+ throw new Error(`Target path already exists as a directory: ${targetPath}. Re-run with --force to remove it.`);
1741
+ }
1742
+ if (status.kind === "file" && !options.force) {
1743
+ throw new Error(`Target path already exists as a file: ${targetPath}. Re-run with --force to remove it.`);
1744
+ }
1745
+ }
1746
+ }
1371
1747
  const removed = [];
1372
1748
  for (const agentId of agents) {
1373
1749
  const skillsDir = resolveAgentSkillsDir(agentId, options.scope, baseDir);
1374
1750
  for (const skillName of skillNames) {
1375
- const targetPath = `${skillsDir}/${skillName}`;
1376
- const wasRemoved = await removeManagedProjection(targetPath);
1751
+ const targetPath = path12.join(skillsDir, skillName);
1752
+ const wasRemoved = await removeProjectionTarget(targetPath, {
1753
+ force: options.force,
1754
+ centralSkillsDir
1755
+ });
1377
1756
  if (wasRemoved) {
1378
1757
  removed.push(`${agentId}:${skillName}`);
1379
1758
  }
@@ -1387,10 +1766,11 @@ async function runDisable(context, options) {
1387
1766
 
1388
1767
  // src/commands/enable.ts
1389
1768
  import { mkdir as mkdir6 } from "fs/promises";
1390
- function getProjectDir3(context, explicitProjectDir) {
1769
+ import path13 from "path";
1770
+ function getProjectDir2(context, explicitProjectDir) {
1391
1771
  return explicitProjectDir ?? context.cwd;
1392
1772
  }
1393
- async function resolveAgentsForScope3(context, requestedAgents, scope, projectDir) {
1773
+ async function resolveAgentsForScope2(context, requestedAgents, scope, projectDir) {
1394
1774
  if (requestedAgents.length === 0 || requestedAgents.includes("all")) {
1395
1775
  const detected = await detectInstalledAgents({
1396
1776
  homeDir: context.homeDir,
@@ -1446,17 +1826,44 @@ async function resolveSkillNames2(context, type, names) {
1446
1826
  return normalizedNames;
1447
1827
  }
1448
1828
  async function runEnable(context, options) {
1449
- const projectDir = options.scope === "project" ? getProjectDir3(context, options.projectDir) : void 0;
1450
- const agents = await resolveAgentsForScope3(context, options.agents, options.scope, projectDir);
1829
+ const projectDir = options.scope === "project" ? getProjectDir2(context, options.projectDir) : void 0;
1830
+ const agents = await resolveAgentsForScope2(context, options.agents, options.scope, projectDir);
1451
1831
  const skillNames = await resolveSkillNames2(context, options.type, options.name);
1452
1832
  const baseDir = options.scope === "global" ? context.homeDir : projectDir ?? context.cwd;
1833
+ const { skillsDir: centralSkillsDir } = getAweskillPaths(context.homeDir);
1453
1834
  for (const agentId of agents) {
1454
1835
  const skillsDir = resolveAgentSkillsDir(agentId, options.scope, baseDir);
1455
1836
  await mkdir6(skillsDir, { recursive: true });
1456
1837
  for (const skillName of skillNames) {
1457
1838
  const sourcePath = getSkillPath(context.homeDir, skillName);
1458
- const targetPath = `${skillsDir}/${skillName}`;
1459
- await assertProjectionTargetSafe(getProjectionMode(agentId), sourcePath, targetPath);
1839
+ const targetPath = path13.join(skillsDir, skillName);
1840
+ const status = await inspectProjectionTarget(targetPath, { centralSkillsDir, sourcePath });
1841
+ if (status.kind === "missing") {
1842
+ continue;
1843
+ }
1844
+ if (status.kind === "managed_symlink" || status.kind === "managed_copy") {
1845
+ if (status.matchesSource && !options.force) {
1846
+ throw new Error(
1847
+ `Target path is already an aweskill-managed projection for ${skillName}: ${targetPath}. Re-run with --force to recreate it.`
1848
+ );
1849
+ }
1850
+ continue;
1851
+ }
1852
+ if (status.kind === "foreign_symlink" && !options.force) {
1853
+ throw new Error(
1854
+ `Target path is a symlink that is not managed by aweskill: ${targetPath}. Re-run with --force to replace it with an aweskill-managed projection.`
1855
+ );
1856
+ }
1857
+ if (status.kind === "directory" && !options.force) {
1858
+ throw new Error(
1859
+ `Target path already exists as a directory: ${targetPath}. Re-run with --force to replace it with an aweskill-managed projection.`
1860
+ );
1861
+ }
1862
+ if (status.kind === "file" && !options.force) {
1863
+ throw new Error(
1864
+ `Target path already exists as a file: ${targetPath}. Re-run with --force to replace it with an aweskill-managed projection.`
1865
+ );
1866
+ }
1460
1867
  }
1461
1868
  }
1462
1869
  const created = [];
@@ -1465,9 +1872,9 @@ async function runEnable(context, options) {
1465
1872
  const mode = getProjectionMode(agentId);
1466
1873
  for (const skillName of skillNames) {
1467
1874
  const sourcePath = getSkillPath(context.homeDir, skillName);
1468
- const targetPath = `${skillsDir}/${skillName}`;
1469
- const result = mode === "symlink" ? await createSkillSymlink(sourcePath, targetPath) : await createSkillCopy(sourcePath, targetPath);
1470
- if (result === "created") {
1875
+ const targetPath = path13.join(skillsDir, skillName);
1876
+ const result = mode === "symlink" ? await createSkillSymlink(sourcePath, targetPath, { allowReplaceExisting: options.force }) : await createSkillCopy(sourcePath, targetPath, { allowReplaceExisting: options.force });
1877
+ if (result.status === "created") {
1471
1878
  created.push(`${agentId}:${skillName}`);
1472
1879
  }
1473
1880
  }
@@ -1506,17 +1913,23 @@ function formatScanSummary(candidates, verbose = false) {
1506
1913
  return lines.join("\n");
1507
1914
  }
1508
1915
  async function runScan(context, options) {
1916
+ if (options.keepSource && options.linkSource) {
1917
+ throw new Error("Choose either --keep-source or --link-source, not both.");
1918
+ }
1509
1919
  const candidates = await scanSkills({
1510
1920
  homeDir: context.homeDir,
1511
- projectDirs: [context.cwd]
1921
+ scope: options.scope,
1922
+ agents: options.agents,
1923
+ projectDir: options.scope === "project" ? options.projectDir ?? context.cwd : void 0
1512
1924
  });
1513
1925
  context.write(formatScanSummary(candidates, options.verbose));
1514
1926
  if (options.add) {
1927
+ const linkSource = !options.keepSource;
1515
1928
  const result = await importScannedSkills({
1516
1929
  homeDir: context.homeDir,
1517
1930
  candidates,
1518
- mode: options.mode ?? "cp",
1519
- override: options.override
1931
+ override: options.override,
1932
+ linkSource
1520
1933
  });
1521
1934
  for (const warning of result.warnings) {
1522
1935
  context.write(`Warning: ${warning}`);
@@ -1534,6 +1947,11 @@ async function runScan(context, options) {
1534
1947
  if (result.missingSources > 0) {
1535
1948
  context.write(`Missing source files: ${result.missingSources}`);
1536
1949
  }
1950
+ if (linkSource) {
1951
+ context.write(`Replaced ${result.linkedSources.length} scanned source paths with aweskill-managed projections.`);
1952
+ } else {
1953
+ context.write("Source paths were kept in place. Re-run without --keep-source to replace scanned agent skills with aweskill-managed projections.");
1954
+ }
1537
1955
  return { candidates, ...result };
1538
1956
  }
1539
1957
  return candidates;
@@ -1544,7 +1962,7 @@ async function runInit(context, options) {
1544
1962
  await ensureHomeLayout(context.homeDir);
1545
1963
  context.write(`Initialized ${context.homeDir}/.aweskill`);
1546
1964
  if (options.scan) {
1547
- const candidates = await scanSkills({ homeDir: context.homeDir, projectDirs: [context.cwd] });
1965
+ const candidates = await scanSkills({ homeDir: context.homeDir, scope: "global" });
1548
1966
  context.write(formatScanSummary(candidates, options.verbose));
1549
1967
  return candidates;
1550
1968
  }
@@ -1552,14 +1970,15 @@ async function runInit(context, options) {
1552
1970
  }
1553
1971
 
1554
1972
  // src/commands/list.ts
1555
- var DEFAULT_PREVIEW_COUNT2 = 5;
1973
+ var DEFAULT_PREVIEW_COUNT = 5;
1556
1974
  async function runListSkills(context, options = {}) {
1557
- const skills = await listSkills(context.homeDir);
1975
+ const { rootDir, skillsDir, bundlesDir } = getAweskillPaths(context.homeDir);
1976
+ const { validSkills: skills, findings } = await scanStoreHygiene({ rootDir, skillsDir, bundlesDir, includeBundles: true });
1558
1977
  if (skills.length === 0) {
1559
- context.write("No skills found in central repo.");
1978
+ context.write(["No skills found in central repo.", ...formatHygieneHint(findings)].join("\n"));
1560
1979
  return skills;
1561
1980
  }
1562
- const preview = options.verbose ? skills : skills.slice(0, DEFAULT_PREVIEW_COUNT2);
1981
+ const preview = options.verbose ? skills : skills.slice(0, DEFAULT_PREVIEW_COUNT);
1563
1982
  const lines = [`Skills in central repo: ${skills.length} total`];
1564
1983
  if (!options.verbose && skills.length > preview.length) {
1565
1984
  lines.push(`Showing first ${preview.length} skills (use --verbose to show all)`);
@@ -1568,6 +1987,7 @@ async function runListSkills(context, options = {}) {
1568
1987
  const marker = skill.hasSKILLMd ? "\u2713" : "!";
1569
1988
  lines.push(` ${marker} ${skill.name} ${skill.path}`);
1570
1989
  }
1990
+ lines.push(...formatHygieneHint(findings));
1571
1991
  context.write(lines.join("\n"));
1572
1992
  return skills;
1573
1993
  }
@@ -1575,21 +1995,22 @@ function formatBundleLines(title, bundles, verbose) {
1575
1995
  if (bundles.length === 0) {
1576
1996
  return [title, "(none)"];
1577
1997
  }
1578
- const preview = verbose ? bundles : bundles.slice(0, DEFAULT_PREVIEW_COUNT2);
1998
+ const preview = verbose ? bundles : bundles.slice(0, DEFAULT_PREVIEW_COUNT);
1579
1999
  const lines = [`${title}: ${bundles.length} total`];
1580
2000
  if (!verbose && bundles.length > preview.length) {
1581
2001
  lines.push(`Showing first ${preview.length} bundles (use --verbose to show all)`);
1582
2002
  }
1583
2003
  for (const bundle of preview) {
1584
- const skillsPreview = verbose ? bundle.skills : bundle.skills.slice(0, DEFAULT_PREVIEW_COUNT2);
2004
+ const skillsPreview = verbose ? bundle.skills : bundle.skills.slice(0, DEFAULT_PREVIEW_COUNT);
1585
2005
  const suffix = !verbose && bundle.skills.length > skillsPreview.length ? `, ... (+${bundle.skills.length - skillsPreview.length} more)` : "";
1586
2006
  lines.push(` - ${bundle.name}: ${bundle.skills.length} skills${skillsPreview.length > 0 ? ` -> ${skillsPreview.join(", ")}${suffix}` : " -> (empty)"}`);
1587
2007
  }
1588
2008
  return lines;
1589
2009
  }
1590
2010
  async function runListBundles(context, options = {}) {
1591
- const bundles = await listBundles(context.homeDir);
1592
- context.write(formatBundleLines("Bundles in central repo", bundles, options.verbose).join("\n"));
2011
+ const { rootDir, skillsDir, bundlesDir } = getAweskillPaths(context.homeDir);
2012
+ const { validBundles: bundles, findings } = await scanStoreHygiene({ rootDir, skillsDir, bundlesDir, includeSkills: true });
2013
+ context.write([...formatBundleLines("Bundles in central repo", bundles, options.verbose), ...formatHygieneHint(findings)].join("\n"));
1593
2014
  return bundles;
1594
2015
  }
1595
2016
  async function runListTemplateBundles(context, options = {}) {
@@ -1600,12 +2021,12 @@ async function runListTemplateBundles(context, options = {}) {
1600
2021
  }
1601
2022
 
1602
2023
  // src/commands/recover.ts
1603
- import { cp as cp3, mkdir as mkdir7, readlink as readlink4, unlink as unlink2 } from "fs/promises";
1604
- import path11 from "path";
1605
- function getProjectDir4(context, explicitProjectDir) {
2024
+ import { cp as cp4, mkdir as mkdir7, readlink as readlink4, unlink as unlink2 } from "fs/promises";
2025
+ import path14 from "path";
2026
+ function getProjectDir3(context, explicitProjectDir) {
1606
2027
  return explicitProjectDir ?? context.cwd;
1607
2028
  }
1608
- async function resolveAgentsForScope4(context, requestedAgents, scope, projectDir) {
2029
+ async function resolveAgentsForScope3(context, requestedAgents, scope, projectDir) {
1609
2030
  if (requestedAgents.length === 0 || requestedAgents.includes("all")) {
1610
2031
  const detected = await detectInstalledAgents({
1611
2032
  homeDir: context.homeDir,
@@ -1627,8 +2048,8 @@ async function resolveAgentsForScope4(context, requestedAgents, scope, projectDi
1627
2048
  );
1628
2049
  }
1629
2050
  async function runRecover(context, options) {
1630
- const projectDir = options.scope === "project" ? getProjectDir4(context, options.projectDir) : void 0;
1631
- const agents = await resolveAgentsForScope4(context, options.agents, options.scope, projectDir);
2051
+ const projectDir = options.scope === "project" ? getProjectDir3(context, options.projectDir) : void 0;
2052
+ const agents = await resolveAgentsForScope3(context, options.agents, options.scope, projectDir);
1632
2053
  const { skillsDir: centralSkillsDir } = getAweskillPaths(context.homeDir);
1633
2054
  const baseDir = options.scope === "global" ? context.homeDir : projectDir ?? context.cwd;
1634
2055
  const recovered = [];
@@ -1639,16 +2060,16 @@ async function runRecover(context, options) {
1639
2060
  if (mode !== "symlink") {
1640
2061
  continue;
1641
2062
  }
1642
- const targetPath = path11.join(agentSkillsDir, skillName);
1643
- const sourcePath = path11.join(centralSkillsDir, skillName);
2063
+ const targetPath = path14.join(agentSkillsDir, skillName);
2064
+ const sourcePath = path14.join(centralSkillsDir, skillName);
1644
2065
  const currentTarget = await readlink4(targetPath);
1645
- const resolvedCurrent = path11.resolve(path11.dirname(targetPath), currentTarget);
1646
- if (resolvedCurrent !== path11.resolve(sourcePath)) {
2066
+ const resolvedCurrent = path14.resolve(path14.dirname(targetPath), currentTarget);
2067
+ if (resolvedCurrent !== path14.resolve(sourcePath)) {
1647
2068
  continue;
1648
2069
  }
1649
2070
  await unlink2(targetPath);
1650
- await mkdir7(path11.dirname(targetPath), { recursive: true });
1651
- await cp3(sourcePath, targetPath, { recursive: true });
2071
+ await mkdir7(path14.dirname(targetPath), { recursive: true });
2072
+ await cp4(sourcePath, targetPath, { recursive: true });
1652
2073
  recovered.push(`${agentId}:${skillName}`);
1653
2074
  }
1654
2075
  }
@@ -1659,7 +2080,7 @@ async function runRecover(context, options) {
1659
2080
 
1660
2081
  // src/lib/references.ts
1661
2082
  import { rm as rm4 } from "fs/promises";
1662
- import path12 from "path";
2083
+ import path15 from "path";
1663
2084
  async function findSkillReferences(options) {
1664
2085
  const normalizedSkill = sanitizeName(options.skillName);
1665
2086
  const { skillsDir } = getAweskillPaths(options.homeDir);
@@ -1710,7 +2131,7 @@ async function removeSkillWithReferences(options) {
1710
2131
  const agentSkillsDir = resolveAgentSkillsDir(agent.id, scope, dir);
1711
2132
  const managed = await listManagedSkillNames(agentSkillsDir, skillsDir);
1712
2133
  if (managed.has(normalizedSkill)) {
1713
- await removeManagedProjection(path12.join(agentSkillsDir, normalizedSkill));
2134
+ await removeManagedProjection(path15.join(agentSkillsDir, normalizedSkill));
1714
2135
  }
1715
2136
  }
1716
2137
  }
@@ -1744,79 +2165,109 @@ async function runRemove(context, options) {
1744
2165
  }
1745
2166
 
1746
2167
  // src/commands/restore.ts
1747
- import { cp as cp4, mkdir as mkdir8, rm as rm5 } from "fs/promises";
1748
- import path13 from "path";
2168
+ import { cp as cp5, mkdir as mkdir8, rm as rm5 } from "fs/promises";
2169
+ import path16 from "path";
1749
2170
  async function runRestore(context, options) {
1750
- const includeBundles = options.includeBundles ?? false;
2171
+ const includeBundles = options.includeBundles ?? true;
1751
2172
  const { tempDir, extractedSkillsDir, extractedBundlesDir } = await extractSkillsArchive(options.archivePath);
1752
2173
  try {
1753
- const extracted = await listSkillEntriesInDirectory(extractedSkillsDir);
2174
+ const extractedScan = await scanStoreHygiene({
2175
+ rootDir: tempDir,
2176
+ skillsDir: extractedSkillsDir,
2177
+ bundlesDir: extractedBundlesDir,
2178
+ includeBundles
2179
+ });
2180
+ const extracted = extractedScan.validSkills;
1754
2181
  if (extracted.length === 0) {
1755
2182
  throw new Error(`Archive does not contain a skills/ directory: ${options.archivePath}`);
1756
2183
  }
1757
2184
  const { skillsDir, bundlesDir } = getAweskillPaths(context.homeDir);
1758
- const current = await listSkillEntriesInDirectory(skillsDir);
2185
+ const currentScan = await scanStoreHygiene({
2186
+ rootDir: getAweskillPaths(context.homeDir).rootDir,
2187
+ skillsDir,
2188
+ bundlesDir,
2189
+ includeBundles
2190
+ });
2191
+ const current = currentScan.validSkills;
1759
2192
  const currentNames = new Set(current.map((entry) => entry.name));
1760
2193
  const conflicts = extracted.map((entry) => entry.name).filter((name) => currentNames.has(name));
1761
- const extractedBundles = includeBundles ? await listBundlesInDirectory(extractedBundlesDir) : [];
1762
- const currentBundles = includeBundles ? await listBundlesInDirectory(bundlesDir) : [];
2194
+ const extractedBundles = includeBundles ? extractedScan.validBundles : [];
2195
+ const currentBundles = includeBundles ? currentScan.validBundles : [];
1763
2196
  const currentBundleNames = new Set(currentBundles.map((bundle) => bundle.name));
1764
2197
  const bundleConflicts = extractedBundles.map((bundle) => bundle.name).filter((name) => currentBundleNames.has(name));
1765
- if ((conflicts.length > 0 || bundleConflicts.length > 0) && !options.override) {
1766
- const conflictMessages = [];
1767
- if (conflicts.length > 0) {
1768
- conflictMessages.push(`skills: ${conflicts.join(", ")}`);
1769
- }
1770
- if (bundleConflicts.length > 0) {
1771
- conflictMessages.push(`bundles: ${bundleConflicts.join(", ")}`);
1772
- }
1773
- throw new Error(`Restore would overwrite existing ${conflictMessages.join("; ")}. Use --override to replace them.`);
1774
- }
1775
2198
  const backupArchivePath = await createSkillsBackupArchive(context.homeDir, { includeBundles });
2199
+ const skippedSkills = new Set(options.override ? [] : conflicts);
2200
+ const skippedBundles = new Set(options.override ? [] : bundleConflicts);
1776
2201
  if (options.override) {
1777
2202
  await rm5(skillsDir, { recursive: true, force: true });
1778
- await mkdir8(path13.dirname(skillsDir), { recursive: true });
1779
- await cp4(extractedSkillsDir, skillsDir, { recursive: true });
2203
+ await mkdir8(path16.dirname(skillsDir), { recursive: true });
2204
+ await cp5(extractedSkillsDir, skillsDir, { recursive: true });
1780
2205
  if (includeBundles) {
1781
2206
  await rm5(bundlesDir, { recursive: true, force: true });
1782
- await mkdir8(path13.dirname(bundlesDir), { recursive: true });
1783
- await cp4(extractedBundlesDir, bundlesDir, { recursive: true });
2207
+ if (await pathExists(extractedBundlesDir)) {
2208
+ await mkdir8(path16.dirname(bundlesDir), { recursive: true });
2209
+ await cp5(extractedBundlesDir, bundlesDir, { recursive: true });
2210
+ }
1784
2211
  }
1785
2212
  } else {
1786
2213
  await mkdir8(skillsDir, { recursive: true });
1787
2214
  for (const entry of extracted) {
1788
- await cp4(entry.path, path13.join(skillsDir, entry.name), { recursive: true });
2215
+ if (skippedSkills.has(entry.name)) {
2216
+ continue;
2217
+ }
2218
+ await cp5(entry.path, path16.join(skillsDir, entry.name), { recursive: true });
1789
2219
  }
1790
2220
  if (includeBundles) {
1791
2221
  await mkdir8(bundlesDir, { recursive: true });
1792
2222
  for (const bundle of extractedBundles) {
1793
- await cp4(path13.join(extractedBundlesDir, `${bundle.name}.yaml`), path13.join(bundlesDir, `${bundle.name}.yaml`), { recursive: true });
2223
+ if (skippedBundles.has(bundle.name)) {
2224
+ continue;
2225
+ }
2226
+ await cp5(path16.join(extractedBundlesDir, `${bundle.name}.yaml`), path16.join(bundlesDir, `${bundle.name}.yaml`), { recursive: true });
1794
2227
  }
1795
2228
  }
1796
2229
  }
1797
- const restoredLabel = includeBundles ? `Restored ${extracted.length} skills and ${extractedBundles.length} bundles from ${options.archivePath}` : `Restored ${extracted.length} skills from ${options.archivePath}`;
2230
+ const restoredSkillCount = options.override ? extracted.length : extracted.length - skippedSkills.size;
2231
+ const restoredBundleCount = includeBundles ? options.override ? extractedBundles.length : extractedBundles.length - skippedBundles.size : 0;
2232
+ const restoredLabel = includeBundles ? `Restored ${restoredSkillCount} skills and ${restoredBundleCount} bundles from ${options.archivePath}` : `Restored ${restoredSkillCount} skills from ${options.archivePath}`;
1798
2233
  context.write(restoredLabel);
2234
+ if (skippedSkills.size > 0) {
2235
+ context.write(`Skipped existing skills: ${[...skippedSkills].sort().join(", ")}`);
2236
+ }
2237
+ if (skippedBundles.size > 0) {
2238
+ context.write(`Skipped existing bundles: ${[...skippedBundles].sort().join(", ")}`);
2239
+ }
2240
+ if (extractedScan.findings.length > 0) {
2241
+ context.write(`Skipped suspicious restore source entries: ${extractedScan.findings.map((finding) => finding.relativePath).join(", ")}`);
2242
+ }
1799
2243
  context.write(`Backed up current ${formatBackupLabel(includeBundles)} to ${backupArchivePath}`);
1800
- return { restored: extracted.map((entry) => entry.name), backupArchivePath };
2244
+ return {
2245
+ restored: extracted.map((entry) => entry.name).filter((name) => !skippedSkills.has(name)),
2246
+ skipped: [...skippedSkills],
2247
+ backupArchivePath
2248
+ };
1801
2249
  } finally {
1802
2250
  await rm5(tempDir, { recursive: true, force: true });
1803
2251
  }
1804
2252
  }
1805
2253
 
1806
2254
  // src/lib/rmdup.ts
1807
- import { mkdir as mkdir9, readdir as readdir7, rename as rename2, rm as rm6 } from "fs/promises";
1808
- import path14 from "path";
2255
+ import { mkdir as mkdir9, readdir as readdir9, rename, rm as rm6 } from "fs/promises";
2256
+ import path17 from "path";
1809
2257
  var NUMERIC_SUFFIX_PATTERN = /^(.*?)-(\d+(?:\.\d+)*)$/;
1810
2258
  function parseSkillName(name) {
1811
- const match = name.match(NUMERIC_SUFFIX_PATTERN);
2259
+ const normalizedName = sanitizeName(name);
2260
+ const match = normalizedName.match(NUMERIC_SUFFIX_PATTERN);
1812
2261
  if (!match) {
1813
2262
  return {
1814
- baseName: name,
2263
+ baseName: stripVersionSuffix(normalizedName),
2264
+ matchKey: getDuplicateMatchKey(name),
1815
2265
  hasNumericSuffix: false
1816
2266
  };
1817
2267
  }
1818
2268
  return {
1819
- baseName: match[1],
2269
+ baseName: stripVersionSuffix(normalizedName),
2270
+ matchKey: getDuplicateMatchKey(name),
1820
2271
  hasNumericSuffix: true,
1821
2272
  numericKey: match[2].split(".").map((part) => Number.parseInt(part, 10))
1822
2273
  };
@@ -1851,14 +2302,35 @@ function choosePreferredSkill(entries) {
1851
2302
  return left.name.localeCompare(right.name);
1852
2303
  })[0];
1853
2304
  }
2305
+ function buildCanonicalSkillIndex(entries) {
2306
+ const grouped = /* @__PURE__ */ new Map();
2307
+ for (const entry of entries) {
2308
+ const parsed = parseSkillName(entry.name);
2309
+ const bucket = grouped.get(parsed.matchKey) ?? [];
2310
+ bucket.push(entry);
2311
+ grouped.set(parsed.matchKey, bucket);
2312
+ }
2313
+ const canonical = /* @__PURE__ */ new Map();
2314
+ for (const [baseName, group] of grouped) {
2315
+ canonical.set(baseName, choosePreferredSkill(group));
2316
+ }
2317
+ return canonical;
2318
+ }
2319
+ function resolveCanonicalSkillName(skillName, canonicalSkills) {
2320
+ if (canonicalSkills.has(skillName)) {
2321
+ return canonicalSkills.get(skillName).name;
2322
+ }
2323
+ const parsed = parseSkillName(skillName);
2324
+ return canonicalSkills.get(parsed.matchKey)?.name;
2325
+ }
1854
2326
  async function findDuplicateSkills(homeDir) {
1855
2327
  const skills = await listSkills(homeDir);
1856
2328
  const grouped = /* @__PURE__ */ new Map();
1857
2329
  for (const skill of skills) {
1858
2330
  const parsed = parseSkillName(skill.name);
1859
- const bucket = grouped.get(parsed.baseName) ?? [];
2331
+ const bucket = grouped.get(parsed.matchKey) ?? [];
1860
2332
  bucket.push(skill);
1861
- grouped.set(parsed.baseName, bucket);
2333
+ grouped.set(parsed.matchKey, bucket);
1862
2334
  }
1863
2335
  const duplicates = [];
1864
2336
  for (const [baseName, entries] of grouped) {
@@ -1868,7 +2340,7 @@ async function findDuplicateSkills(homeDir) {
1868
2340
  const kept = choosePreferredSkill(entries);
1869
2341
  const removed = entries.filter((entry) => entry.path !== kept.path).sort((left, right) => left.name.localeCompare(right.name));
1870
2342
  if (removed.length > 0) {
1871
- duplicates.push({ baseName, kept, removed });
2343
+ duplicates.push({ baseName: parseSkillName(kept.name).baseName, kept, removed });
1872
2344
  }
1873
2345
  }
1874
2346
  return duplicates.sort((left, right) => left.baseName.localeCompare(right.baseName));
@@ -1886,28 +2358,28 @@ async function removeDuplicateSkills(homeDir, duplicates, options = {}) {
1886
2358
  continue;
1887
2359
  }
1888
2360
  const targetPath = await nextAvailableDupPath(paths.dupSkillsDir, skill.name);
1889
- await rename2(skill.path, targetPath);
2361
+ await rename(skill.path, targetPath);
1890
2362
  moved.push(`${skill.name} -> ${targetPath}`);
1891
2363
  }
1892
2364
  }
1893
2365
  return { moved, deleted };
1894
2366
  }
1895
2367
  async function nextAvailableDupPath(dupSkillsDir, skillName) {
1896
- const entries = new Set(await readdir7(dupSkillsDir).catch(() => []));
2368
+ const entries = new Set(await readdir9(dupSkillsDir).catch(() => []));
1897
2369
  if (!entries.has(skillName)) {
1898
- return path14.join(dupSkillsDir, skillName);
2370
+ return path17.join(dupSkillsDir, skillName);
1899
2371
  }
1900
2372
  let index = 1;
1901
2373
  while (entries.has(`${skillName}-${index}`)) {
1902
2374
  index += 1;
1903
2375
  }
1904
- return path14.join(dupSkillsDir, `${skillName}-${index}`);
2376
+ return path17.join(dupSkillsDir, `${skillName}-${index}`);
1905
2377
  }
1906
2378
 
1907
2379
  // src/commands/rmdup.ts
1908
2380
  async function runRmdup(context, options) {
1909
- if (options.delete && !options.remove) {
1910
- throw new Error("--delete requires --remove");
2381
+ if (options.delete && !options.apply) {
2382
+ throw new Error("--delete requires --apply");
1911
2383
  }
1912
2384
  const duplicates = await findDuplicateSkills(context.homeDir);
1913
2385
  if (duplicates.length === 0) {
@@ -1924,7 +2396,7 @@ async function runRmdup(context, options) {
1924
2396
  }
1925
2397
  let moved = [];
1926
2398
  let deleted = [];
1927
- if (options.remove) {
2399
+ if (options.apply) {
1928
2400
  const result = await removeDuplicateSkills(context.homeDir, duplicates, { delete: options.delete });
1929
2401
  moved = result.moved;
1930
2402
  deleted = result.deleted;
@@ -1936,55 +2408,328 @@ async function runRmdup(context, options) {
1936
2408
  }
1937
2409
  } else {
1938
2410
  lines.push("");
1939
- lines.push("Dry run only. Use --remove to move duplicates into dup_skills, or --remove --delete to delete them.");
2411
+ lines.push("Dry run only. Use --apply to move duplicates into dup_skills, or --apply --delete to delete them.");
1940
2412
  }
1941
2413
  context.write(lines.join("\n"));
1942
2414
  return { duplicates, moved, deleted };
1943
2415
  }
1944
2416
 
1945
2417
  // src/commands/sync.ts
1946
- import path15 from "path";
2418
+ import path18 from "path";
2419
+ import { rm as rm7 } from "fs/promises";
2420
+
2421
+ // src/commands/agent-inspection.ts
2422
+ function classifyCheckedSkill(skill, managed, canonicalSkillNames) {
2423
+ const suspicionReason = getSkillSuspicionReason(skill);
2424
+ if (suspicionReason) {
2425
+ return {
2426
+ name: skill.name,
2427
+ path: skill.path,
2428
+ category: "suspicious",
2429
+ hasSKILLMd: skill.hasSKILLMd,
2430
+ suspicionReason
2431
+ };
2432
+ }
2433
+ let category = "new";
2434
+ let duplicateKind;
2435
+ let canonicalName;
2436
+ if (managed.has(skill.name)) {
2437
+ category = "linked";
2438
+ } else {
2439
+ canonicalName = resolveCanonicalSkillName(skill.name, canonicalSkillNames);
2440
+ if (canonicalName) {
2441
+ category = canonicalName === skill.name ? "duplicate" : "matched";
2442
+ duplicateKind = canonicalName === skill.name ? "exact" : "family";
2443
+ }
2444
+ }
2445
+ if (category === "linked") {
2446
+ const parsed = parseSkillName(skill.name);
2447
+ canonicalName = canonicalSkillNames.get(parsed.baseName)?.name;
2448
+ }
2449
+ return {
2450
+ name: skill.name,
2451
+ path: skill.path,
2452
+ category,
2453
+ hasSKILLMd: skill.hasSKILLMd,
2454
+ duplicateKind,
2455
+ canonicalName
2456
+ };
2457
+ }
2458
+ function buildCentralCanonicalSkills(centralSkillEntries) {
2459
+ return buildCanonicalSkillIndex(centralSkillEntries);
2460
+ }
2461
+
2462
+ // src/commands/sync.ts
2463
+ var DEFAULT_PREVIEW_COUNT2 = 5;
2464
+ function getProjectDir4(context, explicitProjectDir) {
2465
+ return explicitProjectDir ?? context.cwd;
2466
+ }
2467
+ function formatSkillBlockWithSummary(title, skills, verbose = false) {
2468
+ if (skills.length === 0) {
2469
+ return [`No skills found for ${title.replace(/:$/, "").toLowerCase()}.`];
2470
+ }
2471
+ const lines = [title];
2472
+ const categories = [
2473
+ { title: " linked", marker: "\u2713", key: "linked" },
2474
+ { title: " broken", marker: "!", key: "broken" },
2475
+ { title: " duplicate", marker: "!", key: "duplicate" },
2476
+ { title: " matched", marker: "~", key: "matched" },
2477
+ { title: " new", marker: "+", key: "new" },
2478
+ { title: " suspicious", marker: "?", key: "suspicious" }
2479
+ ];
2480
+ for (const category of categories) {
2481
+ const entries = skills.filter((skill) => skill.category === category.key);
2482
+ lines.push(`${category.title}: ${entries.length}`);
2483
+ const preview = verbose ? entries : entries.slice(0, DEFAULT_PREVIEW_COUNT2);
2484
+ for (const skill of preview) {
2485
+ lines.push(` ${category.marker} ${skill.name} ${skill.path}`);
2486
+ }
2487
+ if (!verbose && entries.length > preview.length) {
2488
+ lines.push(` ... and ${entries.length - preview.length} more (use --verbose to show all)`);
2489
+ }
2490
+ }
2491
+ return lines;
2492
+ }
1947
2493
  async function runSync(context, options) {
1948
- const { skillsDir } = getAweskillPaths(context.homeDir);
1949
- const baseDirs = /* @__PURE__ */ new Set();
1950
- baseDirs.add({ scope: "global", dir: context.homeDir });
1951
- if (options.projectDir) {
1952
- baseDirs.add({ scope: "project", dir: options.projectDir });
2494
+ if (options.removeSuspicious && !options.apply) {
2495
+ throw new Error("--remove-suspicious requires --apply.");
1953
2496
  }
1954
- if (await pathExists(path15.join(context.cwd, ".aweskill.yaml"))) {
1955
- baseDirs.add({ scope: "project", dir: context.cwd });
2497
+ const projectDir = options.scope === "project" ? getProjectDir4(context, options.projectDir) : void 0;
2498
+ const { agents, explicit } = await resolveAgentsForListingOrSync({
2499
+ requestedAgents: options.agents,
2500
+ scope: options.scope,
2501
+ homeDir: context.homeDir,
2502
+ projectDir
2503
+ });
2504
+ if (agents.length === 0) {
2505
+ context.write(formatNoAgentsDetectedForScope(options.scope, projectDir));
2506
+ return { relinked: [], repairedBroken: [], removedBroken: [], removedSuspicious: [], newEntries: [] };
1956
2507
  }
1957
- let removed = 0;
1958
- const warnings = [];
1959
- for (const { scope, dir } of baseDirs) {
1960
- for (const agent of listSupportedAgents()) {
1961
- if (!supportsScope(agent.id, scope)) {
2508
+ const { skillsDir: centralSkillsDir } = getAweskillPaths(context.homeDir);
2509
+ const centralSkillEntries = await listSkills(context.homeDir);
2510
+ const canonicalSkillNames = buildCentralCanonicalSkills(centralSkillEntries);
2511
+ const baseDir = options.scope === "global" ? context.homeDir : projectDir;
2512
+ const lines = [];
2513
+ if (!explicit) {
2514
+ lines.push(formatDetectedAgentsForScope(options.scope, agents, projectDir));
2515
+ }
2516
+ const relinked = [];
2517
+ const repairedBroken = [];
2518
+ const removedBroken = [];
2519
+ const removedSuspicious = [];
2520
+ const newEntries = [];
2521
+ let suspiciousCount = 0;
2522
+ let repairableCount = 0;
2523
+ for (const agentId of agents) {
2524
+ const skillsDir = resolveAgentSkillsDir(agentId, options.scope, baseDir);
2525
+ const managed = await listManagedSkillNames(skillsDir, centralSkillsDir);
2526
+ const brokenSymlinks = await listBrokenSymlinkNames(skillsDir);
2527
+ const skills = await listSkillEntriesInDirectory(skillsDir);
2528
+ const entries = [];
2529
+ for (const skillName of Array.from(brokenSymlinks).sort((left, right) => left.localeCompare(right))) {
2530
+ const targetPath = path18.join(skillsDir, skillName);
2531
+ entries.push({ name: skillName, path: targetPath, category: "broken" });
2532
+ repairableCount += 1;
2533
+ if (!options.apply) {
1962
2534
  continue;
1963
2535
  }
1964
- const skillsDir2 = resolveAgentSkillsDir(agent.id, scope, dir);
1965
- const managed = await listManagedSkillNames(skillsDir2, skillsDir);
1966
- for (const [skillName] of managed) {
1967
- const sourcePath = path15.join(skillsDir, skillName);
1968
- if (!await pathExists(sourcePath)) {
1969
- const wasRemoved = await removeManagedProjection(path15.join(skillsDir2, skillName));
1970
- if (wasRemoved) {
1971
- removed += 1;
1972
- warnings.push(`Removed stale projection: ${agent.id}:${skillName} (source missing)`);
1973
- }
1974
- }
2536
+ const canonicalName = resolveCanonicalSkillName(skillName, canonicalSkillNames);
2537
+ if (canonicalName) {
2538
+ await createSkillSymlink(getSkillPath(context.homeDir, canonicalName), targetPath, {
2539
+ allowReplaceExisting: true
2540
+ });
2541
+ repairedBroken.push(`${agentId}:${skillName}`);
2542
+ continue;
2543
+ }
2544
+ const wasRemoved = await removeManagedProjection(targetPath);
2545
+ if (wasRemoved) {
2546
+ removedBroken.push(`${agentId}:${skillName}`);
2547
+ }
2548
+ }
2549
+ for (const skill of skills) {
2550
+ if (brokenSymlinks.has(skill.name)) {
2551
+ continue;
2552
+ }
2553
+ const checkedSkill = classifyCheckedSkill(skill, managed, canonicalSkillNames);
2554
+ entries.push({ name: checkedSkill.name, path: checkedSkill.path, category: checkedSkill.category });
2555
+ if (checkedSkill.category === "duplicate" || checkedSkill.category === "matched") {
2556
+ repairableCount += 1;
2557
+ }
2558
+ if (checkedSkill.category === "suspicious") {
2559
+ suspiciousCount += 1;
2560
+ }
2561
+ if (checkedSkill.category === "new") {
2562
+ newEntries.push(`${agentId}:${checkedSkill.name}`);
2563
+ }
2564
+ if (!options.apply) {
2565
+ continue;
2566
+ }
2567
+ if (checkedSkill.category === "duplicate" || checkedSkill.category === "matched") {
2568
+ await createSkillSymlink(getSkillPath(context.homeDir, checkedSkill.canonicalName ?? checkedSkill.name), checkedSkill.path, {
2569
+ allowReplaceExisting: true
2570
+ });
2571
+ relinked.push(`${agentId}:${checkedSkill.name}`);
2572
+ continue;
2573
+ }
2574
+ if (checkedSkill.category === "suspicious" && options.removeSuspicious) {
2575
+ await rm7(checkedSkill.path, { recursive: true, force: true });
2576
+ removedSuspicious.push(`${agentId}:${checkedSkill.name}`);
2577
+ }
2578
+ }
2579
+ for (const [skillName] of managed) {
2580
+ if (brokenSymlinks.has(skillName)) {
2581
+ continue;
2582
+ }
2583
+ const sourcePath = path18.join(centralSkillsDir, skillName);
2584
+ if (await pathExists(sourcePath)) {
2585
+ continue;
2586
+ }
2587
+ const targetPath = path18.join(skillsDir, skillName);
2588
+ entries.push({ name: skillName, path: targetPath, category: "broken" });
2589
+ repairableCount += 1;
2590
+ if (!options.apply) {
2591
+ continue;
2592
+ }
2593
+ const wasRemoved = await removeManagedProjection(targetPath);
2594
+ if (wasRemoved) {
2595
+ removedBroken.push(`${agentId}:${skillName}`);
1975
2596
  }
1976
2597
  }
2598
+ const title = options.scope === "global" ? `Global skills for ${agentId}:` : `Project skills for ${agentId} (${projectDir}):`;
2599
+ lines.push("");
2600
+ lines.push(...formatSkillBlockWithSummary(title, entries, options.verbose));
2601
+ }
2602
+ lines.push("");
2603
+ if (!options.apply) {
2604
+ if (repairableCount > 0) {
2605
+ lines.push("Re-run with aweskill doctor sync --apply to repair broken projections and relink duplicate/matched entries.");
2606
+ }
2607
+ if (suspiciousCount > 0) {
2608
+ lines.push("Suspicious agent skill entries were reported only. Re-run with aweskill doctor sync --apply --remove-suspicious to remove them.");
2609
+ }
2610
+ if (newEntries.length > 0) {
2611
+ lines.push("New agent skill entries found. Use aweskill store import --scan with same scope and agent filters to import them.");
2612
+ }
2613
+ } else {
2614
+ lines.push(`Repaired ${repairedBroken.length} broken symlink projection${repairedBroken.length === 1 ? "" : "s"}.`);
2615
+ lines.push(`Removed ${removedBroken.length} broken projection${removedBroken.length === 1 ? "" : "s"}.`);
2616
+ lines.push(`Relinked ${relinked.length} duplicate or matched agent skill entr${relinked.length === 1 ? "y" : "ies"}.`);
2617
+ if (options.removeSuspicious) {
2618
+ lines.push(`Removed ${removedSuspicious.length} suspicious agent skill entr${removedSuspicious.length === 1 ? "y" : "ies"}.`);
2619
+ } else if (suspiciousCount > 0) {
2620
+ lines.push("Suspicious agent skill entries were reported only. Re-run with --apply --remove-suspicious to remove them.");
2621
+ }
2622
+ if (newEntries.length > 0) {
2623
+ lines.push("New agent skill entries were found. Use aweskill store import --scan with same scope and agent filters to import them.");
2624
+ }
2625
+ }
2626
+ context.write(lines.join("\n").trim());
2627
+ return { relinked, repairedBroken, removedBroken, removedSuspicious, newEntries };
2628
+ }
2629
+
2630
+ // src/commands/clean.ts
2631
+ import { rm as rm8 } from "fs/promises";
2632
+ var DEFAULT_PREVIEW_COUNT3 = 5;
2633
+ function formatFindingGroups(relativePaths, options) {
2634
+ const groups = [
2635
+ options.includeSkills ? {
2636
+ label: "skills",
2637
+ entries: relativePaths.filter((entry) => entry.startsWith("skills/"))
2638
+ } : void 0,
2639
+ options.includeBundles ? {
2640
+ label: "bundles",
2641
+ entries: relativePaths.filter((entry) => entry.startsWith("bundles/"))
2642
+ } : void 0
2643
+ ].filter((group) => Boolean(group));
2644
+ const lines = [];
2645
+ for (const group of groups) {
2646
+ if (group.entries.length === 0) {
2647
+ continue;
2648
+ }
2649
+ lines.push(`${group.label}: ${group.entries.length}`);
2650
+ const preview = options.verbose ? group.entries : group.entries.slice(0, DEFAULT_PREVIEW_COUNT3);
2651
+ if (!options.verbose && group.entries.length > preview.length) {
2652
+ lines.push(`Showing first ${preview.length} suspicious entries in ${group.label} (use --verbose to show all)`);
2653
+ }
2654
+ for (const entry of preview) {
2655
+ lines.push(` - ${entry}`);
2656
+ }
2657
+ if (!options.verbose && group.entries.length > preview.length) {
2658
+ lines.push(`... and ${group.entries.length - preview.length} more (use --verbose to show all)`);
2659
+ }
2660
+ }
2661
+ return lines;
2662
+ }
2663
+ async function runClean(context, options = {}) {
2664
+ const { rootDir, skillsDir, bundlesDir } = getAweskillPaths(context.homeDir);
2665
+ const includeSkills = !options.bundlesOnly;
2666
+ const includeBundles = !options.skillsOnly;
2667
+ const { findings } = await scanStoreHygiene({ rootDir, skillsDir, bundlesDir, includeSkills, includeBundles });
2668
+ if (findings.length === 0) {
2669
+ context.write("No suspicious store entries found.");
2670
+ return { findings, removed: [] };
2671
+ }
2672
+ const lines = [
2673
+ "Suspicious store entries:",
2674
+ ...formatFindingGroups(
2675
+ findings.map((finding) => finding.relativePath),
2676
+ { verbose: options.verbose, includeSkills, includeBundles }
2677
+ )
2678
+ ];
2679
+ if (!options.apply) {
2680
+ lines.push("");
2681
+ lines.push("Dry run only. Use --apply to remove suspicious entries.");
2682
+ context.write(lines.join("\n"));
2683
+ return { findings, removed: [] };
1977
2684
  }
1978
- context.write(`Sync complete. Removed ${removed} stale projection(s).`);
1979
- if (warnings.length > 0) {
1980
- context.write(warnings.join("\n"));
2685
+ const removed = [];
2686
+ for (const finding of findings) {
2687
+ await rm8(finding.path, { recursive: true, force: true });
2688
+ removed.push(finding.relativePath);
2689
+ }
2690
+ lines.push("");
2691
+ lines.push(`Removed ${removed.length} suspicious store entries`);
2692
+ context.write(lines.join("\n"));
2693
+ return { findings, removed };
2694
+ }
2695
+
2696
+ // src/commands/where.ts
2697
+ import { readdir as readdir10 } from "fs/promises";
2698
+ async function countEntries(targetPath) {
2699
+ if (!await pathExists(targetPath)) {
2700
+ return 0;
2701
+ }
2702
+ return (await readdir10(targetPath)).length;
2703
+ }
2704
+ function formatEntryCount(count) {
2705
+ return `${count} ${count === 1 ? "entry" : "entries"}`;
2706
+ }
2707
+ async function runStoreWhere(context, options = {}) {
2708
+ const paths = getAweskillPaths(context.homeDir);
2709
+ if (!options.verbose) {
2710
+ context.write(`aweskill store: ${paths.rootDir}`);
2711
+ return paths.rootDir;
2712
+ }
2713
+ const entries = [
2714
+ { label: "skills", path: paths.skillsDir, entryCount: await countEntries(paths.skillsDir) },
2715
+ { label: "dup_skills", path: paths.dupSkillsDir, entryCount: await countEntries(paths.dupSkillsDir) },
2716
+ { label: "backup", path: paths.backupDir, entryCount: await countEntries(paths.backupDir) },
2717
+ { label: "bundles", path: paths.bundlesDir, entryCount: await countEntries(paths.bundlesDir) }
2718
+ ];
2719
+ const lines = [`aweskill store: ${paths.rootDir}`];
2720
+ for (const entry of entries) {
2721
+ lines.push(` - ${entry.label}: ${formatEntryCount(entry.entryCount)} -> ${entry.path}`);
1981
2722
  }
1982
- return { removed, warnings };
2723
+ context.write(lines.join("\n"));
2724
+ return {
2725
+ rootDir: paths.rootDir,
2726
+ entries
2727
+ };
1983
2728
  }
1984
2729
 
1985
2730
  // src/lib/version.ts
1986
2731
  import { existsSync, readFileSync } from "fs";
1987
- import path16 from "path";
2732
+ import path19 from "path";
1988
2733
  import { fileURLToPath as fileURLToPath2 } from "url";
1989
2734
  function readVersionFromPackageJson(packageJsonPath) {
1990
2735
  const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
@@ -1994,9 +2739,9 @@ function readVersionFromPackageJson(packageJsonPath) {
1994
2739
  return packageJson.version;
1995
2740
  }
1996
2741
  function resolveVersionForModuleUrl(moduleUrl) {
1997
- const moduleDir = path16.dirname(fileURLToPath2(moduleUrl));
2742
+ const moduleDir = path19.dirname(fileURLToPath2(moduleUrl));
1998
2743
  for (const relativePath of ["../package.json", "../../package.json"]) {
1999
- const packageJsonPath = path16.resolve(moduleDir, relativePath);
2744
+ const packageJsonPath = path19.resolve(moduleDir, relativePath);
2000
2745
  if (existsSync(packageJsonPath)) {
2001
2746
  return readVersionFromPackageJson(packageJsonPath);
2002
2747
  }
@@ -2041,11 +2786,11 @@ function emitMessage(line) {
2041
2786
  p.log.success(trimmed);
2042
2787
  return;
2043
2788
  }
2044
- if (trimmed.startsWith("Skills in central repo:") || trimmed === "Scanned skills:" || trimmed === "Duplicate skill groups in central repo:" || trimmed === "Bundles:" || trimmed.startsWith("Global skills for ") || trimmed.startsWith("Project skills for ")) {
2789
+ if (trimmed.startsWith("Skills in central repo:") || trimmed === "Scanned skills:" || trimmed === "Duplicate skill groups in central repo:" || trimmed === "Bundles:" || trimmed === "Supported agents:" || trimmed.startsWith("Global skills for ") || trimmed.startsWith("Project skills for ")) {
2045
2790
  console.log(pc.bold(trimmed));
2046
2791
  return;
2047
2792
  }
2048
- if (trimmed.startsWith("linked:") || trimmed.startsWith("duplicate:") || trimmed.startsWith("new:")) {
2793
+ if (trimmed.startsWith("linked:") || trimmed.startsWith("duplicate:") || trimmed.startsWith("matched:") || trimmed.startsWith("new:") || trimmed.startsWith("suspicious:")) {
2049
2794
  console.log(pc.dim(trimmed));
2050
2795
  return;
2051
2796
  }
@@ -2075,6 +2820,19 @@ function emitMessage(line) {
2075
2820
  console.log(` ${pc.green("\u2713")} ${pc.cyan(name)} ${pc.dim(location)}`);
2076
2821
  return;
2077
2822
  }
2823
+ if (line.startsWith("\u2713 ")) {
2824
+ const rest = line.slice(2);
2825
+ const firstSpace = rest.indexOf(" ");
2826
+ const name = firstSpace === -1 ? rest : rest.slice(0, firstSpace);
2827
+ const location = firstSpace === -1 ? "" : rest.slice(firstSpace + 1);
2828
+ console.log(`${pc.green("\u2713")} ${pc.cyan(name)}${location ? ` ${pc.dim(location)}` : ""}`);
2829
+ return;
2830
+ }
2831
+ if (line.startsWith("x ")) {
2832
+ const rest = line.slice(2);
2833
+ console.log(`${pc.red("x")} ${pc.cyan(rest)}`);
2834
+ return;
2835
+ }
2078
2836
  if (line.startsWith(" ! ")) {
2079
2837
  const rest = line.slice(4);
2080
2838
  const firstSpace = rest.indexOf(" ");
@@ -2087,13 +2845,13 @@ function emitMessage(line) {
2087
2845
  console.log(` ${pc.yellow("!")} ${pc.cyan(name)} ${pc.dim(location)}`);
2088
2846
  return;
2089
2847
  }
2090
- if (line.startsWith(" \u2713 ") || line.startsWith(" ! ") || line.startsWith(" + ")) {
2848
+ if (line.startsWith(" \u2713 ") || line.startsWith(" ! ") || line.startsWith(" ~ ") || line.startsWith(" + ") || line.startsWith(" ? ")) {
2091
2849
  const marker = line.slice(4, 5);
2092
2850
  const rest = line.slice(6);
2093
2851
  const firstSpace = rest.indexOf(" ");
2094
2852
  const name = firstSpace === -1 ? rest : rest.slice(0, firstSpace);
2095
2853
  const location = firstSpace === -1 ? "" : rest.slice(firstSpace + 1);
2096
- const coloredMarker = marker === "\u2713" ? pc.green("\u2713") : marker === "!" ? pc.yellow("!") : pc.cyan("+");
2854
+ const coloredMarker = marker === "\u2713" ? pc.green("\u2713") : marker === "!" ? pc.yellow("!") : marker === "~" ? pc.blue("~") : marker === "?" ? pc.red("?") : pc.cyan("+");
2097
2855
  console.log(` ${coloredMarker} ${pc.cyan(name)}${location ? ` ${pc.dim(location)}` : ""}`);
2098
2856
  return;
2099
2857
  }
@@ -2144,12 +2902,6 @@ async function runFramedCommand(title, action) {
2144
2902
  function collectAgents(value, previous) {
2145
2903
  return [...previous ?? [], ...value.split(",").map((entry) => entry.trim()).filter(Boolean)];
2146
2904
  }
2147
- function getMode(value) {
2148
- if (value === "mv" || value === "cp") {
2149
- return value;
2150
- }
2151
- throw new Error(`Unsupported import mode: ${value}. Use "cp" or "mv".`);
2152
- }
2153
2905
  function getActivationType(value) {
2154
2906
  if (value === "bundle" || value === "skill") {
2155
2907
  return value;
@@ -2174,7 +2926,7 @@ function formatCliErrorMessage(message) {
2174
2926
  }
2175
2927
  const unknownSkillMatch = normalizedMessage.match(/^Unknown skill: (.+)$/);
2176
2928
  if (unknownSkillMatch) {
2177
- return `Unknown skill: ${unknownSkillMatch[1]}. Run "aweskill skill list" to see available skills.`;
2929
+ return `Unknown skill: ${unknownSkillMatch[1]}. Run "aweskill store list" to see available skills.`;
2178
2930
  }
2179
2931
  const bundleNotFoundMatch = normalizedMessage.match(/^Bundle not found: (.+)$/);
2180
2932
  if (bundleNotFoundMatch) {
@@ -2193,11 +2945,16 @@ function formatCliErrorMessage(message) {
2193
2945
  const hint = hints[argName];
2194
2946
  return `Missing required argument <${argName}>.${hint ? ` ${hint}` : ""}`;
2195
2947
  }
2196
- function writeSupportedAgents(context) {
2197
- const lines = [
2198
- "Supported agents:",
2199
- ...listSupportedAgents().map((agent) => `${agent.id} (${agent.displayName})`)
2200
- ];
2948
+ async function writeSupportedAgents(context) {
2949
+ const lines = ["Supported agents:"];
2950
+ const agents = await listSupportedAgentsWithGlobalStatus(context.homeDir);
2951
+ const installedAgents = agents.filter((agent) => agent.installed).map((agent) => agent.id);
2952
+ lines.push(
2953
+ `Detected ${installedAgents.length} installed global agent${installedAgents.length === 1 ? "" : "s"}: ${installedAgents.length > 0 ? installedAgents.join(", ") : "none"}`
2954
+ );
2955
+ for (const agent of agents) {
2956
+ lines.push(agent.installed ? `\u2713 ${agent.id} (${agent.displayName}) ${agent.skillsDir}` : `x ${agent.id} (${agent.displayName})`);
2957
+ }
2201
2958
  for (const line of lines) {
2202
2959
  context.write(line);
2203
2960
  }
@@ -2205,49 +2962,44 @@ function writeSupportedAgents(context) {
2205
2962
  function configureCommandTree(command) {
2206
2963
  command.showHelpAfterError();
2207
2964
  command.exitOverride((error) => {
2208
- throw new Error(formatCliErrorMessage(error.message));
2965
+ error.message = formatCliErrorMessage(error.message);
2966
+ throw error;
2209
2967
  });
2210
2968
  for (const child of command.commands) {
2211
2969
  configureCommandTree(child);
2212
2970
  }
2213
2971
  }
2972
+ function normalizeVersionAlias(argv) {
2973
+ return argv.map((arg, index) => {
2974
+ if (index >= 2 && arg === "-V") {
2975
+ return "-v";
2976
+ }
2977
+ return arg;
2978
+ });
2979
+ }
2980
+ function isInitializationExempt(args) {
2981
+ if (args.length === 0) {
2982
+ return true;
2983
+ }
2984
+ if (args.includes("-h") || args.includes("--help") || args.includes("-v") || args.includes("-V") || args.includes("--version")) {
2985
+ return true;
2986
+ }
2987
+ return args[0] === "store" && args[1] === "init";
2988
+ }
2989
+ async function assertStoreInitialized(homeDir, args) {
2990
+ if (isInitializationExempt(args)) {
2991
+ return;
2992
+ }
2993
+ const { rootDir } = getAweskillPaths(homeDir);
2994
+ if (await pathExists(rootDir)) {
2995
+ return;
2996
+ }
2997
+ throw new Error(`aweskill store is not initialized at ${rootDir}. Run "aweskill store init" first.`);
2998
+ }
2214
2999
  function createProgram(overrides = {}) {
2215
3000
  const context = createRuntimeContext(overrides);
2216
3001
  const program = new Command();
2217
- program.name("aweskill").description("Local skill orchestration CLI for AI agents").version(AWESKILL_VERSION).helpOption("-h, --help", "Display help");
2218
- const skill = program.command("skill").description("Manage skills in the central store");
2219
- skill.command("list").description("List skills in the central store").option("--verbose", "show all skills instead of a short preview", false).action(async (options) => {
2220
- await runListSkills(context, { verbose: options.verbose });
2221
- });
2222
- skill.command("scan").description("Scan supported agent skill directories").option("--verbose", "show scanned skill details instead of per-agent totals", false).action(async (options) => {
2223
- await runFramedCommand(
2224
- " aweskill skill scan ",
2225
- async () => runScan(context, {
2226
- verbose: options.verbose
2227
- })
2228
- );
2229
- });
2230
- skill.command("import").argument("[path]").description("Import one skill or a skills root directory").option("--scan", "import scanned skills", false).option("--mode <mode>", "import mode: cp (default) or mv", getMode, "cp").option("--override", "overwrite existing files when importing", false).action(async (sourcePath, options) => {
2231
- await runFramedCommand(
2232
- " aweskill skill import ",
2233
- async () => runImport(context, {
2234
- sourcePath,
2235
- scan: options.scan,
2236
- mode: options.mode,
2237
- override: options.override
2238
- })
2239
- );
2240
- });
2241
- skill.command("remove").argument("<skill>").description("Remove a skill from the central store").option("--force", "remove and clean references", false).option("--project <dir>", "project config to inspect").action(async (skillName, options) => {
2242
- await runFramedCommand(
2243
- " aweskill skill remove ",
2244
- async () => runRemove(context, {
2245
- skillName,
2246
- force: options.force,
2247
- projectDir: options.project
2248
- })
2249
- );
2250
- });
3002
+ program.name("aweskill").description("Local skill orchestration CLI for AI agents").version(AWESKILL_VERSION, "-v, --version", "output the version number").helpOption("-h, --help", "Display help");
2251
3003
  const bundle = program.command("bundle").description("Manage skill bundles");
2252
3004
  bundle.command("list").description("List bundles in the central store").option("--verbose", "show all bundles instead of a short preview", false).action(async (options) => {
2253
3005
  await runListBundles(context, { verbose: options.verbose });
@@ -2276,24 +3028,28 @@ function createProgram(overrides = {}) {
2276
3028
  });
2277
3029
  const agent = program.command("agent").description("Manage skills used by agents");
2278
3030
  agent.command("supported").description("List supported agent ids and display names").action(async () => {
2279
- writeSupportedAgents(context);
3031
+ await writeSupportedAgents(context);
2280
3032
  });
2281
- agent.command("list").description("Inspect agent skill directories and optionally normalize them").option("--global", "check global scope (default when no scope flag given)").option("--project [dir]", "check project scope; uses cwd when dir is omitted").option("--agent <agent>", 'repeat or use comma list; defaults to all; run "aweskill agent supported" to see supported ids', collectAgents).option("--update", "import missing skills into the central store and relink duplicates/new skills", false).option("--verbose", "show all skills in each category instead of a short preview", false).action(async (options) => {
3033
+ agent.command("list").description("Inspect agent skill directories").option("--global", "check global scope (default when no scope flag given)").option("--project [dir]", "check project scope; uses cwd when dir is omitted").option(
3034
+ "--agent <agent>",
3035
+ 'repeat or use comma list; defaults to all agents detected at this scope; run "aweskill agent supported" to see supported ids',
3036
+ collectAgents
3037
+ ).option("--verbose", "show all skills in each category instead of a short preview", false).action(async (options) => {
2282
3038
  const isProject = options.project !== void 0;
2283
3039
  const scope = isProject ? "project" : "global";
2284
3040
  const projectDir = isProject && typeof options.project === "string" ? options.project : void 0;
2285
3041
  await runFramedCommand(
2286
3042
  " aweskill agent list ",
2287
- async () => runCheck(context, {
3043
+ async () => runSync(context, {
2288
3044
  scope,
2289
3045
  agents: options.agent ?? [],
2290
3046
  projectDir,
2291
- update: options.update,
3047
+ apply: false,
2292
3048
  verbose: options.verbose
2293
3049
  })
2294
3050
  );
2295
3051
  });
2296
- agent.command("add").description("Create skill projections in agent directories").argument("<type>", "bundle or skill", getActivationType).argument("<name>", 'bundle or skill name(s), comma-separated, or "all"').option("--global", "apply to global scope (default when no scope flag given)").option("--project [dir]", "apply to project scope; uses cwd when dir is omitted").option("--agent <agent>", 'repeat or use comma list; defaults to all; run "aweskill agent supported" to see supported ids', collectAgents).action(async (type, targetName, options) => {
3052
+ agent.command("add").description("Create skill projections in agent directories").argument("<type>", "bundle or skill", getActivationType).argument("<name>", 'bundle or skill name(s), comma-separated, or "all"').option("--global", "apply to global scope (default when no scope flag given)").option("--project [dir]", "apply to project scope; uses cwd when dir is omitted").option("--agent <agent>", 'repeat or use comma list; defaults to all; run "aweskill agent supported" to see supported ids', collectAgents).option("--force", "replace existing duplicate, foreign, or unmanaged targets in agent directories", false).action(async (type, targetName, options) => {
2297
3053
  const isProject = options.project !== void 0;
2298
3054
  const scope = isProject ? "project" : "global";
2299
3055
  const projectDir = isProject && typeof options.project === "string" ? options.project : void 0;
@@ -2304,13 +3060,14 @@ function createProgram(overrides = {}) {
2304
3060
  name: targetName,
2305
3061
  scope,
2306
3062
  agents: options.agent ?? [],
2307
- projectDir
3063
+ projectDir,
3064
+ force: options.force
2308
3065
  })
2309
3066
  );
2310
3067
  });
2311
3068
  agent.command("remove").description("Remove skill projections in agent directories").argument("<type>", "bundle or skill", getActivationType).argument("<name>", 'bundle or skill name(s), comma-separated, or "all"').option("--global", "apply to global scope (default when no scope flag given)").option("--project [dir]", "apply to project scope; uses cwd when dir is omitted").option("--agent <agent>", 'repeat or use comma list; defaults to all; run "aweskill agent supported" to see supported ids', collectAgents).option(
2312
3069
  "--force",
2313
- "with skill: remove projection even when this skill is in a bundle that still has other members enabled here",
3070
+ "with skill: remove a single bundle member or delete duplicate, foreign, or unmanaged targets in agent directories",
2314
3071
  false
2315
3072
  ).action(async (type, targetName, options) => {
2316
3073
  const isProject = options.project !== void 0;
@@ -2328,9 +3085,6 @@ function createProgram(overrides = {}) {
2328
3085
  })
2329
3086
  );
2330
3087
  });
2331
- agent.command("sync").description("Remove stale managed projections whose source skill no longer exists").option("--project <dir>", "also check this project directory").action(async (options) => {
2332
- await runFramedCommand(" aweskill agent sync ", async () => runSync(context, { projectDir: options.project }));
2333
- });
2334
3088
  agent.command("recover").description("Convert aweskill-managed symlink projections into full skill directories").option("--global", "recover global scope (default when no scope flag given)").option("--project [dir]", "recover project scope; uses cwd when dir is omitted").option("--agent <agent>", 'repeat or use comma list; defaults to all; run "aweskill agent supported" to see supported ids', collectAgents).action(async (options) => {
2335
3089
  const isProject = options.project !== void 0;
2336
3090
  const scope = isProject ? "project" : "global";
@@ -2348,43 +3102,129 @@ function createProgram(overrides = {}) {
2348
3102
  store.command("init").description("Create ~/.aweskill layout").option("--scan", "scan existing agent directories after init", false).option("--verbose", "show scanned skill details instead of per-agent totals", false).action(async (options) => {
2349
3103
  await runFramedCommand(" aweskill store init ", async () => runInit(context, options));
2350
3104
  });
2351
- store.command("backup").argument("[archive]").description("Create a timestamped archive of the central skills repository").option("--both", "include bundle definitions in the backup archive", false).action(async (archivePath, options) => {
3105
+ store.command("list").description("List skills in the central store").option("--verbose", "show all skills instead of a short preview", false).action(async (options) => {
3106
+ await runListSkills(context, { verbose: options.verbose });
3107
+ });
3108
+ store.command("scan").description("Scan supported agent skill directories").option("--global", "scan global scope (default when no scope flag given)").option("--project [dir]", "scan project scope; uses cwd when dir is omitted").option("--agent <agent>", 'repeat or use comma list; defaults to all; run "aweskill agent supported" to see supported ids', collectAgents).option("--verbose", "show scanned skill details instead of per-agent totals", false).action(async (options) => {
3109
+ const isProject = options.project !== void 0;
3110
+ const scope = isProject ? "project" : "global";
3111
+ const projectDir = isProject && typeof options.project === "string" ? options.project : void 0;
3112
+ await runFramedCommand(
3113
+ " aweskill store scan ",
3114
+ async () => runScan(context, {
3115
+ scope,
3116
+ agents: options.agent ?? [],
3117
+ projectDir,
3118
+ verbose: options.verbose
3119
+ })
3120
+ );
3121
+ });
3122
+ store.command("import").argument("[path]").description("Import one skill or a skills root directory").option("--scan", "import scanned skills", false).option("--global", "scan global scope when used with --scan (default when no scope flag given)").option("--project [dir]", "scan project scope when used with --scan; uses cwd when dir is omitted").option("--agent <agent>", 'repeat or use comma list; defaults to all; run "aweskill agent supported" to see supported ids', collectAgents).option("--keep-source", "keep the source path in place after importing", false).option("--link-source", "replace the source path with an aweskill-managed projection after importing", false).option("--override", "overwrite existing files when importing", false).action(async (sourcePath, options) => {
3123
+ const isProject = options.project !== void 0;
3124
+ const scope = isProject ? "project" : "global";
3125
+ const projectDir = isProject && typeof options.project === "string" ? options.project : void 0;
3126
+ await runFramedCommand(
3127
+ " aweskill store import ",
3128
+ async () => runImport(context, {
3129
+ sourcePath,
3130
+ scan: options.scan,
3131
+ scope,
3132
+ agents: options.agent ?? [],
3133
+ projectDir,
3134
+ override: options.override,
3135
+ keepSource: options.keepSource,
3136
+ linkSource: options.linkSource
3137
+ })
3138
+ );
3139
+ });
3140
+ store.command("remove").argument("<skill>").description("Remove a skill from the central store").option("--force", "remove and clean references", false).option("--project <dir>", "project config to inspect").action(async (skillName, options) => {
3141
+ await runFramedCommand(
3142
+ " aweskill store remove ",
3143
+ async () => runRemove(context, {
3144
+ skillName,
3145
+ force: options.force,
3146
+ projectDir: options.project
3147
+ })
3148
+ );
3149
+ });
3150
+ store.command("backup").argument("[archive]").description("Create a timestamped archive of the central skills repository").option("--both", "include bundle definitions in the backup archive (default behavior)", true).option("--skills-only", "back up only skills/ without bundles/", false).action(async (archivePath, options) => {
2352
3151
  await runFramedCommand(
2353
3152
  " aweskill store backup ",
2354
3153
  async () => runBackup(context, {
2355
3154
  archivePath,
2356
- includeBundles: options.both
3155
+ includeBundles: options.skillsOnly ? false : options.both
2357
3156
  })
2358
3157
  );
2359
3158
  });
2360
- store.command("restore").argument("<archive>").description("Restore skills from a backup archive and auto-back up the current skills first").option("--override", "replace existing skills with the archive contents", false).option("--both", "restore bundle definitions and include them in the pre-restore backup", false).action(async (archivePath, options) => {
3159
+ store.command("restore").argument("<archive>").description("Restore skills from a backup archive or unpacked backup directory and auto-back up the current store first").option("--override", "replace existing skills with the archive contents", false).option("--both", "restore bundle definitions and include them in the pre-restore backup (default behavior)", true).option("--skills-only", "restore only skills/ without bundles/", false).action(async (archivePath, options) => {
2361
3160
  await runFramedCommand(
2362
3161
  " aweskill store restore ",
2363
3162
  async () => runRestore(context, {
2364
3163
  archivePath,
2365
3164
  override: options.override,
2366
- includeBundles: options.both
3165
+ includeBundles: options.skillsOnly ? false : options.both
2367
3166
  })
2368
3167
  );
2369
3168
  });
3169
+ store.command("where").description("Show the aweskill store location and optionally summarize its contents").option("--verbose", "show core store directories and entry counts", false).action(async (options) => {
3170
+ await runStoreWhere(context, { verbose: options.verbose });
3171
+ });
2370
3172
  const doctor = program.command("doctor").description("Diagnose and repair repository issues");
2371
- doctor.command("dedupe").description("Find or remove duplicate central-store skills with numeric/version suffixes").option("--fix", "move duplicate skills into dup_skills (or delete them with --delete)", false).option("--delete", "when used with --fix, permanently delete duplicates instead of moving them", false).action(async (options) => {
3173
+ doctor.command("clean").description("Find and optionally remove suspicious non-store entries in skills/ and bundles/").option("--apply", "remove suspicious entries instead of reporting only", false).option("--skills-only", "scan only skills/", false).option("--bundles-only", "scan only bundles/", false).option("--verbose", "show all suspicious entries instead of a short preview", false).action(async (options) => {
3174
+ await runFramedCommand(
3175
+ " aweskill doctor clean ",
3176
+ async () => runClean(context, {
3177
+ apply: options.apply,
3178
+ skillsOnly: options.skillsOnly,
3179
+ bundlesOnly: options.bundlesOnly,
3180
+ verbose: options.verbose
3181
+ })
3182
+ );
3183
+ });
3184
+ doctor.command("dedup").description("Find or remove duplicate central-store skills with numeric/version suffixes").option("--apply", "move duplicate skills into dup_skills (or delete them with --delete)", false).option("--delete", "when used with --apply, permanently delete duplicates instead of moving them", false).action(async (options) => {
2372
3185
  await runFramedCommand(
2373
- " aweskill doctor dedupe ",
3186
+ " aweskill doctor dedup ",
2374
3187
  async () => runRmdup(context, {
2375
- remove: options.fix,
3188
+ apply: options.apply,
2376
3189
  delete: options.delete
2377
3190
  })
2378
3191
  );
2379
3192
  });
3193
+ doctor.command("sync").description("Inspect and optionally repair agent-side projections; dry-run by default").option("--apply", "repair broken projections and relink duplicate and matched entries", false).option("--remove-suspicious", "when used with --apply, remove suspicious agent entries instead of only reporting them", false).option("--global", "check global scope (default when no scope flag given)").option("--project [dir]", "check project scope; uses cwd when dir is omitted").option(
3194
+ "--agent <agent>",
3195
+ 'repeat or use comma list; defaults to all agents detected at this scope; run "aweskill agent supported" to see supported ids',
3196
+ collectAgents
3197
+ ).option("--verbose", "show all agent skill entries instead of a short preview", false).action(async (options) => {
3198
+ const isProject = options.project !== void 0;
3199
+ const scope = isProject ? "project" : "global";
3200
+ const projectDir = isProject && typeof options.project === "string" ? options.project : void 0;
3201
+ await runFramedCommand(
3202
+ " aweskill doctor sync ",
3203
+ async () => runSync(context, {
3204
+ apply: options.apply,
3205
+ removeSuspicious: options.removeSuspicious,
3206
+ scope,
3207
+ agents: options.agent ?? [],
3208
+ projectDir,
3209
+ verbose: options.verbose
3210
+ })
3211
+ );
3212
+ });
2380
3213
  configureCommandTree(program);
2381
3214
  return program;
2382
3215
  }
2383
3216
  async function main(argv = process.argv) {
2384
3217
  const program = createProgram();
2385
3218
  try {
2386
- await program.parseAsync(argv);
3219
+ const normalizedArgv = normalizeVersionAlias(argv);
3220
+ const context = createRuntimeContext();
3221
+ await assertStoreInitialized(context.homeDir, normalizedArgv.slice(2));
3222
+ await program.parseAsync(normalizedArgv);
2387
3223
  } catch (error) {
3224
+ const code = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
3225
+ if (code === "commander.version" || code === "commander.helpDisplayed") {
3226
+ return;
3227
+ }
2388
3228
  const message = error instanceof Error ? error.message : String(error);
2389
3229
  if (message === "(outputHelp)" || message === "outputHelp") {
2390
3230
  return;