skiller 0.7.15 → 0.7.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -5
- package/dist/core/ClaudePluginSync.js +183 -96
- package/dist/core/ClaudeProjectSync.js +393 -0
- package/dist/core/SkillsManifest.js +285 -0
- package/dist/core/SkillsProcessor.js +11 -0
- package/package.json +77 -77
package/README.md
CHANGED
|
@@ -59,11 +59,21 @@ A Claude-centric fork of [ruler](https://github.com/intellectronica/ruler) with
|
|
|
59
59
|
- Reads `.claude/settings.json` `enabledPlugins`
|
|
60
60
|
- Syncs enabled plugin `skills/` into agent skills directories on `skiller apply`
|
|
61
61
|
- Syncs enabled plugin `commands/*.md` as skills (`SKILL.md`) into agent skills directories
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
62
|
+
- Syncs enabled plugin `agents/**/*.md` as skills (`SKILL.md`) into agent skills directories
|
|
63
|
+
- Uses the skill/command/agent name by default (matches existing Codex skill names)
|
|
64
|
+
- If a name conflicts, local skills win and the plugin skill is namespaced as `<pluginId>-<name>`
|
|
65
|
+
- Tracks plugin-managed skills in a single `.skiller.json` file per agent skills directory
|
|
65
66
|
- Removes stale plugin skills when plugins are disabled
|
|
66
67
|
|
|
68
|
+
## 10. Claude Commands/Agents → Skills
|
|
69
|
+
|
|
70
|
+
- Syncs `.claude/commands/**/*.md` as skills (`SKILL.md`) into agent skills directories
|
|
71
|
+
- Syncs `.claude/agents/**/*.md` as skills (`SKILL.md`) into agent skills directories
|
|
72
|
+
- Uses the command/agent name by default
|
|
73
|
+
- If a name conflicts, existing local/manual skills win and the project item is namespaced as `claude-<name>`
|
|
74
|
+
- Project items win over plugin skills/commands/agents on name conflicts
|
|
75
|
+
- Tracks project-managed items in a single `.skiller.json` file per agent skills directory
|
|
76
|
+
|
|
67
77
|
---
|
|
68
78
|
|
|
69
79
|
# Skiller: Centralise Your AI Coding Assistant Instructions
|
|
@@ -596,8 +606,8 @@ If your project enables Claude Code plugins in `.claude/settings.json`, Skiller
|
|
|
596
606
|
- Plugin `skills/` are copied as skills
|
|
597
607
|
- Plugin `commands/*.md` are converted into skills (`SKILL.md`)
|
|
598
608
|
- Plugin skills use their original skill/command name by default
|
|
599
|
-
- If a name conflicts, local skills win and the plugin skill is namespaced as `<pluginId
|
|
600
|
-
- Plugin-managed skills are tracked via `.skiller
|
|
609
|
+
- If a name conflicts, local skills win and the plugin skill is namespaced as `<pluginId>-<name>`
|
|
610
|
+
- Plugin-managed skills are tracked via `.skiller.json` in each agent skills directory
|
|
601
611
|
|
|
602
612
|
### Skills Directory Structure
|
|
603
613
|
|
|
@@ -38,6 +38,7 @@ exports.readInstalledPluginsIndex = readInstalledPluginsIndex;
|
|
|
38
38
|
exports.resolvePluginInstall = resolvePluginInstall;
|
|
39
39
|
exports.discoverPluginSkillDirs = discoverPluginSkillDirs;
|
|
40
40
|
exports.discoverPluginCommandFiles = discoverPluginCommandFiles;
|
|
41
|
+
exports.discoverPluginAgentFiles = discoverPluginAgentFiles;
|
|
41
42
|
exports.syncClaudePluginsToSkillsDirs = syncClaudePluginsToSkillsDirs;
|
|
42
43
|
const fs = __importStar(require("fs/promises"));
|
|
43
44
|
const os = __importStar(require("os"));
|
|
@@ -46,9 +47,8 @@ const yaml = __importStar(require("js-yaml"));
|
|
|
46
47
|
const constants_1 = require("../constants");
|
|
47
48
|
const FrontmatterParser_1 = require("./FrontmatterParser");
|
|
48
49
|
const SkillsUtils_1 = require("./SkillsUtils");
|
|
50
|
+
const SkillsManifest_1 = require("./SkillsManifest");
|
|
49
51
|
const LEGACY_MARKER_FILENAME = '.skiller-plugin.json';
|
|
50
|
-
const MANIFEST_FILENAME = '.skiller-plugins.json';
|
|
51
|
-
const MANIFEST_VERSION = 1;
|
|
52
52
|
function getUserHomeDir() {
|
|
53
53
|
// Prefer env vars so tests (and users) can override deterministically.
|
|
54
54
|
return process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
@@ -219,6 +219,49 @@ async function discoverPluginCommandFiles(installPath) {
|
|
|
219
219
|
file: path.join(commandsRoot, e.name),
|
|
220
220
|
}));
|
|
221
221
|
}
|
|
222
|
+
async function discoverPluginAgentFiles(installPath) {
|
|
223
|
+
const agentsRoot = path.join(installPath, 'agents');
|
|
224
|
+
if (!(await fileExists(agentsRoot)))
|
|
225
|
+
return [];
|
|
226
|
+
const results = [];
|
|
227
|
+
async function walk(current, depth) {
|
|
228
|
+
if (depth >= constants_1.MAX_RECURSION_DEPTH)
|
|
229
|
+
return;
|
|
230
|
+
let entries;
|
|
231
|
+
try {
|
|
232
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
for (const entry of entries) {
|
|
238
|
+
const full = path.join(current, entry.name);
|
|
239
|
+
if (entry.isDirectory()) {
|
|
240
|
+
await walk(full, depth + 1);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
244
|
+
continue;
|
|
245
|
+
let content;
|
|
246
|
+
try {
|
|
247
|
+
content = await fs.readFile(full, 'utf8');
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const parsed = (0, FrontmatterParser_1.parseFrontmatter)(content);
|
|
253
|
+
const fmName = parsed.rawFrontmatter && typeof parsed.rawFrontmatter.name === 'string'
|
|
254
|
+
? parsed.rawFrontmatter.name
|
|
255
|
+
: parsed.frontmatter?.name;
|
|
256
|
+
if (typeof fmName !== 'string' || fmName.trim() === '')
|
|
257
|
+
continue;
|
|
258
|
+
const rel = path.relative(agentsRoot, full).replace(/\\/g, '/');
|
|
259
|
+
results.push({ name: fmName.trim(), file: full, rel });
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
await walk(agentsRoot, 0);
|
|
263
|
+
return results;
|
|
264
|
+
}
|
|
222
265
|
function generateBaseNameFromRelId(relId) {
|
|
223
266
|
const normalized = relId.replace(/\\/g, '/');
|
|
224
267
|
const segments = normalized.split('/').filter(Boolean);
|
|
@@ -228,76 +271,7 @@ function generateBaseNameFromCommand(commandName) {
|
|
|
228
271
|
return sanitizeId(commandName);
|
|
229
272
|
}
|
|
230
273
|
function generateNamespacedName(pluginId, baseName) {
|
|
231
|
-
return `${sanitizeId(pluginId)}
|
|
232
|
-
}
|
|
233
|
-
async function readManifestFile(targetSkillsDir) {
|
|
234
|
-
const manifestPath = path.join(targetSkillsDir, MANIFEST_FILENAME);
|
|
235
|
-
try {
|
|
236
|
-
const raw = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
|
|
237
|
-
if (!raw || typeof raw !== 'object')
|
|
238
|
-
return null;
|
|
239
|
-
const obj = raw;
|
|
240
|
-
if (typeof obj.version !== 'number')
|
|
241
|
-
return null;
|
|
242
|
-
if (!Array.isArray(obj.entries))
|
|
243
|
-
return null;
|
|
244
|
-
const entries = [];
|
|
245
|
-
for (const entry of obj.entries) {
|
|
246
|
-
if (!entry || typeof entry !== 'object')
|
|
247
|
-
continue;
|
|
248
|
-
const e = entry;
|
|
249
|
-
if (typeof e.pluginId !== 'string')
|
|
250
|
-
continue;
|
|
251
|
-
const sourceKind = e.sourceKind;
|
|
252
|
-
if (sourceKind !== 'skill' && sourceKind !== 'command')
|
|
253
|
-
continue;
|
|
254
|
-
if (typeof e.sourceRelPath !== 'string')
|
|
255
|
-
continue;
|
|
256
|
-
if (typeof e.destRelPath !== 'string')
|
|
257
|
-
continue;
|
|
258
|
-
const pluginVersion = typeof e.pluginVersion === 'string' ? e.pluginVersion : undefined;
|
|
259
|
-
entries.push({
|
|
260
|
-
pluginId: e.pluginId,
|
|
261
|
-
pluginVersion,
|
|
262
|
-
sourceKind,
|
|
263
|
-
sourceRelPath: e.sourceRelPath,
|
|
264
|
-
destRelPath: e.destRelPath,
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
return {
|
|
268
|
-
version: obj.version,
|
|
269
|
-
entries,
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
catch {
|
|
273
|
-
return null;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
async function writeManifestFile(targetSkillsDir, entries, dryRun) {
|
|
277
|
-
const manifestPath = path.join(targetSkillsDir, MANIFEST_FILENAME);
|
|
278
|
-
if (entries.length === 0) {
|
|
279
|
-
if (dryRun)
|
|
280
|
-
return;
|
|
281
|
-
try {
|
|
282
|
-
await fs.unlink(manifestPath);
|
|
283
|
-
}
|
|
284
|
-
catch {
|
|
285
|
-
// ignore
|
|
286
|
-
}
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
const manifest = {
|
|
290
|
-
version: MANIFEST_VERSION,
|
|
291
|
-
entries: [...entries].sort((a, b) => {
|
|
292
|
-
// Stable ordering for diffs.
|
|
293
|
-
const ak = `${a.destRelPath}::${a.pluginId}::${a.sourceKind}::${a.sourceRelPath}`;
|
|
294
|
-
const bk = `${b.destRelPath}::${b.pluginId}::${b.sourceKind}::${b.sourceRelPath}`;
|
|
295
|
-
return ak.localeCompare(bk);
|
|
296
|
-
}),
|
|
297
|
-
};
|
|
298
|
-
if (dryRun)
|
|
299
|
-
return;
|
|
300
|
-
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
274
|
+
return `${sanitizeId(pluginId)}-${baseName}`;
|
|
301
275
|
}
|
|
302
276
|
async function readLegacyMarkerFile(dir) {
|
|
303
277
|
const markerPath = path.join(dir, LEGACY_MARKER_FILENAME);
|
|
@@ -310,7 +284,9 @@ async function readLegacyMarkerFile(dir) {
|
|
|
310
284
|
return null;
|
|
311
285
|
if (typeof obj.generatedName !== 'string')
|
|
312
286
|
return null;
|
|
313
|
-
if (obj.sourceKind !== 'skill' &&
|
|
287
|
+
if (obj.sourceKind !== 'skill' &&
|
|
288
|
+
obj.sourceKind !== 'command' &&
|
|
289
|
+
obj.sourceKind !== 'agent')
|
|
314
290
|
return null;
|
|
315
291
|
if (typeof obj.sourceRelPath !== 'string')
|
|
316
292
|
return null;
|
|
@@ -339,10 +315,17 @@ async function removeLegacyMarkerFile(dir, dryRun) {
|
|
|
339
315
|
}
|
|
340
316
|
}
|
|
341
317
|
async function loadManagedEntries(targetSkillsDir, dryRun) {
|
|
342
|
-
const
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
318
|
+
const allEntries = await (0, SkillsManifest_1.loadSkillsManifestEntries)(targetSkillsDir);
|
|
319
|
+
const pluginEntries = [];
|
|
320
|
+
const otherEntries = [];
|
|
321
|
+
for (const entry of allEntries) {
|
|
322
|
+
if ((0, SkillsManifest_1.isPluginManifestEntry)(entry)) {
|
|
323
|
+
pluginEntries.push(entry);
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
otherEntries.push(entry);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
346
329
|
// Migration: absorb legacy per-skill markers and remove them so they don't
|
|
347
330
|
// show up in every skill folder.
|
|
348
331
|
let dirents;
|
|
@@ -350,7 +333,7 @@ async function loadManagedEntries(targetSkillsDir, dryRun) {
|
|
|
350
333
|
dirents = await fs.readdir(targetSkillsDir, { withFileTypes: true });
|
|
351
334
|
}
|
|
352
335
|
catch {
|
|
353
|
-
return
|
|
336
|
+
return { pluginEntries, otherEntries };
|
|
354
337
|
}
|
|
355
338
|
for (const dirent of dirents) {
|
|
356
339
|
if (!dirent.isDirectory())
|
|
@@ -360,22 +343,23 @@ async function loadManagedEntries(targetSkillsDir, dryRun) {
|
|
|
360
343
|
if (!legacy)
|
|
361
344
|
continue;
|
|
362
345
|
const managed = {
|
|
346
|
+
sourceType: 'plugin',
|
|
363
347
|
pluginId: legacy.pluginId,
|
|
364
348
|
pluginVersion: legacy.pluginVersion,
|
|
365
349
|
sourceKind: legacy.sourceKind,
|
|
366
350
|
sourceRelPath: legacy.sourceRelPath,
|
|
367
351
|
destRelPath: dirent.name,
|
|
368
352
|
};
|
|
369
|
-
const already =
|
|
353
|
+
const already = pluginEntries.some((e) => e.destRelPath === managed.destRelPath &&
|
|
370
354
|
e.pluginId === managed.pluginId &&
|
|
371
355
|
e.sourceKind === managed.sourceKind &&
|
|
372
356
|
e.sourceRelPath === managed.sourceRelPath);
|
|
373
357
|
if (!already) {
|
|
374
|
-
|
|
358
|
+
pluginEntries.push(managed);
|
|
375
359
|
}
|
|
376
360
|
await removeLegacyMarkerFile(folder, dryRun);
|
|
377
361
|
}
|
|
378
|
-
return
|
|
362
|
+
return { pluginEntries, otherEntries };
|
|
379
363
|
}
|
|
380
364
|
async function discoverLocalSkillNames(projectRoot) {
|
|
381
365
|
const localSkillsDir = path.join(projectRoot, '.claude', 'skills');
|
|
@@ -406,6 +390,77 @@ async function discoverLocalSkillNames(projectRoot) {
|
|
|
406
390
|
await walk(localSkillsDir, 0);
|
|
407
391
|
return names;
|
|
408
392
|
}
|
|
393
|
+
async function discoverLocalCommandNames(projectRoot) {
|
|
394
|
+
const localCommandsDir = path.join(projectRoot, '.claude', 'commands');
|
|
395
|
+
if (!(await fileExists(localCommandsDir)))
|
|
396
|
+
return new Set();
|
|
397
|
+
const names = new Set();
|
|
398
|
+
async function walk(current, depth) {
|
|
399
|
+
if (depth >= constants_1.MAX_RECURSION_DEPTH)
|
|
400
|
+
return;
|
|
401
|
+
let entries;
|
|
402
|
+
try {
|
|
403
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
for (const entry of entries) {
|
|
409
|
+
const full = path.join(current, entry.name);
|
|
410
|
+
if (entry.isDirectory()) {
|
|
411
|
+
await walk(full, depth + 1);
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
415
|
+
continue;
|
|
416
|
+
names.add(sanitizeId(path.basename(entry.name, '.md')));
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
await walk(localCommandsDir, 0);
|
|
420
|
+
return names;
|
|
421
|
+
}
|
|
422
|
+
async function discoverLocalAgentNames(projectRoot) {
|
|
423
|
+
const localAgentsDir = path.join(projectRoot, '.claude', 'agents');
|
|
424
|
+
if (!(await fileExists(localAgentsDir)))
|
|
425
|
+
return new Set();
|
|
426
|
+
const names = new Set();
|
|
427
|
+
async function walk(current, depth) {
|
|
428
|
+
if (depth >= constants_1.MAX_RECURSION_DEPTH)
|
|
429
|
+
return;
|
|
430
|
+
let entries;
|
|
431
|
+
try {
|
|
432
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
for (const entry of entries) {
|
|
438
|
+
const full = path.join(current, entry.name);
|
|
439
|
+
if (entry.isDirectory()) {
|
|
440
|
+
await walk(full, depth + 1);
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
444
|
+
continue;
|
|
445
|
+
let content;
|
|
446
|
+
try {
|
|
447
|
+
content = await fs.readFile(full, 'utf8');
|
|
448
|
+
}
|
|
449
|
+
catch {
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
const parsed = (0, FrontmatterParser_1.parseFrontmatter)(content);
|
|
453
|
+
const fmName = parsed.rawFrontmatter && typeof parsed.rawFrontmatter.name === 'string'
|
|
454
|
+
? parsed.rawFrontmatter.name
|
|
455
|
+
: parsed.frontmatter?.name;
|
|
456
|
+
if (typeof fmName !== 'string' || fmName.trim() === '')
|
|
457
|
+
continue;
|
|
458
|
+
names.add(sanitizeId(fmName.trim()));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
await walk(localAgentsDir, 0);
|
|
462
|
+
return names;
|
|
463
|
+
}
|
|
409
464
|
async function ensureDir(dir, dryRun) {
|
|
410
465
|
if (dryRun)
|
|
411
466
|
return;
|
|
@@ -439,15 +494,15 @@ async function rewriteSkillMdName(skillMdPath, name, pluginId, dryRun) {
|
|
|
439
494
|
return;
|
|
440
495
|
await fs.writeFile(skillMdPath, next, 'utf8');
|
|
441
496
|
}
|
|
442
|
-
async function
|
|
443
|
-
const content = await fs.readFile(
|
|
497
|
+
async function writeMarkdownAsSkill(srcMarkdownPath, destDir, generatedName, pluginId, kindLabel, dryRun) {
|
|
498
|
+
const content = await fs.readFile(srcMarkdownPath, 'utf8');
|
|
444
499
|
const { rawFrontmatter, body } = (0, FrontmatterParser_1.parseFrontmatter)(content);
|
|
445
500
|
const fm = rawFrontmatter
|
|
446
501
|
? { ...rawFrontmatter }
|
|
447
502
|
: {};
|
|
448
503
|
fm.name = generatedName;
|
|
449
504
|
if (typeof fm.description !== 'string' || fm.description.trim() === '') {
|
|
450
|
-
fm.description =
|
|
505
|
+
fm.description = `${kindLabel} from ${pluginId}: ${path.basename(srcMarkdownPath, '.md')}`;
|
|
451
506
|
}
|
|
452
507
|
const next = `---\n${yaml
|
|
453
508
|
.dump(fm, { lineWidth: -1, noRefs: true })
|
|
@@ -466,6 +521,13 @@ async function syncClaudePluginsToSkillsDirs(args) {
|
|
|
466
521
|
const claudeDir = path.join(getUserHomeDir(), '.claude');
|
|
467
522
|
const index = await readInstalledPluginsIndex(claudeDir);
|
|
468
523
|
const localSkillNames = await discoverLocalSkillNames(projectRoot);
|
|
524
|
+
const localCommandNames = await discoverLocalCommandNames(projectRoot);
|
|
525
|
+
const localAgentNames = await discoverLocalAgentNames(projectRoot);
|
|
526
|
+
const localReservedNames = new Set([
|
|
527
|
+
...localSkillNames,
|
|
528
|
+
...localCommandNames,
|
|
529
|
+
...localAgentNames,
|
|
530
|
+
]);
|
|
469
531
|
// If we can't read the installed plugins index, we can't install/update
|
|
470
532
|
// anything, but we can still clean up managed folders for plugins that
|
|
471
533
|
// are no longer enabled.
|
|
@@ -473,10 +535,10 @@ async function syncClaudePluginsToSkillsDirs(args) {
|
|
|
473
535
|
for (const targetSkillsDir of targetSkillsDirs) {
|
|
474
536
|
if (!(await fileExists(targetSkillsDir)))
|
|
475
537
|
continue;
|
|
476
|
-
const managedEntries = await loadManagedEntries(targetSkillsDir, dryRun);
|
|
538
|
+
const { pluginEntries: managedEntries, otherEntries } = await loadManagedEntries(targetSkillsDir, dryRun);
|
|
477
539
|
const nextEntries = [];
|
|
478
540
|
// Build reserved set (local skills always win).
|
|
479
|
-
const reserved = new Set(
|
|
541
|
+
const reserved = new Set(localReservedNames);
|
|
480
542
|
// Also reserve any existing non-managed directories.
|
|
481
543
|
const managedDest = new Set(managedEntries.map((e) => e.destRelPath));
|
|
482
544
|
let dirents = [];
|
|
@@ -508,7 +570,7 @@ async function syncClaudePluginsToSkillsDirs(args) {
|
|
|
508
570
|
}
|
|
509
571
|
nextEntries.push(entry);
|
|
510
572
|
}
|
|
511
|
-
await
|
|
573
|
+
await (0, SkillsManifest_1.writeSkillsManifestEntries)(targetSkillsDir, [...otherEntries, ...nextEntries], dryRun);
|
|
512
574
|
}
|
|
513
575
|
return;
|
|
514
576
|
}
|
|
@@ -553,6 +615,20 @@ async function syncClaudePluginsToSkillsDirs(args) {
|
|
|
553
615
|
baseName,
|
|
554
616
|
});
|
|
555
617
|
}
|
|
618
|
+
const agentFiles = await discoverPluginAgentFiles(plugin.installPath);
|
|
619
|
+
for (const a of agentFiles) {
|
|
620
|
+
const baseName = sanitizeId(a.name);
|
|
621
|
+
const sourceRelPath = `agents/${a.rel}`;
|
|
622
|
+
expectedItems.push({
|
|
623
|
+
itemKey: makeItemKey(plugin.pluginId, 'agent', sourceRelPath),
|
|
624
|
+
pluginId: plugin.pluginId,
|
|
625
|
+
pluginVersion: plugin.version,
|
|
626
|
+
kind: 'agent',
|
|
627
|
+
sourcePath: a.file,
|
|
628
|
+
sourceRelPath,
|
|
629
|
+
baseName,
|
|
630
|
+
});
|
|
631
|
+
}
|
|
556
632
|
}
|
|
557
633
|
const sortedItems = [...expectedItems].sort((a, b) => {
|
|
558
634
|
const ak = `${a.baseName}::${a.pluginId}::${a.kind}::${a.sourceRelPath}`;
|
|
@@ -564,9 +640,13 @@ async function syncClaudePluginsToSkillsDirs(args) {
|
|
|
564
640
|
const targetExists = await fileExists(targetSkillsDir);
|
|
565
641
|
if (!targetExists && sortedItems.length === 0)
|
|
566
642
|
continue;
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
643
|
+
let managedEntries = [];
|
|
644
|
+
let otherEntries = [];
|
|
645
|
+
if (targetExists) {
|
|
646
|
+
const loaded = await loadManagedEntries(targetSkillsDir, dryRun);
|
|
647
|
+
managedEntries = loaded.pluginEntries;
|
|
648
|
+
otherEntries = loaded.otherEntries;
|
|
649
|
+
}
|
|
570
650
|
// Map previous destinations by itemKey for stability.
|
|
571
651
|
const prevDestByItemKey = new Map();
|
|
572
652
|
for (const entry of managedEntries) {
|
|
@@ -574,7 +654,7 @@ async function syncClaudePluginsToSkillsDirs(args) {
|
|
|
574
654
|
}
|
|
575
655
|
const managedDest = new Set(managedEntries.map((e) => e.destRelPath));
|
|
576
656
|
// Reserve: local skills always win. Also reserve any existing non-managed folders.
|
|
577
|
-
const reserved = new Set(
|
|
657
|
+
const reserved = new Set(localReservedNames);
|
|
578
658
|
if (targetExists) {
|
|
579
659
|
let dirents = [];
|
|
580
660
|
try {
|
|
@@ -599,6 +679,11 @@ async function syncClaudePluginsToSkillsDirs(args) {
|
|
|
599
679
|
const prev = prevDestByItemKey.get(item.itemKey);
|
|
600
680
|
if (!prev)
|
|
601
681
|
continue;
|
|
682
|
+
// Migration: previous versions used `${pluginId}__${name}`.
|
|
683
|
+
// Don't preserve legacy namespaced destinations so we can rename to the
|
|
684
|
+
// new `${pluginId}-${name}` format.
|
|
685
|
+
if (prev.startsWith(`${sanitizeId(item.pluginId)}__`))
|
|
686
|
+
continue;
|
|
602
687
|
if (taken.has(prev))
|
|
603
688
|
continue;
|
|
604
689
|
assignedDestByItemKey.set(item.itemKey, prev);
|
|
@@ -618,7 +703,7 @@ async function syncClaudePluginsToSkillsDirs(args) {
|
|
|
618
703
|
let candidate = namespacedBase;
|
|
619
704
|
let i = 2;
|
|
620
705
|
while (taken.has(candidate)) {
|
|
621
|
-
candidate = `${namespacedBase}
|
|
706
|
+
candidate = `${namespacedBase}-${i++}`;
|
|
622
707
|
}
|
|
623
708
|
assignedDestByItemKey.set(item.itemKey, candidate);
|
|
624
709
|
taken.add(candidate);
|
|
@@ -658,12 +743,13 @@ async function syncClaudePluginsToSkillsDirs(args) {
|
|
|
658
743
|
await removeLegacyMarkerFile(destDir, dryRun);
|
|
659
744
|
}
|
|
660
745
|
else {
|
|
746
|
+
const kindLabel = item.kind === 'command' ? 'command' : 'agent';
|
|
661
747
|
(0, constants_1.logVerboseInfo)(dryRun
|
|
662
|
-
? `DRY RUN: Would install plugin
|
|
663
|
-
: `Installing plugin
|
|
748
|
+
? `DRY RUN: Would install plugin ${kindLabel} '${destRelPath}' as skill to ${targetSkillsDir}`
|
|
749
|
+
: `Installing plugin ${kindLabel} '${destRelPath}' as skill to ${targetSkillsDir}`, verbose, dryRun);
|
|
664
750
|
await ensureDir(destDir, dryRun);
|
|
665
751
|
if (!dryRun) {
|
|
666
|
-
await
|
|
752
|
+
await writeMarkdownAsSkill(item.sourcePath, destDir, destRelPath, item.pluginId, item.kind === 'command' ? 'Command' : 'Agent', dryRun);
|
|
667
753
|
}
|
|
668
754
|
await removeLegacyMarkerFile(destDir, dryRun);
|
|
669
755
|
}
|
|
@@ -706,6 +792,7 @@ async function syncClaudePluginsToSkillsDirs(args) {
|
|
|
706
792
|
// Add expected entries for installed items
|
|
707
793
|
for (const item of assignedItems) {
|
|
708
794
|
nextEntries.push({
|
|
795
|
+
sourceType: 'plugin',
|
|
709
796
|
pluginId: item.pluginId,
|
|
710
797
|
pluginVersion: item.pluginVersion,
|
|
711
798
|
sourceKind: item.kind,
|
|
@@ -713,6 +800,6 @@ async function syncClaudePluginsToSkillsDirs(args) {
|
|
|
713
800
|
destRelPath: item.destRelPath,
|
|
714
801
|
});
|
|
715
802
|
}
|
|
716
|
-
await
|
|
803
|
+
await (0, SkillsManifest_1.writeSkillsManifestEntries)(targetSkillsDir, [...otherEntries, ...nextEntries], dryRun);
|
|
717
804
|
}
|
|
718
805
|
}
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.syncClaudeProjectCommandsAndAgentsToSkillsDirs = syncClaudeProjectCommandsAndAgentsToSkillsDirs;
|
|
37
|
+
const fs = __importStar(require("fs/promises"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const yaml = __importStar(require("js-yaml"));
|
|
40
|
+
const constants_1 = require("../constants");
|
|
41
|
+
const FrontmatterParser_1 = require("./FrontmatterParser");
|
|
42
|
+
const SkillsManifest_1 = require("./SkillsManifest");
|
|
43
|
+
const LEGACY_PLUGIN_MARKER_FILENAME = '.skiller-plugin.json';
|
|
44
|
+
function sanitizeId(value) {
|
|
45
|
+
return value.replace(/[^A-Za-z0-9._-]+/g, '_');
|
|
46
|
+
}
|
|
47
|
+
async function fileExists(p) {
|
|
48
|
+
try {
|
|
49
|
+
await fs.access(p);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function makeItemKey(sourceKind, sourceRelPath) {
|
|
57
|
+
return `${sourceKind}::${sourceRelPath}`;
|
|
58
|
+
}
|
|
59
|
+
function getCommandsRoot(projectRoot) {
|
|
60
|
+
return path.join(projectRoot, '.claude', 'commands');
|
|
61
|
+
}
|
|
62
|
+
function getAgentsRoot(projectRoot) {
|
|
63
|
+
return path.join(projectRoot, '.claude', 'agents');
|
|
64
|
+
}
|
|
65
|
+
async function discoverCommandFiles(projectRoot) {
|
|
66
|
+
const commandsRoot = getCommandsRoot(projectRoot);
|
|
67
|
+
if (!(await fileExists(commandsRoot)))
|
|
68
|
+
return [];
|
|
69
|
+
const results = [];
|
|
70
|
+
async function walk(current, depth) {
|
|
71
|
+
if (depth >= constants_1.MAX_RECURSION_DEPTH)
|
|
72
|
+
return;
|
|
73
|
+
let entries;
|
|
74
|
+
try {
|
|
75
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
const full = path.join(current, entry.name);
|
|
82
|
+
if (entry.isDirectory()) {
|
|
83
|
+
await walk(full, depth + 1);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
87
|
+
continue;
|
|
88
|
+
const name = sanitizeId(path.basename(entry.name, '.md'));
|
|
89
|
+
const rel = path.relative(projectRoot, full).replace(/\\/g, '/');
|
|
90
|
+
results.push({ name, file: full, rel });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
await walk(commandsRoot, 0);
|
|
94
|
+
return results;
|
|
95
|
+
}
|
|
96
|
+
async function discoverAgentFiles(projectRoot) {
|
|
97
|
+
const agentsRoot = getAgentsRoot(projectRoot);
|
|
98
|
+
if (!(await fileExists(agentsRoot)))
|
|
99
|
+
return [];
|
|
100
|
+
const results = [];
|
|
101
|
+
async function walk(current, depth) {
|
|
102
|
+
if (depth >= constants_1.MAX_RECURSION_DEPTH)
|
|
103
|
+
return;
|
|
104
|
+
let entries;
|
|
105
|
+
try {
|
|
106
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
for (const entry of entries) {
|
|
112
|
+
const full = path.join(current, entry.name);
|
|
113
|
+
if (entry.isDirectory()) {
|
|
114
|
+
await walk(full, depth + 1);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
118
|
+
continue;
|
|
119
|
+
let content;
|
|
120
|
+
try {
|
|
121
|
+
content = await fs.readFile(full, 'utf8');
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const parsed = (0, FrontmatterParser_1.parseFrontmatter)(content);
|
|
127
|
+
const fmName = parsed.rawFrontmatter && typeof parsed.rawFrontmatter.name === 'string'
|
|
128
|
+
? parsed.rawFrontmatter.name
|
|
129
|
+
: parsed.frontmatter?.name;
|
|
130
|
+
if (typeof fmName !== 'string' || fmName.trim() === '')
|
|
131
|
+
continue;
|
|
132
|
+
const name = sanitizeId(fmName.trim());
|
|
133
|
+
const rel = path.relative(projectRoot, full).replace(/\\/g, '/');
|
|
134
|
+
results.push({ name, file: full, rel });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
await walk(agentsRoot, 0);
|
|
138
|
+
return results;
|
|
139
|
+
}
|
|
140
|
+
async function ensureDir(dir, dryRun) {
|
|
141
|
+
if (dryRun)
|
|
142
|
+
return;
|
|
143
|
+
await fs.mkdir(dir, { recursive: true });
|
|
144
|
+
}
|
|
145
|
+
async function removeDir(dir, dryRun) {
|
|
146
|
+
if (dryRun)
|
|
147
|
+
return;
|
|
148
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
149
|
+
}
|
|
150
|
+
async function writeMarkdownAsSkill(srcPath, destDir, generatedName, kindLabel, dryRun) {
|
|
151
|
+
const content = await fs.readFile(srcPath, 'utf8');
|
|
152
|
+
const { rawFrontmatter, body } = (0, FrontmatterParser_1.parseFrontmatter)(content);
|
|
153
|
+
const fm = rawFrontmatter
|
|
154
|
+
? { ...rawFrontmatter }
|
|
155
|
+
: {};
|
|
156
|
+
fm.name = generatedName;
|
|
157
|
+
if (typeof fm.description !== 'string' || fm.description.trim() === '') {
|
|
158
|
+
fm.description = `${kindLabel}: ${path.basename(srcPath, '.md')}`;
|
|
159
|
+
}
|
|
160
|
+
const next = `---\n${yaml
|
|
161
|
+
.dump(fm, { lineWidth: -1, noRefs: true })
|
|
162
|
+
.trim()}\n---\n\n${body}\n`;
|
|
163
|
+
if (dryRun)
|
|
164
|
+
return;
|
|
165
|
+
await fs.writeFile(path.join(destDir, 'SKILL.md'), next, 'utf8');
|
|
166
|
+
}
|
|
167
|
+
async function readPluginManagedDestNames(targetSkillsDir) {
|
|
168
|
+
const names = new Set();
|
|
169
|
+
for (const entry of await (0, SkillsManifest_1.loadSkillsManifestEntries)(targetSkillsDir)) {
|
|
170
|
+
if ((0, SkillsManifest_1.isPluginManifestEntry)(entry)) {
|
|
171
|
+
names.add(entry.destRelPath);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Legacy: prior versions wrote per-skill plugin marker files. Treat any
|
|
175
|
+
// folder containing one as plugin-managed so project items can take over.
|
|
176
|
+
try {
|
|
177
|
+
const dirents = await fs.readdir(targetSkillsDir, { withFileTypes: true });
|
|
178
|
+
for (const d of dirents) {
|
|
179
|
+
if (!d.isDirectory())
|
|
180
|
+
continue;
|
|
181
|
+
try {
|
|
182
|
+
await fs.access(path.join(targetSkillsDir, d.name, LEGACY_PLUGIN_MARKER_FILENAME));
|
|
183
|
+
names.add(d.name);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// ignore
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// ignore
|
|
192
|
+
}
|
|
193
|
+
return names;
|
|
194
|
+
}
|
|
195
|
+
async function discoverLocalSkillNames(projectRoot) {
|
|
196
|
+
const localSkillsDir = path.join(projectRoot, '.claude', 'skills');
|
|
197
|
+
if (!(await fileExists(localSkillsDir)))
|
|
198
|
+
return new Set();
|
|
199
|
+
const names = new Set();
|
|
200
|
+
async function walk(current, depth) {
|
|
201
|
+
if (depth >= constants_1.MAX_RECURSION_DEPTH)
|
|
202
|
+
return;
|
|
203
|
+
let entries;
|
|
204
|
+
try {
|
|
205
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const hasSkillMd = entries.some((e) => e.isFile() && e.name === 'SKILL.md');
|
|
211
|
+
if (hasSkillMd) {
|
|
212
|
+
names.add(path.basename(current));
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
for (const entry of entries) {
|
|
216
|
+
if (!entry.isDirectory())
|
|
217
|
+
continue;
|
|
218
|
+
await walk(path.join(current, entry.name), depth + 1);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
await walk(localSkillsDir, 0);
|
|
222
|
+
return names;
|
|
223
|
+
}
|
|
224
|
+
async function syncClaudeProjectCommandsAndAgentsToSkillsDirs(args) {
|
|
225
|
+
const { projectRoot, targetSkillsDirs, verbose, dryRun } = args;
|
|
226
|
+
const localSkillNames = await discoverLocalSkillNames(projectRoot);
|
|
227
|
+
const commands = await discoverCommandFiles(projectRoot);
|
|
228
|
+
const agents = await discoverAgentFiles(projectRoot);
|
|
229
|
+
const expectedItems = [];
|
|
230
|
+
for (const cmd of commands) {
|
|
231
|
+
expectedItems.push({
|
|
232
|
+
itemKey: makeItemKey('command', cmd.rel),
|
|
233
|
+
sourceKind: 'command',
|
|
234
|
+
sourcePath: cmd.file,
|
|
235
|
+
sourceRelPath: cmd.rel,
|
|
236
|
+
baseName: cmd.name,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
for (const agent of agents) {
|
|
240
|
+
expectedItems.push({
|
|
241
|
+
itemKey: makeItemKey('agent', agent.rel),
|
|
242
|
+
sourceKind: 'agent',
|
|
243
|
+
sourcePath: agent.file,
|
|
244
|
+
sourceRelPath: agent.rel,
|
|
245
|
+
baseName: agent.name,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
const sortedItems = [...expectedItems].sort((a, b) => {
|
|
249
|
+
const ak = `${a.baseName}::${a.sourceKind}::${a.sourceRelPath}`;
|
|
250
|
+
const bk = `${b.baseName}::${b.sourceKind}::${b.sourceRelPath}`;
|
|
251
|
+
return ak.localeCompare(bk);
|
|
252
|
+
});
|
|
253
|
+
for (const targetSkillsDir of targetSkillsDirs) {
|
|
254
|
+
const targetExists = await fileExists(targetSkillsDir);
|
|
255
|
+
const managedEntries = [];
|
|
256
|
+
const otherEntries = [];
|
|
257
|
+
if (targetExists) {
|
|
258
|
+
const allEntries = await (0, SkillsManifest_1.loadSkillsManifestEntries)(targetSkillsDir);
|
|
259
|
+
for (const entry of allEntries) {
|
|
260
|
+
if ((0, SkillsManifest_1.isClaudeManifestEntry)(entry)) {
|
|
261
|
+
managedEntries.push(entry);
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
otherEntries.push(entry);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const prevDestByItemKey = new Map();
|
|
269
|
+
for (const entry of managedEntries) {
|
|
270
|
+
prevDestByItemKey.set(makeItemKey(entry.sourceKind, entry.sourceRelPath), entry.destRelPath);
|
|
271
|
+
}
|
|
272
|
+
const managedDest = new Set(managedEntries.map((e) => e.destRelPath));
|
|
273
|
+
const pluginManagedDest = targetExists
|
|
274
|
+
? await readPluginManagedDestNames(targetSkillsDir)
|
|
275
|
+
: new Set();
|
|
276
|
+
const reserved = new Set(localSkillNames);
|
|
277
|
+
if (targetExists) {
|
|
278
|
+
let dirents = [];
|
|
279
|
+
try {
|
|
280
|
+
dirents = await fs.readdir(targetSkillsDir, { withFileTypes: true });
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
dirents = [];
|
|
284
|
+
}
|
|
285
|
+
for (const d of dirents) {
|
|
286
|
+
if (!d.isDirectory())
|
|
287
|
+
continue;
|
|
288
|
+
// Reserve any existing folder we do not manage, except plugin-managed
|
|
289
|
+
// folders (project should be able to take those over).
|
|
290
|
+
if (!managedDest.has(d.name) && !pluginManagedDest.has(d.name)) {
|
|
291
|
+
reserved.add(d.name);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const taken = new Set(reserved);
|
|
296
|
+
const assignedDestByItemKey = new Map();
|
|
297
|
+
// Preserve previous destinations when they are still available.
|
|
298
|
+
for (const item of sortedItems) {
|
|
299
|
+
const prev = prevDestByItemKey.get(item.itemKey);
|
|
300
|
+
if (!prev)
|
|
301
|
+
continue;
|
|
302
|
+
// Migration: previous versions used `claude__<name>`.
|
|
303
|
+
// Don't preserve legacy namespaced destinations so we can rename to the
|
|
304
|
+
// new `claude-<name>` format.
|
|
305
|
+
if (prev.startsWith('claude__'))
|
|
306
|
+
continue;
|
|
307
|
+
if (taken.has(prev))
|
|
308
|
+
continue;
|
|
309
|
+
assignedDestByItemKey.set(item.itemKey, prev);
|
|
310
|
+
taken.add(prev);
|
|
311
|
+
}
|
|
312
|
+
// Assign baseName, otherwise namespace with "claude-".
|
|
313
|
+
for (const item of sortedItems) {
|
|
314
|
+
if (assignedDestByItemKey.has(item.itemKey))
|
|
315
|
+
continue;
|
|
316
|
+
const base = item.baseName;
|
|
317
|
+
if (!taken.has(base)) {
|
|
318
|
+
assignedDestByItemKey.set(item.itemKey, base);
|
|
319
|
+
taken.add(base);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
const namespacedBase = `claude-${base}`;
|
|
323
|
+
let candidate = namespacedBase;
|
|
324
|
+
let i = 2;
|
|
325
|
+
while (taken.has(candidate)) {
|
|
326
|
+
candidate = `${namespacedBase}-${i++}`;
|
|
327
|
+
}
|
|
328
|
+
assignedDestByItemKey.set(item.itemKey, candidate);
|
|
329
|
+
taken.add(candidate);
|
|
330
|
+
}
|
|
331
|
+
const assignedItems = sortedItems.map((item) => ({
|
|
332
|
+
...item,
|
|
333
|
+
destRelPath: assignedDestByItemKey.get(item.itemKey),
|
|
334
|
+
}));
|
|
335
|
+
if (assignedItems.length > 0) {
|
|
336
|
+
await ensureDir(targetSkillsDir, dryRun);
|
|
337
|
+
}
|
|
338
|
+
else if (!targetExists && managedEntries.length === 0) {
|
|
339
|
+
// Nothing to do.
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
// Install/update expected items
|
|
343
|
+
for (const item of assignedItems) {
|
|
344
|
+
const destRelPath = item.destRelPath;
|
|
345
|
+
const destDir = path.join(targetSkillsDir, destRelPath);
|
|
346
|
+
if (await fileExists(destDir)) {
|
|
347
|
+
const isManagedByProject = managedDest.has(destRelPath);
|
|
348
|
+
const isManagedByPlugin = pluginManagedDest.has(destRelPath);
|
|
349
|
+
if (!isManagedByProject && !isManagedByPlugin) {
|
|
350
|
+
(0, constants_1.logWarn)(`[claude] Destination exists but is not skiller-managed, skipping: ${destDir}`, dryRun);
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
(0, constants_1.logVerboseInfo)(dryRun
|
|
354
|
+
? `DRY RUN: Would update claude ${item.sourceKind} '${destRelPath}' in ${targetSkillsDir}`
|
|
355
|
+
: `Updating claude ${item.sourceKind} '${destRelPath}' in ${targetSkillsDir}`, verbose, dryRun);
|
|
356
|
+
await removeDir(destDir, dryRun);
|
|
357
|
+
}
|
|
358
|
+
(0, constants_1.logVerboseInfo)(dryRun
|
|
359
|
+
? `DRY RUN: Would install claude ${item.sourceKind} '${destRelPath}' to ${targetSkillsDir}`
|
|
360
|
+
: `Installing claude ${item.sourceKind} '${destRelPath}' to ${targetSkillsDir}`, verbose, dryRun);
|
|
361
|
+
await ensureDir(destDir, dryRun);
|
|
362
|
+
if (!dryRun) {
|
|
363
|
+
await writeMarkdownAsSkill(item.sourcePath, destDir, destRelPath, item.sourceKind === 'command' ? 'Command' : 'Agent', dryRun);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
// Cleanup stale managed folders
|
|
367
|
+
const expectedDest = new Set(assignedItems.map((i) => i.destRelPath));
|
|
368
|
+
const nextEntries = [];
|
|
369
|
+
for (const entry of managedEntries) {
|
|
370
|
+
if (expectedDest.has(entry.destRelPath)) {
|
|
371
|
+
// Still expected; re-add below.
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
if (reserved.has(entry.destRelPath)) {
|
|
375
|
+
// User/local took over the folder name; stop managing it but don't delete.
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
(0, constants_1.logVerboseInfo)(dryRun
|
|
379
|
+
? `DRY RUN: Would remove stale claude skill '${entry.destRelPath}' from ${targetSkillsDir}`
|
|
380
|
+
: `Removing stale claude skill '${entry.destRelPath}' from ${targetSkillsDir}`, verbose, dryRun);
|
|
381
|
+
await removeDir(path.join(targetSkillsDir, entry.destRelPath), dryRun);
|
|
382
|
+
}
|
|
383
|
+
for (const item of assignedItems) {
|
|
384
|
+
nextEntries.push({
|
|
385
|
+
sourceType: 'claude',
|
|
386
|
+
sourceKind: item.sourceKind,
|
|
387
|
+
sourceRelPath: item.sourceRelPath,
|
|
388
|
+
destRelPath: item.destRelPath,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
await (0, SkillsManifest_1.writeSkillsManifestEntries)(targetSkillsDir, [...otherEntries, ...nextEntries], dryRun);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.SKILLS_MANIFEST_VERSION = exports.LEGACY_CLAUDE_MANIFEST_FILENAME = exports.LEGACY_PLUGIN_MANIFEST_FILENAME = exports.LEGACY_UNIFIED_SKILLS_MANIFEST_FILENAME = exports.SKILLS_MANIFEST_FILENAME = void 0;
|
|
37
|
+
exports.isPluginManifestEntry = isPluginManifestEntry;
|
|
38
|
+
exports.isClaudeManifestEntry = isClaudeManifestEntry;
|
|
39
|
+
exports.loadSkillsManifestEntries = loadSkillsManifestEntries;
|
|
40
|
+
exports.writeSkillsManifestEntries = writeSkillsManifestEntries;
|
|
41
|
+
exports.listSkillDirectories = listSkillDirectories;
|
|
42
|
+
const fs = __importStar(require("fs/promises"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
exports.SKILLS_MANIFEST_FILENAME = '.skiller.json';
|
|
45
|
+
exports.LEGACY_UNIFIED_SKILLS_MANIFEST_FILENAME = '.skiller-skills.json';
|
|
46
|
+
exports.LEGACY_PLUGIN_MANIFEST_FILENAME = '.skiller-plugins.json';
|
|
47
|
+
exports.LEGACY_CLAUDE_MANIFEST_FILENAME = '.skiller-claude.json';
|
|
48
|
+
exports.SKILLS_MANIFEST_VERSION = 1;
|
|
49
|
+
async function fileExists(p) {
|
|
50
|
+
try {
|
|
51
|
+
await fs.access(p);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function isPluginManifestEntry(entry) {
|
|
59
|
+
return entry.sourceType === 'plugin';
|
|
60
|
+
}
|
|
61
|
+
function isClaudeManifestEntry(entry) {
|
|
62
|
+
return entry.sourceType === 'claude';
|
|
63
|
+
}
|
|
64
|
+
function normalizeEntries(entries) {
|
|
65
|
+
// Dedupe by a stable identity that doesn't depend on JSON key order.
|
|
66
|
+
const seen = new Set();
|
|
67
|
+
const out = [];
|
|
68
|
+
for (const e of entries) {
|
|
69
|
+
const key = e.sourceType === 'plugin'
|
|
70
|
+
? `plugin::${e.destRelPath}::${e.pluginId}::${e.sourceKind}::${e.sourceRelPath}`
|
|
71
|
+
: `claude::${e.destRelPath}::${e.sourceKind}::${e.sourceRelPath}`;
|
|
72
|
+
if (seen.has(key))
|
|
73
|
+
continue;
|
|
74
|
+
seen.add(key);
|
|
75
|
+
out.push(e);
|
|
76
|
+
}
|
|
77
|
+
out.sort((a, b) => {
|
|
78
|
+
const ak = a.sourceType === 'plugin'
|
|
79
|
+
? `plugin::${a.destRelPath}::${a.pluginId}::${a.sourceKind}::${a.sourceRelPath}`
|
|
80
|
+
: `claude::${a.destRelPath}::${a.sourceKind}::${a.sourceRelPath}`;
|
|
81
|
+
const bk = b.sourceType === 'plugin'
|
|
82
|
+
? `plugin::${b.destRelPath}::${b.pluginId}::${b.sourceKind}::${b.sourceRelPath}`
|
|
83
|
+
: `claude::${b.destRelPath}::${b.sourceKind}::${b.sourceRelPath}`;
|
|
84
|
+
return ak.localeCompare(bk);
|
|
85
|
+
});
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
function parseUnifiedEntries(raw) {
|
|
89
|
+
if (!raw || typeof raw !== 'object')
|
|
90
|
+
return [];
|
|
91
|
+
const obj = raw;
|
|
92
|
+
if (!Array.isArray(obj.entries))
|
|
93
|
+
return [];
|
|
94
|
+
const entries = [];
|
|
95
|
+
for (const entry of obj.entries) {
|
|
96
|
+
if (!entry || typeof entry !== 'object')
|
|
97
|
+
continue;
|
|
98
|
+
const e = entry;
|
|
99
|
+
const sourceType = e.sourceType;
|
|
100
|
+
const sourceKind = e.sourceKind;
|
|
101
|
+
if (sourceType === 'plugin') {
|
|
102
|
+
if (typeof e.pluginId !== 'string')
|
|
103
|
+
continue;
|
|
104
|
+
if (sourceKind !== 'skill' &&
|
|
105
|
+
sourceKind !== 'command' &&
|
|
106
|
+
sourceKind !== 'agent')
|
|
107
|
+
continue;
|
|
108
|
+
if (typeof e.sourceRelPath !== 'string')
|
|
109
|
+
continue;
|
|
110
|
+
if (typeof e.destRelPath !== 'string')
|
|
111
|
+
continue;
|
|
112
|
+
entries.push({
|
|
113
|
+
sourceType: 'plugin',
|
|
114
|
+
pluginId: e.pluginId,
|
|
115
|
+
pluginVersion: typeof e.pluginVersion === 'string' ? e.pluginVersion : undefined,
|
|
116
|
+
sourceKind,
|
|
117
|
+
sourceRelPath: e.sourceRelPath,
|
|
118
|
+
destRelPath: e.destRelPath,
|
|
119
|
+
});
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (sourceType === 'claude') {
|
|
123
|
+
if (sourceKind !== 'command' && sourceKind !== 'agent')
|
|
124
|
+
continue;
|
|
125
|
+
if (typeof e.sourceRelPath !== 'string')
|
|
126
|
+
continue;
|
|
127
|
+
if (typeof e.destRelPath !== 'string')
|
|
128
|
+
continue;
|
|
129
|
+
entries.push({
|
|
130
|
+
sourceType: 'claude',
|
|
131
|
+
sourceKind,
|
|
132
|
+
sourceRelPath: e.sourceRelPath,
|
|
133
|
+
destRelPath: e.destRelPath,
|
|
134
|
+
});
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return entries;
|
|
139
|
+
}
|
|
140
|
+
function parseLegacyPluginEntries(raw) {
|
|
141
|
+
if (!raw || typeof raw !== 'object')
|
|
142
|
+
return [];
|
|
143
|
+
const obj = raw;
|
|
144
|
+
if (!Array.isArray(obj.entries))
|
|
145
|
+
return [];
|
|
146
|
+
const entries = [];
|
|
147
|
+
for (const entry of obj.entries) {
|
|
148
|
+
if (!entry || typeof entry !== 'object')
|
|
149
|
+
continue;
|
|
150
|
+
const e = entry;
|
|
151
|
+
if (typeof e.pluginId !== 'string')
|
|
152
|
+
continue;
|
|
153
|
+
const sourceKind = e.sourceKind;
|
|
154
|
+
if (sourceKind !== 'skill' &&
|
|
155
|
+
sourceKind !== 'command' &&
|
|
156
|
+
sourceKind !== 'agent')
|
|
157
|
+
continue;
|
|
158
|
+
if (typeof e.sourceRelPath !== 'string')
|
|
159
|
+
continue;
|
|
160
|
+
if (typeof e.destRelPath !== 'string')
|
|
161
|
+
continue;
|
|
162
|
+
entries.push({
|
|
163
|
+
sourceType: 'plugin',
|
|
164
|
+
pluginId: e.pluginId,
|
|
165
|
+
pluginVersion: typeof e.pluginVersion === 'string' ? e.pluginVersion : undefined,
|
|
166
|
+
sourceKind,
|
|
167
|
+
sourceRelPath: e.sourceRelPath,
|
|
168
|
+
destRelPath: e.destRelPath,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return entries;
|
|
172
|
+
}
|
|
173
|
+
function parseLegacyClaudeEntries(raw) {
|
|
174
|
+
if (!raw || typeof raw !== 'object')
|
|
175
|
+
return [];
|
|
176
|
+
const obj = raw;
|
|
177
|
+
if (!Array.isArray(obj.entries))
|
|
178
|
+
return [];
|
|
179
|
+
const entries = [];
|
|
180
|
+
for (const entry of obj.entries) {
|
|
181
|
+
if (!entry || typeof entry !== 'object')
|
|
182
|
+
continue;
|
|
183
|
+
const e = entry;
|
|
184
|
+
const sourceKind = e.sourceKind;
|
|
185
|
+
if (sourceKind !== 'command' && sourceKind !== 'agent')
|
|
186
|
+
continue;
|
|
187
|
+
if (typeof e.sourceRelPath !== 'string')
|
|
188
|
+
continue;
|
|
189
|
+
if (typeof e.destRelPath !== 'string')
|
|
190
|
+
continue;
|
|
191
|
+
entries.push({
|
|
192
|
+
sourceType: 'claude',
|
|
193
|
+
sourceKind,
|
|
194
|
+
sourceRelPath: e.sourceRelPath,
|
|
195
|
+
destRelPath: e.destRelPath,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
return entries;
|
|
199
|
+
}
|
|
200
|
+
async function loadSkillsManifestEntries(targetSkillsDir) {
|
|
201
|
+
const manifestPath = path.join(targetSkillsDir, exports.SKILLS_MANIFEST_FILENAME);
|
|
202
|
+
if (await fileExists(manifestPath)) {
|
|
203
|
+
try {
|
|
204
|
+
const raw = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
|
|
205
|
+
return normalizeEntries(parseUnifiedEntries(raw));
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Migration: previous versions used a different unified filename.
|
|
212
|
+
const legacyUnifiedPath = path.join(targetSkillsDir, exports.LEGACY_UNIFIED_SKILLS_MANIFEST_FILENAME);
|
|
213
|
+
if (await fileExists(legacyUnifiedPath)) {
|
|
214
|
+
try {
|
|
215
|
+
const raw = JSON.parse(await fs.readFile(legacyUnifiedPath, 'utf8'));
|
|
216
|
+
return normalizeEntries(parseUnifiedEntries(raw));
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// No unified manifest yet: merge legacy manifests if present.
|
|
223
|
+
const merged = [];
|
|
224
|
+
const legacyPluginPath = path.join(targetSkillsDir, exports.LEGACY_PLUGIN_MANIFEST_FILENAME);
|
|
225
|
+
if (await fileExists(legacyPluginPath)) {
|
|
226
|
+
try {
|
|
227
|
+
const raw = JSON.parse(await fs.readFile(legacyPluginPath, 'utf8'));
|
|
228
|
+
merged.push(...parseLegacyPluginEntries(raw));
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// ignore
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const legacyClaudePath = path.join(targetSkillsDir, exports.LEGACY_CLAUDE_MANIFEST_FILENAME);
|
|
235
|
+
if (await fileExists(legacyClaudePath)) {
|
|
236
|
+
try {
|
|
237
|
+
const raw = JSON.parse(await fs.readFile(legacyClaudePath, 'utf8'));
|
|
238
|
+
merged.push(...parseLegacyClaudeEntries(raw));
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// ignore
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return normalizeEntries(merged);
|
|
245
|
+
}
|
|
246
|
+
async function writeSkillsManifestEntries(targetSkillsDir, entries, dryRun) {
|
|
247
|
+
const manifestPath = path.join(targetSkillsDir, exports.SKILLS_MANIFEST_FILENAME);
|
|
248
|
+
const legacyUnifiedPath = path.join(targetSkillsDir, exports.LEGACY_UNIFIED_SKILLS_MANIFEST_FILENAME);
|
|
249
|
+
const legacyPluginPath = path.join(targetSkillsDir, exports.LEGACY_PLUGIN_MANIFEST_FILENAME);
|
|
250
|
+
const legacyClaudePath = path.join(targetSkillsDir, exports.LEGACY_CLAUDE_MANIFEST_FILENAME);
|
|
251
|
+
const normalized = normalizeEntries(entries);
|
|
252
|
+
if (normalized.length === 0) {
|
|
253
|
+
if (dryRun)
|
|
254
|
+
return;
|
|
255
|
+
await Promise.allSettled([
|
|
256
|
+
fs.unlink(manifestPath),
|
|
257
|
+
fs.unlink(legacyUnifiedPath),
|
|
258
|
+
fs.unlink(legacyPluginPath),
|
|
259
|
+
fs.unlink(legacyClaudePath),
|
|
260
|
+
]);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const manifest = {
|
|
264
|
+
version: exports.SKILLS_MANIFEST_VERSION,
|
|
265
|
+
entries: normalized,
|
|
266
|
+
};
|
|
267
|
+
if (dryRun)
|
|
268
|
+
return;
|
|
269
|
+
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
270
|
+
// Clean up legacy manifests once unified is written.
|
|
271
|
+
await Promise.allSettled([
|
|
272
|
+
fs.unlink(legacyUnifiedPath),
|
|
273
|
+
fs.unlink(legacyPluginPath),
|
|
274
|
+
fs.unlink(legacyClaudePath),
|
|
275
|
+
]);
|
|
276
|
+
}
|
|
277
|
+
async function listSkillDirectories(targetSkillsDir) {
|
|
278
|
+
try {
|
|
279
|
+
const dirents = await fs.readdir(targetSkillsDir, { withFileTypes: true });
|
|
280
|
+
return dirents.filter((d) => d.isDirectory());
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
return [];
|
|
284
|
+
}
|
|
285
|
+
}
|
|
@@ -589,6 +589,17 @@ async function propagateSkills(projectRoot, agents, skillsEnabled, verbose, dryR
|
|
|
589
589
|
}
|
|
590
590
|
}
|
|
591
591
|
}
|
|
592
|
+
// Sync project Claude commands + agents as skills into agent skills dirs.
|
|
593
|
+
// This intentionally does NOT write into the committed .claude/skills source-of-truth.
|
|
594
|
+
if (destinationPaths.size > 0) {
|
|
595
|
+
const { syncClaudeProjectCommandsAndAgentsToSkillsDirs } = await Promise.resolve().then(() => __importStar(require('./ClaudeProjectSync')));
|
|
596
|
+
await syncClaudeProjectCommandsAndAgentsToSkillsDirs({
|
|
597
|
+
projectRoot,
|
|
598
|
+
targetSkillsDirs: [...destinationPaths],
|
|
599
|
+
verbose,
|
|
600
|
+
dryRun,
|
|
601
|
+
});
|
|
602
|
+
}
|
|
592
603
|
// Sync Claude plugins (skills + commands converted to skills) into agent skills dirs.
|
|
593
604
|
// This intentionally does NOT write into the committed .claude/skills source-of-truth.
|
|
594
605
|
if (destinationPaths.size > 0) {
|
package/package.json
CHANGED
|
@@ -1,79 +1,79 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
2
|
+
"name": "skiller",
|
|
3
|
+
"version": "0.7.17",
|
|
4
|
+
"description": "Skiller — apply the same rules to all coding agents",
|
|
5
|
+
"main": "dist/lib.js",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"postinstall": "[ -n \"$CI\" ] || npx skiller@latest apply",
|
|
11
|
+
"lint": "eslint \"src/**/*.{ts,tsx}\"",
|
|
12
|
+
"lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix",
|
|
13
|
+
"format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"",
|
|
14
|
+
"test": "jest",
|
|
15
|
+
"test:watch": "jest --watch",
|
|
16
|
+
"test:coverage": "jest --coverage",
|
|
17
|
+
"test:integration": "jest tests/e2e/skiller.integration.test.ts --verbose",
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"release": "pnpm build && pnpm changeset publish"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/udecode/skiller.git"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"ai",
|
|
27
|
+
"developer-tools",
|
|
28
|
+
"copilot",
|
|
29
|
+
"codex",
|
|
30
|
+
"claude",
|
|
31
|
+
"cursor",
|
|
32
|
+
"aider",
|
|
33
|
+
"config",
|
|
34
|
+
"rules",
|
|
35
|
+
"automation"
|
|
36
|
+
],
|
|
37
|
+
"author": "Eleanor Berger",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/udecode/skiller/issues"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18"
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"dist",
|
|
47
|
+
"README.md",
|
|
48
|
+
"LICENSE"
|
|
49
|
+
],
|
|
50
|
+
"bin": {
|
|
51
|
+
"skiller": "dist/cli/index.js"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@changesets/changelog-github": "^0.5.2",
|
|
55
|
+
"@changesets/cli": "2.29.8",
|
|
56
|
+
"@eslint/js": "^9.39.1",
|
|
57
|
+
"@types/iarna__toml": "^2.0.5",
|
|
58
|
+
"@types/jest": "^29.5.14",
|
|
59
|
+
"@types/js-yaml": "^4.0.9",
|
|
60
|
+
"@types/node": "^24.9.2",
|
|
61
|
+
"@types/yargs": "^17.0.34",
|
|
62
|
+
"@typescript-eslint/eslint-plugin": "^8.46.2",
|
|
63
|
+
"@typescript-eslint/parser": "^8.46.2",
|
|
64
|
+
"eslint": "^9.38.0",
|
|
65
|
+
"eslint-config-prettier": "^10.1.8",
|
|
66
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
67
|
+
"jest": "^29.7.0",
|
|
68
|
+
"prettier": "^3.6.2",
|
|
69
|
+
"ts-jest": "^29.4.5",
|
|
70
|
+
"typescript": "^5.9.3",
|
|
71
|
+
"typescript-eslint": "^8.46.2"
|
|
72
|
+
},
|
|
73
|
+
"dependencies": {
|
|
74
|
+
"@iarna/toml": "^2.2.5",
|
|
75
|
+
"js-yaml": "^4.1.0",
|
|
76
|
+
"yargs": "^18.0.0",
|
|
77
|
+
"zod": "^4.1.12"
|
|
78
|
+
}
|
|
79
79
|
}
|