skiller 0.7.21 → 0.7.23

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/index.js CHANGED
File without changes
@@ -143,6 +143,50 @@ async function resolvePluginMarketplaceRoot(pluginId, claudeDir) {
143
143
  }
144
144
  return null;
145
145
  }
146
+ async function hasPluginContent(root) {
147
+ const candidates = [
148
+ path.join(root, 'skills'),
149
+ path.join(root, 'commands'),
150
+ path.join(root, 'agents'),
151
+ ];
152
+ for (const candidate of candidates) {
153
+ if (await dirExists(candidate))
154
+ return true;
155
+ }
156
+ return false;
157
+ }
158
+ async function readPluginPackageManifestEntries(installPath) {
159
+ const packageJsonPath = path.join(installPath, 'package.json');
160
+ try {
161
+ const raw = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
162
+ if (!Array.isArray(raw.plugins))
163
+ return [];
164
+ return raw.plugins.filter((entry) => Boolean(entry) && typeof entry === 'object');
165
+ }
166
+ catch {
167
+ return [];
168
+ }
169
+ }
170
+ async function resolveInstalledPluginSourceRoot(pluginId, installPath) {
171
+ const manifestEntries = await readPluginPackageManifestEntries(installPath);
172
+ const parsedPluginId = parsePluginId(pluginId);
173
+ const matchingEntry = manifestEntries.find((entry) => entry.name === parsedPluginId?.pluginName) ?? (manifestEntries.length === 1 ? manifestEntries[0] : null);
174
+ const rawSource = typeof matchingEntry?.source === 'string' &&
175
+ matchingEntry.source.trim() !== ''
176
+ ? matchingEntry.source
177
+ : '.';
178
+ const sourceRoot = path.resolve(installPath, rawSource);
179
+ const normalizedInstallPath = path.resolve(installPath);
180
+ const isWithinInstallPath = sourceRoot === normalizedInstallPath ||
181
+ sourceRoot.startsWith(normalizedInstallPath + path.sep);
182
+ if (isWithinInstallPath && (await hasPluginContent(sourceRoot))) {
183
+ return sourceRoot;
184
+ }
185
+ if (await hasPluginContent(installPath)) {
186
+ return installPath;
187
+ }
188
+ return null;
189
+ }
146
190
  function resolvePluginInstall(pluginId, projectRoot, index) {
147
191
  const entries = index.plugins?.[pluginId];
148
192
  if (!entries || !Array.isArray(entries) || entries.length === 0)
@@ -622,21 +666,27 @@ async function syncClaudePluginsToSkillsDirs(args) {
622
666
  const resolvedSources = [];
623
667
  const unresolvedEnabled = new Set();
624
668
  for (const pluginId of enabledPlugins) {
625
- const pluginRoot = await resolvePluginMarketplaceRoot(pluginId, claudeDir);
669
+ const resolved = index
670
+ ? resolvePluginInstall(pluginId, projectRoot, index)
671
+ : null;
672
+ const marketplaceRoot = await resolvePluginMarketplaceRoot(pluginId, claudeDir);
673
+ const pluginRoot = (marketplaceRoot && (await hasPluginContent(marketplaceRoot))
674
+ ? marketplaceRoot
675
+ : null) ??
676
+ (resolved
677
+ ? await resolveInstalledPluginSourceRoot(pluginId, resolved.installPath)
678
+ : null);
626
679
  if (!pluginRoot) {
627
680
  unresolvedEnabled.add(pluginId);
628
681
  const hasIndexEntry = Boolean(index?.plugins?.[pluginId]?.length);
629
682
  if (hasIndexEntry) {
630
- (0, constants_1.logVerboseInfo)(`[plugins] Enabled plugin has no marketplace content, skipping: ${pluginId}`, verbose, dryRun);
683
+ (0, constants_1.logVerboseInfo)(`[plugins] Enabled plugin has no syncable content, skipping: ${pluginId}`, verbose, dryRun);
631
684
  }
632
685
  else {
633
686
  (0, constants_1.logWarn)(`[plugins] Enabled plugin not installed: ${pluginId}`, dryRun);
634
687
  }
635
688
  continue;
636
689
  }
637
- const resolved = index
638
- ? resolvePluginInstall(pluginId, projectRoot, index)
639
- : null;
640
690
  resolvedSources.push({
641
691
  pluginId,
642
692
  pluginRoot,
@@ -742,6 +792,7 @@ async function syncClaudePluginsToSkillsDirs(args) {
742
792
  continue;
743
793
  const desiredPrefix = pluginNamespacePrefixByPluginId.get(item.pluginId) ??
744
794
  sanitizeId(item.pluginId);
795
+ const currentNamespacedBase = `${desiredPrefix}-${item.baseName}`;
745
796
  // Migration: previous versions used `${pluginId}__${name}`, then
746
797
  // `${pluginId}-${name}`. If we changed the namespace prefix (for example
747
798
  // to omit marketplace), don't preserve the old destination so the item
@@ -751,6 +802,13 @@ async function syncClaudePluginsToSkillsDirs(args) {
751
802
  if (prev.startsWith(`${sanitizeId(item.pluginId)}-`) &&
752
803
  desiredPrefix !== sanitizeId(item.pluginId))
753
804
  continue;
805
+ // If the item was previously namespaced only because its base name was
806
+ // unavailable, drop that sticky destination once the base name is free.
807
+ if ((prev === currentNamespacedBase ||
808
+ prev.startsWith(`${currentNamespacedBase}-`)) &&
809
+ !taken.has(item.baseName)) {
810
+ continue;
811
+ }
754
812
  if (taken.has(prev))
755
813
  continue;
756
814
  assignedDestByItemKey.set(item.itemKey, prev);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skiller",
3
- "version": "0.7.21",
3
+ "version": "0.7.23",
4
4
  "description": "Skiller — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "publishConfig": {