stack-cleaner 1.1.5 → 1.2.0

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/public/scan.mjs +31 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stack-cleaner",
3
- "version": "1.1.5",
3
+ "version": "1.2.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "description": "See, organize, and clean up your Claude Code skills, plugins, MCP servers, and agents (split by global vs. project).",
package/public/scan.mjs CHANGED
@@ -397,6 +397,35 @@ function buildProjectLabels(paths) {
397
397
  return labels;
398
398
  }
399
399
 
400
+ // Enumerate what an installed plugin bundles, by reading its on-disk dir:
401
+ // <installDir>/skills/<name>/SKILL.md → skill names
402
+ // <installDir>/agents/<name>.md → agent names
403
+ // <installDir>/.mcp.json mcpServers → mcp server names
404
+ // Names only — no contents. Returns undefined when nothing is found.
405
+ export function collectPluginBundles(installDir) {
406
+ if (!installDir || !exists(installDir)) return undefined;
407
+ const skills = [];
408
+ const skillsDir = path.join(installDir, "skills");
409
+ for (const d of listDir(skillsDir, "dir")) {
410
+ if (exists(path.join(skillsDir, d, "SKILL.md"))) skills.push(d);
411
+ }
412
+ const agents = [];
413
+ const agentsDir = path.join(installDir, "agents");
414
+ for (const f of listDir(agentsDir, "file")) {
415
+ if (f.endsWith(".md")) agents.push(f.replace(/\.md$/, ""));
416
+ }
417
+ const mcps = [];
418
+ const mcpCfg = readJSON(path.join(installDir, ".mcp.json"));
419
+ if (mcpCfg && mcpCfg.mcpServers && typeof mcpCfg.mcpServers === "object") {
420
+ for (const name of Object.keys(mcpCfg.mcpServers)) mcps.push(name);
421
+ }
422
+ const out = {};
423
+ if (skills.length) out.skills = skills;
424
+ if (agents.length) out.agents = agents;
425
+ if (mcps.length) out.mcps = mcps;
426
+ return Object.keys(out).length ? out : undefined;
427
+ }
428
+
400
429
  // ---------- inventory build ----------
401
430
  // Collects every skill / agent / plugin / MCP item from the local install,
402
431
  // optionally overlays transcript usage, and returns the inventory OBJECT.
@@ -504,6 +533,7 @@ export async function buildInventory(opts = {}) {
504
533
  const installPath = entry && entry.installPath
505
534
  ? (entry.installPath.startsWith(HOME) ? tilde(entry.installPath) : "<external>")
506
535
  : undefined;
536
+ const bundles = entry && entry.installPath ? collectPluginBundles(entry.installPath) : undefined;
507
537
  items.push({
508
538
  // Bare name; the marketplace lives in `source` (matches the demo shape).
509
539
  id: `plugin:global:${pluginName}`,
@@ -513,6 +543,7 @@ export async function buildInventory(opts = {}) {
513
543
  source: marketplace || "",
514
544
  version: entry ? entry.version : undefined,
515
545
  path: installPath,
546
+ bundles,
516
547
  usageCount, lastUsedAt, usageClass,
517
548
  usageLabel: usageLabel(usageCount, lastUsedAt, usageClass),
518
549
  // The CLI wants the full name@marketplace form to uninstall.