skiller 0.7.14 → 0.7.16
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 +13 -3
- package/dist/core/ClaudePluginSync.js +176 -14
- package/dist/core/ClaudeProjectSync.js +460 -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
|
-
-
|
|
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>`
|
|
64
65
|
- Tracks plugin-managed skills in a single `.skiller-plugins.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-claude.json` file per agent skills directory
|
|
76
|
+
|
|
67
77
|
---
|
|
68
78
|
|
|
69
79
|
# Skiller: Centralise Your AI Coding Assistant Instructions
|
|
@@ -596,7 +606,7 @@ 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
|
|
609
|
+
- If a name conflicts, local skills win and the plugin skill is namespaced as `<pluginId>-<name>`
|
|
600
610
|
- Plugin-managed skills are tracked via `.skiller-plugins.json` in each agent skills directory
|
|
601
611
|
|
|
602
612
|
### Skills Directory Structure
|
|
@@ -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"));
|
|
@@ -141,8 +142,24 @@ function resolvePluginInstall(pluginId, projectRoot, index) {
|
|
|
141
142
|
};
|
|
142
143
|
}
|
|
143
144
|
const userCandidates = entries.filter((e) => e && e.scope === 'user');
|
|
144
|
-
if (userCandidates.length === 0)
|
|
145
|
-
|
|
145
|
+
if (userCandidates.length === 0) {
|
|
146
|
+
// Fallback: if the plugin is installed for a different project but enabled
|
|
147
|
+
// here, still use the newest available install. This avoids noisy
|
|
148
|
+
// "not installed" warnings for plugins that don't ship skills/commands.
|
|
149
|
+
const anyCandidates = entries.filter((e) => e && typeof e.installPath === 'string' && e.installPath.length > 0);
|
|
150
|
+
if (anyCandidates.length === 0)
|
|
151
|
+
return null;
|
|
152
|
+
anyCandidates.sort((a, b) => {
|
|
153
|
+
const at = parseDate(a.lastUpdated) || parseDate(a.installedAt);
|
|
154
|
+
const bt = parseDate(b.lastUpdated) || parseDate(b.installedAt);
|
|
155
|
+
return bt - at;
|
|
156
|
+
});
|
|
157
|
+
return {
|
|
158
|
+
pluginId,
|
|
159
|
+
installPath: anyCandidates[0].installPath,
|
|
160
|
+
version: anyCandidates[0].version,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
146
163
|
userCandidates.sort((a, b) => {
|
|
147
164
|
const at = parseDate(a.lastUpdated) || parseDate(a.installedAt);
|
|
148
165
|
const bt = parseDate(b.lastUpdated) || parseDate(b.installedAt);
|
|
@@ -203,6 +220,49 @@ async function discoverPluginCommandFiles(installPath) {
|
|
|
203
220
|
file: path.join(commandsRoot, e.name),
|
|
204
221
|
}));
|
|
205
222
|
}
|
|
223
|
+
async function discoverPluginAgentFiles(installPath) {
|
|
224
|
+
const agentsRoot = path.join(installPath, 'agents');
|
|
225
|
+
if (!(await fileExists(agentsRoot)))
|
|
226
|
+
return [];
|
|
227
|
+
const results = [];
|
|
228
|
+
async function walk(current, depth) {
|
|
229
|
+
if (depth >= constants_1.MAX_RECURSION_DEPTH)
|
|
230
|
+
return;
|
|
231
|
+
let entries;
|
|
232
|
+
try {
|
|
233
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
for (const entry of entries) {
|
|
239
|
+
const full = path.join(current, entry.name);
|
|
240
|
+
if (entry.isDirectory()) {
|
|
241
|
+
await walk(full, depth + 1);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
245
|
+
continue;
|
|
246
|
+
let content;
|
|
247
|
+
try {
|
|
248
|
+
content = await fs.readFile(full, 'utf8');
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const parsed = (0, FrontmatterParser_1.parseFrontmatter)(content);
|
|
254
|
+
const fmName = parsed.rawFrontmatter && typeof parsed.rawFrontmatter.name === 'string'
|
|
255
|
+
? parsed.rawFrontmatter.name
|
|
256
|
+
: parsed.frontmatter?.name;
|
|
257
|
+
if (typeof fmName !== 'string' || fmName.trim() === '')
|
|
258
|
+
continue;
|
|
259
|
+
const rel = path.relative(agentsRoot, full).replace(/\\/g, '/');
|
|
260
|
+
results.push({ name: fmName.trim(), file: full, rel });
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
await walk(agentsRoot, 0);
|
|
264
|
+
return results;
|
|
265
|
+
}
|
|
206
266
|
function generateBaseNameFromRelId(relId) {
|
|
207
267
|
const normalized = relId.replace(/\\/g, '/');
|
|
208
268
|
const segments = normalized.split('/').filter(Boolean);
|
|
@@ -212,7 +272,7 @@ function generateBaseNameFromCommand(commandName) {
|
|
|
212
272
|
return sanitizeId(commandName);
|
|
213
273
|
}
|
|
214
274
|
function generateNamespacedName(pluginId, baseName) {
|
|
215
|
-
return `${sanitizeId(pluginId)}
|
|
275
|
+
return `${sanitizeId(pluginId)}-${baseName}`;
|
|
216
276
|
}
|
|
217
277
|
async function readManifestFile(targetSkillsDir) {
|
|
218
278
|
const manifestPath = path.join(targetSkillsDir, MANIFEST_FILENAME);
|
|
@@ -233,7 +293,9 @@ async function readManifestFile(targetSkillsDir) {
|
|
|
233
293
|
if (typeof e.pluginId !== 'string')
|
|
234
294
|
continue;
|
|
235
295
|
const sourceKind = e.sourceKind;
|
|
236
|
-
if (sourceKind !== 'skill' &&
|
|
296
|
+
if (sourceKind !== 'skill' &&
|
|
297
|
+
sourceKind !== 'command' &&
|
|
298
|
+
sourceKind !== 'agent')
|
|
237
299
|
continue;
|
|
238
300
|
if (typeof e.sourceRelPath !== 'string')
|
|
239
301
|
continue;
|
|
@@ -294,7 +356,9 @@ async function readLegacyMarkerFile(dir) {
|
|
|
294
356
|
return null;
|
|
295
357
|
if (typeof obj.generatedName !== 'string')
|
|
296
358
|
return null;
|
|
297
|
-
if (obj.sourceKind !== 'skill' &&
|
|
359
|
+
if (obj.sourceKind !== 'skill' &&
|
|
360
|
+
obj.sourceKind !== 'command' &&
|
|
361
|
+
obj.sourceKind !== 'agent')
|
|
298
362
|
return null;
|
|
299
363
|
if (typeof obj.sourceRelPath !== 'string')
|
|
300
364
|
return null;
|
|
@@ -390,6 +454,77 @@ async function discoverLocalSkillNames(projectRoot) {
|
|
|
390
454
|
await walk(localSkillsDir, 0);
|
|
391
455
|
return names;
|
|
392
456
|
}
|
|
457
|
+
async function discoverLocalCommandNames(projectRoot) {
|
|
458
|
+
const localCommandsDir = path.join(projectRoot, '.claude', 'commands');
|
|
459
|
+
if (!(await fileExists(localCommandsDir)))
|
|
460
|
+
return new Set();
|
|
461
|
+
const names = new Set();
|
|
462
|
+
async function walk(current, depth) {
|
|
463
|
+
if (depth >= constants_1.MAX_RECURSION_DEPTH)
|
|
464
|
+
return;
|
|
465
|
+
let entries;
|
|
466
|
+
try {
|
|
467
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
468
|
+
}
|
|
469
|
+
catch {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
for (const entry of entries) {
|
|
473
|
+
const full = path.join(current, entry.name);
|
|
474
|
+
if (entry.isDirectory()) {
|
|
475
|
+
await walk(full, depth + 1);
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
479
|
+
continue;
|
|
480
|
+
names.add(sanitizeId(path.basename(entry.name, '.md')));
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
await walk(localCommandsDir, 0);
|
|
484
|
+
return names;
|
|
485
|
+
}
|
|
486
|
+
async function discoverLocalAgentNames(projectRoot) {
|
|
487
|
+
const localAgentsDir = path.join(projectRoot, '.claude', 'agents');
|
|
488
|
+
if (!(await fileExists(localAgentsDir)))
|
|
489
|
+
return new Set();
|
|
490
|
+
const names = new Set();
|
|
491
|
+
async function walk(current, depth) {
|
|
492
|
+
if (depth >= constants_1.MAX_RECURSION_DEPTH)
|
|
493
|
+
return;
|
|
494
|
+
let entries;
|
|
495
|
+
try {
|
|
496
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
497
|
+
}
|
|
498
|
+
catch {
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
for (const entry of entries) {
|
|
502
|
+
const full = path.join(current, entry.name);
|
|
503
|
+
if (entry.isDirectory()) {
|
|
504
|
+
await walk(full, depth + 1);
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
508
|
+
continue;
|
|
509
|
+
let content;
|
|
510
|
+
try {
|
|
511
|
+
content = await fs.readFile(full, 'utf8');
|
|
512
|
+
}
|
|
513
|
+
catch {
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
const parsed = (0, FrontmatterParser_1.parseFrontmatter)(content);
|
|
517
|
+
const fmName = parsed.rawFrontmatter && typeof parsed.rawFrontmatter.name === 'string'
|
|
518
|
+
? parsed.rawFrontmatter.name
|
|
519
|
+
: parsed.frontmatter?.name;
|
|
520
|
+
if (typeof fmName !== 'string' || fmName.trim() === '')
|
|
521
|
+
continue;
|
|
522
|
+
names.add(sanitizeId(fmName.trim()));
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
await walk(localAgentsDir, 0);
|
|
526
|
+
return names;
|
|
527
|
+
}
|
|
393
528
|
async function ensureDir(dir, dryRun) {
|
|
394
529
|
if (dryRun)
|
|
395
530
|
return;
|
|
@@ -423,15 +558,15 @@ async function rewriteSkillMdName(skillMdPath, name, pluginId, dryRun) {
|
|
|
423
558
|
return;
|
|
424
559
|
await fs.writeFile(skillMdPath, next, 'utf8');
|
|
425
560
|
}
|
|
426
|
-
async function
|
|
427
|
-
const content = await fs.readFile(
|
|
561
|
+
async function writeMarkdownAsSkill(srcMarkdownPath, destDir, generatedName, pluginId, kindLabel, dryRun) {
|
|
562
|
+
const content = await fs.readFile(srcMarkdownPath, 'utf8');
|
|
428
563
|
const { rawFrontmatter, body } = (0, FrontmatterParser_1.parseFrontmatter)(content);
|
|
429
564
|
const fm = rawFrontmatter
|
|
430
565
|
? { ...rawFrontmatter }
|
|
431
566
|
: {};
|
|
432
567
|
fm.name = generatedName;
|
|
433
568
|
if (typeof fm.description !== 'string' || fm.description.trim() === '') {
|
|
434
|
-
fm.description =
|
|
569
|
+
fm.description = `${kindLabel} from ${pluginId}: ${path.basename(srcMarkdownPath, '.md')}`;
|
|
435
570
|
}
|
|
436
571
|
const next = `---\n${yaml
|
|
437
572
|
.dump(fm, { lineWidth: -1, noRefs: true })
|
|
@@ -450,6 +585,13 @@ async function syncClaudePluginsToSkillsDirs(args) {
|
|
|
450
585
|
const claudeDir = path.join(getUserHomeDir(), '.claude');
|
|
451
586
|
const index = await readInstalledPluginsIndex(claudeDir);
|
|
452
587
|
const localSkillNames = await discoverLocalSkillNames(projectRoot);
|
|
588
|
+
const localCommandNames = await discoverLocalCommandNames(projectRoot);
|
|
589
|
+
const localAgentNames = await discoverLocalAgentNames(projectRoot);
|
|
590
|
+
const localReservedNames = new Set([
|
|
591
|
+
...localSkillNames,
|
|
592
|
+
...localCommandNames,
|
|
593
|
+
...localAgentNames,
|
|
594
|
+
]);
|
|
453
595
|
// If we can't read the installed plugins index, we can't install/update
|
|
454
596
|
// anything, but we can still clean up managed folders for plugins that
|
|
455
597
|
// are no longer enabled.
|
|
@@ -460,7 +602,7 @@ async function syncClaudePluginsToSkillsDirs(args) {
|
|
|
460
602
|
const managedEntries = await loadManagedEntries(targetSkillsDir, dryRun);
|
|
461
603
|
const nextEntries = [];
|
|
462
604
|
// Build reserved set (local skills always win).
|
|
463
|
-
const reserved = new Set(
|
|
605
|
+
const reserved = new Set(localReservedNames);
|
|
464
606
|
// Also reserve any existing non-managed directories.
|
|
465
607
|
const managedDest = new Set(managedEntries.map((e) => e.destRelPath));
|
|
466
608
|
let dirents = [];
|
|
@@ -537,6 +679,20 @@ async function syncClaudePluginsToSkillsDirs(args) {
|
|
|
537
679
|
baseName,
|
|
538
680
|
});
|
|
539
681
|
}
|
|
682
|
+
const agentFiles = await discoverPluginAgentFiles(plugin.installPath);
|
|
683
|
+
for (const a of agentFiles) {
|
|
684
|
+
const baseName = sanitizeId(a.name);
|
|
685
|
+
const sourceRelPath = `agents/${a.rel}`;
|
|
686
|
+
expectedItems.push({
|
|
687
|
+
itemKey: makeItemKey(plugin.pluginId, 'agent', sourceRelPath),
|
|
688
|
+
pluginId: plugin.pluginId,
|
|
689
|
+
pluginVersion: plugin.version,
|
|
690
|
+
kind: 'agent',
|
|
691
|
+
sourcePath: a.file,
|
|
692
|
+
sourceRelPath,
|
|
693
|
+
baseName,
|
|
694
|
+
});
|
|
695
|
+
}
|
|
540
696
|
}
|
|
541
697
|
const sortedItems = [...expectedItems].sort((a, b) => {
|
|
542
698
|
const ak = `${a.baseName}::${a.pluginId}::${a.kind}::${a.sourceRelPath}`;
|
|
@@ -558,7 +714,7 @@ async function syncClaudePluginsToSkillsDirs(args) {
|
|
|
558
714
|
}
|
|
559
715
|
const managedDest = new Set(managedEntries.map((e) => e.destRelPath));
|
|
560
716
|
// Reserve: local skills always win. Also reserve any existing non-managed folders.
|
|
561
|
-
const reserved = new Set(
|
|
717
|
+
const reserved = new Set(localReservedNames);
|
|
562
718
|
if (targetExists) {
|
|
563
719
|
let dirents = [];
|
|
564
720
|
try {
|
|
@@ -583,6 +739,11 @@ async function syncClaudePluginsToSkillsDirs(args) {
|
|
|
583
739
|
const prev = prevDestByItemKey.get(item.itemKey);
|
|
584
740
|
if (!prev)
|
|
585
741
|
continue;
|
|
742
|
+
// Migration: previous versions used `${pluginId}__${name}`.
|
|
743
|
+
// Don't preserve legacy namespaced destinations so we can rename to the
|
|
744
|
+
// new `${pluginId}-${name}` format.
|
|
745
|
+
if (prev.startsWith(`${sanitizeId(item.pluginId)}__`))
|
|
746
|
+
continue;
|
|
586
747
|
if (taken.has(prev))
|
|
587
748
|
continue;
|
|
588
749
|
assignedDestByItemKey.set(item.itemKey, prev);
|
|
@@ -602,7 +763,7 @@ async function syncClaudePluginsToSkillsDirs(args) {
|
|
|
602
763
|
let candidate = namespacedBase;
|
|
603
764
|
let i = 2;
|
|
604
765
|
while (taken.has(candidate)) {
|
|
605
|
-
candidate = `${namespacedBase}
|
|
766
|
+
candidate = `${namespacedBase}-${i++}`;
|
|
606
767
|
}
|
|
607
768
|
assignedDestByItemKey.set(item.itemKey, candidate);
|
|
608
769
|
taken.add(candidate);
|
|
@@ -642,12 +803,13 @@ async function syncClaudePluginsToSkillsDirs(args) {
|
|
|
642
803
|
await removeLegacyMarkerFile(destDir, dryRun);
|
|
643
804
|
}
|
|
644
805
|
else {
|
|
806
|
+
const kindLabel = item.kind === 'command' ? 'command' : 'agent';
|
|
645
807
|
(0, constants_1.logVerboseInfo)(dryRun
|
|
646
|
-
? `DRY RUN: Would install plugin
|
|
647
|
-
: `Installing plugin
|
|
808
|
+
? `DRY RUN: Would install plugin ${kindLabel} '${destRelPath}' as skill to ${targetSkillsDir}`
|
|
809
|
+
: `Installing plugin ${kindLabel} '${destRelPath}' as skill to ${targetSkillsDir}`, verbose, dryRun);
|
|
648
810
|
await ensureDir(destDir, dryRun);
|
|
649
811
|
if (!dryRun) {
|
|
650
|
-
await
|
|
812
|
+
await writeMarkdownAsSkill(item.sourcePath, destDir, destRelPath, item.pluginId, item.kind === 'command' ? 'Command' : 'Agent', dryRun);
|
|
651
813
|
}
|
|
652
814
|
await removeLegacyMarkerFile(destDir, dryRun);
|
|
653
815
|
}
|
|
@@ -0,0 +1,460 @@
|
|
|
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 MANIFEST_FILENAME = '.skiller-claude.json';
|
|
43
|
+
const MANIFEST_VERSION = 1;
|
|
44
|
+
const LEGACY_PLUGIN_MARKER_FILENAME = '.skiller-plugin.json';
|
|
45
|
+
function sanitizeId(value) {
|
|
46
|
+
return value.replace(/[^A-Za-z0-9._-]+/g, '_');
|
|
47
|
+
}
|
|
48
|
+
async function fileExists(p) {
|
|
49
|
+
try {
|
|
50
|
+
await fs.access(p);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function makeItemKey(sourceKind, sourceRelPath) {
|
|
58
|
+
return `${sourceKind}::${sourceRelPath}`;
|
|
59
|
+
}
|
|
60
|
+
function getCommandsRoot(projectRoot) {
|
|
61
|
+
return path.join(projectRoot, '.claude', 'commands');
|
|
62
|
+
}
|
|
63
|
+
function getAgentsRoot(projectRoot) {
|
|
64
|
+
return path.join(projectRoot, '.claude', 'agents');
|
|
65
|
+
}
|
|
66
|
+
async function discoverCommandFiles(projectRoot) {
|
|
67
|
+
const commandsRoot = getCommandsRoot(projectRoot);
|
|
68
|
+
if (!(await fileExists(commandsRoot)))
|
|
69
|
+
return [];
|
|
70
|
+
const results = [];
|
|
71
|
+
async function walk(current, depth) {
|
|
72
|
+
if (depth >= constants_1.MAX_RECURSION_DEPTH)
|
|
73
|
+
return;
|
|
74
|
+
let entries;
|
|
75
|
+
try {
|
|
76
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
const full = path.join(current, entry.name);
|
|
83
|
+
if (entry.isDirectory()) {
|
|
84
|
+
await walk(full, depth + 1);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
88
|
+
continue;
|
|
89
|
+
const name = sanitizeId(path.basename(entry.name, '.md'));
|
|
90
|
+
const rel = path.relative(projectRoot, full).replace(/\\/g, '/');
|
|
91
|
+
results.push({ name, file: full, rel });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
await walk(commandsRoot, 0);
|
|
95
|
+
return results;
|
|
96
|
+
}
|
|
97
|
+
async function discoverAgentFiles(projectRoot) {
|
|
98
|
+
const agentsRoot = getAgentsRoot(projectRoot);
|
|
99
|
+
if (!(await fileExists(agentsRoot)))
|
|
100
|
+
return [];
|
|
101
|
+
const results = [];
|
|
102
|
+
async function walk(current, depth) {
|
|
103
|
+
if (depth >= constants_1.MAX_RECURSION_DEPTH)
|
|
104
|
+
return;
|
|
105
|
+
let entries;
|
|
106
|
+
try {
|
|
107
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
for (const entry of entries) {
|
|
113
|
+
const full = path.join(current, entry.name);
|
|
114
|
+
if (entry.isDirectory()) {
|
|
115
|
+
await walk(full, depth + 1);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
119
|
+
continue;
|
|
120
|
+
let content;
|
|
121
|
+
try {
|
|
122
|
+
content = await fs.readFile(full, 'utf8');
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const parsed = (0, FrontmatterParser_1.parseFrontmatter)(content);
|
|
128
|
+
const fmName = parsed.rawFrontmatter && typeof parsed.rawFrontmatter.name === 'string'
|
|
129
|
+
? parsed.rawFrontmatter.name
|
|
130
|
+
: parsed.frontmatter?.name;
|
|
131
|
+
if (typeof fmName !== 'string' || fmName.trim() === '')
|
|
132
|
+
continue;
|
|
133
|
+
const name = sanitizeId(fmName.trim());
|
|
134
|
+
const rel = path.relative(projectRoot, full).replace(/\\/g, '/');
|
|
135
|
+
results.push({ name, file: full, rel });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
await walk(agentsRoot, 0);
|
|
139
|
+
return results;
|
|
140
|
+
}
|
|
141
|
+
async function readManifestFile(targetSkillsDir) {
|
|
142
|
+
const manifestPath = path.join(targetSkillsDir, MANIFEST_FILENAME);
|
|
143
|
+
try {
|
|
144
|
+
const raw = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
|
|
145
|
+
if (!raw || typeof raw !== 'object')
|
|
146
|
+
return null;
|
|
147
|
+
const obj = raw;
|
|
148
|
+
if (typeof obj.version !== 'number')
|
|
149
|
+
return null;
|
|
150
|
+
if (!Array.isArray(obj.entries))
|
|
151
|
+
return null;
|
|
152
|
+
const entries = [];
|
|
153
|
+
for (const entry of obj.entries) {
|
|
154
|
+
if (!entry || typeof entry !== 'object')
|
|
155
|
+
continue;
|
|
156
|
+
const e = entry;
|
|
157
|
+
if (e.sourceKind !== 'command' && e.sourceKind !== 'agent')
|
|
158
|
+
continue;
|
|
159
|
+
if (typeof e.sourceRelPath !== 'string')
|
|
160
|
+
continue;
|
|
161
|
+
if (typeof e.destRelPath !== 'string')
|
|
162
|
+
continue;
|
|
163
|
+
entries.push({
|
|
164
|
+
sourceKind: e.sourceKind,
|
|
165
|
+
sourceRelPath: e.sourceRelPath,
|
|
166
|
+
destRelPath: e.destRelPath,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
version: obj.version,
|
|
171
|
+
entries,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function writeManifestFile(targetSkillsDir, entries, dryRun) {
|
|
179
|
+
const manifestPath = path.join(targetSkillsDir, MANIFEST_FILENAME);
|
|
180
|
+
if (entries.length === 0) {
|
|
181
|
+
if (dryRun)
|
|
182
|
+
return;
|
|
183
|
+
try {
|
|
184
|
+
await fs.unlink(manifestPath);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
// ignore
|
|
188
|
+
}
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const manifest = {
|
|
192
|
+
version: MANIFEST_VERSION,
|
|
193
|
+
entries: [...entries].sort((a, b) => {
|
|
194
|
+
const ak = `${a.destRelPath}::${a.sourceKind}::${a.sourceRelPath}`;
|
|
195
|
+
const bk = `${b.destRelPath}::${b.sourceKind}::${b.sourceRelPath}`;
|
|
196
|
+
return ak.localeCompare(bk);
|
|
197
|
+
}),
|
|
198
|
+
};
|
|
199
|
+
if (dryRun)
|
|
200
|
+
return;
|
|
201
|
+
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
202
|
+
}
|
|
203
|
+
async function ensureDir(dir, dryRun) {
|
|
204
|
+
if (dryRun)
|
|
205
|
+
return;
|
|
206
|
+
await fs.mkdir(dir, { recursive: true });
|
|
207
|
+
}
|
|
208
|
+
async function removeDir(dir, dryRun) {
|
|
209
|
+
if (dryRun)
|
|
210
|
+
return;
|
|
211
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
212
|
+
}
|
|
213
|
+
async function writeMarkdownAsSkill(srcPath, destDir, generatedName, kindLabel, dryRun) {
|
|
214
|
+
const content = await fs.readFile(srcPath, 'utf8');
|
|
215
|
+
const { rawFrontmatter, body } = (0, FrontmatterParser_1.parseFrontmatter)(content);
|
|
216
|
+
const fm = rawFrontmatter
|
|
217
|
+
? { ...rawFrontmatter }
|
|
218
|
+
: {};
|
|
219
|
+
fm.name = generatedName;
|
|
220
|
+
if (typeof fm.description !== 'string' || fm.description.trim() === '') {
|
|
221
|
+
fm.description = `${kindLabel}: ${path.basename(srcPath, '.md')}`;
|
|
222
|
+
}
|
|
223
|
+
const next = `---\n${yaml
|
|
224
|
+
.dump(fm, { lineWidth: -1, noRefs: true })
|
|
225
|
+
.trim()}\n---\n\n${body}\n`;
|
|
226
|
+
if (dryRun)
|
|
227
|
+
return;
|
|
228
|
+
await fs.writeFile(path.join(destDir, 'SKILL.md'), next, 'utf8');
|
|
229
|
+
}
|
|
230
|
+
async function readPluginManagedDestNames(targetSkillsDir) {
|
|
231
|
+
const manifestPath = path.join(targetSkillsDir, '.skiller-plugins.json');
|
|
232
|
+
const names = new Set();
|
|
233
|
+
try {
|
|
234
|
+
const raw = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
|
|
235
|
+
if (raw && typeof raw === 'object') {
|
|
236
|
+
const obj = raw;
|
|
237
|
+
if (Array.isArray(obj.entries)) {
|
|
238
|
+
for (const entry of obj.entries) {
|
|
239
|
+
if (!entry || typeof entry !== 'object')
|
|
240
|
+
continue;
|
|
241
|
+
const e = entry;
|
|
242
|
+
if (typeof e.destRelPath === 'string' && e.destRelPath.length > 0) {
|
|
243
|
+
names.add(e.destRelPath);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// ignore
|
|
251
|
+
}
|
|
252
|
+
// Legacy: prior versions wrote per-skill plugin marker files. Treat any
|
|
253
|
+
// folder containing one as plugin-managed so project items can take over.
|
|
254
|
+
try {
|
|
255
|
+
const dirents = await fs.readdir(targetSkillsDir, { withFileTypes: true });
|
|
256
|
+
for (const d of dirents) {
|
|
257
|
+
if (!d.isDirectory())
|
|
258
|
+
continue;
|
|
259
|
+
try {
|
|
260
|
+
await fs.access(path.join(targetSkillsDir, d.name, LEGACY_PLUGIN_MARKER_FILENAME));
|
|
261
|
+
names.add(d.name);
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
// ignore
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
// ignore
|
|
270
|
+
}
|
|
271
|
+
return names;
|
|
272
|
+
}
|
|
273
|
+
async function discoverLocalSkillNames(projectRoot) {
|
|
274
|
+
const localSkillsDir = path.join(projectRoot, '.claude', 'skills');
|
|
275
|
+
if (!(await fileExists(localSkillsDir)))
|
|
276
|
+
return new Set();
|
|
277
|
+
const names = new Set();
|
|
278
|
+
async function walk(current, depth) {
|
|
279
|
+
if (depth >= constants_1.MAX_RECURSION_DEPTH)
|
|
280
|
+
return;
|
|
281
|
+
let entries;
|
|
282
|
+
try {
|
|
283
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const hasSkillMd = entries.some((e) => e.isFile() && e.name === 'SKILL.md');
|
|
289
|
+
if (hasSkillMd) {
|
|
290
|
+
names.add(path.basename(current));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
for (const entry of entries) {
|
|
294
|
+
if (!entry.isDirectory())
|
|
295
|
+
continue;
|
|
296
|
+
await walk(path.join(current, entry.name), depth + 1);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
await walk(localSkillsDir, 0);
|
|
300
|
+
return names;
|
|
301
|
+
}
|
|
302
|
+
async function syncClaudeProjectCommandsAndAgentsToSkillsDirs(args) {
|
|
303
|
+
const { projectRoot, targetSkillsDirs, verbose, dryRun } = args;
|
|
304
|
+
const localSkillNames = await discoverLocalSkillNames(projectRoot);
|
|
305
|
+
const commands = await discoverCommandFiles(projectRoot);
|
|
306
|
+
const agents = await discoverAgentFiles(projectRoot);
|
|
307
|
+
const expectedItems = [];
|
|
308
|
+
for (const cmd of commands) {
|
|
309
|
+
expectedItems.push({
|
|
310
|
+
itemKey: makeItemKey('command', cmd.rel),
|
|
311
|
+
sourceKind: 'command',
|
|
312
|
+
sourcePath: cmd.file,
|
|
313
|
+
sourceRelPath: cmd.rel,
|
|
314
|
+
baseName: cmd.name,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
for (const agent of agents) {
|
|
318
|
+
expectedItems.push({
|
|
319
|
+
itemKey: makeItemKey('agent', agent.rel),
|
|
320
|
+
sourceKind: 'agent',
|
|
321
|
+
sourcePath: agent.file,
|
|
322
|
+
sourceRelPath: agent.rel,
|
|
323
|
+
baseName: agent.name,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
const sortedItems = [...expectedItems].sort((a, b) => {
|
|
327
|
+
const ak = `${a.baseName}::${a.sourceKind}::${a.sourceRelPath}`;
|
|
328
|
+
const bk = `${b.baseName}::${b.sourceKind}::${b.sourceRelPath}`;
|
|
329
|
+
return ak.localeCompare(bk);
|
|
330
|
+
});
|
|
331
|
+
for (const targetSkillsDir of targetSkillsDirs) {
|
|
332
|
+
const targetExists = await fileExists(targetSkillsDir);
|
|
333
|
+
const managedEntries = targetExists
|
|
334
|
+
? ((await readManifestFile(targetSkillsDir))?.entries ?? [])
|
|
335
|
+
: [];
|
|
336
|
+
const prevDestByItemKey = new Map();
|
|
337
|
+
for (const entry of managedEntries) {
|
|
338
|
+
prevDestByItemKey.set(makeItemKey(entry.sourceKind, entry.sourceRelPath), entry.destRelPath);
|
|
339
|
+
}
|
|
340
|
+
const managedDest = new Set(managedEntries.map((e) => e.destRelPath));
|
|
341
|
+
const pluginManagedDest = targetExists
|
|
342
|
+
? await readPluginManagedDestNames(targetSkillsDir)
|
|
343
|
+
: new Set();
|
|
344
|
+
const reserved = new Set(localSkillNames);
|
|
345
|
+
if (targetExists) {
|
|
346
|
+
let dirents = [];
|
|
347
|
+
try {
|
|
348
|
+
dirents = await fs.readdir(targetSkillsDir, { withFileTypes: true });
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
dirents = [];
|
|
352
|
+
}
|
|
353
|
+
for (const d of dirents) {
|
|
354
|
+
if (!d.isDirectory())
|
|
355
|
+
continue;
|
|
356
|
+
// Reserve any existing folder we do not manage, except plugin-managed
|
|
357
|
+
// folders (project should be able to take those over).
|
|
358
|
+
if (!managedDest.has(d.name) && !pluginManagedDest.has(d.name)) {
|
|
359
|
+
reserved.add(d.name);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const taken = new Set(reserved);
|
|
364
|
+
const assignedDestByItemKey = new Map();
|
|
365
|
+
// Preserve previous destinations when they are still available.
|
|
366
|
+
for (const item of sortedItems) {
|
|
367
|
+
const prev = prevDestByItemKey.get(item.itemKey);
|
|
368
|
+
if (!prev)
|
|
369
|
+
continue;
|
|
370
|
+
// Migration: previous versions used `claude__<name>`.
|
|
371
|
+
// Don't preserve legacy namespaced destinations so we can rename to the
|
|
372
|
+
// new `claude-<name>` format.
|
|
373
|
+
if (prev.startsWith('claude__'))
|
|
374
|
+
continue;
|
|
375
|
+
if (taken.has(prev))
|
|
376
|
+
continue;
|
|
377
|
+
assignedDestByItemKey.set(item.itemKey, prev);
|
|
378
|
+
taken.add(prev);
|
|
379
|
+
}
|
|
380
|
+
// Assign baseName, otherwise namespace with "claude-".
|
|
381
|
+
for (const item of sortedItems) {
|
|
382
|
+
if (assignedDestByItemKey.has(item.itemKey))
|
|
383
|
+
continue;
|
|
384
|
+
const base = item.baseName;
|
|
385
|
+
if (!taken.has(base)) {
|
|
386
|
+
assignedDestByItemKey.set(item.itemKey, base);
|
|
387
|
+
taken.add(base);
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
const namespacedBase = `claude-${base}`;
|
|
391
|
+
let candidate = namespacedBase;
|
|
392
|
+
let i = 2;
|
|
393
|
+
while (taken.has(candidate)) {
|
|
394
|
+
candidate = `${namespacedBase}-${i++}`;
|
|
395
|
+
}
|
|
396
|
+
assignedDestByItemKey.set(item.itemKey, candidate);
|
|
397
|
+
taken.add(candidate);
|
|
398
|
+
}
|
|
399
|
+
const assignedItems = sortedItems.map((item) => ({
|
|
400
|
+
...item,
|
|
401
|
+
destRelPath: assignedDestByItemKey.get(item.itemKey),
|
|
402
|
+
}));
|
|
403
|
+
if (assignedItems.length > 0) {
|
|
404
|
+
await ensureDir(targetSkillsDir, dryRun);
|
|
405
|
+
}
|
|
406
|
+
else if (!targetExists && managedEntries.length === 0) {
|
|
407
|
+
// Nothing to do.
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
// Install/update expected items
|
|
411
|
+
for (const item of assignedItems) {
|
|
412
|
+
const destRelPath = item.destRelPath;
|
|
413
|
+
const destDir = path.join(targetSkillsDir, destRelPath);
|
|
414
|
+
if (await fileExists(destDir)) {
|
|
415
|
+
const isManagedByProject = managedDest.has(destRelPath);
|
|
416
|
+
const isManagedByPlugin = pluginManagedDest.has(destRelPath);
|
|
417
|
+
if (!isManagedByProject && !isManagedByPlugin) {
|
|
418
|
+
(0, constants_1.logWarn)(`[claude] Destination exists but is not skiller-managed, skipping: ${destDir}`, dryRun);
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
(0, constants_1.logVerboseInfo)(dryRun
|
|
422
|
+
? `DRY RUN: Would update claude ${item.sourceKind} '${destRelPath}' in ${targetSkillsDir}`
|
|
423
|
+
: `Updating claude ${item.sourceKind} '${destRelPath}' in ${targetSkillsDir}`, verbose, dryRun);
|
|
424
|
+
await removeDir(destDir, dryRun);
|
|
425
|
+
}
|
|
426
|
+
(0, constants_1.logVerboseInfo)(dryRun
|
|
427
|
+
? `DRY RUN: Would install claude ${item.sourceKind} '${destRelPath}' to ${targetSkillsDir}`
|
|
428
|
+
: `Installing claude ${item.sourceKind} '${destRelPath}' to ${targetSkillsDir}`, verbose, dryRun);
|
|
429
|
+
await ensureDir(destDir, dryRun);
|
|
430
|
+
if (!dryRun) {
|
|
431
|
+
await writeMarkdownAsSkill(item.sourcePath, destDir, destRelPath, item.sourceKind === 'command' ? 'Command' : 'Agent', dryRun);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
// Cleanup stale managed folders
|
|
435
|
+
const expectedDest = new Set(assignedItems.map((i) => i.destRelPath));
|
|
436
|
+
const nextEntries = [];
|
|
437
|
+
for (const entry of managedEntries) {
|
|
438
|
+
if (expectedDest.has(entry.destRelPath)) {
|
|
439
|
+
// Still expected; re-add below.
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
if (reserved.has(entry.destRelPath)) {
|
|
443
|
+
// User/local took over the folder name; stop managing it but don't delete.
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
(0, constants_1.logVerboseInfo)(dryRun
|
|
447
|
+
? `DRY RUN: Would remove stale claude skill '${entry.destRelPath}' from ${targetSkillsDir}`
|
|
448
|
+
: `Removing stale claude skill '${entry.destRelPath}' from ${targetSkillsDir}`, verbose, dryRun);
|
|
449
|
+
await removeDir(path.join(targetSkillsDir, entry.destRelPath), dryRun);
|
|
450
|
+
}
|
|
451
|
+
for (const item of assignedItems) {
|
|
452
|
+
nextEntries.push({
|
|
453
|
+
sourceKind: item.sourceKind,
|
|
454
|
+
sourceRelPath: item.sourceRelPath,
|
|
455
|
+
destRelPath: item.destRelPath,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
await writeManifestFile(targetSkillsDir, nextEntries, dryRun);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
@@ -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.16",
|
|
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
|
}
|