skiller 0.9.4 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/handlers.js
CHANGED
|
@@ -71,6 +71,15 @@ async function executeSkillsWrapper(projectRoot, args) {
|
|
|
71
71
|
function buildClaudePluginMigrationArgs(source) {
|
|
72
72
|
return ['add', source, '--agent', 'universal', '--skill', '*', '-y'];
|
|
73
73
|
}
|
|
74
|
+
function resolveRegistryMatchSource(match) {
|
|
75
|
+
if (match.source)
|
|
76
|
+
return match.source;
|
|
77
|
+
const parts = match.slug.split('/').filter(Boolean);
|
|
78
|
+
if (parts.length >= 2) {
|
|
79
|
+
return `${parts[0]}/${parts[1]}`;
|
|
80
|
+
}
|
|
81
|
+
return match.slug;
|
|
82
|
+
}
|
|
74
83
|
function formatInstalls(count) {
|
|
75
84
|
if (!count || count <= 0)
|
|
76
85
|
return '0 installs';
|
|
@@ -86,6 +95,116 @@ function formatMatch(match) {
|
|
|
86
95
|
const source = match.source || match.slug;
|
|
87
96
|
return `${source}@${match.name} (${formatInstalls(match.installs)})`;
|
|
88
97
|
}
|
|
98
|
+
async function readJsonObject(filePath) {
|
|
99
|
+
try {
|
|
100
|
+
const raw = JSON.parse(await fs.readFile(filePath, 'utf8'));
|
|
101
|
+
return raw && typeof raw === 'object'
|
|
102
|
+
? raw
|
|
103
|
+
: null;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function cleanupLegacyClaudePluginState(projectRoot, pluginIds) {
|
|
110
|
+
const pluginIdSet = new Set(pluginIds);
|
|
111
|
+
if (pluginIdSet.size === 0)
|
|
112
|
+
return;
|
|
113
|
+
const settingsPath = path.join(projectRoot, '.claude', 'settings.json');
|
|
114
|
+
const settings = await readJsonObject(settingsPath);
|
|
115
|
+
if (settings) {
|
|
116
|
+
const enabledPlugins = settings.enabledPlugins;
|
|
117
|
+
if (enabledPlugins && typeof enabledPlugins === 'object') {
|
|
118
|
+
const nextEnabledPlugins = Object.fromEntries(Object.entries(enabledPlugins).filter(([pluginId]) => !pluginIdSet.has(pluginId)));
|
|
119
|
+
if (Object.keys(nextEnabledPlugins).length !==
|
|
120
|
+
Object.keys(enabledPlugins).length) {
|
|
121
|
+
if (Object.keys(nextEnabledPlugins).length === 0) {
|
|
122
|
+
delete settings.enabledPlugins;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
settings.enabledPlugins = nextEnabledPlugins;
|
|
126
|
+
}
|
|
127
|
+
await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (const manifestPath of [
|
|
132
|
+
path.join(projectRoot, '.agents', '.skiller.json'),
|
|
133
|
+
path.join(projectRoot, '.claude', '.skiller.json'),
|
|
134
|
+
]) {
|
|
135
|
+
const manifest = await readJsonObject(manifestPath);
|
|
136
|
+
if (!manifest)
|
|
137
|
+
continue;
|
|
138
|
+
const targets = manifest.targets;
|
|
139
|
+
if (!targets || typeof targets !== 'object')
|
|
140
|
+
continue;
|
|
141
|
+
let changed = false;
|
|
142
|
+
const nextTargets = {};
|
|
143
|
+
for (const [targetKey, rawEntries] of Object.entries(targets)) {
|
|
144
|
+
if (!Array.isArray(rawEntries)) {
|
|
145
|
+
nextTargets[targetKey] = rawEntries;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const filteredEntries = rawEntries.filter((entry) => {
|
|
149
|
+
if (!entry || typeof entry !== 'object')
|
|
150
|
+
return true;
|
|
151
|
+
const sourceType = entry.sourceType;
|
|
152
|
+
const pluginId = entry.pluginId;
|
|
153
|
+
if (sourceType !== 'plugin' || typeof pluginId !== 'string') {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
return !pluginIdSet.has(pluginId);
|
|
157
|
+
});
|
|
158
|
+
if (filteredEntries.length !== rawEntries.length) {
|
|
159
|
+
changed = true;
|
|
160
|
+
}
|
|
161
|
+
if (filteredEntries.length > 0) {
|
|
162
|
+
nextTargets[targetKey] = filteredEntries;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
changed = true;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (!changed)
|
|
169
|
+
continue;
|
|
170
|
+
manifest.targets = nextTargets;
|
|
171
|
+
const localSkills = manifest.localSkills;
|
|
172
|
+
const hasLocalSkills = Array.isArray(localSkills) && localSkills.length > 0;
|
|
173
|
+
if (Object.keys(nextTargets).length === 0 && !hasLocalSkills) {
|
|
174
|
+
await fs.rm(manifestPath, { force: true });
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async function cleanupMigratedPluginAuxiliaryRules(projectRoot, sources) {
|
|
181
|
+
const candidateRuleNames = await (0, ClaudePluginMigration_1.listClaudePluginAuxiliaryRuleNames)(sources);
|
|
182
|
+
if (candidateRuleNames.length === 0)
|
|
183
|
+
return [];
|
|
184
|
+
const sourceSet = new Set(sources);
|
|
185
|
+
const plan = await (0, RulesToSkillsMigration_1.planRulesToSkillsMigration)(projectRoot, candidateRuleNames);
|
|
186
|
+
const removable = new Set(plan.unmatched);
|
|
187
|
+
for (const candidate of plan.candidates) {
|
|
188
|
+
const exactMatchSources = candidate.matches.map(resolveRegistryMatchSource);
|
|
189
|
+
if (exactMatchSources.length === 0 ||
|
|
190
|
+
exactMatchSources.every((source) => sourceSet.has(source))) {
|
|
191
|
+
removable.add(candidate.ruleName);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const removed = [...removable].sort((a, b) => a.localeCompare(b));
|
|
195
|
+
for (const ruleName of removed) {
|
|
196
|
+
await (0, RulesToSkillsMigration_1.removeLocalRuleReplacementState)(projectRoot, ruleName, false);
|
|
197
|
+
await fs.rm(path.join(projectRoot, '.agents', 'skills', ruleName), {
|
|
198
|
+
force: true,
|
|
199
|
+
recursive: true,
|
|
200
|
+
});
|
|
201
|
+
await fs.rm(path.join(projectRoot, '.claude', 'skills', ruleName), {
|
|
202
|
+
force: true,
|
|
203
|
+
recursive: true,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return removed;
|
|
207
|
+
}
|
|
89
208
|
async function promptLine(message) {
|
|
90
209
|
const rl = readline.createInterface({
|
|
91
210
|
input: process.stdin,
|
|
@@ -329,10 +448,18 @@ async function migrateClaudePluginsHandler(argv) {
|
|
|
329
448
|
for (const install of plan.installs) {
|
|
330
449
|
await (0, skills_cli_1.runSkillsCli)(projectRoot, buildClaudePluginMigrationArgs(install.source));
|
|
331
450
|
}
|
|
451
|
+
await cleanupLegacyClaudePluginState(projectRoot, [
|
|
452
|
+
...plan.installs.flatMap((install) => install.pluginIds),
|
|
453
|
+
...plan.unresolved.map((entry) => entry.pluginId),
|
|
454
|
+
]);
|
|
455
|
+
const removedAuxiliaryRules = await cleanupMigratedPluginAuxiliaryRules(projectRoot, plan.installs.map((install) => install.source));
|
|
456
|
+
if (removedAuxiliaryRules.length > 0) {
|
|
457
|
+
console.log(`[skiller] Removed stale plugin-derived local rules:\n${removedAuxiliaryRules.map((name) => `- ${name}`).join('\n')}`);
|
|
458
|
+
}
|
|
332
459
|
if (plan.unresolved.length > 0) {
|
|
333
460
|
console.log(`[skiller] Skipped unresolved plugins:\n${plan.unresolved.map((entry) => `- ${entry.pluginId}: ${entry.reason}`).join('\n')}`);
|
|
334
461
|
}
|
|
335
|
-
console.log('[skiller] Claude plugin repo migration completed.
|
|
462
|
+
console.log('[skiller] Claude plugin repo migration completed. Legacy Claude plugin entries were removed from settings/manifests; rerun skiller apply.');
|
|
336
463
|
}
|
|
337
464
|
catch (err) {
|
|
338
465
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.listClaudePluginAuxiliaryRuleNames = listClaudePluginAuxiliaryRuleNames;
|
|
36
37
|
exports.planClaudePluginSkillsMigration = planClaudePluginSkillsMigration;
|
|
37
38
|
const fs = __importStar(require("fs/promises"));
|
|
38
39
|
const os = __importStar(require("os"));
|
|
@@ -86,6 +87,20 @@ function normalizeCloneSource(source) {
|
|
|
86
87
|
}
|
|
87
88
|
return source;
|
|
88
89
|
}
|
|
90
|
+
function normalizeSkillNameForDir(value) {
|
|
91
|
+
return value.replace(/:/g, '-').trim();
|
|
92
|
+
}
|
|
93
|
+
function extractAuxiliarySkillNames(skillMd) {
|
|
94
|
+
const names = new Set();
|
|
95
|
+
const pattern = /\b[a-z0-9-]+:[a-z0-9-]+:([a-z0-9-]+)\b/g;
|
|
96
|
+
for (const match of skillMd.toLowerCase().matchAll(pattern)) {
|
|
97
|
+
const candidate = match[1]?.trim();
|
|
98
|
+
if (!candidate || !/[a-z]/.test(candidate))
|
|
99
|
+
continue;
|
|
100
|
+
names.add(candidate);
|
|
101
|
+
}
|
|
102
|
+
return [...names];
|
|
103
|
+
}
|
|
89
104
|
function formatInspectionError(source, err) {
|
|
90
105
|
if (typeof err === 'object' && err !== null) {
|
|
91
106
|
const stderr = 'stderr' in err ? err.stderr : '';
|
|
@@ -112,18 +127,30 @@ async function inspectSkillsInstallSource(source) {
|
|
|
112
127
|
repoDir,
|
|
113
128
|
]);
|
|
114
129
|
const { skills } = await (0, SkillsUtils_1.walkSkillsTree)(repoDir);
|
|
130
|
+
const publishedSkillNames = new Set();
|
|
131
|
+
const auxiliarySkillNames = new Set();
|
|
115
132
|
for (const skill of skills) {
|
|
116
133
|
try {
|
|
117
134
|
const skillMd = await fs.readFile(path.join(skill.path, 'SKILL.md'), 'utf8');
|
|
118
135
|
const { frontmatter } = (0, FrontmatterParser_1.parseFrontmatter)(skillMd);
|
|
119
136
|
if (frontmatter?.name && frontmatter.description) {
|
|
120
|
-
|
|
137
|
+
publishedSkillNames.add(normalizeSkillNameForDir(String(frontmatter.name)));
|
|
138
|
+
for (const auxiliaryName of extractAuxiliarySkillNames(skillMd)) {
|
|
139
|
+
auxiliarySkillNames.add(auxiliaryName);
|
|
140
|
+
}
|
|
121
141
|
}
|
|
122
142
|
}
|
|
123
143
|
catch {
|
|
124
144
|
// Keep scanning. One malformed skill should not hide valid siblings.
|
|
125
145
|
}
|
|
126
146
|
}
|
|
147
|
+
if (publishedSkillNames.size > 0) {
|
|
148
|
+
return {
|
|
149
|
+
installable: true,
|
|
150
|
+
auxiliarySkillNames: [...auxiliarySkillNames].sort((a, b) => a.localeCompare(b)),
|
|
151
|
+
publishedSkillNames: [...publishedSkillNames].sort((a, b) => a.localeCompare(b)),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
127
154
|
return {
|
|
128
155
|
installable: false,
|
|
129
156
|
reason: `Resolved source ${source} has no valid SKILL.md files with name and description`,
|
|
@@ -139,6 +166,23 @@ async function inspectSkillsInstallSource(source) {
|
|
|
139
166
|
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
140
167
|
}
|
|
141
168
|
}
|
|
169
|
+
async function listClaudePluginAuxiliaryRuleNames(sources, options = {}) {
|
|
170
|
+
const inspectSource = options.inspectSource ?? inspectSkillsInstallSource;
|
|
171
|
+
const auxiliarySkillNames = new Set();
|
|
172
|
+
for (const source of [...new Set(sources)].sort((a, b) => a.localeCompare(b))) {
|
|
173
|
+
const inspection = await inspectSource(source);
|
|
174
|
+
if (!inspection.installable)
|
|
175
|
+
continue;
|
|
176
|
+
const published = new Set((inspection.publishedSkillNames ?? []).map(normalizeSkillNameForDir));
|
|
177
|
+
for (const auxiliaryName of inspection.auxiliarySkillNames ?? []) {
|
|
178
|
+
const normalized = normalizeSkillNameForDir(auxiliaryName);
|
|
179
|
+
if (!normalized || published.has(normalized))
|
|
180
|
+
continue;
|
|
181
|
+
auxiliarySkillNames.add(normalized);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return [...auxiliarySkillNames].sort((a, b) => a.localeCompare(b));
|
|
185
|
+
}
|
|
142
186
|
async function readEnabledPluginIds(projectRoot) {
|
|
143
187
|
const settingsPath = path.join(projectRoot, project_paths_1.LEGACY_SKILLER_DIR, 'settings.json');
|
|
144
188
|
const raw = await readJsonFile(settingsPath);
|
|
@@ -218,6 +218,27 @@ async function planDirectoryMigration(sourceDir, destinationDir, plannedWrites,
|
|
|
218
218
|
deletePaths.add(sourceDir);
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
|
+
async function planLegacySkillsMigration(legacySkillsDir, canonicalSkillsDir, plannedWrites, deletePaths, conflicts) {
|
|
222
|
+
if (!(await pathExists(legacySkillsDir)))
|
|
223
|
+
return;
|
|
224
|
+
const entries = await fs.readdir(legacySkillsDir, { withFileTypes: true });
|
|
225
|
+
for (const entry of entries) {
|
|
226
|
+
if (!entry.isDirectory())
|
|
227
|
+
continue;
|
|
228
|
+
const sourceDir = path.join(legacySkillsDir, entry.name);
|
|
229
|
+
const destinationDir = path.join(canonicalSkillsDir, entry.name);
|
|
230
|
+
if (await pathExists(destinationDir)) {
|
|
231
|
+
// Canonical .agents/skills already owns this name now. Legacy
|
|
232
|
+
// .claude/skills duplicates are stale migration debris.
|
|
233
|
+
deletePaths.add(sourceDir);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
await planDirectoryMigration(sourceDir, destinationDir, plannedWrites, deletePaths, conflicts);
|
|
237
|
+
}
|
|
238
|
+
if (conflicts.length === 0) {
|
|
239
|
+
deletePaths.add(legacySkillsDir);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
221
242
|
async function findRuleSkillFolders(dir) {
|
|
222
243
|
const folders = [];
|
|
223
244
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
@@ -395,7 +416,7 @@ async function migrateLegacyProjectState(projectRoot, dryRun) {
|
|
|
395
416
|
}
|
|
396
417
|
}
|
|
397
418
|
await planFileMigration(path.join(legacyDir, project_paths_1.PROJECT_AGENTS_FILE), path.join(canonicalDir, project_paths_1.PROJECT_AGENTS_FILE), plannedWrites, deletePaths, conflicts);
|
|
398
|
-
await
|
|
419
|
+
await planLegacySkillsMigration(path.join(legacyDir, 'skills'), canonicalSkillsDir, plannedWrites, deletePaths, conflicts);
|
|
399
420
|
await planLegacyRulesMigration(path.join(legacyDir, 'rules'), canonicalRulesDir, canonicalSkillsDir, plannedWrites, deletePaths, conflicts);
|
|
400
421
|
if (conflicts.length > 0) {
|
|
401
422
|
throw new Error(`Legacy .claude migration conflicts:\n- ${conflicts.join('\n- ')}`);
|