shelfware 0.1.2 → 0.1.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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "date": "2026-09-06T17:42:05.675Z",
2
+ "date": "2026-09-20T15:39:57.442Z",
3
3
  "preset": "node-server",
4
4
  "framework": {
5
5
  "name": "nuxt",
@@ -1 +1 @@
1
- {"id":"bc384f67-a2f8-4b6b-9aa1-cb68fb93d366","timestamp":1788716517486}
1
+ {"id":"87d7ca63-ebd6-4c54-a087-e23ce5bed209","timestamp":1789918788535}
@@ -0,0 +1 @@
1
+ {"id":"87d7ca63-ebd6-4c54-a087-e23ce5bed209","timestamp":1789918788535,"prerendered":[]}
@@ -587,6 +587,7 @@ const SKIP_HOME_DOTDIRS = /* @__PURE__ */ new Set([
587
587
  ".skill-cabinet"
588
588
  ]);
589
589
  const SKIP_WALK = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".cache", "upstream"]);
590
+ const PLUGIN_SPLITS = ["cache", "marketplaces"];
590
591
  function pathExists(p) {
591
592
  try {
592
593
  return fs.existsSync(p);
@@ -617,7 +618,20 @@ function idFor(absPath) {
617
618
  return nodeCrypto.createHash("sha1").update(absPath).digest("hex").slice(0, 16);
618
619
  }
619
620
  const NAMED_SKILL_FILES = /* @__PURE__ */ new Set(["skill.md", "SKILL.md"]);
620
- const IGNORE_LOOSE_MD = /* @__PURE__ */ new Set(["readme.md", "changelog.md", "license.md", "licence.md"]);
621
+ const IGNORE_LOOSE_MD = /* @__PURE__ */ new Set([
622
+ "readme.md",
623
+ "readme.es.md",
624
+ "readme.ko.md",
625
+ "changelog.md",
626
+ "license.md",
627
+ "licence.md",
628
+ "description.md",
629
+ "security.md",
630
+ "contributing.md",
631
+ "pull_request_template.md",
632
+ "access.md",
633
+ "benchmark.md"
634
+ ]);
621
635
  function isSkillFileName(name) {
622
636
  if (NAMED_SKILL_FILES.has(name)) return true;
623
637
  if (!/\.md$/i.test(name)) return false;
@@ -690,11 +704,19 @@ function discoverRoots(opts) {
690
704
  const base = path.join(home, entry.name);
691
705
  const scopeId = entry.name.slice(1);
692
706
  for (const folder of ["skills", "skill"]) {
693
- add(scopeId, entry.name, path.join(base, folder), "user", false);
707
+ add(scopeId, entry.name, path.join(base, folder), "user", false, true);
708
+ }
709
+ const pluginsRoot = path.join(base, "plugins");
710
+ const splits = PLUGIN_SPLITS.filter((name) => isDir(path.join(pluginsRoot, name)));
711
+ if (splits.length > 0) {
712
+ for (const name of splits) {
713
+ add(`${scopeId}-plugins-${name}`, `${entry.name}/plugins/${name}`, path.join(pluginsRoot, name), "plugin", true, true);
714
+ }
715
+ } else {
716
+ add(`${scopeId}-plugins`, `${entry.name}/plugins`, pluginsRoot, "plugin", true, true);
694
717
  }
695
718
  if (entry.name === ".cursor") {
696
719
  add("cursor-builtin", ".cursor/skills-cursor", path.join(base, "skills-cursor"), "builtin", false);
697
- add("cursor-plugins", ".cursor/plugins", path.join(base, "plugins"), "plugin", true);
698
720
  }
699
721
  }
700
722
  add("gemini", ".gemini/antigravity", path.join(home, ".gemini/antigravity/skills"), "user", false);
@@ -741,7 +763,7 @@ function collectDirectSkills(root, list, nested = false, depth = 0) {
741
763
  const skillMd = findSkillFile(abs);
742
764
  if (skillMd) {
743
765
  list.push({ dir: abs, skillMd, root, ...install, file: false });
744
- } else if (nested) {
766
+ } else if (nested && !install.link) {
745
767
  collectDirectSkills({ ...root, root: abs }, list, true, depth + 1);
746
768
  }
747
769
  continue;
@@ -756,7 +778,7 @@ function walkSkillContainers(dir, root, list, depth = 0) {
756
778
  const entries = readDirents(dir);
757
779
  const base = path.basename(dir);
758
780
  if (base === "skills" || base === "skill") {
759
- collectDirectSkills({ ...root, root: dir }, list);
781
+ collectDirectSkills({ ...root, root: dir }, list, root.deep === true);
760
782
  return;
761
783
  }
762
784
  for (const entry of entries) {
@@ -974,10 +996,10 @@ function scanRoots(roots, opts) {
974
996
  const realpaths = /* @__PURE__ */ new Map();
975
997
  const found = [];
976
998
  for (const root of roots) {
977
- if (root.deep) {
978
- collectDirectSkills(root, found, true);
979
- } else if (root.recursive) {
999
+ if (root.recursive) {
980
1000
  walkSkillContainers(root.root, root, found);
1001
+ } else if (root.deep) {
1002
+ collectDirectSkills(root, found, true);
981
1003
  } else {
982
1004
  collectDirectSkills(root, found);
983
1005
  }
@@ -1074,6 +1096,9 @@ function assertSkillTarget(summary, roots, action = "delete", opts) {
1074
1096
  const target = path.resolve(summary.path);
1075
1097
  const inside = roots.some((r) => contained(target, r.root));
1076
1098
  if (!inside) throw fail(403, "Skill is outside known cabinet roots");
1099
+ const real = path.join(realPath(path.dirname(target)), path.basename(target));
1100
+ const reallyInside = roots.some((r) => contained(real, realPath(r.root)));
1101
+ if (!reallyInside) throw fail(403, "Skill resolves outside known cabinet roots");
1077
1102
  const isRoot = roots.some((r) => path.resolve(r.root) === target);
1078
1103
  if (isRoot || target === homeOf(opts)) throw fail(403, `Refusing to ${action} a cabinet root`);
1079
1104
  const install = describeInstall(target);
@@ -4056,7 +4056,7 @@ function _expandFromEnv(value) {
4056
4056
  const _inlineRuntimeConfig = {
4057
4057
  "app": {
4058
4058
  "baseURL": "/",
4059
- "buildId": "bc384f67-a2f8-4b6b-9aa1-cb68fb93d366",
4059
+ "buildId": "87d7ca63-ebd6-4c54-a087-e23ce5bed209",
4060
4060
  "buildAssetsDir": "/_nuxt/",
4061
4061
  "cdnURL": ""
4062
4062
  },
@@ -4574,107 +4574,107 @@ const assets = {
4574
4574
  "/_nuxt/BDNMzG2s.js": {
4575
4575
  "type": "text/javascript; charset=utf-8",
4576
4576
  "etag": "\"54-MasMfSk/A98C3Gn9uIOxtFxkWNw\"",
4577
- "mtime": "2026-09-06T17:42:02.389Z",
4577
+ "mtime": "2026-09-20T15:39:53.933Z",
4578
4578
  "size": 84,
4579
4579
  "path": "../public/_nuxt/BDNMzG2s.js"
4580
4580
  },
4581
4581
  "/_nuxt/BW3xJdEx.js": {
4582
4582
  "type": "text/javascript; charset=utf-8",
4583
4583
  "etag": "\"2da-zDmvCG/tLW/peRiDRHBkm+3Edw4\"",
4584
- "mtime": "2026-09-06T17:42:02.389Z",
4584
+ "mtime": "2026-09-20T15:39:53.933Z",
4585
4585
  "size": 730,
4586
4586
  "path": "../public/_nuxt/BW3xJdEx.js"
4587
4587
  },
4588
4588
  "/_nuxt/Bzmuhhqm.js": {
4589
4589
  "type": "text/javascript; charset=utf-8",
4590
4590
  "etag": "\"d4e-gP63vGSa9YNUx8rX9IMYEHDofck\"",
4591
- "mtime": "2026-09-06T17:42:02.389Z",
4591
+ "mtime": "2026-09-20T15:39:53.933Z",
4592
4592
  "size": 3406,
4593
4593
  "path": "../public/_nuxt/Bzmuhhqm.js"
4594
4594
  },
4595
4595
  "/_nuxt/CJmYvhe6.js": {
4596
4596
  "type": "text/javascript; charset=utf-8",
4597
4597
  "etag": "\"e90-WCRBrvGTpCy77xWxtXI2/olAzuA\"",
4598
- "mtime": "2026-09-06T17:42:02.389Z",
4598
+ "mtime": "2026-09-20T15:39:53.933Z",
4599
4599
  "size": 3728,
4600
4600
  "path": "../public/_nuxt/CJmYvhe6.js"
4601
4601
  },
4602
4602
  "/_nuxt/CMk5jOCS.js": {
4603
4603
  "type": "text/javascript; charset=utf-8",
4604
4604
  "etag": "\"1c01-lGS5cFf7bjhmRvkeRxUtYYhz3dw\"",
4605
- "mtime": "2026-09-06T17:42:02.389Z",
4605
+ "mtime": "2026-09-20T15:39:53.933Z",
4606
4606
  "size": 7169,
4607
4607
  "path": "../public/_nuxt/CMk5jOCS.js"
4608
4608
  },
4609
- "/_nuxt/DJQqUjJu.js": {
4610
- "type": "text/javascript; charset=utf-8",
4611
- "etag": "\"9f1d-Ja0Ka8FFBfh/wxDphcm6zq2k/Aw\"",
4612
- "mtime": "2026-09-06T17:42:02.389Z",
4613
- "size": 40733,
4614
- "path": "../public/_nuxt/DJQqUjJu.js"
4615
- },
4616
4609
  "/_nuxt/DfJzPSBi.js": {
4617
4610
  "type": "text/javascript; charset=utf-8",
4618
4611
  "etag": "\"1c1-4bfhhLtFN8RKoheWCeIIphoXgv0\"",
4619
- "mtime": "2026-09-06T17:42:02.389Z",
4612
+ "mtime": "2026-09-20T15:39:53.933Z",
4620
4613
  "size": 449,
4621
4614
  "path": "../public/_nuxt/DfJzPSBi.js"
4622
4615
  },
4616
+ "/_nuxt/DJQqUjJu.js": {
4617
+ "type": "text/javascript; charset=utf-8",
4618
+ "etag": "\"9f1d-Ja0Ka8FFBfh/wxDphcm6zq2k/Aw\"",
4619
+ "mtime": "2026-09-20T15:39:53.933Z",
4620
+ "size": 40733,
4621
+ "path": "../public/_nuxt/DJQqUjJu.js"
4622
+ },
4623
4623
  "/_nuxt/error-404.1qC5mjOI.css": {
4624
4624
  "type": "text/css; charset=utf-8",
4625
4625
  "etag": "\"97d-ClY1xVMSVUOi3w+npB6k6oGQGlM\"",
4626
- "mtime": "2026-09-06T17:42:02.389Z",
4626
+ "mtime": "2026-09-20T15:39:53.933Z",
4627
4627
  "size": 2429,
4628
4628
  "path": "../public/_nuxt/error-404.1qC5mjOI.css"
4629
4629
  },
4630
4630
  "/_nuxt/error-500.BcOmuN7K.css": {
4631
4631
  "type": "text/css; charset=utf-8",
4632
4632
  "etag": "\"772-O0XjmZiN9Tmmh4xccNArXpeMXMg\"",
4633
- "mtime": "2026-09-06T17:42:02.390Z",
4633
+ "mtime": "2026-09-20T15:39:53.933Z",
4634
4634
  "size": 1906,
4635
4635
  "path": "../public/_nuxt/error-500.BcOmuN7K.css"
4636
4636
  },
4637
4637
  "/_nuxt/EBA28doP.js": {
4638
4638
  "type": "text/javascript; charset=utf-8",
4639
4639
  "etag": "\"25526-S2M2g51QVQiD0lfERMcQ2TJCNvo\"",
4640
- "mtime": "2026-09-06T17:42:02.389Z",
4640
+ "mtime": "2026-09-20T15:39:53.933Z",
4641
4641
  "size": 152870,
4642
4642
  "path": "../public/_nuxt/EBA28doP.js"
4643
4643
  },
4644
4644
  "/_nuxt/entry.CKteCcdN.css": {
4645
4645
  "type": "text/css; charset=utf-8",
4646
4646
  "etag": "\"300c8-0WAu9DRw8xrNZdnpJNGrjxOdjtk\"",
4647
- "mtime": "2026-09-06T17:42:02.389Z",
4647
+ "mtime": "2026-09-20T15:39:53.933Z",
4648
4648
  "size": 196808,
4649
4649
  "path": "../public/_nuxt/entry.CKteCcdN.css"
4650
4650
  },
4651
+ "/_nuxt/builds/latest.json": {
4652
+ "type": "application/json",
4653
+ "etag": "\"47-tWu0moVuUg+mZHEvO0WlAqsbf7Q\"",
4654
+ "mtime": "2026-09-20T15:39:53.925Z",
4655
+ "size": 71,
4656
+ "path": "../public/_nuxt/builds/latest.json"
4657
+ },
4651
4658
  "/_nuxt/sK9Xse3o.js": {
4652
4659
  "type": "text/javascript; charset=utf-8",
4653
4660
  "etag": "\"16ebc-llACAH/LeVfHtbCyV/Od8ae+rn8\"",
4654
- "mtime": "2026-09-06T17:42:02.390Z",
4661
+ "mtime": "2026-09-20T15:39:53.933Z",
4655
4662
  "size": 93884,
4656
4663
  "path": "../public/_nuxt/sK9Xse3o.js"
4657
4664
  },
4658
- "/_nuxt/builds/latest.json": {
4665
+ "/_nuxt/builds/meta/87d7ca63-ebd6-4c54-a087-e23ce5bed209.json": {
4659
4666
  "type": "application/json",
4660
- "etag": "\"47-y/Uibk2CT3wmZ2jWKLpHXHWksOc\"",
4661
- "mtime": "2026-09-06T17:42:02.382Z",
4662
- "size": 71,
4663
- "path": "../public/_nuxt/builds/latest.json"
4667
+ "etag": "\"58-4wd6NcE3wBxF/Zfjr3lBrhnNofg\"",
4668
+ "mtime": "2026-09-20T15:39:53.921Z",
4669
+ "size": 88,
4670
+ "path": "../public/_nuxt/builds/meta/87d7ca63-ebd6-4c54-a087-e23ce5bed209.json"
4664
4671
  },
4665
4672
  "/_nuxt/rWsrwf2L.js": {
4666
4673
  "type": "text/javascript; charset=utf-8",
4667
4674
  "etag": "\"7606d-30wm5zPsBwgXACgEnaCLHx2ZHVk\"",
4668
- "mtime": "2026-09-06T17:42:02.390Z",
4675
+ "mtime": "2026-09-20T15:39:53.933Z",
4669
4676
  "size": 483437,
4670
4677
  "path": "../public/_nuxt/rWsrwf2L.js"
4671
- },
4672
- "/_nuxt/builds/meta/bc384f67-a2f8-4b6b-9aa1-cb68fb93d366.json": {
4673
- "type": "application/json",
4674
- "etag": "\"58-t+Qo1Xc7SXVzy2SU0Q6z9Np3At0\"",
4675
- "mtime": "2026-09-06T17:42:02.343Z",
4676
- "size": 88,
4677
- "path": "../public/_nuxt/builds/meta/bc384f67-a2f8-4b6b-9aa1-cb68fb93d366.json"
4678
4678
  }
4679
4679
  };
4680
4680
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shelfware-prod",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "private": true,
6
6
  "dependencies": {
package/README.md CHANGED
@@ -2,7 +2,11 @@
2
2
 
3
3
  shelfware finds the shelfware in your skill drawers.
4
4
 
5
- A local catalog of the AI-agent skills installed on this machine: every `~/.<tool>/skills` drawer (`~/.claude/skills`, `~/.cursor/skills`, `~/.codex/skills`, `~/.agents/skills`, a custom `~/.claude-work/skills`, …), Cursor plugins, Gemini Antigravity, Hermes profiles, and friends. It scans, lists, searches, renders, **edits**, audits, quarantines, restores, and permanently deletes skills, from a browser tab that only your machine can reach.
5
+ A local catalog of the AI-agent skills installed on this machine: every `~/.<tool>/skills` drawer (`~/.claude/skills`, `~/.cursor/skills`, `~/.codex/skills`, `~/.agents/skills`, a custom `~/.claude-work/skills`, …), the plugin trees beside them (`~/.<tool>/plugins`, split into the installed `cache` and `marketplaces` checkouts where a tool keeps them apart — one `plugins` drawer otherwise), Cursor's own builtin drawer (`~/.cursor/skills-cursor`), Gemini Antigravity, Hermes profiles, and friends. It scans, lists, searches, renders, **edits**, audits, quarantines, restores, and permanently deletes skills, from a browser tab that only your machine can reach.
6
+
7
+ ![shelfware in dark mode: drawers on the left, the card list in the middle, the selected skill with its audit finding on the right](docs/screenshot_dark.png)
8
+
9
+ ![shelfware in light mode](docs/screenshot_light.png)
6
10
 
7
11
  ## Run it
8
12
 
@@ -16,16 +20,16 @@ Node 20 or newer. No build step: the package ships the prebuilt app. For a perma
16
20
 
17
21
  ### First run
18
22
 
19
- Starting shelfware only reads. It walks the hidden directories directly under your home (`$HOME`), skipping caches and toolchains, and picks up every `skills/` (or `skill/`) folder it finds, plus the Cursor, Gemini Antigravity and Hermes locations above. Nothing on disk changes until you quarantine, restore, delete or save a skill yourself.
23
+ Starting shelfware only reads. It walks the hidden directories directly under your home (`$HOME`), skipping caches and toolchains, and picks up every `skills/` (or `skill/`) folder it finds — including skills grouped into sub-folders inside it, such as `~/.codex/skills/.system/<skill>` — plus each tool's `plugins/` tree and the Cursor, Gemini Antigravity and Hermes locations above. Inside a plugin tree only folders named `skills/` (or `skill/`) are read, so a plugin's own README, its `docs/`, and its source outside those folders are never catalogued; inside those folders, familiar repository documents — README, CHANGELOG, LICENCE, CONTRIBUTING, SECURITY and the rest of a short ignore list — are skipped, while any other loose `*.md` still becomes a card. Nothing on disk changes until you quarantine, restore, delete or save a skill yourself.
20
24
 
21
- Skills outside your home directory (project-level `.claude/skills` inside a repository, an arbitrary path) are not scanned in v0.1; a `--root <path>` flag is planned. To scan a different home, run `HOME=/other/home npx shelfware@latest`.
25
+ Skills outside your home directory (project-level `.claude/skills` inside a repository, an arbitrary path) are not scanned; a `--root <path>` flag is planned. To scan a different home, run `HOME=/other/home npx shelfware@latest`.
22
26
 
23
27
  ### Troubleshooting
24
28
 
25
29
  - **`npx shelfware` prints "coming soon".** Your npx cache still holds the `0.0.1` placeholder. Run `npx shelfware@latest` once; npx then caches the real release.
26
30
  - **The browser did not open.** Copy the URL from the `shelfware at http://127.0.0.1:<port>` line, or start with `--no-open` and open it yourself.
27
31
  - **Port in use.** An explicit `--port <n>` that is taken is refused at once with a one-line message; without either the flag or PORT shelfware tries 3781 through 3800. A typo in a flag prints the message and the help text instead of a stack trace.
28
- - **The catalog is empty.** Check that your skills live under `$HOME/.<tool>/skills`; other locations are not scanned yet.
32
+ - **The catalog is empty.** Check that your skills live under `$HOME/.<tool>/skills`, `$HOME/.<tool>/plugins`, or one of the Cursor, Gemini Antigravity or Hermes locations above; other locations are not scanned yet.
29
33
 
30
34
  ## What you get
31
35
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shelfware",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "shelfware finds the shelfware in your skill drawers.",
5
5
  "keywords": [
6
6
  "skills",
@@ -1 +0,0 @@
1
- {"id":"bc384f67-a2f8-4b6b-9aa1-cb68fb93d366","timestamp":1788716517486,"prerendered":[]}