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/README.md +136 -101
- package/README.zh-CN.md +135 -100
- package/dist/index.js +1570 -730
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/resources/bundle_templates/Nature-Paper-Skills.yaml +11 -0
- package/resources/bundle_templates/caveman.yaml +7 -0
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
|
|
8
|
+
import path4 from "path";
|
|
9
9
|
|
|
10
10
|
// src/lib/backup.ts
|
|
11
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
124
|
+
return path2.join(homeDir, targetPath.slice(2));
|
|
39
125
|
}
|
|
40
126
|
return targetPath;
|
|
41
127
|
}
|
|
42
128
|
function getAweskillPaths(homeDir) {
|
|
43
|
-
const rootDir =
|
|
129
|
+
const rootDir = path2.join(homeDir, ".aweskill");
|
|
44
130
|
return {
|
|
45
131
|
homeDir,
|
|
46
132
|
rootDir,
|
|
47
|
-
skillsDir:
|
|
48
|
-
dupSkillsDir:
|
|
49
|
-
backupDir:
|
|
50
|
-
bundlesDir:
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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
|
|
78
|
-
|
|
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
|
-
|
|
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 {
|
|
324
|
+
const { backupDir } = getAweskillPaths(homeDir);
|
|
86
325
|
const includeBundles = options.includeBundles ?? false;
|
|
87
326
|
const archivePath = await resolveBackupArchivePath(backupDir, options.archivePath);
|
|
88
|
-
await mkdir(
|
|
89
|
-
|
|
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(
|
|
94
|
-
await
|
|
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:
|
|
98
|
-
extractedBundlesDir:
|
|
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 =
|
|
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 =
|
|
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 ?
|
|
134
|
-
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(
|
|
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
|
|
142
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
430
|
+
const skillPath = path5.join(skillsDir, entry.name);
|
|
171
431
|
return {
|
|
172
432
|
name: entry.name,
|
|
173
433
|
path: skillPath,
|
|
174
|
-
hasSKILLMd: await pathExists(
|
|
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 =
|
|
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
|
|
461
|
+
return path6.join(getAweskillPaths(homeDir).bundlesDir, `${sanitizeName(bundleName)}.yaml`);
|
|
193
462
|
}
|
|
194
463
|
function bundleFilePathInDirectory(bundlesDir, bundleName) {
|
|
195
|
-
return
|
|
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
|
|
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
|
|
219
|
-
return normalizeBundle(
|
|
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
|
|
224
|
-
return normalizeBundle(
|
|
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(
|
|
230
|
-
await
|
|
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
|
|
531
|
+
import path7 from "path";
|
|
263
532
|
import { fileURLToPath } from "url";
|
|
264
533
|
async function getTemplateBundlesDir() {
|
|
265
|
-
const moduleDir =
|
|
534
|
+
const moduleDir = path7.dirname(fileURLToPath(import.meta.url));
|
|
266
535
|
const candidates = [
|
|
267
|
-
|
|
268
|
-
|
|
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
|
|
356
|
-
import
|
|
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
|
|
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
|
|
376
|
-
const resolvedSourcePath =
|
|
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
|
|
389
|
-
const entries = await
|
|
885
|
+
await mkdir5(destinationPath, { recursive: true });
|
|
886
|
+
const entries = await readdir6(sourcePath, { withFileTypes: true });
|
|
390
887
|
for (const entry of entries) {
|
|
391
|
-
await mergeMissingEntries(
|
|
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
|
|
399
|
-
await
|
|
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
|
|
903
|
+
await cp3(sourcePath, destination, { recursive: true, errorOnExist: false, force: override });
|
|
407
904
|
}
|
|
408
|
-
async function
|
|
409
|
-
if (
|
|
410
|
-
|
|
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
|
|
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
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
475
|
-
if (await pathExists(
|
|
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(
|
|
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
|
|
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
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
|
|
519
|
-
|
|
1017
|
+
override: options.override,
|
|
1018
|
+
linkSource: options.linkSource
|
|
520
1019
|
});
|
|
521
1020
|
}
|
|
522
1021
|
async function importPath(options) {
|
|
523
|
-
if (await pathExists(
|
|
524
|
-
const skillName = sanitizeName(
|
|
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
|
-
|
|
546
|
-
|
|
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
|
|
553
|
-
import
|
|
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
|
|
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) =>
|
|
572
|
-
globalSkillsDir: (homeDir) =>
|
|
573
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
577
|
-
globalSkillsDir: (homeDir) =>
|
|
578
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
582
|
-
globalSkillsDir: (homeDir) =>
|
|
583
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
587
|
-
globalSkillsDir: (homeDir) =>
|
|
588
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
592
|
-
globalSkillsDir: (homeDir) =>
|
|
593
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
597
|
-
globalSkillsDir: (homeDir) =>
|
|
598
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
602
|
-
globalSkillsDir: (homeDir) =>
|
|
603
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
607
|
-
globalSkillsDir: (homeDir) =>
|
|
608
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
612
|
-
globalSkillsDir: (homeDir) =>
|
|
613
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
617
|
-
globalSkillsDir: (homeDir) =>
|
|
618
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
622
|
-
globalSkillsDir: (homeDir) =>
|
|
623
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
627
|
-
globalSkillsDir: (homeDir) =>
|
|
628
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
632
|
-
globalSkillsDir: (homeDir) =>
|
|
633
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
637
|
-
globalSkillsDir: (homeDir) =>
|
|
638
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
642
|
-
globalSkillsDir: (homeDir) =>
|
|
643
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
647
|
-
globalSkillsDir: (homeDir) =>
|
|
648
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
652
|
-
globalSkillsDir: (homeDir) =>
|
|
653
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
657
|
-
globalSkillsDir: (homeDir) =>
|
|
658
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
662
|
-
globalSkillsDir: (homeDir) =>
|
|
663
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
667
|
-
globalSkillsDir: (homeDir) =>
|
|
668
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
672
|
-
globalSkillsDir: (homeDir) =>
|
|
673
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
677
|
-
globalSkillsDir: (homeDir) =>
|
|
678
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
682
|
-
globalSkillsDir: (homeDir) =>
|
|
683
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
687
|
-
globalSkillsDir: (homeDir) =>
|
|
688
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
692
|
-
globalSkillsDir: (homeDir) =>
|
|
693
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
697
|
-
globalSkillsDir: (homeDir) =>
|
|
698
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
702
|
-
globalSkillsDir: (homeDir) =>
|
|
703
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
707
|
-
globalSkillsDir: (homeDir) =>
|
|
708
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
712
|
-
globalSkillsDir: (homeDir) =>
|
|
713
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
717
|
-
globalSkillsDir: (homeDir) =>
|
|
718
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
722
|
-
globalSkillsDir: (homeDir) =>
|
|
723
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
727
|
-
globalSkillsDir: (homeDir) =>
|
|
728
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
732
|
-
globalSkillsDir: (homeDir) =>
|
|
733
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
737
|
-
globalSkillsDir: (homeDir) =>
|
|
738
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
742
|
-
globalSkillsDir: (homeDir) =>
|
|
743
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
747
|
-
globalSkillsDir: (homeDir) =>
|
|
748
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
752
|
-
globalSkillsDir: (homeDir) =>
|
|
753
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
757
|
-
globalSkillsDir: (homeDir) =>
|
|
758
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
762
|
-
globalSkillsDir: (homeDir) =>
|
|
763
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
767
|
-
globalSkillsDir: (homeDir) =>
|
|
768
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
772
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
776
|
-
globalSkillsDir: (homeDir) =>
|
|
777
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
781
|
-
globalSkillsDir: (homeDir) =>
|
|
782
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
786
|
-
globalSkillsDir: (homeDir) =>
|
|
787
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
791
|
-
globalSkillsDir: (homeDir) =>
|
|
792
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
796
|
-
globalSkillsDir: (homeDir) =>
|
|
797
|
-
projectSkillsDir: (projectDir) =>
|
|
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) =>
|
|
801
|
-
globalSkillsDir: (homeDir) =>
|
|
802
|
-
projectSkillsDir: (projectDir) =>
|
|
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
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
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
|
|
867
|
-
const sourcePath =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
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
|
-
|
|
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
|
-
|
|
934
|
-
|
|
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
|
-
|
|
961
|
-
|
|
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
|
-
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
|
|
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/
|
|
1135
|
-
|
|
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" ?
|
|
1341
|
-
const agents = await
|
|
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 =
|
|
1376
|
-
const wasRemoved = await
|
|
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
|
-
|
|
1769
|
+
import path13 from "path";
|
|
1770
|
+
function getProjectDir2(context, explicitProjectDir) {
|
|
1391
1771
|
return explicitProjectDir ?? context.cwd;
|
|
1392
1772
|
}
|
|
1393
|
-
async function
|
|
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" ?
|
|
1450
|
-
const agents = await
|
|
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 =
|
|
1459
|
-
await
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
1519
|
-
|
|
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,
|
|
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
|
|
1973
|
+
var DEFAULT_PREVIEW_COUNT = 5;
|
|
1556
1974
|
async function runListSkills(context, options = {}) {
|
|
1557
|
-
const
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
1592
|
-
|
|
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
|
|
1604
|
-
import
|
|
1605
|
-
function
|
|
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
|
|
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" ?
|
|
1631
|
-
const agents = await
|
|
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 =
|
|
1643
|
-
const sourcePath =
|
|
2063
|
+
const targetPath = path14.join(agentSkillsDir, skillName);
|
|
2064
|
+
const sourcePath = path14.join(centralSkillsDir, skillName);
|
|
1644
2065
|
const currentTarget = await readlink4(targetPath);
|
|
1645
|
-
const resolvedCurrent =
|
|
1646
|
-
if (resolvedCurrent !==
|
|
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(
|
|
1651
|
-
await
|
|
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
|
|
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(
|
|
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
|
|
1748
|
-
import
|
|
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 ??
|
|
2171
|
+
const includeBundles = options.includeBundles ?? true;
|
|
1751
2172
|
const { tempDir, extractedSkillsDir, extractedBundlesDir } = await extractSkillsArchive(options.archivePath);
|
|
1752
2173
|
try {
|
|
1753
|
-
const
|
|
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
|
|
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 ?
|
|
1762
|
-
const currentBundles = includeBundles ?
|
|
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(
|
|
1779
|
-
await
|
|
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
|
|
1783
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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
|
|
1808
|
-
import
|
|
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
|
|
2259
|
+
const normalizedName = sanitizeName(name);
|
|
2260
|
+
const match = normalizedName.match(NUMERIC_SUFFIX_PATTERN);
|
|
1812
2261
|
if (!match) {
|
|
1813
2262
|
return {
|
|
1814
|
-
baseName:
|
|
2263
|
+
baseName: stripVersionSuffix(normalizedName),
|
|
2264
|
+
matchKey: getDuplicateMatchKey(name),
|
|
1815
2265
|
hasNumericSuffix: false
|
|
1816
2266
|
};
|
|
1817
2267
|
}
|
|
1818
2268
|
return {
|
|
1819
|
-
baseName:
|
|
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.
|
|
2331
|
+
const bucket = grouped.get(parsed.matchKey) ?? [];
|
|
1860
2332
|
bucket.push(skill);
|
|
1861
|
-
grouped.set(parsed.
|
|
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
|
|
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
|
|
2368
|
+
const entries = new Set(await readdir9(dupSkillsDir).catch(() => []));
|
|
1897
2369
|
if (!entries.has(skillName)) {
|
|
1898
|
-
return
|
|
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
|
|
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.
|
|
1910
|
-
throw new Error("--delete requires --
|
|
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.
|
|
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 --
|
|
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
|
|
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
|
-
|
|
1949
|
-
|
|
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
|
-
|
|
1955
|
-
|
|
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
|
-
|
|
1958
|
-
const
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
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
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
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
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
2742
|
+
const moduleDir = path19.dirname(fileURLToPath2(moduleUrl));
|
|
1998
2743
|
for (const relativePath of ["../package.json", "../../package.json"]) {
|
|
1999
|
-
const packageJsonPath =
|
|
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
|
|
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
|
-
|
|
2199
|
-
|
|
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
|
-
|
|
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
|
|
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 () =>
|
|
3043
|
+
async () => runSync(context, {
|
|
2288
3044
|
scope,
|
|
2289
3045
|
agents: options.agent ?? [],
|
|
2290
3046
|
projectDir,
|
|
2291
|
-
|
|
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
|
|
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("
|
|
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
|
|
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("
|
|
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
|
|
3186
|
+
" aweskill doctor dedup ",
|
|
2374
3187
|
async () => runRmdup(context, {
|
|
2375
|
-
|
|
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
|
-
|
|
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;
|