skiller 0.9.1 → 0.9.3
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/commands.js +42 -35
- package/dist/core/ClaudePluginMigration.js +83 -1
- package/package.json +1 -1
package/dist/cli/commands.js
CHANGED
|
@@ -16,6 +16,42 @@ function skillsArgsBuilder(y) {
|
|
|
16
16
|
description: 'Arguments passed through to the local skills CLI',
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
|
+
function migrateClaudePluginsArgsBuilder(y) {
|
|
20
|
+
return y
|
|
21
|
+
.option('project-root', {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'Project root directory',
|
|
24
|
+
default: process.cwd(),
|
|
25
|
+
})
|
|
26
|
+
.option('execute', {
|
|
27
|
+
type: 'boolean',
|
|
28
|
+
description: 'Actually install the resolved repos through the local skills CLI',
|
|
29
|
+
default: false,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function migrateRulesToSkillsArgsBuilder(y) {
|
|
33
|
+
return y
|
|
34
|
+
.option('project-root', {
|
|
35
|
+
type: 'string',
|
|
36
|
+
description: 'Project root directory',
|
|
37
|
+
default: process.cwd(),
|
|
38
|
+
})
|
|
39
|
+
.option('execute', {
|
|
40
|
+
type: 'boolean',
|
|
41
|
+
description: 'Actually replace selected local rules after detection',
|
|
42
|
+
default: false,
|
|
43
|
+
})
|
|
44
|
+
.option('yes', {
|
|
45
|
+
type: 'boolean',
|
|
46
|
+
description: 'Auto-replace only unambiguous exact matches without prompting',
|
|
47
|
+
default: false,
|
|
48
|
+
})
|
|
49
|
+
.positional('rules', {
|
|
50
|
+
type: 'string',
|
|
51
|
+
array: true,
|
|
52
|
+
description: 'Specific rule names or .mdc files to check (default: all .agents/rules/*.mdc)',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
19
55
|
/**
|
|
20
56
|
* Sets up and parses CLI commands.
|
|
21
57
|
*/
|
|
@@ -110,42 +146,13 @@ async function run() {
|
|
|
110
146
|
default: false,
|
|
111
147
|
});
|
|
112
148
|
}, handlers_1.initHandler)
|
|
113
|
-
.command('migrate
|
|
114
|
-
return y
|
|
115
|
-
.option('project-root', {
|
|
116
|
-
type: 'string',
|
|
117
|
-
description: 'Project root directory',
|
|
118
|
-
default: process.cwd(),
|
|
119
|
-
})
|
|
120
|
-
.option('execute', {
|
|
121
|
-
type: 'boolean',
|
|
122
|
-
description: 'Actually install the resolved repos through the local skills CLI',
|
|
123
|
-
default: false,
|
|
124
|
-
});
|
|
125
|
-
}, handlers_1.migrateClaudePluginsHandler)
|
|
126
|
-
.command('migrate rules-to-skills [rules..]', 'Detect local .agents/rules .mdc files that already exist on skills.sh and optionally replace them', (y) => {
|
|
149
|
+
.command('migrate', 'Migration utilities', (y) => {
|
|
127
150
|
return y
|
|
128
|
-
.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
.option('execute', {
|
|
134
|
-
type: 'boolean',
|
|
135
|
-
description: 'Actually replace selected local rules after detection',
|
|
136
|
-
default: false,
|
|
137
|
-
})
|
|
138
|
-
.option('yes', {
|
|
139
|
-
type: 'boolean',
|
|
140
|
-
description: 'Auto-replace only unambiguous exact matches without prompting',
|
|
141
|
-
default: false,
|
|
142
|
-
})
|
|
143
|
-
.positional('rules', {
|
|
144
|
-
type: 'string',
|
|
145
|
-
array: true,
|
|
146
|
-
description: 'Specific rule names or .mdc files to check (default: all .agents/rules/*.mdc)',
|
|
147
|
-
});
|
|
148
|
-
}, handlers_1.migrateRulesToSkillsHandler)
|
|
151
|
+
.command('claude-plugins', 'Plan or execute a one-shot migration from legacy Claude plugins to skills installs', migrateClaudePluginsArgsBuilder, handlers_1.migrateClaudePluginsHandler)
|
|
152
|
+
.command('rules-to-skills [rules..]', 'Detect local .agents/rules .mdc files that already exist on skills.sh and optionally replace them', migrateRulesToSkillsArgsBuilder, handlers_1.migrateRulesToSkillsHandler)
|
|
153
|
+
.demandCommand(1, 'You need to specify a migrate subcommand')
|
|
154
|
+
.strict();
|
|
155
|
+
}, () => undefined)
|
|
149
156
|
.command('revert', 'Revert skiller configurations from supported AI agents', (y) => {
|
|
150
157
|
return y
|
|
151
158
|
.option('project-root', {
|
|
@@ -37,8 +37,13 @@ exports.planClaudePluginSkillsMigration = planClaudePluginSkillsMigration;
|
|
|
37
37
|
const fs = __importStar(require("fs/promises"));
|
|
38
38
|
const os = __importStar(require("os"));
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
const util_1 = require("util");
|
|
40
42
|
const project_paths_1 = require("./project-paths");
|
|
41
43
|
const SkillsManifest_1 = require("./SkillsManifest");
|
|
44
|
+
const SkillsUtils_1 = require("./SkillsUtils");
|
|
45
|
+
const FrontmatterParser_1 = require("./FrontmatterParser");
|
|
46
|
+
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
|
|
42
47
|
function getClaudeHomeDir() {
|
|
43
48
|
return process.env.HOME || os.homedir();
|
|
44
49
|
}
|
|
@@ -72,6 +77,68 @@ function normalizeInstallSource(source) {
|
|
|
72
77
|
}
|
|
73
78
|
return null;
|
|
74
79
|
}
|
|
80
|
+
function normalizeCloneSource(source) {
|
|
81
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(source) || source.startsWith('git@')) {
|
|
82
|
+
return source;
|
|
83
|
+
}
|
|
84
|
+
if (/^[^/\s]+\/[^/\s]+$/.test(source)) {
|
|
85
|
+
return `https://github.com/${source}.git`;
|
|
86
|
+
}
|
|
87
|
+
return source;
|
|
88
|
+
}
|
|
89
|
+
function formatInspectionError(source, err) {
|
|
90
|
+
if (typeof err === 'object' && err !== null) {
|
|
91
|
+
const stderr = 'stderr' in err ? err.stderr : '';
|
|
92
|
+
const stdout = 'stdout' in err ? err.stdout : '';
|
|
93
|
+
const message = stderr?.trim() ||
|
|
94
|
+
stdout?.trim() ||
|
|
95
|
+
('message' in err ? String(err.message) : '');
|
|
96
|
+
if (message.length > 0) {
|
|
97
|
+
return `Failed to inspect resolved source ${source}: ${message.split('\n')[0]}`;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return `Failed to inspect resolved source ${source}`;
|
|
101
|
+
}
|
|
102
|
+
async function inspectSkillsInstallSource(source) {
|
|
103
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'skiller-plugin-source-'));
|
|
104
|
+
const repoDir = path.join(tmpDir, 'repo');
|
|
105
|
+
try {
|
|
106
|
+
await execFileAsync('git', [
|
|
107
|
+
'clone',
|
|
108
|
+
'--depth',
|
|
109
|
+
'1',
|
|
110
|
+
'--quiet',
|
|
111
|
+
normalizeCloneSource(source),
|
|
112
|
+
repoDir,
|
|
113
|
+
]);
|
|
114
|
+
const { skills } = await (0, SkillsUtils_1.walkSkillsTree)(repoDir);
|
|
115
|
+
for (const skill of skills) {
|
|
116
|
+
try {
|
|
117
|
+
const skillMd = await fs.readFile(path.join(skill.path, 'SKILL.md'), 'utf8');
|
|
118
|
+
const { frontmatter } = (0, FrontmatterParser_1.parseFrontmatter)(skillMd);
|
|
119
|
+
if (frontmatter?.name && frontmatter.description) {
|
|
120
|
+
return { installable: true };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// Keep scanning. One malformed skill should not hide valid siblings.
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
installable: false,
|
|
129
|
+
reason: `Resolved source ${source} has no valid SKILL.md files with name and description`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
return {
|
|
134
|
+
installable: false,
|
|
135
|
+
reason: formatInspectionError(source, err),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
75
142
|
async function readEnabledPluginIds(projectRoot) {
|
|
76
143
|
const settingsPath = path.join(projectRoot, project_paths_1.LEGACY_SKILLER_DIR, 'settings.json');
|
|
77
144
|
const raw = await readJsonFile(settingsPath);
|
|
@@ -178,7 +245,8 @@ function sortPlan(installsBySource, unresolved) {
|
|
|
178
245
|
unresolved: [...unresolved].sort((a, b) => a.pluginId.localeCompare(b.pluginId)),
|
|
179
246
|
};
|
|
180
247
|
}
|
|
181
|
-
async function planClaudePluginSkillsMigration(projectRoot) {
|
|
248
|
+
async function planClaudePluginSkillsMigration(projectRoot, options = {}) {
|
|
249
|
+
const inspectSource = options.inspectSource ?? inspectSkillsInstallSource;
|
|
182
250
|
const pluginIds = new Set([
|
|
183
251
|
...(await readEnabledPluginIds(projectRoot)),
|
|
184
252
|
...(await readManifestPluginIds(projectRoot)),
|
|
@@ -189,6 +257,7 @@ async function planClaudePluginSkillsMigration(projectRoot) {
|
|
|
189
257
|
const installsBySource = new Map();
|
|
190
258
|
const unresolved = [];
|
|
191
259
|
const pluginCatalogCache = new Map();
|
|
260
|
+
const sourceInspectionCache = new Map();
|
|
192
261
|
for (const pluginId of [...pluginIds].sort((a, b) => a.localeCompare(b))) {
|
|
193
262
|
const parts = parsePluginId(pluginId);
|
|
194
263
|
if (!parts) {
|
|
@@ -215,6 +284,19 @@ async function planClaudePluginSkillsMigration(projectRoot) {
|
|
|
215
284
|
});
|
|
216
285
|
continue;
|
|
217
286
|
}
|
|
287
|
+
let inspection = sourceInspectionCache.get(resolvedSource);
|
|
288
|
+
if (!inspection) {
|
|
289
|
+
inspection = await inspectSource(resolvedSource);
|
|
290
|
+
sourceInspectionCache.set(resolvedSource, inspection);
|
|
291
|
+
}
|
|
292
|
+
if (!inspection.installable) {
|
|
293
|
+
unresolved.push({
|
|
294
|
+
pluginId,
|
|
295
|
+
reason: inspection.reason ??
|
|
296
|
+
`Resolved source ${resolvedSource} is not installable through skills`,
|
|
297
|
+
});
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
218
300
|
const existing = installsBySource.get(resolvedSource);
|
|
219
301
|
if (existing) {
|
|
220
302
|
existing.pluginIds.add(pluginId);
|