shelfware 0.0.1 → 0.1.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 (206) hide show
  1. package/.output/nitro.json +15 -0
  2. package/.output/public/_nuxt/6IstVYrV.js +1 -0
  3. package/.output/public/_nuxt/9C5WUlLE.js +36 -0
  4. package/.output/public/_nuxt/BDNMzG2s.js +1 -0
  5. package/.output/public/_nuxt/BW3xJdEx.js +1 -0
  6. package/.output/public/_nuxt/BXXtFil5.js +1 -0
  7. package/.output/public/_nuxt/BZuUK3cr.js +1 -0
  8. package/.output/public/_nuxt/BeLyMmd4.js +1 -0
  9. package/.output/public/_nuxt/C_VgD9ZI.js +3 -0
  10. package/.output/public/_nuxt/DB5Vxbte.js +2 -0
  11. package/.output/public/_nuxt/DMg0j7gv.js +14 -0
  12. package/.output/public/_nuxt/builds/latest.json +1 -0
  13. package/.output/public/_nuxt/builds/meta/4c307968-4837-497b-a570-d6ee336accc4.json +1 -0
  14. package/.output/public/_nuxt/entry.b3_DJTEt.css +2 -0
  15. package/.output/public/_nuxt/error-404.CHsn7XF-.css +1 -0
  16. package/.output/public/_nuxt/error-500.Bawx2bLr.css +1 -0
  17. package/.output/server/chunks/_/api-handler.mjs +44 -0
  18. package/.output/server/chunks/_/batch.mjs +27 -0
  19. package/.output/server/chunks/_/catalog.mjs +1115 -0
  20. package/.output/server/chunks/_/detail.mjs +16 -0
  21. package/.output/server/chunks/_/error-500.mjs +19 -0
  22. package/.output/server/chunks/_/quarantine.mjs +159 -0
  23. package/.output/server/chunks/nitro/nitro.mjs +5399 -0
  24. package/.output/server/chunks/routes/api/_..._.mjs +16 -0
  25. package/.output/server/chunks/routes/api/health.get.mjs +13 -0
  26. package/.output/server/chunks/routes/api/skills/_id/file.get.mjs +25 -0
  27. package/.output/server/chunks/routes/api/skills/_id_.get.mjs +25 -0
  28. package/.output/server/chunks/routes/api/skills/_id_.put.mjs +74 -0
  29. package/.output/server/chunks/routes/api/skills/delete.post.mjs +48 -0
  30. package/.output/server/chunks/routes/api/skills/quarantine.post.mjs +26 -0
  31. package/.output/server/chunks/routes/api/skills/restore.post.mjs +26 -0
  32. package/.output/server/chunks/routes/api/skills.get.mjs +41 -0
  33. package/.output/server/chunks/routes/renderer.mjs +341 -0
  34. package/.output/server/chunks/virtual/_virtual_spa-template.mjs +3 -0
  35. package/.output/server/chunks/virtual/precomputed.mjs +3 -0
  36. package/.output/server/index.mjs +9 -0
  37. package/.output/server/node_modules/@babel/parser/lib/index.js +14611 -0
  38. package/.output/server/node_modules/@babel/parser/package.json +50 -0
  39. package/.output/server/node_modules/@vue/compiler-core/dist/compiler-core.cjs.prod.js +6801 -0
  40. package/.output/server/node_modules/@vue/compiler-core/package.json +58 -0
  41. package/.output/server/node_modules/@vue/compiler-dom/dist/compiler-dom.cjs.prod.js +692 -0
  42. package/.output/server/node_modules/@vue/compiler-dom/package.json +57 -0
  43. package/.output/server/node_modules/@vue/compiler-ssr/dist/compiler-ssr.cjs.js +1420 -0
  44. package/.output/server/node_modules/@vue/compiler-ssr/package.json +34 -0
  45. package/.output/server/node_modules/@vue/reactivity/dist/reactivity.cjs.prod.js +1903 -0
  46. package/.output/server/node_modules/@vue/reactivity/package.json +55 -0
  47. package/.output/server/node_modules/@vue/runtime-core/dist/runtime-core.cjs.prod.js +6980 -0
  48. package/.output/server/node_modules/@vue/runtime-core/package.json +52 -0
  49. package/.output/server/node_modules/@vue/runtime-dom/dist/runtime-dom.cjs.prod.js +1867 -0
  50. package/.output/server/node_modules/@vue/runtime-dom/package.json +60 -0
  51. package/.output/server/node_modules/@vue/server-renderer/dist/server-renderer.cjs.prod.js +904 -0
  52. package/.output/server/node_modules/@vue/server-renderer/package.json +53 -0
  53. package/.output/server/node_modules/@vue/shared/dist/shared.cjs.prod.js +636 -0
  54. package/.output/server/node_modules/@vue/shared/package.json +47 -0
  55. package/.output/server/node_modules/devalue/index.js +17 -0
  56. package/.output/server/node_modules/devalue/package.json +39 -0
  57. package/.output/server/node_modules/devalue/src/base64.js +60 -0
  58. package/.output/server/node_modules/devalue/src/constants.js +12 -0
  59. package/.output/server/node_modules/devalue/src/operations.js +193 -0
  60. package/.output/server/node_modules/devalue/src/parse.js +274 -0
  61. package/.output/server/node_modules/devalue/src/stringify.js +428 -0
  62. package/.output/server/node_modules/devalue/src/uneval.js +606 -0
  63. package/.output/server/node_modules/devalue/src/utils.js +185 -0
  64. package/.output/server/node_modules/entities/dist/commonjs/decode-codepoint.js +77 -0
  65. package/.output/server/node_modules/entities/dist/commonjs/decode.js +568 -0
  66. package/.output/server/node_modules/entities/dist/commonjs/generated/decode-data-html.js +7 -0
  67. package/.output/server/node_modules/entities/dist/commonjs/generated/decode-data-xml.js +7 -0
  68. package/.output/server/node_modules/entities/dist/commonjs/internal/bin-trie-flags.js +21 -0
  69. package/.output/server/node_modules/entities/dist/commonjs/internal/decode-shared.js +31 -0
  70. package/.output/server/node_modules/entities/dist/commonjs/package.json +3 -0
  71. package/.output/server/node_modules/entities/package.json +120 -0
  72. package/.output/server/node_modules/estree-walker/dist/umd/estree-walker.js +344 -0
  73. package/.output/server/node_modules/estree-walker/package.json +37 -0
  74. package/.output/server/node_modules/hookable/dist/index.mjs +257 -0
  75. package/.output/server/node_modules/hookable/package.json +53 -0
  76. package/.output/server/node_modules/nostics/dist/formatters/ansi.mjs +19 -0
  77. package/.output/server/node_modules/nostics/dist/index.mjs +187 -0
  78. package/.output/server/node_modules/nostics/package.json +125 -0
  79. package/.output/server/node_modules/source-map-js/lib/array-set.js +121 -0
  80. package/.output/server/node_modules/source-map-js/lib/base64-vlq.js +140 -0
  81. package/.output/server/node_modules/source-map-js/lib/base64.js +67 -0
  82. package/.output/server/node_modules/source-map-js/lib/binary-search.js +111 -0
  83. package/.output/server/node_modules/source-map-js/lib/mapping-list.js +79 -0
  84. package/.output/server/node_modules/source-map-js/lib/quick-sort.js +132 -0
  85. package/.output/server/node_modules/source-map-js/lib/source-map-consumer.js +1188 -0
  86. package/.output/server/node_modules/source-map-js/lib/source-map-generator.js +444 -0
  87. package/.output/server/node_modules/source-map-js/lib/source-node.js +413 -0
  88. package/.output/server/node_modules/source-map-js/lib/util.js +594 -0
  89. package/.output/server/node_modules/source-map-js/package.json +71 -0
  90. package/.output/server/node_modules/source-map-js/source-map.js +8 -0
  91. package/.output/server/node_modules/ufo/dist/index.mjs +645 -0
  92. package/.output/server/node_modules/ufo/package.json +48 -0
  93. package/.output/server/node_modules/unhead/dist/client.mjs +22 -0
  94. package/.output/server/node_modules/unhead/dist/legacy.mjs +33 -0
  95. package/.output/server/node_modules/unhead/dist/minify.mjs +183 -0
  96. package/.output/server/node_modules/unhead/dist/parser.mjs +518 -0
  97. package/.output/server/node_modules/unhead/dist/plugins.mjs +720 -0
  98. package/.output/server/node_modules/unhead/dist/server.mjs +99 -0
  99. package/.output/server/node_modules/unhead/dist/shared/unhead.-hZVKou0.mjs +400 -0
  100. package/.output/server/node_modules/unhead/dist/shared/unhead.BEmVMkeA.mjs +673 -0
  101. package/.output/server/node_modules/unhead/dist/shared/unhead.BGFxPGPQ.mjs +47 -0
  102. package/.output/server/node_modules/unhead/dist/shared/unhead.BQWSw36o.mjs +192 -0
  103. package/.output/server/node_modules/unhead/dist/shared/unhead.Bf_fPVYA.mjs +6 -0
  104. package/.output/server/node_modules/unhead/dist/shared/unhead.Bjbp1C8D.mjs +224 -0
  105. package/.output/server/node_modules/unhead/dist/shared/unhead.Bm4Y6XQI.mjs +17 -0
  106. package/.output/server/node_modules/unhead/dist/shared/unhead.C5Bksi2B.mjs +49 -0
  107. package/.output/server/node_modules/unhead/dist/shared/unhead.CGPOfp5O.mjs +32 -0
  108. package/.output/server/node_modules/unhead/dist/shared/unhead.CHEy9ana.mjs +7 -0
  109. package/.output/server/node_modules/unhead/dist/shared/unhead.D7HkBzZn.mjs +59 -0
  110. package/.output/server/node_modules/unhead/dist/shared/unhead.Dssd7L5I.mjs +196 -0
  111. package/.output/server/node_modules/unhead/dist/shared/unhead.jEy9BmRq.mjs +264 -0
  112. package/.output/server/node_modules/unhead/dist/shared/unhead.oZ7pkTV1.mjs +19 -0
  113. package/.output/server/node_modules/unhead/dist/shared/unhead.tQnRaXfX.mjs +240 -0
  114. package/.output/server/node_modules/unhead/dist/stream/client.mjs +33 -0
  115. package/.output/server/node_modules/unhead/dist/stream/iife.mjs +2 -0
  116. package/.output/server/node_modules/unhead/dist/stream/server.mjs +445 -0
  117. package/.output/server/node_modules/unhead/dist/utils.mjs +7 -0
  118. package/.output/server/node_modules/unhead/package.json +179 -0
  119. package/.output/server/node_modules/vue/dist/vue.cjs.js +80 -0
  120. package/.output/server/node_modules/vue/dist/vue.cjs.prod.js +66 -0
  121. package/.output/server/node_modules/vue/index.js +7 -0
  122. package/.output/server/node_modules/vue/index.mjs +1 -0
  123. package/.output/server/node_modules/vue/package.json +112 -0
  124. package/.output/server/node_modules/vue/server-renderer/index.mjs +1 -0
  125. package/.output/server/node_modules/vue-bundle-renderer/dist/runtime.mjs +278 -0
  126. package/.output/server/node_modules/vue-bundle-renderer/package.json +54 -0
  127. package/.output/server/node_modules/yaml/dist/compose/compose-collection.js +90 -0
  128. package/.output/server/node_modules/yaml/dist/compose/compose-doc.js +45 -0
  129. package/.output/server/node_modules/yaml/dist/compose/compose-node.js +112 -0
  130. package/.output/server/node_modules/yaml/dist/compose/compose-scalar.js +88 -0
  131. package/.output/server/node_modules/yaml/dist/compose/composer.js +224 -0
  132. package/.output/server/node_modules/yaml/dist/compose/resolve-block-map.js +117 -0
  133. package/.output/server/node_modules/yaml/dist/compose/resolve-block-scalar.js +200 -0
  134. package/.output/server/node_modules/yaml/dist/compose/resolve-block-seq.js +51 -0
  135. package/.output/server/node_modules/yaml/dist/compose/resolve-end.js +39 -0
  136. package/.output/server/node_modules/yaml/dist/compose/resolve-flow-collection.js +209 -0
  137. package/.output/server/node_modules/yaml/dist/compose/resolve-flow-scalar.js +227 -0
  138. package/.output/server/node_modules/yaml/dist/compose/resolve-props.js +148 -0
  139. package/.output/server/node_modules/yaml/dist/compose/util-contains-newline.js +36 -0
  140. package/.output/server/node_modules/yaml/dist/compose/util-empty-scalar-position.js +28 -0
  141. package/.output/server/node_modules/yaml/dist/compose/util-flow-indent-check.js +17 -0
  142. package/.output/server/node_modules/yaml/dist/compose/util-map-includes.js +15 -0
  143. package/.output/server/node_modules/yaml/dist/doc/Document.js +337 -0
  144. package/.output/server/node_modules/yaml/dist/doc/anchors.js +76 -0
  145. package/.output/server/node_modules/yaml/dist/doc/applyReviver.js +57 -0
  146. package/.output/server/node_modules/yaml/dist/doc/createNode.js +90 -0
  147. package/.output/server/node_modules/yaml/dist/doc/directives.js +178 -0
  148. package/.output/server/node_modules/yaml/dist/errors.js +62 -0
  149. package/.output/server/node_modules/yaml/dist/index.js +50 -0
  150. package/.output/server/node_modules/yaml/dist/log.js +19 -0
  151. package/.output/server/node_modules/yaml/dist/nodes/Alias.js +118 -0
  152. package/.output/server/node_modules/yaml/dist/nodes/Collection.js +151 -0
  153. package/.output/server/node_modules/yaml/dist/nodes/Node.js +40 -0
  154. package/.output/server/node_modules/yaml/dist/nodes/Pair.js +39 -0
  155. package/.output/server/node_modules/yaml/dist/nodes/Scalar.js +27 -0
  156. package/.output/server/node_modules/yaml/dist/nodes/YAMLMap.js +147 -0
  157. package/.output/server/node_modules/yaml/dist/nodes/YAMLSeq.js +115 -0
  158. package/.output/server/node_modules/yaml/dist/nodes/addPairToJSMap.js +65 -0
  159. package/.output/server/node_modules/yaml/dist/nodes/identity.js +53 -0
  160. package/.output/server/node_modules/yaml/dist/nodes/toJS.js +39 -0
  161. package/.output/server/node_modules/yaml/dist/parse/cst-scalar.js +218 -0
  162. package/.output/server/node_modules/yaml/dist/parse/cst-stringify.js +63 -0
  163. package/.output/server/node_modules/yaml/dist/parse/cst-visit.js +99 -0
  164. package/.output/server/node_modules/yaml/dist/parse/cst.js +112 -0
  165. package/.output/server/node_modules/yaml/dist/parse/lexer.js +723 -0
  166. package/.output/server/node_modules/yaml/dist/parse/line-counter.js +41 -0
  167. package/.output/server/node_modules/yaml/dist/parse/parser.js +980 -0
  168. package/.output/server/node_modules/yaml/dist/public-api.js +107 -0
  169. package/.output/server/node_modules/yaml/dist/schema/Schema.js +39 -0
  170. package/.output/server/node_modules/yaml/dist/schema/common/map.js +19 -0
  171. package/.output/server/node_modules/yaml/dist/schema/common/null.js +17 -0
  172. package/.output/server/node_modules/yaml/dist/schema/common/seq.js +19 -0
  173. package/.output/server/node_modules/yaml/dist/schema/common/string.js +16 -0
  174. package/.output/server/node_modules/yaml/dist/schema/core/bool.js +21 -0
  175. package/.output/server/node_modules/yaml/dist/schema/core/float.js +47 -0
  176. package/.output/server/node_modules/yaml/dist/schema/core/int.js +42 -0
  177. package/.output/server/node_modules/yaml/dist/schema/core/schema.js +25 -0
  178. package/.output/server/node_modules/yaml/dist/schema/json/schema.js +64 -0
  179. package/.output/server/node_modules/yaml/dist/schema/tags.js +99 -0
  180. package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/binary.js +70 -0
  181. package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/bool.js +29 -0
  182. package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/float.js +50 -0
  183. package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/int.js +76 -0
  184. package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/merge.js +71 -0
  185. package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/omap.js +77 -0
  186. package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +82 -0
  187. package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/schema.js +41 -0
  188. package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/set.js +96 -0
  189. package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +105 -0
  190. package/.output/server/node_modules/yaml/dist/stringify/foldFlowLines.js +151 -0
  191. package/.output/server/node_modules/yaml/dist/stringify/stringify.js +132 -0
  192. package/.output/server/node_modules/yaml/dist/stringify/stringifyCollection.js +155 -0
  193. package/.output/server/node_modules/yaml/dist/stringify/stringifyComment.js +24 -0
  194. package/.output/server/node_modules/yaml/dist/stringify/stringifyDocument.js +87 -0
  195. package/.output/server/node_modules/yaml/dist/stringify/stringifyNumber.js +27 -0
  196. package/.output/server/node_modules/yaml/dist/stringify/stringifyPair.js +152 -0
  197. package/.output/server/node_modules/yaml/dist/stringify/stringifyString.js +338 -0
  198. package/.output/server/node_modules/yaml/dist/visit.js +236 -0
  199. package/.output/server/node_modules/yaml/package.json +97 -0
  200. package/.output/server/package.json +28 -0
  201. package/LICENSE +21 -0
  202. package/README.md +57 -1
  203. package/bin/launch.mjs +158 -0
  204. package/bin/shelfware.mjs +6 -0
  205. package/package.json +64 -5
  206. package/cli.mjs +0 -3
