skiller 0.9.3 → 0.9.5
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 +92 -3
- package/dist/core/SkillOwnership.js +22 -1
- package/package.json +1 -1
package/dist/cli/handlers.js
CHANGED
|
@@ -86,6 +86,88 @@ function formatMatch(match) {
|
|
|
86
86
|
const source = match.source || match.slug;
|
|
87
87
|
return `${source}@${match.name} (${formatInstalls(match.installs)})`;
|
|
88
88
|
}
|
|
89
|
+
async function readJsonObject(filePath) {
|
|
90
|
+
try {
|
|
91
|
+
const raw = JSON.parse(await fs.readFile(filePath, 'utf8'));
|
|
92
|
+
return raw && typeof raw === 'object'
|
|
93
|
+
? raw
|
|
94
|
+
: null;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function cleanupLegacyClaudePluginState(projectRoot, pluginIds) {
|
|
101
|
+
const pluginIdSet = new Set(pluginIds);
|
|
102
|
+
if (pluginIdSet.size === 0)
|
|
103
|
+
return;
|
|
104
|
+
const settingsPath = path.join(projectRoot, '.claude', 'settings.json');
|
|
105
|
+
const settings = await readJsonObject(settingsPath);
|
|
106
|
+
if (settings) {
|
|
107
|
+
const enabledPlugins = settings.enabledPlugins;
|
|
108
|
+
if (enabledPlugins && typeof enabledPlugins === 'object') {
|
|
109
|
+
const nextEnabledPlugins = Object.fromEntries(Object.entries(enabledPlugins).filter(([pluginId]) => !pluginIdSet.has(pluginId)));
|
|
110
|
+
if (Object.keys(nextEnabledPlugins).length !==
|
|
111
|
+
Object.keys(enabledPlugins).length) {
|
|
112
|
+
if (Object.keys(nextEnabledPlugins).length === 0) {
|
|
113
|
+
delete settings.enabledPlugins;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
settings.enabledPlugins = nextEnabledPlugins;
|
|
117
|
+
}
|
|
118
|
+
await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
for (const manifestPath of [
|
|
123
|
+
path.join(projectRoot, '.agents', '.skiller.json'),
|
|
124
|
+
path.join(projectRoot, '.claude', '.skiller.json'),
|
|
125
|
+
]) {
|
|
126
|
+
const manifest = await readJsonObject(manifestPath);
|
|
127
|
+
if (!manifest)
|
|
128
|
+
continue;
|
|
129
|
+
const targets = manifest.targets;
|
|
130
|
+
if (!targets || typeof targets !== 'object')
|
|
131
|
+
continue;
|
|
132
|
+
let changed = false;
|
|
133
|
+
const nextTargets = {};
|
|
134
|
+
for (const [targetKey, rawEntries] of Object.entries(targets)) {
|
|
135
|
+
if (!Array.isArray(rawEntries)) {
|
|
136
|
+
nextTargets[targetKey] = rawEntries;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const filteredEntries = rawEntries.filter((entry) => {
|
|
140
|
+
if (!entry || typeof entry !== 'object')
|
|
141
|
+
return true;
|
|
142
|
+
const sourceType = entry.sourceType;
|
|
143
|
+
const pluginId = entry.pluginId;
|
|
144
|
+
if (sourceType !== 'plugin' || typeof pluginId !== 'string') {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
return !pluginIdSet.has(pluginId);
|
|
148
|
+
});
|
|
149
|
+
if (filteredEntries.length !== rawEntries.length) {
|
|
150
|
+
changed = true;
|
|
151
|
+
}
|
|
152
|
+
if (filteredEntries.length > 0) {
|
|
153
|
+
nextTargets[targetKey] = filteredEntries;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
changed = true;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (!changed)
|
|
160
|
+
continue;
|
|
161
|
+
manifest.targets = nextTargets;
|
|
162
|
+
const localSkills = manifest.localSkills;
|
|
163
|
+
const hasLocalSkills = Array.isArray(localSkills) && localSkills.length > 0;
|
|
164
|
+
if (Object.keys(nextTargets).length === 0 && !hasLocalSkills) {
|
|
165
|
+
await fs.rm(manifestPath, { force: true });
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
89
171
|
async function promptLine(message) {
|
|
90
172
|
const rl = readline.createInterface({
|
|
91
173
|
input: process.stdin,
|
|
@@ -323,13 +405,20 @@ async function migrateClaudePluginsHandler(argv) {
|
|
|
323
405
|
console.log('[skiller] Run again with --execute to install the resolved repos through skills.');
|
|
324
406
|
return;
|
|
325
407
|
}
|
|
326
|
-
if (plan.unresolved.length > 0) {
|
|
327
|
-
throw new Error(`Cannot execute migration
|
|
408
|
+
if (plan.installs.length === 0 && plan.unresolved.length > 0) {
|
|
409
|
+
throw new Error(`Cannot execute migration because no installable plugin repos were found:\n${plan.unresolved.map((entry) => `- ${entry.pluginId}: ${entry.reason}`).join('\n')}`);
|
|
328
410
|
}
|
|
329
411
|
for (const install of plan.installs) {
|
|
330
412
|
await (0, skills_cli_1.runSkillsCli)(projectRoot, buildClaudePluginMigrationArgs(install.source));
|
|
331
413
|
}
|
|
332
|
-
|
|
414
|
+
await cleanupLegacyClaudePluginState(projectRoot, [
|
|
415
|
+
...plan.installs.flatMap((install) => install.pluginIds),
|
|
416
|
+
...plan.unresolved.map((entry) => entry.pluginId),
|
|
417
|
+
]);
|
|
418
|
+
if (plan.unresolved.length > 0) {
|
|
419
|
+
console.log(`[skiller] Skipped unresolved plugins:\n${plan.unresolved.map((entry) => `- ${entry.pluginId}: ${entry.reason}`).join('\n')}`);
|
|
420
|
+
}
|
|
421
|
+
console.log('[skiller] Claude plugin repo migration completed. Legacy Claude plugin entries were removed from settings/manifests; rerun skiller apply.');
|
|
333
422
|
}
|
|
334
423
|
catch (err) {
|
|
335
424
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -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- ')}`);
|