skiller 0.9.2 → 0.9.4

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.
@@ -323,12 +323,15 @@ async function migrateClaudePluginsHandler(argv) {
323
323
  console.log('[skiller] Run again with --execute to install the resolved repos through skills.');
324
324
  return;
325
325
  }
326
- if (plan.unresolved.length > 0) {
327
- throw new Error(`Cannot execute migration until all plugins resolve:\n${plan.unresolved.map((entry) => `- ${entry.pluginId}: ${entry.reason}`).join('\n')}`);
326
+ if (plan.installs.length === 0 && plan.unresolved.length > 0) {
327
+ 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
328
  }
329
329
  for (const install of plan.installs) {
330
330
  await (0, skills_cli_1.runSkillsCli)(projectRoot, buildClaudePluginMigrationArgs(install.source));
331
331
  }
332
+ if (plan.unresolved.length > 0) {
333
+ console.log(`[skiller] Skipped unresolved plugins:\n${plan.unresolved.map((entry) => `- ${entry.pluginId}: ${entry.reason}`).join('\n')}`);
334
+ }
332
335
  console.log('[skiller] Claude plugin repo migration completed. Remove the plugin entries from .claude/settings.json, then rerun skiller apply.');
333
336
  }
334
337
  catch (err) {
@@ -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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skiller",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "description": "Skiller — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "publishConfig": {