@@ -0,0 +1,1115 @@
1
+ import nodeCrypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { f as fail } from './api-handler.mjs';
6
+ import { parse } from 'yaml';
7
+
8
+ const SEVERITY_ORDER = {
9
+ none: 0,
10
+ low: 1,
11
+ medium: 2,
12
+ high: 3,
13
+ critical: 4
14
+ };
15
+ const AUDIT_SKIP_WALK = /* @__PURE__ */ new Set([
16
+ "node_modules",
17
+ ".git",
18
+ "dist",
19
+ ".cache",
20
+ "upstream",
21
+ "__pycache__",
22
+ ".venv",
23
+ "venv"
24
+ ]);
25
+ const COMPANION_EXTENSIONS = /* @__PURE__ */ new Set([
26
+ ".sh",
27
+ ".bash",
28
+ ".zsh",
29
+ ".fish",
30
+ ".ps1",
31
+ ".py",
32
+ ".js",
33
+ ".mjs",
34
+ ".cjs"
35
+ ]);
36
+ const MAX_FILE_BYTES = 256e3;
37
+ const MAX_TREE_BYTES = 512e3;
38
+ const MAX_DEPTH = 4;
39
+ const MAX_FILES = 12;
40
+ const SHELL_FENCE_LANGUAGES = /* @__PURE__ */ new Set([
41
+ "",
42
+ "sh",
43
+ "shell",
44
+ "bash",
45
+ "zsh",
46
+ "fish",
47
+ "console",
48
+ "terminal"
49
+ ]);
50
+ const DENYLIST_PATTERN = /(?:\bdo\s+not\b|\bdon't\b|\bnever\b|\bmust\s+not\b|\bavoid\b|\bforbidden\b|\bdenylist\b|\bblocklist\b)/i;
51
+ const SHELLISH_LINE = /^(?:[-*+]\s+)?(?:[$>]\s*)?(?:sudo\s+)?(?:curl|wget|rm|git|bash|sh|zsh|fish|python(?:3)?|node|npm|npx|pnpm|yarn|eval|exec)\b/i;
52
+ const IMPERATIVE_LINE = /^(?:[-*+]\s+)?(?:read|open|copy|upload|download|delete|remove|write|modify|send|execute|run)\b/i;
53
+ const PROMPT_COMMAND = /^(?:[-*+]\s+)?(?:ignore|disregard)\b/i;
54
+ const AUDIT_RULES = [
55
+ {
56
+ severity: "critical",
57
+ rule: "shell.remote-pipe",
58
+ pattern: /(?:curl|wget)\b[^\n|]{0,500}\|\s*(?:sudo\s+)?(?:sh|bash|zsh)\b/gi,
59
+ message: "Downloads are piped directly to a shell"
60
+ },
61
+ {
62
+ severity: "high",
63
+ rule: "filesystem.broad-delete",
64
+ pattern: /\brm\s+-[a-zA-Z]*r[a-zA-Z]*f[a-zA-Z]*\s+(?:\/|~|\$HOME)(?:\s|$)/gi,
65
+ message: "Command may recursively delete a broad filesystem root"
66
+ },
67
+ {
68
+ severity: "high",
69
+ rule: "credentials.sensitive-path",
70
+ pattern: /(?:\.ssh\/(?:id_|config)|\.aws\/credentials|\.config\/gcloud|login\.keychain)/gi,
71
+ message: "References a sensitive credential location"
72
+ },
73
+ {
74
+ severity: "high",
75
+ rule: "execution.obfuscated",
76
+ pattern: /(?:eval|exec)\s*\([^\n]{0,200}(?:base64|b64decode)/gi,
77
+ message: "Executes obfuscated or decoded content"
78
+ },
79
+ {
80
+ severity: "medium",
81
+ rule: "prompt.override",
82
+ pattern: /(?:ignore|disregard)\s+(?:all\s+)?(?:previous|prior|system)\s+instructions/gi,
83
+ message: "Contains an instruction-override phrase"
84
+ },
85
+ {
86
+ severity: "medium",
87
+ rule: "git.global-config",
88
+ pattern: /git\s+config\s+--global/gi,
89
+ message: "Modifies global Git configuration"
90
+ },
91
+ {
92
+ severity: "low",
93
+ rule: "network.download",
94
+ pattern: /\b(?:curl|wget)\b/gi,
95
+ message: "Uses a network download command"
96
+ }
97
+ ];
98
+ const SUBSUMED = {
99
+ "network.download": /* @__PURE__ */ new Set(["shell.remote-pipe"])
100
+ };
101
+
102
+ function maxSeverity(values) {
103
+ let best = "none";
104
+ for (const value of values) {
105
+ if (SEVERITY_ORDER[value] > SEVERITY_ORDER[best]) best = value;
106
+ }
107
+ return best;
108
+ }
109
+ function isDocument(rel) {
110
+ const ext = path.extname(rel).toLowerCase();
111
+ if (ext === ".md" || ext === ".txt" || ext === ".rst") return true;
112
+ return /(^|\/)skill\.md$/i.test(rel.replace(/\\/g, "/"));
113
+ }
114
+ function newlineStarts(content) {
115
+ const starts = [0];
116
+ for (let i = 0; i < content.length; i += 1) {
117
+ if (content.charCodeAt(i) === 10) starts.push(i + 1);
118
+ }
119
+ return starts;
120
+ }
121
+ function lineNumberAt(starts, index) {
122
+ let lo = 0;
123
+ let hi = starts.length - 1;
124
+ while (lo <= hi) {
125
+ const mid = lo + hi >> 1;
126
+ if (starts[mid] <= index) lo = mid + 1;
127
+ else hi = mid - 1;
128
+ }
129
+ return hi + 1;
130
+ }
131
+ function fenceLanguage(line) {
132
+ const match = line.match(/^\s*```\s*([\w+-]*)/);
133
+ return match ? match[1].toLowerCase() : null;
134
+ }
135
+ function lineContext(rel, lines, lineIndex, rule, inShellFence) {
136
+ const line = lines[lineIndex] || "";
137
+ if (DENYLIST_PATTERN.test(line)) return "denylist";
138
+ const previous = [...lines.slice(Math.max(0, lineIndex - 4), lineIndex)].reverse().find((item) => item.trim());
139
+ if (previous && DENYLIST_PATTERN.test(previous) && (previous.trim().endsWith(":") || previous.trim().startsWith("#"))) {
140
+ return "denylist";
141
+ }
142
+ const stripped = line.trim();
143
+ if (isDocument(rel)) {
144
+ if (inShellFence) return "command_invocation";
145
+ if (SHELLISH_LINE.test(stripped) || IMPERATIVE_LINE.test(stripped)) return "command_invocation";
146
+ if (rule === "prompt.override" && PROMPT_COMMAND.test(stripped)) return "command_invocation";
147
+ return "documentation";
148
+ }
149
+ if (stripped.startsWith("#") || stripped.startsWith("//") || stripped.startsWith("*") || stripped.startsWith("/*")) {
150
+ return "documentation";
151
+ }
152
+ return "command_invocation";
153
+ }
154
+ function collectFromText(rel, content, findings) {
155
+ const lines = content.split(/\r?\n/);
156
+ let starts = null;
157
+ const fence = lines.map(() => false);
158
+ let inFence = false;
159
+ let language = "";
160
+ for (let i = 0; i < lines.length; i += 1) {
161
+ const lang = fenceLanguage(lines[i]);
162
+ if (lang != null) {
163
+ if (inFence) {
164
+ inFence = false;
165
+ language = "";
166
+ } else {
167
+ inFence = true;
168
+ language = lang;
169
+ }
170
+ }
171
+ fence[i] = inFence && SHELL_FENCE_LANGUAGES.has(language);
172
+ }
173
+ for (const spec of AUDIT_RULES) {
174
+ spec.pattern.lastIndex = 0;
175
+ let match;
176
+ while (match = spec.pattern.exec(content)) {
177
+ if (!starts) starts = newlineStarts(content);
178
+ const line = lineNumberAt(starts, match.index);
179
+ const lineIndex = Math.max(0, line - 1);
180
+ const context = lineContext(rel, lines, lineIndex, spec.rule, fence[lineIndex]);
181
+ if (context !== "command_invocation") continue;
182
+ if (findings.some((item) => item.file === rel && item.line === line && item.rule === spec.rule)) continue;
183
+ const subsumedBy = SUBSUMED[spec.rule];
184
+ if (subsumedBy && findings.some((item) => item.file === rel && item.line === line && subsumedBy.has(item.rule))) continue;
185
+ findings.push({ severity: spec.severity, rule: spec.rule, message: spec.message, file: rel, line });
186
+ }
187
+ }
188
+ }
189
+ function listTextFiles(root) {
190
+ const files = [];
191
+ const walk = (current, rel, depth) => {
192
+ if (depth > MAX_DEPTH || files.length >= MAX_FILES) return;
193
+ let entries;
194
+ try {
195
+ entries = fs.readdirSync(current, { withFileTypes: true });
196
+ } catch {
197
+ return;
198
+ }
199
+ for (const entry of entries) {
200
+ if (AUDIT_SKIP_WALK.has(entry.name)) continue;
201
+ const abs = path.join(current, entry.name);
202
+ const nextRel = rel ? `${rel}/${entry.name}` : entry.name;
203
+ let listed;
204
+ try {
205
+ listed = fs.lstatSync(abs);
206
+ } catch {
207
+ continue;
208
+ }
209
+ if (listed.isSymbolicLink()) continue;
210
+ if (listed.isDirectory()) {
211
+ walk(abs, nextRel, depth + 1);
212
+ continue;
213
+ }
214
+ if (!listed.isFile()) continue;
215
+ const ext = path.extname(entry.name).toLowerCase();
216
+ if (!COMPANION_EXTENSIONS.has(ext)) continue;
217
+ files.push({ abs, rel: nextRel, size: listed.size });
218
+ }
219
+ };
220
+ walk(root, "", 0);
221
+ return files;
222
+ }
223
+ function resolveWalkRoot(root) {
224
+ try {
225
+ const listed = fs.lstatSync(root);
226
+ if (listed.isSymbolicLink()) return fs.realpathSync(root);
227
+ } catch {
228
+ }
229
+ return path.resolve(root);
230
+ }
231
+ function auditSkill(input) {
232
+ const findings = [];
233
+ const skillRel = path.basename(input.skillFile);
234
+ if (typeof input.text === "string" && input.text) {
235
+ collectFromText(skillRel, input.text, findings);
236
+ }
237
+ if (!input.fileOnly) {
238
+ const start = resolveWalkRoot(input.root);
239
+ const already = path.resolve(input.skillFile);
240
+ let treeBytes = Buffer.byteLength(input.text || "", "utf8");
241
+ for (const file of listTextFiles(start)) {
242
+ if (path.resolve(file.abs) === already) continue;
243
+ if (file.size > MAX_FILE_BYTES) continue;
244
+ if (treeBytes + file.size > MAX_TREE_BYTES) break;
245
+ let content = "";
246
+ try {
247
+ content = fs.readFileSync(file.abs, "utf8");
248
+ } catch {
249
+ continue;
250
+ }
251
+ treeBytes += Buffer.byteLength(content, "utf8");
252
+ collectFromText(file.rel.replace(/\\/g, "/"), content, findings);
253
+ }
254
+ }
255
+ const hasPipe = findings.some((item) => item.rule === "shell.remote-pipe");
256
+ const visible = hasPipe ? findings.filter((item) => item.rule !== "network.download") : findings;
257
+ return {
258
+ severity: maxSeverity(visible.map((item) => item.severity)),
259
+ findings: visible
260
+ };
261
+ }
262
+
263
+ const FRONTMATTER_PARSE_ERROR = "YAML frontmatter could not be parsed";
264
+ function parseFrontmatter(text) {
265
+ const normalized = text.replace(/\r\n/g, "\n");
266
+ if (!normalized.startsWith("---")) return { data: {}, content: normalized, raw: "" };
267
+ const end = normalized.indexOf("\n---", 3);
268
+ if (end === -1) return { data: {}, content: normalized, raw: "" };
269
+ const raw = normalized.slice(3, end).replace(/^\n/, "");
270
+ let data = {};
271
+ try {
272
+ const parsed = parse(raw, { logLevel: "error" });
273
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
274
+ data = parsed;
275
+ }
276
+ } catch {
277
+ data = { _parseError: FRONTMATTER_PARSE_ERROR };
278
+ }
279
+ const content = normalized.slice(end + 4).replace(/^\n\n?/, "");
280
+ return { data, content, raw };
281
+ }
282
+ function stringField(data, key) {
283
+ const value = data[key];
284
+ return typeof value === "string" ? value : "";
285
+ }
286
+
287
+ const STANDING_ORDER = /\b(?:must\s+always\s+apply|always\s+apply|on\s+every\s+(?:request|turn|message|prompt)|before\s+every\s+(?:request|turn|message|prompt)|hooked\s+into\s+every|injected\s+at\s+session\s+start)\b/gi;
288
+ const VERBAL_NEGATION = /\b(?:do\s+not|don't|does\s+not|doesn't|did\s+not|didn't|can\s+not|can't|cannot|never)\b/gi;
289
+ function pathExists$1(p) {
290
+ try {
291
+ return fs.existsSync(p);
292
+ } catch {
293
+ return false;
294
+ }
295
+ }
296
+ function isTruthy(value) {
297
+ return value === true || value === "true" || value === "yes";
298
+ }
299
+ function isFalsy(value) {
300
+ return value === false || value === "false" || value === "no";
301
+ }
302
+ function hooksAt(dir) {
303
+ const nested = path.join(dir, "hooks", "hooks.json");
304
+ if (pathExists$1(nested)) return nested;
305
+ const flat = path.join(dir, "hooks.json");
306
+ if (pathExists$1(flat)) return flat;
307
+ return "";
308
+ }
309
+ function findSkillHooks(skillDir, { fileOnly = false } = {}) {
310
+ if (fileOnly) return "";
311
+ return hooksAt(skillDir);
312
+ }
313
+ function lastClauseCut(before) {
314
+ let cut = Math.max(before.lastIndexOf("."), before.lastIndexOf("!"), before.lastIndexOf("?"), before.lastIndexOf(";"));
315
+ const coord = /(?:,\s*)?\b(?:but|yet)\b|,\s*\b(?:and|or)\b/gi;
316
+ for (const match of before.matchAll(coord)) {
317
+ cut = Math.max(cut, match.index + match[0].length - 1);
318
+ }
319
+ return cut;
320
+ }
321
+ function clauseBefore(text, index) {
322
+ const before = text.slice(0, index);
323
+ return before.slice(lastClauseCut(before) + 1);
324
+ }
325
+ function negationApplies(clause) {
326
+ const re = new RegExp(VERBAL_NEGATION.source, "gi");
327
+ let last = null;
328
+ for (const match of clause.matchAll(re)) last = match;
329
+ if (!last || last.index === void 0) return false;
330
+ const rest = clause.slice(last.index + last[0].length).trim();
331
+ const words = rest ? rest.split(/\s+/).filter(Boolean) : [];
332
+ return words.length <= 5;
333
+ }
334
+ function hasStandingOrder(text) {
335
+ if (!text) return false;
336
+ const re = new RegExp(STANDING_ORDER.source, "gi");
337
+ for (const match of text.matchAll(re)) {
338
+ if (match.index !== void 0 && !negationApplies(clauseBefore(text, match.index))) return true;
339
+ }
340
+ return false;
341
+ }
342
+ function metadataOf(frontmatter) {
343
+ const meta = frontmatter.metadata;
344
+ return meta && typeof meta === "object" && !Array.isArray(meta) ? meta : {};
345
+ }
346
+ function skillInvocation({ skillDir, fileOnly = false, frontmatter = {}, description = "" }) {
347
+ var _a;
348
+ const hookPath = findSkillHooks(skillDir, { fileOnly });
349
+ if (hookPath) {
350
+ return { invocation: "hook", invocationEvidence: `hooks.json at ${hookPath}` };
351
+ }
352
+ const meta = metadataOf(frontmatter);
353
+ if (isTruthy(frontmatter.sessionStart) || isTruthy(meta.sessionStart) || isTruthy(frontmatter.alwaysApply) || isTruthy(meta.alwaysApply)) {
354
+ const key = isTruthy(frontmatter.sessionStart) || isTruthy(meta.sessionStart) ? "sessionStart" : "alwaysApply";
355
+ return { invocation: "hook", invocationEvidence: `frontmatter ${key}` };
356
+ }
357
+ const blurb = description || (typeof frontmatter.description === "string" ? frontmatter.description : "");
358
+ if (hasStandingOrder(blurb)) {
359
+ return { invocation: "hook", invocationEvidence: "description standing order" };
360
+ }
361
+ if (isTruthy(frontmatter["disable-model-invocation"])) {
362
+ const userInvokable = (_a = frontmatter["user-invokable"]) != null ? _a : meta["user-invokable"];
363
+ if (isFalsy(userInvokable)) {
364
+ return { invocation: "off", invocationEvidence: "frontmatter disable-model-invocation and user-invokable false" };
365
+ }
366
+ return { invocation: "user", invocationEvidence: "frontmatter disable-model-invocation" };
367
+ }
368
+ return { invocation: "model", invocationEvidence: "default: the model may call this" };
369
+ }
370
+
371
+ function createOriginContext(home) {
372
+ return { home: path.resolve(home), cache: /* @__PURE__ */ new Map() };
373
+ }
374
+ const PLUGIN_JSON = [
375
+ ["plugin.json"],
376
+ [".cursor-plugin", "plugin.json"],
377
+ [".claude-plugin", "plugin.json"],
378
+ [".plugin", "plugin.json"]
379
+ ];
380
+ function githubFromString(raw) {
381
+ if (!raw) return null;
382
+ const text = String(raw).trim().replace(/^["']|["']$/g, "");
383
+ const match = text.match(/(?:https?:\/\/|git@|ssh:\/\/git@)github\.com[:/]+([^\s#?]+)/i);
384
+ if (match) {
385
+ const parts = match[1].replace(/\.git$/i, "").split("/").filter(Boolean);
386
+ if (parts.length >= 2) {
387
+ const spec = `${parts[0]}/${parts[1]}`;
388
+ return { kind: "github", label: spec, url: `https://github.com/${spec}` };
389
+ }
390
+ }
391
+ if (/^[\w.-]+\/[\w.-]+$/.test(text)) {
392
+ return { kind: "github", label: text, url: `https://github.com/${text}` };
393
+ }
394
+ return null;
395
+ }
396
+ function originFromText(raw) {
397
+ if (typeof raw !== "string" || !raw.trim()) return null;
398
+ const github = githubFromString(raw);
399
+ if (github) return github;
400
+ const text = raw.trim().replace(/^["']|["']$/g, "");
401
+ if (!/^https?:\/\//i.test(text)) return null;
402
+ try {
403
+ const parsed = new URL(text);
404
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
405
+ parsed.username = "";
406
+ parsed.password = "";
407
+ const label = `${parsed.host}${parsed.pathname}`.replace(/\/+$/, "");
408
+ return { kind: "url", label, url: parsed.href };
409
+ } catch {
410
+ return null;
411
+ }
412
+ }
413
+ function originFromValue(value) {
414
+ if (typeof value === "string") return originFromText(value);
415
+ if (value && typeof value === "object" && typeof value.url === "string") {
416
+ return originFromText(value.url);
417
+ }
418
+ return null;
419
+ }
420
+ function withOrigin(origin, via, certainty) {
421
+ if (!origin) return null;
422
+ return { ...origin, via, certainty };
423
+ }
424
+ function originFromFrontmatter(data) {
425
+ const rawMeta = data.metadata;
426
+ const meta = rawMeta && typeof rawMeta === "object" && !Array.isArray(rawMeta) ? rawMeta : {};
427
+ for (const key of ["source", "repository"]) {
428
+ const found = originFromValue(data[key]) || originFromValue(meta[key]);
429
+ if (found) return withOrigin(found, "frontmatter", "attested");
430
+ }
431
+ for (const key of ["homepage", "url"]) {
432
+ const found = originFromValue(data[key]) || originFromValue(meta[key]);
433
+ if ((found == null ? void 0 : found.kind) === "github") return withOrigin(found, "frontmatter", "attested");
434
+ }
435
+ return null;
436
+ }
437
+ function originFromPath(p) {
438
+ const norm = p.replace(/\\/g, "/");
439
+ const market = norm.match(/\/marketplaces\/github\.com\/([^/]+)\/([^/]+)/);
440
+ if (market) {
441
+ return withOrigin(
442
+ { kind: "github", label: `${market[1]}/${market[2]}`, url: `https://github.com/${market[1]}/${market[2]}` },
443
+ "path",
444
+ "attested"
445
+ );
446
+ }
447
+ const nested = norm.match(/\/github\.com\/([^/]+)\/([^/]+)/);
448
+ if (nested && nested[1] !== "www") {
449
+ return withOrigin(
450
+ { kind: "github", label: `${nested[1]}/${nested[2]}`, url: `https://github.com/${nested[1]}/${nested[2]}` },
451
+ "path",
452
+ "attested"
453
+ );
454
+ }
455
+ return null;
456
+ }
457
+ function originFromPluginFile(file) {
458
+ try {
459
+ const st = fs.statSync(file, { throwIfNoEntry: false });
460
+ if (!st || !st.isFile()) return null;
461
+ const data = JSON.parse(fs.readFileSync(file, "utf8"));
462
+ const repo = originFromValue(data.repository);
463
+ if (repo) return repo;
464
+ const home = originFromText(data.homepage);
465
+ return (home == null ? void 0 : home.kind) === "github" ? home : null;
466
+ } catch {
467
+ return null;
468
+ }
469
+ }
470
+ function originFromGitDir(dir) {
471
+ const gitPath = path.join(dir, ".git");
472
+ try {
473
+ const listed = fs.lstatSync(gitPath, { throwIfNoEntry: false });
474
+ if (!listed) return null;
475
+ let configPath = "";
476
+ if (listed.isFile()) {
477
+ const text = fs.readFileSync(gitPath, "utf8");
478
+ const marker = text.match(/gitdir:\s*(.+)/i);
479
+ if (!marker) return null;
480
+ let gitdir = marker[1].trim();
481
+ if (!path.isAbsolute(gitdir)) gitdir = path.resolve(dir, gitdir);
482
+ configPath = path.join(gitdir, "config");
483
+ } else if (listed.isDirectory()) {
484
+ configPath = path.join(gitPath, "config");
485
+ } else {
486
+ return null;
487
+ }
488
+ const config = fs.readFileSync(configPath, "utf8");
489
+ const url = config.match(/\[remote "origin"\][\s\S]*?url\s*=\s*(\S+)/);
490
+ return url ? originFromText(url[1].replace(/^["']|["']$/g, "")) : null;
491
+ } catch {
492
+ return null;
493
+ }
494
+ }
495
+ function originStartDir(start) {
496
+ const resolved = path.resolve(start);
497
+ try {
498
+ const listed = fs.lstatSync(resolved);
499
+ if (listed.isSymbolicLink()) {
500
+ try {
501
+ if (fs.statSync(resolved).isFile()) return path.dirname(resolved);
502
+ } catch {
503
+ return path.dirname(resolved);
504
+ }
505
+ }
506
+ if (listed.isFile()) return path.dirname(resolved);
507
+ } catch {
508
+ }
509
+ return resolved;
510
+ }
511
+ function fromCachedOrigin(hit, startDir) {
512
+ if (!hit) return null;
513
+ const here = path.resolve(hit.at) === path.resolve(startDir);
514
+ return withOrigin(hit.origin, hit.via, here ? "attested" : "inferred");
515
+ }
516
+ function originFromAncestors(start, ctx) {
517
+ var _a;
518
+ const chain = [];
519
+ let current = originStartDir(start);
520
+ const startDir = current;
521
+ for (let i = 0; i < 14; i += 1) {
522
+ if (ctx.cache.has(current)) {
523
+ const hit = (_a = ctx.cache.get(current)) != null ? _a : null;
524
+ for (const dir of chain) ctx.cache.set(dir, hit);
525
+ return fromCachedOrigin(hit, startDir);
526
+ }
527
+ chain.push(current);
528
+ for (const parts of PLUGIN_JSON) {
529
+ const found = originFromPluginFile(path.join(current, ...parts));
530
+ if (found) {
531
+ const packed = { origin: found, via: "plugin", at: current };
532
+ for (const dir of chain) ctx.cache.set(dir, packed);
533
+ return fromCachedOrigin(packed, startDir);
534
+ }
535
+ }
536
+ const git = originFromGitDir(current);
537
+ if (git) {
538
+ const packed = { origin: git, via: "git", at: current };
539
+ for (const dir of chain) ctx.cache.set(dir, packed);
540
+ return fromCachedOrigin(packed, startDir);
541
+ }
542
+ const parent = path.dirname(current);
543
+ if (parent === current || parent === ctx.home) break;
544
+ current = parent;
545
+ }
546
+ for (const dir of chain) ctx.cache.set(dir, null);
547
+ return null;
548
+ }
549
+ function inferOrigin(dir, data, linkTarget, ctx) {
550
+ const yaml = originFromFrontmatter(data);
551
+ if (yaml) return yaml;
552
+ const fromHere = originFromPath(dir);
553
+ if (fromHere) return fromHere;
554
+ if (linkTarget) {
555
+ const resolved = path.isAbsolute(linkTarget) ? path.resolve(linkTarget) : path.resolve(path.dirname(dir), linkTarget);
556
+ const fromLink = originFromPath(resolved) || originFromAncestors(resolved, ctx);
557
+ if (fromLink) return fromLink;
558
+ }
559
+ return originFromAncestors(dir, ctx);
560
+ }
561
+
562
+ function homeOf(opts) {
563
+ var _a;
564
+ return realPath(path.resolve((_a = opts == null ? void 0 : opts.home) != null ? _a : os.homedir()));
565
+ }
566
+ function quarantineRoot(opts) {
567
+ return path.join(homeOf(opts), ".skill-cabinet", "quarantine");
568
+ }
569
+ const SKIP_HOME_DOTDIRS = /* @__PURE__ */ new Set([
570
+ ".cache",
571
+ ".local",
572
+ ".npm",
573
+ ".nvm",
574
+ ".rustup",
575
+ ".cargo",
576
+ ".docker",
577
+ ".mozilla",
578
+ ".config",
579
+ ".steam",
580
+ ".var",
581
+ ".wine",
582
+ ".thumbnails",
583
+ ".Trash",
584
+ ".android",
585
+ ".gradle",
586
+ ".java",
587
+ ".skill-cabinet"
588
+ ]);
589
+ const SKIP_WALK = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".cache", "upstream"]);
590
+ function pathExists(p) {
591
+ try {
592
+ return fs.existsSync(p);
593
+ } catch {
594
+ return false;
595
+ }
596
+ }
597
+ function isDir(p) {
598
+ try {
599
+ return fs.statSync(p).isDirectory();
600
+ } catch {
601
+ return false;
602
+ }
603
+ }
604
+ function realPath(p) {
605
+ try {
606
+ return fs.realpathSync(p);
607
+ } catch {
608
+ return path.resolve(p);
609
+ }
610
+ }
611
+ function contained(child, parent) {
612
+ const c = path.resolve(child);
613
+ const p = path.resolve(parent);
614
+ return c === p || c.startsWith(p + path.sep);
615
+ }
616
+ function idFor(absPath) {
617
+ return nodeCrypto.createHash("sha1").update(absPath).digest("hex").slice(0, 16);
618
+ }
619
+ 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
+ function isSkillFileName(name) {
622
+ if (NAMED_SKILL_FILES.has(name)) return true;
623
+ if (!/\.md$/i.test(name)) return false;
624
+ return !IGNORE_LOOSE_MD.has(name.toLowerCase());
625
+ }
626
+ function findSkillFile(dir) {
627
+ for (const name of ["SKILL.md", "skill.md"]) {
628
+ const p = path.join(dir, name);
629
+ if (pathExists(p) && !isDir(p)) return p;
630
+ }
631
+ return null;
632
+ }
633
+ function readLinkTarget(p) {
634
+ try {
635
+ return fs.readlinkSync(p);
636
+ } catch {
637
+ return "";
638
+ }
639
+ }
640
+ function describeInstall(p) {
641
+ let link = false;
642
+ let file = false;
643
+ let linkTarget = "";
644
+ let dev = 0;
645
+ let ino = 0;
646
+ try {
647
+ const listed = fs.lstatSync(p);
648
+ link = listed.isSymbolicLink();
649
+ if (link) {
650
+ linkTarget = readLinkTarget(p);
651
+ try {
652
+ const followed = fs.statSync(p);
653
+ file = followed.isFile();
654
+ dev = followed.dev;
655
+ ino = followed.ino;
656
+ } catch {
657
+ file = false;
658
+ }
659
+ } else {
660
+ file = listed.isFile();
661
+ dev = listed.dev;
662
+ ino = listed.ino;
663
+ }
664
+ } catch {
665
+ }
666
+ return { link, file, linkTarget, dev, ino };
667
+ }
668
+ function readDirents(dir) {
669
+ try {
670
+ return fs.readdirSync(dir, { withFileTypes: true });
671
+ } catch {
672
+ return [];
673
+ }
674
+ }
675
+ function discoverRoots(opts) {
676
+ const home = homeOf(opts);
677
+ const roots = [];
678
+ const seen = /* @__PURE__ */ new Set();
679
+ const add = (scopeId, scopeLabel, root, kind, recursive = false, deep = false) => {
680
+ if (!pathExists(root) || !isDir(root)) return;
681
+ const resolved = realPath(root);
682
+ if (seen.has(resolved)) return;
683
+ seen.add(resolved);
684
+ roots.push({ scopeId, scopeLabel, root: resolved, kind, recursive, deep });
685
+ };
686
+ for (const entry of readDirents(home)) {
687
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
688
+ if (!entry.name.startsWith(".")) continue;
689
+ if (SKIP_HOME_DOTDIRS.has(entry.name)) continue;
690
+ const base = path.join(home, entry.name);
691
+ const scopeId = entry.name.slice(1);
692
+ for (const folder of ["skills", "skill"]) {
693
+ add(scopeId, entry.name, path.join(base, folder), "user", false);
694
+ }
695
+ if (entry.name === ".cursor") {
696
+ 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
+ }
699
+ }
700
+ add("gemini", ".gemini/antigravity", path.join(home, ".gemini/antigravity/skills"), "user", false);
701
+ add("gemini", ".gemini/antigravity (global)", path.join(home, ".gemini/antigravity/global_skills"), "user", false);
702
+ const hermesProfiles = path.join(home, ".hermes", "profiles");
703
+ for (const entry of readDirents(hermesProfiles)) {
704
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
705
+ if (entry.name.startsWith(".")) continue;
706
+ add(`hermes-profile:${entry.name}`, `Hermes profile \xB7 ${entry.name}`, path.join(hermesProfiles, entry.name, "skills"), "user", false, true);
707
+ }
708
+ const quarantine = quarantineRoot(opts);
709
+ for (const entry of readDirents(quarantine)) {
710
+ if (!entry.isDirectory()) continue;
711
+ const folder = path.join(quarantine, entry.name);
712
+ if (!pathExists(folder) || !isDir(folder)) continue;
713
+ const resolved = realPath(folder);
714
+ if (seen.has(resolved)) continue;
715
+ seen.add(resolved);
716
+ roots.push({
717
+ scopeId: "quarantine",
718
+ scopeLabel: "Quarantine",
719
+ root: resolved,
720
+ kind: "quarantine",
721
+ recursive: false,
722
+ fromScope: entry.name
723
+ });
724
+ }
725
+ return roots;
726
+ }
727
+ function tokenEstimateFor(text) {
728
+ return Math.ceil(text.length / 4);
729
+ }
730
+ function collectDirectSkills(root, list, nested = false, depth = 0) {
731
+ if (nested && depth > 14) return;
732
+ for (const entry of readDirents(root.root)) {
733
+ if (SKIP_WALK.has(entry.name)) continue;
734
+ const abs = path.resolve(path.join(root.root, entry.name));
735
+ const install = describeInstall(abs);
736
+ if (install.link && !install.file && !isDir(abs)) {
737
+ list.push({ dir: abs, skillMd: abs, root, ...install, file: false, dangling: true });
738
+ continue;
739
+ }
740
+ if (isDir(abs)) {
741
+ const skillMd = findSkillFile(abs);
742
+ if (skillMd) {
743
+ list.push({ dir: abs, skillMd, root, ...install, file: false });
744
+ } else if (nested) {
745
+ collectDirectSkills({ ...root, root: abs }, list, true, depth + 1);
746
+ }
747
+ continue;
748
+ }
749
+ if (install.file && isSkillFileName(entry.name)) {
750
+ list.push({ dir: abs, skillMd: abs, root, ...install, file: true });
751
+ }
752
+ }
753
+ }
754
+ function walkSkillContainers(dir, root, list, depth = 0) {
755
+ if (depth > 14) return;
756
+ const entries = readDirents(dir);
757
+ const base = path.basename(dir);
758
+ if (base === "skills" || base === "skill") {
759
+ collectDirectSkills({ ...root, root: dir }, list);
760
+ return;
761
+ }
762
+ for (const entry of entries) {
763
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
764
+ if (SKIP_WALK.has(entry.name)) continue;
765
+ walkSkillContainers(path.join(dir, entry.name), root, list, depth + 1);
766
+ }
767
+ }
768
+ function dirSizeAndFiles(dir) {
769
+ try {
770
+ const followed = fs.statSync(dir);
771
+ if (followed.isFile()) {
772
+ return { files: [{ path: path.basename(dir), size: followed.size, mtime: followed.mtimeMs }], bytes: followed.size };
773
+ }
774
+ } catch {
775
+ }
776
+ const files = [];
777
+ let bytes = 0;
778
+ const walk = (current, rel, depth) => {
779
+ if (depth > 8 || files.length > 250) return;
780
+ for (const entry of readDirents(current)) {
781
+ if (SKIP_WALK.has(entry.name)) continue;
782
+ const abs = path.join(current, entry.name);
783
+ const nextRel = rel ? `${rel}/${entry.name}` : entry.name;
784
+ if (entry.isSymbolicLink()) continue;
785
+ if (entry.isDirectory()) {
786
+ walk(abs, nextRel, depth + 1);
787
+ } else if (entry.isFile()) {
788
+ let size = 0;
789
+ let mtime = 0;
790
+ try {
791
+ const st = fs.statSync(abs);
792
+ size = st.size;
793
+ mtime = st.mtimeMs;
794
+ bytes += size;
795
+ } catch {
796
+ }
797
+ files.push({ path: nextRel, size, mtime });
798
+ }
799
+ }
800
+ };
801
+ walk(dir, "", 0);
802
+ files.sort((a, b) => a.path.localeCompare(b.path));
803
+ return { files, bytes };
804
+ }
805
+ function danglingSummary(item) {
806
+ const { dir, root } = item;
807
+ let mtime = 0;
808
+ try {
809
+ mtime = fs.lstatSync(dir).mtimeMs;
810
+ } catch {
811
+ }
812
+ const base = path.basename(dir);
813
+ const slug = base.replace(/\.md$/i, "");
814
+ return {
815
+ id: idFor(dir),
816
+ name: slug,
817
+ slug,
818
+ description: "",
819
+ frontmatter: {},
820
+ scopeId: root.scopeId,
821
+ scopeLabel: root.scopeLabel,
822
+ kind: root.kind,
823
+ path: dir,
824
+ skillFile: dir,
825
+ skillRel: base,
826
+ file: false,
827
+ link: true,
828
+ linkTarget: item.linkTarget || "",
829
+ origin: null,
830
+ invocation: "model",
831
+ invocationEvidence: "",
832
+ contentHash: null,
833
+ risk: "none",
834
+ findings: [],
835
+ copies: [],
836
+ mtime,
837
+ skillSize: 0,
838
+ tokenEstimate: 0,
839
+ physicality: "broken",
840
+ refTarget: "",
841
+ refSkillId: "",
842
+ quarantined: root.kind === "quarantine",
843
+ fromScope: root.fromScope || ""
844
+ };
845
+ }
846
+ function summarizeSkill(item, memo, realpaths, origins) {
847
+ const { dir, skillMd, root } = item;
848
+ if (item.dangling) return danglingSummary(item);
849
+ const identity = item.dev || item.ino ? `${item.dev}:${item.ino}` : realPath(dir);
850
+ const memoKey = `${identity}|${item.file ? "file" : "dir"}`;
851
+ let shared = memo.get(memoKey);
852
+ if (!shared) {
853
+ let mtime = 0;
854
+ let size = 0;
855
+ let raw;
856
+ try {
857
+ const st = fs.statSync(skillMd);
858
+ mtime = st.mtimeMs;
859
+ size = st.size;
860
+ raw = fs.readFileSync(skillMd);
861
+ } catch {
862
+ return null;
863
+ }
864
+ const text = raw.toString("utf8");
865
+ const { data } = parseFrontmatter(text);
866
+ shared = {
867
+ data,
868
+ mtime,
869
+ size,
870
+ text,
871
+ contentHash: nodeCrypto.createHash("sha256").update(raw).digest("hex"),
872
+ audited: auditSkill({ root: dir, skillFile: skillMd, text, fileOnly: Boolean(item.file) })
873
+ };
874
+ memo.set(memoKey, shared);
875
+ }
876
+ let refTarget = "";
877
+ if (item.link) {
878
+ refTarget = realpaths.get(identity) || "";
879
+ if (!refTarget) {
880
+ refTarget = realPath(dir);
881
+ realpaths.set(identity, refTarget);
882
+ }
883
+ }
884
+ const base = path.basename(dir);
885
+ const slug = item.file ? base.replace(/\.md$/i, "") : base;
886
+ const name = stringField(shared.data, "name") || stringField(shared.data, "displayName") || slug;
887
+ const description = stringField(shared.data, "description");
888
+ const when = skillInvocation({ skillDir: dir, fileOnly: Boolean(item.file), frontmatter: shared.data, description });
889
+ return {
890
+ id: idFor(dir),
891
+ name,
892
+ slug,
893
+ description,
894
+ frontmatter: shared.data,
895
+ scopeId: root.scopeId,
896
+ scopeLabel: root.scopeLabel,
897
+ kind: root.kind,
898
+ path: dir,
899
+ skillFile: skillMd,
900
+ skillRel: path.basename(skillMd),
901
+ file: Boolean(item.file),
902
+ link: Boolean(item.link),
903
+ linkTarget: item.linkTarget || "",
904
+ origin: inferOrigin(dir, shared.data, item.linkTarget, origins),
905
+ invocation: when.invocation,
906
+ invocationEvidence: when.invocationEvidence,
907
+ contentHash: shared.contentHash,
908
+ risk: shared.audited.severity,
909
+ findings: shared.audited.findings.slice(),
910
+ copies: [],
911
+ mtime: shared.mtime,
912
+ skillSize: shared.size,
913
+ tokenEstimate: tokenEstimateFor(shared.text),
914
+ physicality: item.link ? "reference" : "physical",
915
+ refTarget,
916
+ refSkillId: "",
917
+ quarantined: root.kind === "quarantine",
918
+ fromScope: root.fromScope || ""
919
+ };
920
+ }
921
+ function attachCopies(skills) {
922
+ const byHash = /* @__PURE__ */ new Map();
923
+ for (const skill of skills) {
924
+ if (skill.physicality !== "physical") continue;
925
+ if (!skill.contentHash) continue;
926
+ const list = byHash.get(skill.contentHash) || [];
927
+ list.push(skill);
928
+ byHash.set(skill.contentHash, list);
929
+ }
930
+ for (const skill of skills) {
931
+ if (skill.physicality !== "physical") {
932
+ skill.copies = [];
933
+ continue;
934
+ }
935
+ const group = skill.contentHash && byHash.get(skill.contentHash) || [];
936
+ skill.copies = group.filter((other) => other.id !== skill.id).map((other) => ({ id: other.id, scopeLabel: other.scopeLabel, path: other.path }));
937
+ }
938
+ return skills;
939
+ }
940
+ function censusOf(skills) {
941
+ const live = skills.filter((skill) => !skill.quarantined);
942
+ const physical = live.filter((skill) => skill.physicality === "physical");
943
+ const byHash = /* @__PURE__ */ new Map();
944
+ for (const skill of physical) {
945
+ if (!skill.contentHash) continue;
946
+ byHash.set(skill.contentHash, (byHash.get(skill.contentHash) || 0) + 1);
947
+ }
948
+ let duplicateCopies = 0;
949
+ let duplicateBytes = 0;
950
+ let tokenEstimate = 0;
951
+ for (const skill of physical) {
952
+ tokenEstimate += skill.tokenEstimate;
953
+ const twins = skill.contentHash && byHash.get(skill.contentHash) || 1;
954
+ if (twins > 1) {
955
+ duplicateCopies += 1;
956
+ duplicateBytes += dirSizeAndFiles(skill.path).bytes;
957
+ }
958
+ }
959
+ return {
960
+ total: live.length,
961
+ physical: physical.length,
962
+ unique: byHash.size,
963
+ duplicateCopies,
964
+ duplicateBytes,
965
+ references: live.filter((skill) => skill.physicality === "reference").length,
966
+ broken: live.filter((skill) => skill.physicality === "broken").length,
967
+ duplicates: duplicateCopies,
968
+ tokenEstimate
969
+ };
970
+ }
971
+ function scanRoots(roots, opts) {
972
+ const origins = createOriginContext(homeOf(opts));
973
+ const memo = /* @__PURE__ */ new Map();
974
+ const realpaths = /* @__PURE__ */ new Map();
975
+ const found = [];
976
+ for (const root of roots) {
977
+ if (root.deep) {
978
+ collectDirectSkills(root, found, true);
979
+ } else if (root.recursive) {
980
+ walkSkillContainers(root.root, root, found);
981
+ } else {
982
+ collectDirectSkills(root, found);
983
+ }
984
+ }
985
+ const byPath = /* @__PURE__ */ new Map();
986
+ for (const item of found) byPath.set(item.dir, item);
987
+ const skills = [];
988
+ const byId = /* @__PURE__ */ new Map();
989
+ for (const item of byPath.values()) {
990
+ const summary = summarizeSkill(item, memo, realpaths, origins);
991
+ if (!summary) continue;
992
+ skills.push(summary);
993
+ byId.set(summary.id, summary);
994
+ }
995
+ const byReal = /* @__PURE__ */ new Map();
996
+ for (const skill of skills) {
997
+ if (skill.physicality === "physical") byReal.set(realPath(skill.path), skill.id);
998
+ }
999
+ for (const skill of skills) {
1000
+ if (skill.physicality === "reference") skill.refSkillId = byReal.get(skill.refTarget) || "";
1001
+ }
1002
+ attachCopies(skills);
1003
+ skills.sort((a, b) => {
1004
+ const scope = a.scopeLabel.localeCompare(b.scopeLabel);
1005
+ if (scope !== 0) return scope;
1006
+ return a.name.localeCompare(b.name);
1007
+ });
1008
+ return { roots, skills, byId, census: censusOf(skills) };
1009
+ }
1010
+ function scanSkills(opts) {
1011
+ return scanRoots(discoverRoots(opts), opts);
1012
+ }
1013
+ function toCatalogSkill(skill) {
1014
+ return {
1015
+ id: skill.id,
1016
+ name: skill.name,
1017
+ slug: skill.slug,
1018
+ description: skill.description,
1019
+ scopeId: skill.scopeId,
1020
+ scopeLabel: skill.scopeLabel,
1021
+ kind: skill.kind,
1022
+ path: skill.path,
1023
+ skillRel: skill.skillRel,
1024
+ file: skill.file,
1025
+ link: skill.link,
1026
+ linkTarget: skill.linkTarget,
1027
+ origin: skill.origin,
1028
+ invocation: skill.invocation,
1029
+ invocationEvidence: skill.invocationEvidence,
1030
+ risk: skill.risk,
1031
+ physicality: skill.physicality,
1032
+ refTarget: skill.refTarget,
1033
+ refSkillId: skill.refSkillId,
1034
+ copyCount: skill.copies.length,
1035
+ copies: skill.copies,
1036
+ mtime: skill.mtime,
1037
+ quarantined: Boolean(skill.quarantined),
1038
+ fromScope: skill.fromScope || "",
1039
+ skillSize: skill.skillSize,
1040
+ tokenEstimate: skill.tokenEstimate
1041
+ };
1042
+ }
1043
+ function readSkill(summary) {
1044
+ const { skillFile, ...rest } = summary;
1045
+ const base = { ...rest, copyCount: summary.copies.length };
1046
+ if (summary.physicality === "broken") {
1047
+ return { ...base, frontmatter: {}, frontmatterRaw: "", body: "", source: "", files: [], bytes: 0 };
1048
+ }
1049
+ const text = fs.readFileSync(skillFile, "utf8");
1050
+ const { data, content, raw } = parseFrontmatter(text);
1051
+ const { files, bytes } = dirSizeAndFiles(summary.path);
1052
+ return { ...base, frontmatter: data, frontmatterRaw: raw, body: content, source: text, files, bytes };
1053
+ }
1054
+ const TEXT_EXTENSIONS = /\.(md|txt|ya?ml|json|js|mjs|cjs|ts|tsx|jsx|py|sh|html|css|svg|toml|xml|csv|rst)$/i;
1055
+ const MAX_PREVIEW_BYTES = 15e5;
1056
+ function readSkillFile(summary, relPath) {
1057
+ if (summary.file) {
1058
+ const abs2 = realPath(summary.skillFile);
1059
+ const st2 = fs.statSync(abs2);
1060
+ if (st2.size > MAX_PREVIEW_BYTES) throw fail(413, "File too large to preview");
1061
+ return { path: path.basename(summary.skillFile), size: st2.size, binary: false, content: fs.readFileSync(abs2).toString("utf8") };
1062
+ }
1063
+ const abs = realPath(path.resolve(summary.path, path.normalize(relPath)));
1064
+ const root = realPath(summary.path);
1065
+ if (abs !== root && !abs.startsWith(root + path.sep)) throw fail(400, "Path escapes skill directory");
1066
+ if (!pathExists(abs) || isDir(abs)) throw fail(404, "File not found");
1067
+ const st = fs.statSync(abs);
1068
+ if (st.size > MAX_PREVIEW_BYTES) throw fail(413, "File too large to preview");
1069
+ const buf = fs.readFileSync(abs);
1070
+ const looksText = !buf.includes(0) && TEXT_EXTENSIONS.test(abs);
1071
+ return { path: path.relative(root, abs), size: st.size, binary: !looksText, content: looksText ? buf.toString("utf8") : null };
1072
+ }
1073
+ function assertSkillTarget(summary, roots, action = "delete", opts) {
1074
+ const target = path.resolve(summary.path);
1075
+ const inside = roots.some((r) => contained(target, r.root));
1076
+ if (!inside) throw fail(403, "Skill is outside known cabinet roots");
1077
+ const isRoot = roots.some((r) => path.resolve(r.root) === target);
1078
+ if (isRoot || target === homeOf(opts)) throw fail(403, `Refusing to ${action} a cabinet root`);
1079
+ const install = describeInstall(target);
1080
+ const isDeadLink = install.link && !install.file && !isDir(target);
1081
+ const isFolderSkill = isDir(target) && findSkillFile(target);
1082
+ const isFileSkill = install.file && isSkillFileName(path.basename(target));
1083
+ if (!isDeadLink && !isFolderSkill && !isFileSkill) throw fail(400, "Not a skill path");
1084
+ return target;
1085
+ }
1086
+ function assertDeletable(summary, roots, opts) {
1087
+ return assertSkillTarget(summary, roots, "delete", opts);
1088
+ }
1089
+ function deleteSkillDir(target) {
1090
+ const st = fs.lstatSync(target);
1091
+ if (st.isSymbolicLink() || st.isFile()) {
1092
+ fs.unlinkSync(target);
1093
+ return;
1094
+ }
1095
+ fs.rmSync(target, { recursive: true, force: false });
1096
+ }
1097
+
1098
+ const CATALOG_TTL_MS = 15e3;
1099
+ let state = { at: 0, index: null };
1100
+ function getIndex(opts = {}) {
1101
+ var _a;
1102
+ const now = (_a = opts.now) != null ? _a : Date.now;
1103
+ if (!opts.force && state.index && now() - state.at < CATALOG_TTL_MS) return state.index;
1104
+ const index = scanSkills(opts);
1105
+ state = { at: now(), index };
1106
+ return index;
1107
+ }
1108
+ function invalidate() {
1109
+ state = { at: 0, index: null };
1110
+ }
1111
+ function scannedAt() {
1112
+ return state.at;
1113
+ }
1114
+
1115
+ export { assertSkillTarget as a, readSkill as b, contained as c, readSkillFile as d, assertDeletable as e, deleteSkillDir as f, getIndex as g, homeOf as h, invalidate as i, isDir as j, quarantineRoot as q, realPath as r, scannedAt as s, toCatalogSkill as t };