@wrongstack/core 0.313.0 → 0.313.1

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 (182) hide show
  1. package/dist/chronicle/index.js +84 -6391
  2. package/dist/chronicle/project-server.js +84 -4868
  3. package/dist/chunk-2BUUIMQB.js +225 -0
  4. package/dist/chunk-2FK4WTFF.js +207 -0
  5. package/dist/chunk-2FQL4PDC.js +292 -0
  6. package/dist/chunk-2KKODIQU.js +2256 -0
  7. package/dist/chunk-2YU4WVBW.js +134 -0
  8. package/dist/chunk-3E244UDA.js +356 -0
  9. package/dist/chunk-3XPVB7BA.js +135 -0
  10. package/dist/chunk-4LJJYBAU.js +388 -0
  11. package/dist/chunk-4VWO3ZXL.js +342 -0
  12. package/dist/chunk-4XV6IR37.js +187 -0
  13. package/dist/chunk-4ZHTKJHM.js +183 -0
  14. package/dist/chunk-52RTVH4M.js +361 -0
  15. package/dist/chunk-57GEWK5X.js +26 -0
  16. package/dist/chunk-5DSMEVA4.js +931 -0
  17. package/dist/chunk-5RP3QTRL.js +1223 -0
  18. package/dist/chunk-5S437PRC.js +50 -0
  19. package/dist/chunk-5XDUGTKN.js +511 -0
  20. package/dist/chunk-62V2AFVG.js +422 -0
  21. package/dist/chunk-65RDIOR2.js +2470 -0
  22. package/dist/chunk-6AKOTGLM.js +597 -0
  23. package/dist/chunk-7ZHZE5EO.js +28 -0
  24. package/dist/chunk-A455ATZB.js +140 -0
  25. package/dist/chunk-AD7ZFZCS.js +46 -0
  26. package/dist/chunk-AI4LLQ2U.js +856 -0
  27. package/dist/chunk-ASX6AW36.js +789 -0
  28. package/dist/chunk-AT4V4XGI.js +3522 -0
  29. package/dist/chunk-AWNQ6ED6.js +311 -0
  30. package/dist/chunk-B4AMLZPB.js +9 -0
  31. package/dist/chunk-BAKVJLEV.js +417 -0
  32. package/dist/chunk-BJHVULHO.js +55 -0
  33. package/dist/chunk-CB2HFKTP.js +9 -0
  34. package/dist/chunk-CE4ZOCOI.js +1640 -0
  35. package/dist/chunk-CQQL4ZOQ.js +151 -0
  36. package/dist/chunk-DEIQMBD2.js +65 -0
  37. package/dist/chunk-E6I5RXVK.js +58 -0
  38. package/dist/chunk-EFEHDQGY.js +1589 -0
  39. package/dist/chunk-EODDVJBM.js +82 -0
  40. package/dist/chunk-EPC3GZD4.js +308 -0
  41. package/dist/chunk-FAYRX2AD.js +13 -0
  42. package/dist/chunk-FFSX2QDQ.js +362 -0
  43. package/dist/chunk-FHAMQS7Q.js +5856 -0
  44. package/dist/chunk-FIV4IJL7.js +2263 -0
  45. package/dist/chunk-FNXVT7HF.js +52 -0
  46. package/dist/chunk-FSWCWSTS.js +395 -0
  47. package/dist/chunk-FW5LDDMJ.js +16464 -0
  48. package/dist/chunk-FXQLIX3K.js +211 -0
  49. package/dist/chunk-G5AAKEEZ.js +228 -0
  50. package/dist/chunk-G5S3S5ZN.js +38 -0
  51. package/dist/chunk-GAZEWQEZ.js +231 -0
  52. package/dist/chunk-GFJOMSHG.js +125 -0
  53. package/dist/chunk-GFXZTHNI.js +158 -0
  54. package/dist/chunk-GM54GJBU.js +960 -0
  55. package/dist/chunk-GPQ3NJPR.js +900 -0
  56. package/dist/chunk-GTCU6DZ3.js +1 -0
  57. package/dist/chunk-H5E77KUJ.js +15 -0
  58. package/dist/chunk-H72H4XDU.js +1023 -0
  59. package/dist/chunk-HA4ZYA6T.js +178 -0
  60. package/dist/chunk-I45KAW2N.js +5020 -0
  61. package/dist/chunk-IQ35HMQL.js +1396 -0
  62. package/dist/chunk-IYFBFX34.js +86 -0
  63. package/dist/chunk-JJPMWPB6.js +109 -0
  64. package/dist/chunk-JTHKOYWU.js +663 -0
  65. package/dist/chunk-JVGPR3TS.js +365 -0
  66. package/dist/chunk-JVKB2TBL.js +48 -0
  67. package/dist/chunk-JX7VBZDK.js +31 -0
  68. package/dist/chunk-K3G6JBUC.js +5232 -0
  69. package/dist/chunk-K4YUNKU7.js +589 -0
  70. package/dist/chunk-KBZGE6LN.js +710 -0
  71. package/dist/chunk-KDQJCT2J.js +45 -0
  72. package/dist/chunk-KI4L2DUL.js +560 -0
  73. package/dist/chunk-KJ2ZTQSL.js +47 -0
  74. package/dist/chunk-KSJ5UTP7.js +154 -0
  75. package/dist/chunk-LHOA5DWA.js +28 -0
  76. package/dist/chunk-LX5QWLBL.js +27 -0
  77. package/dist/chunk-LZDDLNCZ.js +279 -0
  78. package/dist/chunk-M2WF6AXG.js +115 -0
  79. package/dist/chunk-M7BS2K6U.js +1 -0
  80. package/dist/chunk-NDBIPFNY.js +102 -0
  81. package/dist/chunk-NEMIWEXC.js +1199 -0
  82. package/dist/chunk-NFAYHPRH.js +323 -0
  83. package/dist/chunk-O37BQ65S.js +21 -0
  84. package/dist/chunk-O74MJHXU.js +187 -0
  85. package/dist/chunk-OJZEX73F.js +112 -0
  86. package/dist/chunk-PAAWLLZX.js +74 -0
  87. package/dist/chunk-PFXFQQRT.js +339 -0
  88. package/dist/chunk-PFZO3C6H.js +14 -0
  89. package/dist/chunk-PHH7LD45.js +113 -0
  90. package/dist/chunk-QRTG4XTI.js +188 -0
  91. package/dist/chunk-QVOVMXJR.js +1351 -0
  92. package/dist/chunk-RCQYT2I4.js +757 -0
  93. package/dist/chunk-RD6DEE36.js +1178 -0
  94. package/dist/chunk-RJIZEJ42.js +30 -0
  95. package/dist/chunk-RPOA7IAE.js +150 -0
  96. package/dist/chunk-SN2DQRL6.js +1172 -0
  97. package/dist/chunk-SQJVFEBV.js +41 -0
  98. package/dist/chunk-SQP64RCS.js +5391 -0
  99. package/dist/chunk-SVT4RNTR.js +49 -0
  100. package/dist/chunk-SWUFN7XM.js +43 -0
  101. package/dist/chunk-SXMVYC5H.js +174 -0
  102. package/dist/chunk-SZ6AYUIZ.js +23 -0
  103. package/dist/chunk-T3KGZBU5.js +3171 -0
  104. package/dist/chunk-TBRL2RCC.js +9 -0
  105. package/dist/chunk-TRT2HRWZ.js +18 -0
  106. package/dist/chunk-TYW2GI5I.js +1077 -0
  107. package/dist/chunk-UIWONWVG.js +4959 -0
  108. package/dist/chunk-VB3H5WWB.js +151 -0
  109. package/dist/chunk-W4A6QA3D.js +258 -0
  110. package/dist/chunk-WBPVKEEE.js +227 -0
  111. package/dist/chunk-WEBMSQP2.js +123 -0
  112. package/dist/chunk-WOEZV3Q4.js +333 -0
  113. package/dist/chunk-WQQON3SV.js +473 -0
  114. package/dist/chunk-WQTOVMGV.js +119 -0
  115. package/dist/chunk-WZO23AWO.js +2779 -0
  116. package/dist/chunk-XBDP4QPW.js +7 -0
  117. package/dist/chunk-XEBHSJLT.js +103 -0
  118. package/dist/chunk-XQ2ECNK3.js +1 -0
  119. package/dist/chunk-XQ2XGIBF.js +2086 -0
  120. package/dist/chunk-Y2GE5TSI.js +355 -0
  121. package/dist/chunk-YBUJCZ57.js +351 -0
  122. package/dist/chunk-YNCVSFGF.js +149 -0
  123. package/dist/chunk-YUPMXOGP.js +282 -0
  124. package/dist/chunk-YY6APYUM.js +49 -0
  125. package/dist/chunk-Z2IJUO45.js +691 -0
  126. package/dist/chunk-ZEHUCJHY.js +129 -0
  127. package/dist/chunk-ZYU2YOJ6.js +1496 -0
  128. package/dist/chunk-ZZ4V3WIZ.js +163 -0
  129. package/dist/coordination/agents/index.js +145 -6260
  130. package/dist/coordination/index.js +365 -34324
  131. package/dist/coordination/mailbox-project-server.js +48 -3276
  132. package/dist/coordination/provider-status-tracker.d.ts +4 -0
  133. package/dist/core/agent.d.ts +14 -1
  134. package/dist/core/index.js +267 -12480
  135. package/dist/core/model-ref.js +5 -23
  136. package/dist/core/provider-runner.d.ts +36 -1
  137. package/dist/design/index.js +50 -1087
  138. package/dist/execution/index.d.ts +1 -1
  139. package/dist/execution/index.js +1599 -21888
  140. package/dist/execution/prompt-enhancer.js +16 -535
  141. package/dist/execution/retry-policy.d.ts +31 -1
  142. package/dist/extension/index.js +7 -311
  143. package/dist/goal/index.js +33 -3435
  144. package/dist/hooks/index.js +14 -1138
  145. package/dist/hq/index.js +112 -5139
  146. package/dist/hq/protocol.js +21 -668
  147. package/dist/index.js +1410 -92656
  148. package/dist/infrastructure/index.js +64 -4811
  149. package/dist/kernel/events/provider-events.d.ts +15 -0
  150. package/dist/kernel/index.js +14 -992
  151. package/dist/models/index.js +40 -1117
  152. package/dist/notifications/index.js +3 -108
  153. package/dist/observability/index.js +17 -279
  154. package/dist/plugin/index.js +584 -14835
  155. package/dist/prompts/index.js +14 -948
  156. package/dist/registry/index.js +9 -822
  157. package/dist/registry-R23UBE7H.js +14 -0
  158. package/dist/replay/index.js +6 -84
  159. package/dist/review-finding-store-DKAV3AS6.js +21 -0
  160. package/dist/review-report-store-OWY3EC3N.js +19 -0
  161. package/dist/security/index.js +114 -2202
  162. package/dist/session-catalog/index.js +36 -2103
  163. package/dist/session-catalog/project-server.js +42 -1435
  164. package/dist/skills/index.js +35 -1578
  165. package/dist/storage/index.js +182 -15600
  166. package/dist/tasking/index.js +10 -676
  167. package/dist/tools/index.js +52 -9590
  168. package/dist/types/config/autonomy.d.ts +5 -0
  169. package/dist/types/config/ui.d.ts +1 -1
  170. package/dist/types/errors.d.ts +18 -0
  171. package/dist/types/index.js +121 -1342
  172. package/dist/types/provider-runner.d.ts +8 -0
  173. package/dist/types/session-markers.js +8 -144
  174. package/dist/utils/error.js +3 -4
  175. package/dist/utils/expect-defined.js +3 -9
  176. package/dist/utils/heap-watchdog.js +6 -706
  177. package/dist/utils/index.js +269 -5685
  178. package/dist/utils/sage-output-block.js +5 -42
  179. package/dist/wiring/proxy-rewrite.d.ts +18 -0
  180. package/dist/wiring/proxy-rewrite.js +18 -121
  181. package/dist/worktree/index.js +6 -887
  182. package/package.json +4 -4
@@ -1,1582 +1,39 @@
1
- // src/skills/foreign-sources.ts
2
- var FOREIGN_SKILL_TOOLS = [
3
- { id: "agents", subdir: "skills" },
4
- // shared store (asm / agentskills.io ecosystem)
5
- { id: "codex", subdir: "skills" },
6
- // OpenAI Codex CLI
7
- { id: "gemini", subdir: "skills" },
8
- // Gemini CLI
9
- { id: "cursor", subdir: "skills" },
10
- // Cursor (standard subdir, aligned with skills.sh ecosystem)
11
- { id: "qwen", subdir: "skills" },
12
- // Qwen Code
13
- { id: "trae", subdir: "skills" },
14
- // Trae
15
- { id: "windsurf", subdir: "skills" }
16
- // Windsurf
17
- ];
18
- var KNOWN_FOREIGN_IDS = new Set(FOREIGN_SKILL_TOOLS.map((t) => t.id));
19
- function resolveForeignToolIds(opt) {
20
- return resolveForeignToolIdsWithWarnings(opt).ids;
21
- }
22
- function resolveForeignToolIdsWithWarnings(opt) {
23
- if (opt === false) return { ids: [], unknownIds: [] };
24
- if (Array.isArray(opt)) {
25
- const ids = [];
26
- const unknownIds = [];
27
- for (const id of opt) {
28
- if (KNOWN_FOREIGN_IDS.has(id)) ids.push(id);
29
- else unknownIds.push(id);
30
- }
31
- return { ids, unknownIds };
32
- }
33
- return { ids: FOREIGN_SKILL_TOOLS.map((t) => t.id), unknownIds: [] };
34
- }
35
- function securityScoreToTier(score) {
36
- if (score == null || Number.isNaN(score)) return "low";
37
- if (score < 30) return "low";
38
- if (score < 70) return "medium";
39
- return "high";
40
- }
41
-
42
- // src/skills/frontmatter.ts
43
- var SCALAR_KEYS = /* @__PURE__ */ new Set([
44
- "name",
45
- "description",
46
- "trigger",
47
- "version",
48
- "license",
49
- "compatibility"
50
- ]);
51
- function parseSkillFrontmatter(raw) {
52
- const text = normalizeLineEndings(raw);
53
- if (!text.startsWith("---")) return {};
54
- const end = text.indexOf("\n---", 4);
55
- if (end === -1) return {};
56
- return parseFrontmatterBlock(text.slice(4, end));
57
- }
58
- function stripFrontmatter(raw) {
59
- const text = normalizeLineEndings(raw);
60
- if (!text.startsWith("---")) return text;
61
- const end = text.indexOf("\n---", 4);
62
- if (end === -1) return text;
63
- let body = text.slice(end + 4);
64
- if (body.startsWith("\n")) body = body.slice(1);
65
- return body;
66
- }
67
- function normalizeLineEndings(s) {
68
- return s.replace(/\r\n?/g, "\n");
69
- }
70
- function parseFrontmatterBlock(block) {
71
- const out = {};
72
- const lines = block.split("\n");
73
- let i = 0;
74
- while (i < lines.length) {
75
- const line = lines[i] ?? "";
76
- const m = /^([a-zA-Z][a-zA-Z0-9_-]*):\s*(.*)$/.exec(line);
77
- if (!m) {
78
- i++;
79
- continue;
80
- }
81
- const key = m[1] ?? "";
82
- const rest = (m[2] ?? "").trim();
83
- if (key === "metadata") {
84
- const map = {};
85
- i++;
86
- while (i < lines.length) {
87
- const sub = lines[i] ?? "";
88
- const sm = /^\s+([a-zA-Z0-9_.-]+):\s*(.*)$/.exec(sub);
89
- if (!sm) break;
90
- map[sm[1] ?? ""] = unquote((sm[2] ?? "").trim());
91
- i++;
92
- }
93
- if (Object.keys(map).length > 0) out.metadata = map;
94
- continue;
95
- }
96
- if (rest === "|" || rest === ">") {
97
- const collected = [];
98
- i++;
99
- while (i < lines.length) {
100
- const sub = lines[i] ?? "";
101
- if (sub === "" || sub.startsWith(" ") || sub.startsWith(" ")) {
102
- collected.push(sub.replace(/^\s+/, ""));
103
- i++;
104
- } else break;
105
- }
106
- out[normalizeKey(key)] = collected.join("\n").trim();
107
- continue;
108
- }
109
- if (key === "allowed-tools" || key === "allowedTools" || key === "required-capabilities" || key === "requiredCapabilities" || key === "required-tools" || key === "requiredTools" || key === "optional-capabilities" || key === "optionalCapabilities") {
110
- const values = rest.replace(/^\[/, "").replace(/\]$/, "").split(/[\s,]+/).map(unquote).filter(Boolean);
111
- out[normalizeKey(key)] = values;
112
- i++;
113
- continue;
114
- }
115
- if (SCALAR_KEYS.has(key)) {
116
- out[key] = unquote(rest);
117
- i++;
118
- continue;
119
- }
120
- i++;
121
- }
122
- return out;
123
- }
124
- function normalizeKey(key) {
125
- if (key === "allowed-tools") return "allowedTools";
126
- if (key === "required-capabilities") return "requiredCapabilities";
127
- if (key === "required-tools") return "requiredTools";
128
- if (key === "optional-capabilities") return "optionalCapabilities";
129
- return key;
130
- }
131
- function unquote(s) {
132
- if (s.length >= 2 && (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'"))) {
133
- return s.slice(1, -1);
134
- }
135
- return s;
136
- }
137
- function isValidSkillNameFormat(name) {
138
- return name.length >= 1 && name.length <= 64 && /^[a-z0-9]+(-[a-z0-9]+)*$/.test(name);
139
- }
140
- function validateSkillName(name, parentDirName) {
141
- const errors = [];
142
- if (!name || name.trim().length === 0) {
143
- errors.push("name is empty");
144
- return errors;
145
- }
146
- if (name.length > 64) errors.push(`name is ${name.length} characters (max 64)`);
147
- if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) {
148
- errors.push(
149
- "name must be lowercase letters, digits, and single hyphens only (no leading/trailing/consecutive hyphens)"
150
- );
151
- }
152
- if (parentDirName !== void 0 && name !== parentDirName) {
153
- errors.push(`name "${name}" must match its parent directory "${parentDirName}"`);
154
- }
155
- return errors;
156
- }
157
-
158
- // src/skills/github-fetcher.ts
159
- import { createWriteStream } from "node:fs";
160
- import * as fs from "node:fs/promises";
161
- import * as os from "node:os";
162
- import * as path from "node:path";
163
- import { Readable } from "node:stream";
164
- import { pipeline } from "node:stream/promises";
165
- import { createGunzip } from "node:zlib";
166
-
167
- // src/utils/error.ts
168
- function toErrorMessage(err) {
169
- return err instanceof Error ? err.message : String(err);
170
- }
171
-
172
- // src/types/errors.ts
173
- var ERROR_CODES = {
174
- // Provider
175
- PROVIDER_RATE_LIMITED: "PROVIDER_RATE_LIMITED",
176
- PROVIDER_AUTH_FAILED: "PROVIDER_AUTH_FAILED",
177
- PROVIDER_OVERLOADED: "PROVIDER_OVERLOADED",
178
- PROVIDER_INVALID_REQUEST: "PROVIDER_INVALID_REQUEST",
179
- PROVIDER_SERVER_ERROR: "PROVIDER_SERVER_ERROR",
180
- PROVIDER_NETWORK_ERROR: "PROVIDER_NETWORK_ERROR",
181
- PROVIDER_CONTEXT_OVERFLOW: "PROVIDER_CONTEXT_OVERFLOW",
182
- // Tool
183
- TOOL_NOT_FOUND: "TOOL_NOT_FOUND",
184
- TOOL_PERMISSION_DENIED: "TOOL_PERMISSION_DENIED",
185
- TOOL_EXECUTION_FAILED: "TOOL_EXECUTION_FAILED",
186
- TOOL_TIMEOUT: "TOOL_TIMEOUT",
187
- TOOL_INPUT_INVALID: "TOOL_INPUT_INVALID",
188
- // Config
189
- CONFIG_INVALID: "CONFIG_INVALID",
190
- CONFIG_NOT_FOUND: "CONFIG_NOT_FOUND",
191
- CONFIG_PARSE_FAILED: "CONFIG_PARSE_FAILED",
192
- CONFIG_MIGRATION_NEEDED: "CONFIG_MIGRATION_NEEDED",
193
- // Plugin
194
- PLUGIN_LOAD_FAILED: "PLUGIN_LOAD_FAILED",
195
- PLUGIN_API_MISMATCH: "PLUGIN_API_MISMATCH",
196
- PLUGIN_MISSING_DEPENDENCY: "PLUGIN_MISSING_DEPENDENCY",
197
- // Agent
198
- AGENT_ITERATION_LIMIT: "AGENT_ITERATION_LIMIT",
199
- AGENT_CONTEXT_OVERFLOW: "AGENT_CONTEXT_OVERFLOW",
200
- AGENT_ABORTED: "AGENT_ABORTED",
201
- AGENT_RUN_FAILED: "AGENT_RUN_FAILED",
202
- // Session
203
- SESSION_NOT_FOUND: "SESSION_NOT_FOUND",
204
- SESSION_CORRUPTED: "SESSION_CORRUPTED",
205
- SESSION_WRITE_FAILED: "SESSION_WRITE_FAILED",
206
- // Container / Registry
207
- CONTAINER_TOKEN_ALREADY_BOUND: "CONTAINER_TOKEN_ALREADY_BOUND",
208
- CONTAINER_TOKEN_NOT_BOUND: "CONTAINER_TOKEN_NOT_BOUND",
209
- CONTAINER_CIRCULAR_DEPENDENCY: "CONTAINER_CIRCULAR_DEPENDENCY",
210
- REGISTRY_DUPLICATE: "REGISTRY_DUPLICATE",
211
- REGISTRY_NOT_FOUND: "REGISTRY_NOT_FOUND",
212
- REGISTRY_INVALID: "REGISTRY_INVALID",
213
- // File system
214
- FS_READ_FAILED: "FS_READ_FAILED",
215
- FS_WRITE_FAILED: "FS_WRITE_FAILED",
216
- FS_MKDIR_FAILED: "FS_MKDIR_FAILED",
217
- FS_DELETE_FAILED: "FS_DELETE_FAILED",
218
- FS_ATOMIC_WRITE_FAILED: "FS_ATOMIC_WRITE_FAILED",
219
- // SDD (Spec-Driven Development)
220
- SDD_VALIDATION_FAILED: "SDD_VALIDATION_FAILED",
221
- SDD_PARSE_FAILED: "SDD_PARSE_FAILED",
222
- SDD_INVALID_STATE: "SDD_INVALID_STATE",
223
- SDD_NOT_READY: "SDD_NOT_READY",
224
- // General
225
- VALIDATION_ERROR: "VALIDATION_ERROR",
226
- PARSE_FAILED: "PARSE_FAILED",
227
- UNKNOWN: "UNKNOWN"
228
- };
229
- var WrongStackError = class extends Error {
230
- code;
231
- subsystem;
232
- severity;
233
- recoverable;
234
- context;
235
- constructor(opts) {
236
- super(opts.message, { cause: opts.cause });
237
- this.name = "WrongStackError";
238
- this.code = opts.code;
239
- this.subsystem = opts.subsystem;
240
- this.severity = opts.severity ?? "error";
241
- this.recoverable = opts.recoverable ?? false;
242
- this.context = opts.context;
243
- }
244
- /**
245
- * Render a one-line user-facing description.
246
- * Subclasses should override for domain-specific formatting.
247
- */
248
- describe() {
249
- const ctx = this.context ? ` ${formatContext(this.context)}` : "";
250
- return `${this.code}: ${this.message}${ctx}`;
251
- }
252
- };
253
- function formatContext(ctx) {
254
- const parts = Object.entries(ctx).filter(([, v]) => v !== void 0).slice(0, 3).map(([k, v]) => `${k}=${String(v)}`);
255
- return parts.length > 0 ? `[${parts.join(" ")}]` : "";
256
- }
257
- var FsError = class extends WrongStackError {
258
- path;
259
- constructor(opts) {
260
- super({
261
- message: opts.message,
262
- code: opts.code,
263
- subsystem: "fs",
264
- severity: "error",
265
- recoverable: opts.code !== ERROR_CODES.FS_READ_FAILED,
266
- context: { path: opts.path, ...opts.context },
267
- cause: opts.cause
268
- });
269
- this.name = "FsError";
270
- this.path = opts.path;
271
- }
272
- };
273
- var FetchError = class extends WrongStackError {
274
- status;
275
- constructor(opts) {
276
- super({
277
- message: opts.message,
278
- code: ERROR_CODES.VALIDATION_ERROR,
279
- subsystem: "general",
280
- severity: "error",
281
- recoverable: opts.status === 429 || opts.status >= 500,
282
- context: { status: opts.status, ...opts.context },
283
- cause: opts.cause
284
- });
285
- this.name = "FetchError";
286
- this.status = opts.status;
287
- }
288
- };
289
- var ParseError = class extends WrongStackError {
290
- source;
291
- constructor(opts) {
292
- super({
293
- message: opts.message,
294
- code: ERROR_CODES.PARSE_FAILED,
295
- subsystem: "general",
296
- severity: "error",
297
- recoverable: false,
298
- context: { source: opts.source, ...opts.context },
299
- cause: opts.cause
300
- });
301
- this.name = "ParseError";
302
- this.source = opts.source;
303
- }
304
- };
305
-
306
- // src/utils/expect-defined.ts
307
- function expectDefined(value, label) {
308
- if (value === null || value === void 0) {
309
- const err = new Error(label ? `Expected ${label} to be defined` : "Expected value to be defined");
310
- err.name = "ExpectDefinedError";
311
- throw err;
312
- }
313
- return value;
314
- }
315
-
316
- // src/skills/limits.ts
317
- var SKILL_LIMITS = {
318
- /**
319
- * Per-skill body cap when injecting a full skill body into the system prompt
320
- * (eager mode), and when returning a skill body from the `skill` tool.
321
- * ~4k tokens. Oversized bodies are truncated at a paragraph boundary.
322
- *
323
- * Consumers: `system-prompt-builder.capSkillBody`, `skill` tool body return.
324
- */
325
- MAX_SKILL_BODY_CHARS: 16e3,
326
- /**
327
- * Per-resource cap when the `skill` tool loads a bundled file
328
- * (`scripts/`, `references/`, `assets/`). ~8k tokens.
329
- *
330
- * Consumer: `skill` tool `loadResource`.
331
- */
332
- MAX_RESOURCE_CHARS: 32e3,
333
- /**
334
- * Maximum number of bundled resource paths the `skill` tool lists in one
335
- * response. Caps a pathological skill (huge `assets/` tree) from blowing up
336
- * the tool result.
337
- *
338
- * Consumer: `skill` tool resource listing.
339
- */
340
- MAX_LISTED_RESOURCES: 100,
341
- /**
342
- * Max size of a single installed skill file (SKILL.md or a bundled resource).
343
- * Guards against a malicious registry skill shipping a multi-MB blob that
344
- * then flows into the prompt. 100KB.
345
- *
346
- * Consumer: `SkillInstaller.install` / `importFromDir`.
347
- */
348
- MAX_SKILL_FILE_SIZE: 100 * 1024,
349
- /**
350
- * Max size of a GitHub tarball downloaded by the skill installer. Guards
351
- * against a repo accidentally (or maliciously) shipping a giant tarball that
352
- * would be extracted into a temp dir. 50MB.
353
- *
354
- * Consumer: `downloadGitHubTarball`.
355
- */
356
- MAX_TARBALL_SIZE: 50 * 1024 * 1024,
357
- /**
358
- * Maximum size of a GitHub skill tarball after gzip decompression. The
359
- * compressed download limit alone does not protect against highly
360
- * compressible archives expanding until the process runs out of memory.
361
- *
362
- * Consumer: `downloadGitHubTarball`.
363
- */
364
- MAX_UNCOMPRESSED_TARBALL_SIZE: 64 * 1024 * 1024,
365
- /**
366
- * Default total char budget for skill bodies injected in eager mode when
367
- * `config.skills.eagerMaxChars` is unset. ~6k tokens. Skills are injected
368
- * highest-priority first; once the budget is exhausted the remaining skills
369
- * are listed as a manifest the agent loads via the `skill` tool. Set very
370
- * high to effectively disable budgeting.
371
- *
372
- * Consumer: `DefaultSystemPromptBuilder.buildMemoryAndSkills` (default for
373
- * `skillEagerMaxChars`).
374
- */
375
- EAGER_DEFAULT_MAX_CHARS: 24e3,
376
- /**
377
- * Default total char budget for compact (token-saving) skill bodies injected
378
- * by `buildCompactSkillBodiesText`. Mirrors the eager ceiling so compact mode
379
- * can never inject more total skill text than eager mode. Before this bound
380
- * existed, compact mode injected EVERY skill's compact body with no cap, so
381
- * the section grew linearly with the number of installed skills. Skills past
382
- * the budget overflow to a name list the agent loads via the `skill` tool.
383
- *
384
- * Consumer: `buildCompactSkillBodiesText` (default for its `budget` param).
385
- */
386
- COMPACT_DEFAULT_MAX_CHARS: 24e3,
387
- /**
388
- * Auto-compact body limits (the token-saving fallback used when a skill has
389
- * no hand-crafted `SKILL.save.md`). The Overview and Rules sections are
390
- * extracted and trimmed to these char budgets, then the total is capped.
391
- *
392
- * Consumer: `DefaultSkillLoader.compactSkillBody`.
393
- */
394
- COMPACT_OVERVIEW_MAX: 200,
395
- COMPACT_RULES_MAX: 350,
396
- COMPACT_TOTAL_MAX: 450,
397
- /**
398
- * Max length of a skill name (agentskills.io spec). Enforced by the
399
- * frontmatter validator's format regex plus this length bound.
400
- *
401
- * Consumer: `isValidSkillNameFormat`.
402
- */
403
- SKILL_NAME_MAX_LEN: 64,
404
- /**
405
- * Soft line limit for a SKILL.md body, per the bundled `skill-creator` rule.
406
- * Not enforced in code (a skill can exceed it) — surfaced by `/skill-gen
407
- * validate` and the `skill-creator` skill as guidance to move deep material
408
- * into `references/`.
409
- *
410
- * Consumer: `skill-creator` skill, `/skill-gen validate` advisory.
411
- */
412
- SKILL_BODY_LINE_LIMIT: 500
413
- };
414
-
415
- // src/skills/github-fetcher.ts
416
- function parseSkillRef(input) {
417
- const trimmed = input.trim().replace(/^https?:\/\/github\.com\//, "").replace(/\.git$/, "");
418
- const atIdx = trimmed.indexOf("@");
419
- let refPath;
420
- let ref;
421
- if (atIdx > 0) {
422
- refPath = trimmed.slice(0, atIdx);
423
- ref = trimmed.slice(atIdx + 1);
424
- } else {
425
- refPath = trimmed;
426
- ref = "main";
427
- }
428
- const parts = refPath.split("/").filter(Boolean);
429
- if (parts.length < 2) {
430
- throw new WrongStackError({
431
- message: `Invalid skill reference "${input}". Expected format: user/repo or user/repo@ref`,
432
- code: ERROR_CODES.UNKNOWN,
433
- subsystem: "general",
434
- context: { input }
435
- });
436
- }
437
- return { owner: expectDefined(parts[0]), repo: expectDefined(parts[1]), ref };
438
- }
439
- async function writeBoundedGzipStream(source, outputPath, maxBytes, maxCompressedBytes = SKILL_LIMITS.MAX_TARBALL_SIZE) {
440
- let compressedBytes = 0;
441
- let writtenBytes = 0;
442
- await pipeline(
443
- source,
444
- async function* (chunks) {
445
- for await (const chunk of chunks) {
446
- const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
447
- compressedBytes += buffer.byteLength;
448
- if (compressedBytes > maxCompressedBytes) {
449
- throw new WrongStackError({
450
- message: `Compressed tarball too large. Max: ${maxCompressedBytes / 1024 / 1024}MB`,
451
- code: ERROR_CODES.UNKNOWN,
452
- subsystem: "general",
453
- context: { compressedBytes, maxBytes: maxCompressedBytes }
454
- });
455
- }
456
- yield buffer;
457
- }
458
- },
459
- createGunzip(),
460
- async function* (chunks) {
461
- for await (const chunk of chunks) {
462
- const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
463
- writtenBytes += buffer.byteLength;
464
- if (writtenBytes > maxBytes) {
465
- throw new WrongStackError({
466
- message: `Uncompressed tarball too large. Max: ${maxBytes / 1024 / 1024}MB`,
467
- code: ERROR_CODES.UNKNOWN,
468
- subsystem: "general",
469
- context: { uncompressedBytes: writtenBytes, maxBytes }
470
- });
471
- }
472
- yield buffer;
473
- }
474
- },
475
- createWriteStream(outputPath, { flags: "wx" })
476
- );
477
- return writtenBytes;
478
- }
479
- function resolveGitHubToken(env = process.env) {
480
- const raw = env["WRONGSTACK_GITHUB_TOKEN"] ?? env["GITHUB_TOKEN"] ?? env["GH_TOKEN"];
481
- if (!raw) return void 0;
482
- const trimmed = raw.trim();
483
- return trimmed.length > 0 ? trimmed : void 0;
484
- }
485
- async function downloadGitHubTarball(parsed) {
486
- const url = `https://api.github.com/repos/${parsed.owner}/${parsed.repo}/tarball/${parsed.ref}`;
487
- const token = resolveGitHubToken();
488
- const headers = {
489
- Accept: "application/vnd.github+json",
490
- "User-Agent": "wrongstack-skill-installer"
491
- };
492
- if (token) headers["Authorization"] = `Bearer ${token}`;
493
- let response;
494
- try {
495
- response = await fetch(url, {
496
- signal: AbortSignal.timeout(3e4),
497
- headers,
498
- redirect: "follow"
499
- });
500
- } catch (err) {
501
- throw new FetchError({
502
- message: `Network error fetching ${parsed.owner}/${parsed.repo}: ${err instanceof Error ? err.message : String(err)}`,
503
- status: 0,
504
- context: { owner: parsed.owner, repo: parsed.repo, ref: parsed.ref, op: "tarball" },
505
- cause: err
506
- });
507
- }
508
- if (!response.ok) {
509
- if (response.status === 404) {
510
- throw new WrongStackError({
511
- message: `Repository not found: ${parsed.owner}/${parsed.repo}` + (parsed.ref !== "main" ? ` (ref: ${parsed.ref})` : "") + (token ? "" : ". If this is a private repo, set GITHUB_TOKEN (or GH_TOKEN)."),
512
- code: ERROR_CODES.UNKNOWN,
513
- subsystem: "general",
514
- context: { owner: parsed.owner, repo: parsed.repo, ref: parsed.ref, status: 404 }
515
- });
516
- }
517
- if (response.status === 403) {
518
- const remaining = response.headers.get("x-ratelimit-remaining");
519
- const isRateLimited = remaining === "0";
520
- throw new WrongStackError({
521
- message: isRateLimited ? `GitHub API rate limit exceeded. Set GITHUB_TOKEN (or GH_TOKEN) to use the higher authenticated limit.` : `Access denied: ${parsed.owner}/${parsed.repo}. The repository may be private (set GITHUB_TOKEN to install private repos) or rate-limited.`,
522
- code: ERROR_CODES.UNKNOWN,
523
- subsystem: "general",
524
- context: {
525
- owner: parsed.owner,
526
- repo: parsed.repo,
527
- status: 403,
528
- rateLimited: isRateLimited,
529
- hasToken: Boolean(token)
530
- }
531
- });
532
- }
533
- throw new WrongStackError({
534
- message: `GitHub API error (${response.status}): ${response.statusText}`,
535
- code: ERROR_CODES.UNKNOWN,
536
- subsystem: "general",
537
- context: { owner: parsed.owner, repo: parsed.repo, status: response.status }
538
- });
539
- }
540
- const contentLength = response.headers.get("content-length");
541
- if (contentLength && Number.parseInt(contentLength, 10) > SKILL_LIMITS.MAX_TARBALL_SIZE) {
542
- throw new WrongStackError({
543
- message: `Tarball too large (${(Number.parseInt(contentLength, 10) / 1024 / 1024).toFixed(1)}MB). Max: ${SKILL_LIMITS.MAX_TARBALL_SIZE / 1024 / 1024}MB`,
544
- code: ERROR_CODES.UNKNOWN,
545
- subsystem: "general",
546
- context: {
547
- owner: parsed.owner,
548
- repo: parsed.repo,
549
- contentLengthBytes: Number.parseInt(contentLength, 10),
550
- maxBytes: SKILL_LIMITS.MAX_TARBALL_SIZE
551
- }
552
- });
553
- }
554
- const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "wskill-"));
555
- try {
556
- if (!response.body) {
557
- throw new WrongStackError({
558
- message: "Empty response body from GitHub API",
559
- code: ERROR_CODES.UNKNOWN,
560
- subsystem: "general",
561
- context: { owner: parsed.owner, repo: parsed.repo }
562
- });
563
- }
564
- const nodeStream = Readable.fromWeb(response.body);
565
- const tarPath = path.join(tempDir, ".wrongstack-download.tar");
566
- await writeBoundedGzipStream(nodeStream, tarPath, SKILL_LIMITS.MAX_UNCOMPRESSED_TARBALL_SIZE);
567
- const tarBuf = await fs.readFile(tarPath);
568
- try {
569
- await extractTar(tarBuf, tempDir);
570
- } finally {
571
- await fs.rm(tarPath, { force: true });
572
- }
573
- return { tempDir };
574
- } catch (err) {
575
- await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {
576
- });
577
- throw err;
578
- }
579
- }
580
- function readTarSize(buf, start) {
581
- const first = buf[start] ?? 0;
582
- if ((first & 128) !== 0) {
583
- if (first === 255) {
584
- throw new Error("tar: negative base-256 size field");
585
- }
586
- let value = first & 127;
587
- for (let i = start + 1; i < start + 12; i++) {
588
- value = value * 256 + (buf[i] ?? 0);
589
- if (!Number.isSafeInteger(value)) {
590
- throw new Error("tar: base-256 size exceeds the safe integer range");
591
- }
592
- }
593
- return value;
594
- }
595
- const text = readTarString(buf, start, 12).trim();
596
- if (text === "") return 0;
597
- if (!/^[0-7]+$/.test(text)) {
598
- throw new Error(`tar: unparseable size field ${JSON.stringify(text)}`);
599
- }
600
- const parsed = Number.parseInt(text, 8);
601
- if (!Number.isSafeInteger(parsed) || parsed < 0) {
602
- throw new Error("tar: size field out of range");
603
- }
604
- return parsed;
605
- }
606
- function readTarString(buf, start, maxLen) {
607
- let end = start;
608
- while (end < start + maxLen && end < buf.length && buf[end] !== 0) {
609
- end++;
610
- }
611
- return buf.subarray(start, end).toString("utf8");
612
- }
613
- function stripTopDir(p) {
614
- const idx = p.indexOf("/");
615
- if (idx === -1) return "";
616
- return p.slice(idx + 1);
617
- }
618
- async function extractTar(buf, destDir) {
619
- let offset = 0;
620
- while (offset + 512 <= buf.length) {
621
- const header = buf.subarray(offset, offset + 512);
622
- if (header.every((b) => b === 0)) break;
623
- const name = readTarString(buf, offset, 100);
624
- const prefix = readTarString(buf, offset + 345, 155);
625
- const size = readTarSize(buf, offset + 124);
626
- const typeflag = buf[offset + 156] ?? 0;
627
- const fullPath = prefix ? `${prefix}/${name}` : name;
628
- const relPath = stripTopDir(fullPath);
629
- if (relPath && relPath !== "." && relPath !== "..") {
630
- const destPath = path.join(destDir, relPath);
631
- const resolvedDest = path.resolve(destPath);
632
- const resolvedRoot = path.resolve(destDir);
633
- if (resolvedDest !== resolvedRoot && !resolvedDest.startsWith(resolvedRoot + path.sep)) {
634
- offset += 512 + Math.ceil(size / 512) * 512;
635
- continue;
636
- }
637
- if (typeflag === 53 || typeflag === 0) {
638
- if (relPath.endsWith("/") || typeflag === 53) {
639
- await fs.mkdir(destPath, { recursive: true });
640
- }
641
- }
642
- if ((typeflag === 48 || typeflag === 0 || typeflag === 0) && size > 0) {
643
- const dir = path.dirname(destPath);
644
- await fs.mkdir(dir, { recursive: true });
645
- const dataStart = offset + 512;
646
- const dataEnd = dataStart + size;
647
- if (dataEnd > buf.length) break;
648
- await fs.writeFile(destPath, buf.subarray(dataStart, dataEnd));
649
- }
650
- }
651
- offset += 512 + Math.ceil(size / 512) * 512;
652
- }
653
- }
654
-
655
- // src/skills/manifest-store.ts
656
- import * as fs2 from "node:fs/promises";
657
- import * as path2 from "node:path";
658
-
659
- // src/utils/atomic-write.ts
1
+ import "../chunk-GTCU6DZ3.js";
660
2
  import {
661
- createPersistencePrimitives
662
- } from "@wrongstack/persistence";
663
- var primitives = createPersistencePrimitives({
664
- createLockTimeoutError: ({ targetPath, timeoutMs }) => new FsError({
665
- message: `Timed out waiting for file lock: ${targetPath}`,
666
- code: "FS_ATOMIC_WRITE_FAILED",
667
- path: targetPath,
668
- context: { timeoutMs }
669
- })
670
- });
671
- var atomicWrite = primitives.atomicWrite;
672
- var atomicReplaceWithWriter = primitives.atomicReplaceWithWriter;
673
- var ensureDir = primitives.ensureDir;
674
- var withFileLock = primitives.withFileLock;
675
-
676
- // src/skills/manifest-store.ts
677
- var SkillManifestStore = class {
678
- manifestPath;
679
- cache;
680
- constructor(manifestPath) {
681
- this.manifestPath = manifestPath;
682
- }
683
- async read() {
684
- if (this.cache) return this.cache;
685
- try {
686
- const raw = await fs2.readFile(this.manifestPath, "utf8");
687
- const data = JSON.parse(raw);
688
- if (!Array.isArray(data.skills)) {
689
- this.cache = { skills: [] };
690
- } else {
691
- this.cache = data;
692
- }
693
- } catch {
694
- this.cache = { skills: [] };
695
- }
696
- return this.cache;
697
- }
698
- async write(data) {
699
- const dir = path2.dirname(this.manifestPath);
700
- await fs2.mkdir(dir, { recursive: true });
701
- await atomicWrite(this.manifestPath, JSON.stringify(data, null, 2) + "\n");
702
- this.cache = data;
703
- }
704
- async addEntry(entry) {
705
- const data = await this.read();
706
- data.skills = data.skills.filter((s) => !(s.name === entry.name && s.scope === entry.scope));
707
- data.skills.push(entry);
708
- await this.write(data);
709
- }
710
- async removeEntry(name, scope) {
711
- const data = await this.read();
712
- const before = data.skills.length;
713
- data.skills = data.skills.filter((s) => !(s.name === name && s.scope === scope));
714
- if (data.skills.length === before) return false;
715
- await this.write(data);
716
- return true;
717
- }
718
- async findByName(name) {
719
- const data = await this.read();
720
- return data.skills.filter((s) => s.name === name);
721
- }
722
- async findBySource(source) {
723
- const data = await this.read();
724
- return data.skills.filter((s) => s.source === source);
725
- }
726
- async listAll() {
727
- const data = await this.read();
728
- return data.skills;
729
- }
730
- /** Invalidate the in-memory cache (e.g. after external file changes). */
731
- invalidateCache() {
732
- this.cache = void 0;
733
- }
734
- };
735
-
736
- // src/skills/registry/github-direct-adapter.ts
737
- var githubDirectAdapter = {
738
- id: "github",
739
- displayName: "GitHub (direct)",
740
- async search(_query, _opts = {}) {
741
- return { adapterId: "github", results: [], hasMore: false };
742
- },
743
- resolveInstallRef(registryId) {
744
- const trimmed = registryId.trim();
745
- if (!trimmed.includes("/")) {
746
- return trimmed;
747
- }
748
- return trimmed;
749
- }
750
- };
751
-
752
- // src/skills/registry/skills-sh-adapter.ts
753
- var DEFAULT_SKILLS_SH_URL = "https://skills.sh";
754
- var SEARCH_TIMEOUT_MS = 15e3;
755
- var MAX_PAGE_SIZE = 50;
756
- var defaultFetcher = async (url) => {
757
- let res;
758
- try {
759
- res = await fetch(url, {
760
- signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS),
761
- headers: {
762
- Accept: "application/json",
763
- "User-Agent": "wrongstack-skill-installer"
764
- },
765
- redirect: "follow"
766
- });
767
- } catch (err) {
768
- throw new FetchError({
769
- message: `Network error querying skill registry: ${err instanceof Error ? err.message : String(err)}`,
770
- status: 0,
771
- context: { url, op: "skills.sh.search" },
772
- cause: err
773
- });
774
- }
775
- if (!res.ok) {
776
- throw new FetchError({
777
- message: `Skill registry returned ${res.status} ${res.statusText}`,
778
- status: res.status,
779
- context: { url, op: "skills.sh.search" }
780
- });
781
- }
782
- try {
783
- return await res.json();
784
- } catch (err) {
785
- throw new ParseError({
786
- message: `Skill registry response was not valid JSON: ${err instanceof Error ? err.message : String(err)}`,
787
- source: "skills.sh.search",
788
- context: { url },
789
- cause: err
790
- });
791
- }
792
- };
793
- function createSkillsShAdapter(opts = {}) {
794
- const baseUrl = (opts.baseUrl ?? DEFAULT_SKILLS_SH_URL).replace(/\/+$/, "");
795
- const fetcher = opts.fetcher ?? defaultFetcher;
796
- const id = "skills.sh";
797
- return {
798
- id,
799
- displayName: "skills.sh",
800
- async search(query, sopts = {}) {
801
- const page = Math.max(1, sopts.page ?? 1);
802
- const pageSize = Math.min(MAX_PAGE_SIZE, Math.max(1, sopts.pageSize ?? 20));
803
- const q = query.trim();
804
- if (!q) return { adapterId: id, results: [], hasMore: false };
805
- const url = `${baseUrl}/api/skills?query=${encodeURIComponent(q)}&page=${page}&pageSize=${pageSize}&sortBy=installs`;
806
- const raw = await fetcher(url);
807
- const results = parseResults(raw, url);
808
- return {
809
- adapterId: id,
810
- results,
811
- hasMore: results.length === pageSize
812
- };
813
- },
814
- resolveInstallRef(registryId) {
815
- const trimmed = registryId.trim();
816
- if (!trimmed) {
817
- throw new ParseError({
818
- message: "Empty skills.sh registry id.",
819
- source: "skills.sh.resolveInstallRef"
820
- });
821
- }
822
- const atIdx = trimmed.indexOf("@");
823
- const repoPart = atIdx > 0 ? trimmed.slice(0, atIdx) : trimmed;
824
- const segs = repoPart.split("/").filter(Boolean);
825
- if (segs.length !== 2) {
826
- throw new ParseError({
827
- message: `Invalid skills.sh id "${registryId}". Expected "<owner>/<repo>" or "<owner>/<repo>@<ref>".`,
828
- source: "skills.sh.resolveInstallRef",
829
- context: { registryId }
830
- });
831
- }
832
- return trimmed;
833
- }
834
- };
835
- }
836
- function parseResults(raw, url) {
837
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
838
- throw new ParseError({
839
- message: "Skill registry response was not a JSON object.",
840
- source: "skills.sh.search",
841
- context: { url }
842
- });
843
- }
844
- const results = raw.results;
845
- if (!Array.isArray(results)) {
846
- return [];
847
- }
848
- const out = [];
849
- for (let i = 0; i < results.length; i++) {
850
- const entry = results[i];
851
- if (!entry || typeof entry !== "object") continue;
852
- const name = strField(entry, "name") ?? strField(entry, "slug");
853
- const description = strField(entry, "description") ?? "";
854
- const owner = strField(entry, "owner") ?? strField(entry, "author");
855
- const repo = strField(entry, "repo") ?? strField(entry, "repository");
856
- if (!name || !owner || !repo) continue;
857
- const ref = strField(entry, "ref") ?? strField(entry, "branch") ?? strField(entry, "tag");
858
- const installRef = ref ? `${owner}/${repo}@${ref}` : `${owner}/${repo}`;
859
- out.push({
860
- id: strField(entry, "id") ?? `${owner}/${repo}`,
861
- name,
862
- description,
863
- author: owner,
864
- installs: numField(entry, "installs") ?? numField(entry, "installCount"),
865
- stars: numField(entry, "stars") ?? numField(entry, "starCount"),
866
- securityScore: numField(entry, "securityScore") ?? numField(entry, "score"),
867
- updatedAt: strField(entry, "updatedAt") ?? strField(entry, "updated_at"),
868
- installRef
869
- });
870
- }
871
- return out;
872
- }
873
- function strField(rec, key) {
874
- const v = rec[key];
875
- return typeof v === "string" && v.length > 0 ? v : void 0;
876
- }
877
- function numField(rec, key) {
878
- const v = rec[key];
879
- if (typeof v === "number" && Number.isFinite(v)) return v;
880
- if (typeof v === "string" && /^\d+(\.\d+)?$/.test(v.trim())) {
881
- const n = Number(v);
882
- if (Number.isFinite(n)) return n;
883
- }
884
- return void 0;
885
- }
886
-
887
- // src/skills/skill-generator.ts
888
- import { spawn } from "node:child_process";
889
- import * as fs3 from "node:fs/promises";
890
- import * as path3 from "node:path";
891
-
892
- // src/utils/win32-cmd.ts
893
- var WIN32_CMD_META = /[&|<>"%\r\n\0]/;
894
- function buildWin32CmdShimInvocation(command, args = []) {
895
- assertSafeWin32CmdArgs([command, ...args]);
896
- const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
897
- return {
898
- command: process.env["COMSPEC"] ?? "cmd.exe",
899
- args: ["/d", "/c", line],
900
- windowsVerbatimArguments: true
901
- };
902
- }
903
- function assertSafeWin32CmdArgs(args) {
904
- for (const arg of args) {
905
- if (typeof arg === "string" && WIN32_CMD_META.test(arg)) {
906
- throw new Error(
907
- 'win32 cmd shim spawn: argument contains a shell metacharacter (one of & | < > ", or a newline) that could enable command injection through the .cmd/.bat wrapper - refusing to run. Offending argument: ' + JSON.stringify(arg)
908
- );
909
- }
910
- }
911
- }
912
- function quoteWin32CmdArg(arg) {
913
- return `"${arg}"`;
914
- }
915
-
916
- // src/skills/skill-generator.ts
917
- async function validateSkillNameAvailable(name, loader) {
918
- const formatViolations = validateSkillName(name);
919
- const conflicts = loader ? (await loader.listEntries()).filter((e) => e.name === name) : [];
920
- const formatOk = formatViolations.length === 0;
921
- const collisionBlocks = conflicts.some((c) => c.source === "project" || c.source === "user");
922
- return {
923
- ok: formatOk && !collisionBlocks,
924
- formatViolations,
925
- conflicts
926
- };
927
- }
928
- function generateSkillSkeleton(opts) {
929
- const name = opts.name.trim();
930
- if (!isValidSkillNameFormat(name)) {
931
- throw new WrongStackError({
932
- message: `Cannot generate skeleton: invalid skill name "${name}". Names must be kebab-case (a-z0-9 and hyphens), \u226464 chars.`,
933
- code: ERROR_CODES.VALIDATION_ERROR,
934
- subsystem: "general",
935
- context: { name }
936
- });
937
- }
938
- const description = opts.description.trim();
939
- const triggers = (opts.triggerKeywords ?? []).map((k) => k.trim()).filter(Boolean).map((k) => `"${k}"`).join(", ");
940
- const triggerLine = triggers ? `
941
- Triggers: user says ${triggers}.` : "";
942
- const version = opts.version?.trim() || "1.0.0";
943
- const title = name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
944
- return `---
945
- name: ${name}
946
- description: |
947
- ${description || `Use this skill when <trigger situation>.`}${triggerLine}
948
- version: ${version}
949
- ---
950
-
951
- # ${title}
952
-
953
- ## Overview
954
-
955
- ${description || "One-line description of what this skill does."}
956
-
957
- ## Rules
958
-
959
- 1. Rule one
960
- 2. Rule two
961
-
962
- ## Patterns
963
-
964
- ### Do
965
-
966
- \`\`\`ts
967
- // good example
968
- \`\`\`
969
-
970
- ### Don't
971
-
972
- \`\`\`ts
973
- // bad example
974
- \`\`\`
975
-
976
- ## Skills in scope
977
-
978
- - \`other-skill\` \u2014 for delegation when this skill needs help
979
- `;
980
- }
981
- function extractSkillFromPrompt(prompt) {
982
- const text = prompt.trim();
983
- if (!text) {
984
- return { suggestedName: "", description: "", body: "", triggerKeywords: [] };
985
- }
986
- const headingMatch = text.match(/^#{1,6}\s+(.+?)\s*$/m);
987
- const firstLine = text.split("\n").find((l) => l.trim().length > 0)?.trim() ?? text;
988
- const titleSource = headingMatch?.[1] ?? firstLine.replace(/^#+\s*/, "");
989
- const suggestedName = toKebab(titleSource).slice(0, SKILL_LIMITS.SKILL_NAME_MAX_LEN);
990
- const paragraphs = text.split(/\n\s*\n/).map((p) => p.trim()).filter(Boolean);
991
- const description = paragraphs[0]?.replace(/^#{1,6}\s+/m, "").trim() ?? titleSource;
992
- const body = paragraphs.slice(1).join("\n\n");
993
- const quoted = [...text.matchAll(/"([^"]{2,40})"/g)].map((m) => m[1]).filter((q) => typeof q === "string").map((q) => q.toLowerCase());
994
- const titleWords = titleSource.split(/\W+/).map((w) => w.toLowerCase()).filter((w) => w.length > 3 && !STOPWORDS.has(w));
995
- const triggerKeywords = dedupe([...quoted, ...titleWords]).slice(0, 8);
996
- return { suggestedName, description, body, triggerKeywords };
997
- }
998
- async function openInEditor(filePath, env = process.env) {
999
- const editor = env["VISUAL"]?.trim() && env["VISUAL"] || env["EDITOR"]?.trim() && env["EDITOR"] || defaultEditor();
1000
- if (!editor) {
1001
- throw new WrongStackError({
1002
- message: "No editor found. Set the EDITOR or VISUAL environment variable (e.g. `export EDITOR=code` or `export EDITOR=vim`).",
1003
- code: ERROR_CODES.VALIDATION_ERROR,
1004
- subsystem: "general",
1005
- context: { filePath }
1006
- });
1007
- }
1008
- const shell = process.platform === "win32";
1009
- const parts = editor.split(/\s+/).filter(Boolean);
1010
- if (parts.length === 0) {
1011
- throw new WrongStackError({
1012
- message: "Resolved editor command is empty.",
1013
- code: ERROR_CODES.VALIDATION_ERROR,
1014
- subsystem: "general",
1015
- context: { filePath }
1016
- });
1017
- }
1018
- const editorArgs = [...parts.slice(1), filePath];
1019
- const child = shell ? (() => {
1020
- const inv = buildWin32CmdShimInvocation(parts[0], editorArgs);
1021
- return spawn(inv.command, inv.args, {
1022
- stdio: "ignore",
1023
- detached: true,
1024
- windowsVerbatimArguments: inv.windowsVerbatimArguments,
1025
- // Suppresses the console flash the cmd.exe wrapper would otherwise
1026
- // show before the editor appears. Repo convention — see
1027
- // `core/tests/architecture/spawn-convention.test.ts`.
1028
- windowsHide: true
1029
- });
1030
- })() : spawn(parts[0], editorArgs, {
1031
- stdio: "ignore",
1032
- detached: true,
1033
- windowsHide: true
1034
- });
1035
- child.unref();
1036
- }
1037
- async function writeSkeletonSkill(skillsDir, body, opts = {}) {
1038
- const fm = parseSkillFrontmatter(body);
1039
- const name = fm.name;
1040
- if (!name || !isValidSkillNameFormat(name)) {
1041
- throw new WrongStackError({
1042
- message: "Skeleton body has no valid `name` in its frontmatter.",
1043
- code: ERROR_CODES.VALIDATION_ERROR,
1044
- subsystem: "general"
1045
- });
1046
- }
1047
- const skillDir = path3.join(skillsDir, name);
1048
- const skillFile = path3.join(skillDir, "SKILL.md");
1049
- if (!opts.overwrite) {
1050
- try {
1051
- await fs3.access(skillFile);
1052
- throw new WrongStackError({
1053
- message: `A skill already exists at ${skillFile}. Use --force to overwrite.`,
1054
- code: ERROR_CODES.VALIDATION_ERROR,
1055
- subsystem: "general",
1056
- context: { skillFile }
1057
- });
1058
- } catch (err) {
1059
- if (err instanceof WrongStackError) throw err;
1060
- }
1061
- }
1062
- await fs3.mkdir(skillDir, { recursive: true });
1063
- await fs3.writeFile(skillFile, body, "utf8");
1064
- return skillFile;
1065
- }
1066
- function bodyLineAdvisory(body) {
1067
- const lines = body.split("\n").length;
1068
- return { lines, over: lines > SKILL_LIMITS.SKILL_BODY_LINE_LIMIT };
1069
- }
1070
- var STOPWORDS = /* @__PURE__ */ new Set([
1071
- "the",
1072
- "and",
1073
- "for",
1074
- "with",
1075
- "that",
1076
- "this",
1077
- "from",
1078
- "your",
1079
- "have",
1080
- "will",
1081
- "are",
1082
- "was",
1083
- "were",
1084
- "been",
1085
- "into",
1086
- "when",
1087
- "use",
1088
- "skill",
1089
- "about"
1090
- ]);
1091
- function toKebab(s) {
1092
- return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, SKILL_LIMITS.SKILL_NAME_MAX_LEN) || "skill";
1093
- }
1094
- function dedupe(arr) {
1095
- return [...new Set(arr)];
1096
- }
1097
- function defaultEditor() {
1098
- if (process.platform === "win32") return "notepad";
1099
- if (process.platform === "darwin") return "open";
1100
- return void 0;
1101
- }
1102
-
1103
- // src/skills/skill-installer.ts
1104
- import * as fs4 from "node:fs/promises";
1105
- import * as path4 from "node:path";
1106
- function isInside(resolved, destDir) {
1107
- const root = path4.resolve(destDir);
1108
- return resolved === root || resolved.startsWith(root + path4.sep);
1109
- }
1110
- var MAX_SKILL_FILE_SIZE = SKILL_LIMITS.MAX_SKILL_FILE_SIZE;
1111
- var SkillInstaller = class {
1112
- opts;
1113
- manifest;
1114
- adapters;
1115
- constructor(opts) {
1116
- this.opts = opts;
1117
- this.manifest = new SkillManifestStore(opts.manifestPath);
1118
- this.adapters = opts.registryAdapters?.length ? opts.registryAdapters : [githubDirectAdapter];
1119
- }
1120
- /**
1121
- * Install skills from a skill reference.
1122
- *
1123
- * Accepts two ref formats:
1124
- * - `user/repo[@ref]` — direct GitHub install (original path)
1125
- * - `<adapterId>:<registryId>` — registry-resolved (e.g.
1126
- * `skills.sh:owner/repo@v1`); the
1127
- * adapter resolves it to a `user/repo`
1128
- * ref and the install proceeds as above.
1129
- *
1130
- * Supports both single-skill repos (SKILL.md at root) and multi-skill repos
1131
- * (skills/ subdirectory). The manifest records the GitHub source as
1132
- * `github:owner/repo` (so `/skill-update` keeps working) plus the originating
1133
- * registry in `registryFrom` when the install came through a registry.
1134
- */
1135
- async install(refInput, opts) {
1136
- const resolved = this.resolveRef(refInput);
1137
- const parsed = parseSkillRef(resolved.installRef);
1138
- const scope = opts?.global ? "user" : "project";
1139
- const targetDir = scope === "project" ? this.opts.projectSkillsDir : this.opts.globalSkillsDir;
1140
- const source = `github:${parsed.owner}/${parsed.repo}`;
1141
- this.opts.log?.(
1142
- resolved.fromRegistry ? `Resolving ${refInput} \u2192 ${resolved.installRef} (${resolved.adapterId}), downloading...` : `Downloading ${parsed.owner}/${parsed.repo}@${parsed.ref}...`
1143
- );
1144
- const { tempDir } = await downloadGitHubTarball(parsed);
1145
- try {
1146
- const skills = await this.detectSkills(tempDir);
1147
- if (skills.length === 0) {
1148
- throw new WrongStackError({
1149
- message: "No skills found in repository. Expected SKILL.md at root or skills/ subdirectory.",
1150
- code: ERROR_CODES.VALIDATION_ERROR,
1151
- subsystem: "general",
1152
- context: { owner: parsed.owner, repo: parsed.repo, ref: parsed.ref }
1153
- });
1154
- }
1155
- const results = [];
1156
- for (const skill of skills) {
1157
- const existing = await this.manifest.findByName(skill.name);
1158
- const existingInScope = existing.find((e) => e.scope === scope);
1159
- if (existingInScope) {
1160
- this.opts.log?.(`Overwriting existing skill "${skill.name}" (${scope})...`);
1161
- await this.removeSkillFiles(skill.name, scope);
1162
- }
1163
- const destDir = path4.join(targetDir, skill.name);
1164
- await fs4.mkdir(destDir, { recursive: true });
1165
- const copiedFiles = [];
1166
- for (const file of skill.files) {
1167
- const srcPath = path4.join(skill.baseDir, file);
1168
- const destPath = path4.join(destDir, file);
1169
- const resolved2 = path4.resolve(destPath);
1170
- if (!isInside(resolved2, destDir)) {
1171
- throw new FsError({
1172
- message: `Path traversal detected in skill file: ${file}`,
1173
- code: ERROR_CODES.FS_DELETE_FAILED,
1174
- path: destPath,
1175
- context: { reason: "path_traversal", skillName: skill.name }
1176
- });
1177
- }
1178
- const stat2 = await fs4.stat(srcPath);
1179
- if (stat2.size > MAX_SKILL_FILE_SIZE) {
1180
- throw new FsError({
1181
- message: `Skill file "${file}" is too large (${(stat2.size / 1024).toFixed(1)}KB). Max: ${MAX_SKILL_FILE_SIZE / 1024}KB`,
1182
- code: ERROR_CODES.FS_WRITE_FAILED,
1183
- path: srcPath,
1184
- context: { skillName: skill.name, fileSize: stat2.size, maxSize: MAX_SKILL_FILE_SIZE }
1185
- });
1186
- }
1187
- await fs4.mkdir(path4.dirname(destPath), { recursive: true });
1188
- await fs4.copyFile(srcPath, destPath);
1189
- copiedFiles.push(file);
1190
- }
1191
- const entry = {
1192
- name: skill.name,
1193
- source,
1194
- ref: parsed.ref,
1195
- scope,
1196
- projectHash: scope === "project" ? this.opts.projectHash : void 0,
1197
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1198
- files: copiedFiles,
1199
- // When the install came through a registry (e.g. skills.sh), record
1200
- // which adapter + registry id resolved to this GitHub repo, so the
1201
- // source is traceable. `source` stays `github:owner/repo` for
1202
- // backward compat with `/skill-update`.
1203
- ...resolved.fromRegistry ? { registryFrom: { adapterId: resolved.adapterId, registryId: resolved.registryId } } : {}
1204
- };
1205
- await this.manifest.addEntry(entry);
1206
- results.push({
1207
- name: skill.name,
1208
- path: destDir,
1209
- scope,
1210
- source,
1211
- ref: parsed.ref,
1212
- skillCount: 1
1213
- });
1214
- }
1215
- this.invalidateLoaderCache();
1216
- return results;
1217
- } finally {
1218
- await fs4.rm(tempDir, { recursive: true, force: true }).catch(() => {
1219
- });
1220
- }
1221
- }
1222
- /**
1223
- * Import skills from a local directory (e.g. `.claude/skills`) into the
1224
- * project or user skills dir, optionally as symlinks. Used by `/skill-import`
1225
- * to take ownership of foreign skills so they can be edited/committed.
1226
- * Each direct subdirectory containing a valid `SKILL.md` is copied verbatim.
1227
- */
1228
- async importFromDir(srcDir, opts) {
1229
- const scope = opts?.global ? "user" : "project";
1230
- const targetDir = scope === "project" ? this.opts.projectSkillsDir : this.opts.globalSkillsDir;
1231
- let entries;
1232
- try {
1233
- entries = await fs4.readdir(srcDir, { withFileTypes: true });
1234
- } catch {
1235
- throw new WrongStackError({
1236
- message: `Source directory not found or not readable: ${srcDir}`,
1237
- code: ERROR_CODES.VALIDATION_ERROR,
1238
- subsystem: "general",
1239
- context: { srcDir }
1240
- });
1241
- }
1242
- const results = [];
1243
- for (const e of entries) {
1244
- if (!await entryIsDirectory(srcDir, e)) continue;
1245
- const skillMdPath = path4.join(srcDir, e.name, "SKILL.md");
1246
- let content;
1247
- try {
1248
- content = await fs4.readFile(skillMdPath, "utf8");
1249
- } catch {
1250
- continue;
1251
- }
1252
- const fm = parseSkillFrontmatter(content);
1253
- if (!fm.name || !fm.description || !isValidSkillNameFormat(fm.name)) continue;
1254
- const existing = await this.manifest.findByName(fm.name);
1255
- if (existing.find((x) => x.scope === scope)) {
1256
- await this.removeSkillFiles(fm.name, scope);
1257
- }
1258
- const destDir = path4.join(targetDir, fm.name);
1259
- await fs4.mkdir(destDir, { recursive: true });
1260
- const srcSkillDir = path4.join(srcDir, e.name);
1261
- const files = await collectFiles(srcSkillDir, srcSkillDir);
1262
- const copiedFiles = [];
1263
- for (const file of files) {
1264
- const srcPath = path4.join(srcSkillDir, file);
1265
- const destPath = path4.join(destDir, file);
1266
- const resolved = path4.resolve(destPath);
1267
- if (!isInside(resolved, destDir)) {
1268
- throw new FsError({
1269
- message: `Path traversal detected in skill file: ${file}`,
1270
- code: ERROR_CODES.FS_DELETE_FAILED,
1271
- path: destPath,
1272
- context: { reason: "path_traversal", skillName: fm.name }
1273
- });
1274
- }
1275
- await fs4.mkdir(path4.dirname(destPath), { recursive: true });
1276
- if (opts?.link) {
1277
- try {
1278
- await fs4.symlink(srcPath, destPath);
1279
- } catch (err) {
1280
- await fs4.copyFile(srcPath, destPath);
1281
- this.opts.log?.(`symlink failed for ${file} (${toErrorMessage(err)}); copied instead`);
1282
- }
1283
- } else {
1284
- await fs4.copyFile(srcPath, destPath);
1285
- }
1286
- copiedFiles.push(file);
1287
- }
1288
- await this.manifest.addEntry({
1289
- name: fm.name,
1290
- source: `import:${srcDir}`,
1291
- ref: "-",
1292
- scope,
1293
- projectHash: scope === "project" ? this.opts.projectHash : void 0,
1294
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1295
- files: copiedFiles
1296
- });
1297
- results.push({
1298
- name: fm.name,
1299
- path: destDir,
1300
- scope,
1301
- source: `import:${srcDir}`,
1302
- ref: "-",
1303
- skillCount: 1
1304
- });
1305
- }
1306
- this.invalidateLoaderCache();
1307
- return results;
1308
- }
1309
- /**
1310
- * Update installed skills.
1311
- * - No args: update all
1312
- * - Name: update that specific skill
1313
- * - Name + newRef: update to a different ref
1314
- */
1315
- async update(nameOrRef, _opts) {
1316
- const result = { updated: [], unchanged: [], errors: [] };
1317
- const allEntries = await this.manifest.listAll();
1318
- let targets;
1319
- if (nameOrRef) {
1320
- const byName = allEntries.filter((e) => e.name === nameOrRef);
1321
- if (byName.length > 0) {
1322
- targets = byName;
1323
- } else {
1324
- try {
1325
- const parsed = parseSkillRef(nameOrRef);
1326
- const source = `github:${parsed.owner}/${parsed.repo}`;
1327
- targets = allEntries.filter((e) => e.source === source);
1328
- if (targets.length === 0) {
1329
- result.errors.push({
1330
- name: nameOrRef,
1331
- error: `No installed skills found matching "${nameOrRef}"`
1332
- });
1333
- return result;
1334
- }
1335
- } catch {
1336
- result.errors.push({
1337
- name: nameOrRef,
1338
- error: `Invalid reference: ${nameOrRef}`
1339
- });
1340
- return result;
1341
- }
1342
- }
1343
- } else {
1344
- targets = allEntries;
1345
- }
1346
- const bySource = /* @__PURE__ */ new Map();
1347
- for (const entry of targets) {
1348
- const key = `${entry.source}@${entry.ref}`;
1349
- if (!bySource.has(key)) bySource.set(key, []);
1350
- bySource.get(key)?.push(entry);
1351
- }
1352
- for (const [, entries] of bySource) {
1353
- const first = expectDefined(entries[0]);
1354
- const scope = first.scope;
1355
- const isGlobal = scope === "user";
1356
- try {
1357
- const sourceRepo = first.source.replace("github:", "");
1358
- let refToInstall = first.ref;
1359
- if (nameOrRef && !allEntries.find((e) => e.name === nameOrRef)) {
1360
- try {
1361
- const parsed = parseSkillRef(nameOrRef);
1362
- refToInstall = parsed.ref;
1363
- } catch {
1364
- }
1365
- }
1366
- this.opts.log?.(`Updating ${first.source}@${refToInstall}...`);
1367
- const results = await this.install(`${sourceRepo}@${refToInstall}`, { global: isGlobal });
1368
- for (const r of results) {
1369
- result.updated.push({
1370
- name: r.name,
1371
- oldRef: first.ref,
1372
- newRef: refToInstall
1373
- });
1374
- }
1375
- } catch (err) {
1376
- const msg = toErrorMessage(err);
1377
- for (const entry of entries) {
1378
- result.errors.push({ name: entry.name, error: msg });
1379
- }
1380
- }
1381
- }
1382
- return result;
1383
- }
1384
- /**
1385
- * Uninstall a skill by name.
1386
- */
1387
- async uninstall(name, opts) {
1388
- const scope = opts?.global ? "user" : "project";
1389
- const entries = await this.manifest.findByName(name);
1390
- const entry = entries.find((e) => e.scope === scope);
1391
- if (!entry) {
1392
- throw new WrongStackError({
1393
- message: `Skill "${name}" is not installed${scope === "user" ? " (global)" : ""}.`,
1394
- code: ERROR_CODES.VALIDATION_ERROR,
1395
- subsystem: "general",
1396
- context: { skillName: name, scope }
1397
- });
1398
- }
1399
- await this.removeSkillFiles(name, scope);
1400
- await this.manifest.removeEntry(name, scope);
1401
- this.invalidateLoaderCache();
1402
- }
1403
- /**
1404
- * List all installed skills from the manifest.
1405
- */
1406
- async listInstalled() {
1407
- return this.manifest.listAll();
1408
- }
1409
- /**
1410
- * Search across all configured registry adapters. Results from each adapter
1411
- * are merged (deduplicated by `installRef`, adapters earlier in the list win
1412
- * conflicts). Adapters that don't support search (e.g. github-direct)
1413
- * contribute nothing.
1414
- */
1415
- async search(query, opts) {
1416
- const settled = await Promise.allSettled(this.adapters.map((a) => a.search(query, opts)));
1417
- const perAdapter = [];
1418
- const errors = [];
1419
- settled.forEach((s, i) => {
1420
- if (s.status === "fulfilled") perAdapter.push(s.value);
1421
- else {
1422
- const id = this.adapters[i]?.id ?? "?";
1423
- errors.push(`${id}: ${toErrorMessage(s.reason)}`);
1424
- }
1425
- });
1426
- if (errors.length > 0) {
1427
- this.opts.log?.(`Some registries failed during search: ${errors.join("; ")}`);
1428
- }
1429
- return dedupeSearchResults(perAdapter);
1430
- }
1431
- // ── Private helpers ──────────────────────────────────────────────
1432
- /**
1433
- * Detect skills in an extracted repository.
1434
- * Returns an array of detected skills with their files.
1435
- */
1436
- async detectSkills(baseDir) {
1437
- const results = [];
1438
- const rootSkillMd = path4.join(baseDir, "SKILL.md");
1439
- try {
1440
- await fs4.access(rootSkillMd);
1441
- const content = await fs4.readFile(rootSkillMd, "utf8");
1442
- const fm = parseSkillFrontmatter(content);
1443
- if (fm.name && fm.description && isValidSkillNameFormat(fm.name)) {
1444
- results.push({
1445
- name: fm.name,
1446
- baseDir,
1447
- files: ["SKILL.md"]
1448
- });
1449
- return results;
1450
- }
1451
- } catch {
1452
- }
1453
- const skillsDir = path4.join(baseDir, "skills");
1454
- try {
1455
- const entries = await fs4.readdir(skillsDir, { withFileTypes: true });
1456
- for (const entry of entries) {
1457
- if (!entry.isDirectory()) continue;
1458
- const skillFile = path4.join(skillsDir, entry.name, "SKILL.md");
1459
- try {
1460
- const content = await fs4.readFile(skillFile, "utf8");
1461
- const fm = parseSkillFrontmatter(content);
1462
- if (fm.name && fm.description && isValidSkillNameFormat(fm.name)) {
1463
- const skillDir = path4.join(skillsDir, entry.name);
1464
- const files = await collectFiles(skillDir, skillDir);
1465
- results.push({
1466
- name: fm.name,
1467
- baseDir: skillDir,
1468
- files
1469
- });
1470
- }
1471
- } catch {
1472
- }
1473
- }
1474
- } catch {
1475
- }
1476
- return results;
1477
- }
1478
- /**
1479
- * Remove all files for an installed skill.
1480
- */
1481
- /**
1482
- * Delete an installed skill's directory.
1483
- *
1484
- * The containment check is not ceremonial: this is a `recursive: true,
1485
- * force: true` delete whose target is built from a skill NAME taken from
1486
- * manifest frontmatter. `path.join` normalises `..`, so a crafted name
1487
- * escapes `targetDir` and takes the recursive delete with it. This file
1488
- * already had `isInside` and applied it on the copy path but not here.
1489
- *
1490
- * The strict-subdirectory requirement matters separately: `isInside` allows
1491
- * equality by design, and an empty or `.` name resolves `skillDir` to
1492
- * `targetDir` itself — deleting every installed skill (WS-097).
1493
- */
1494
- async removeSkillFiles(name, scope) {
1495
- const targetDir = scope === "project" ? this.opts.projectSkillsDir : this.opts.globalSkillsDir;
1496
- const root = path4.resolve(targetDir);
1497
- const skillDir = path4.resolve(path4.join(targetDir, name));
1498
- if (skillDir === root || !isInside(skillDir, root)) {
1499
- throw new FsError({
1500
- message: `Refusing to delete skill files outside the skills directory: ${name}`,
1501
- code: ERROR_CODES.FS_DELETE_FAILED,
1502
- path: skillDir,
1503
- context: { reason: "path_traversal", skillName: name, scope }
1504
- });
1505
- }
1506
- await fs4.rm(skillDir, { recursive: true, force: true });
1507
- }
1508
- /**
1509
- * Invalidate the skill loader's cache so newly installed skills appear.
1510
- */
1511
- invalidateLoaderCache() {
1512
- const loader = this.opts.skillLoader;
1513
- if (loader && typeof loader.invalidateCache === "function") {
1514
- loader.invalidateCache();
1515
- }
1516
- }
1517
- /**
1518
- * Resolve an install ref, dispatching registry-prefixed refs to the matching
1519
- * adapter. Returns the concrete `user/repo[@ref]` install ref plus provenance
1520
- * (which adapter resolved it, if any).
1521
- */
1522
- resolveRef(refInput) {
1523
- const trimmed = refInput.trim();
1524
- const colonIdx = trimmed.indexOf(":");
1525
- if (colonIdx > 0) {
1526
- const maybeAdapterId = trimmed.slice(0, colonIdx);
1527
- const adapter = this.adapters.find((a) => a.id === maybeAdapterId);
1528
- if (adapter) {
1529
- const registryId = trimmed.slice(colonIdx + 1);
1530
- const installRef = adapter.resolveInstallRef(registryId);
1531
- return { installRef, fromRegistry: true, adapterId: adapter.id, registryId };
1532
- }
1533
- }
1534
- return { installRef: trimmed, fromRegistry: false, adapterId: "", registryId: "" };
1535
- }
1536
- };
1537
- async function entryIsDirectory(dir, entry) {
1538
- if (entry.isDirectory()) return true;
1539
- if (entry.isSymbolicLink()) {
1540
- try {
1541
- return (await fs4.stat(path4.join(dir, entry.name))).isDirectory();
1542
- } catch {
1543
- return false;
1544
- }
1545
- }
1546
- return false;
1547
- }
1548
- async function collectFiles(dir, baseDir) {
1549
- const results = [];
1550
- const entries = await fs4.readdir(dir, { withFileTypes: true });
1551
- for (const entry of entries) {
1552
- const fullPath = path4.join(dir, entry.name);
1553
- const relPath = path4.relative(baseDir, fullPath);
1554
- if (entry.isDirectory()) {
1555
- if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
1556
- results.push(...await collectFiles(fullPath, baseDir));
1557
- } else {
1558
- results.push(relPath);
1559
- }
1560
- }
1561
- return results;
1562
- }
1563
- function dedupeSearchResults(perAdapter) {
1564
- const seen = /* @__PURE__ */ new Set();
1565
- const out = [];
1566
- for (const block of perAdapter) {
1567
- const kept = [];
1568
- for (const r of block.results) {
1569
- const key = r.installRef;
1570
- if (seen.has(key)) continue;
1571
- seen.add(key);
1572
- kept.push(r);
1573
- }
1574
- if (kept.length > 0 || block.results.length > 0) {
1575
- out.push({ ...block, results: kept });
1576
- }
1577
- }
1578
- return out;
1579
- }
3
+ DEFAULT_SKILLS_SH_URL,
4
+ SkillInstaller,
5
+ SkillManifestStore,
6
+ bodyLineAdvisory,
7
+ createSkillsShAdapter,
8
+ downloadGitHubTarball,
9
+ extractSkillFromPrompt,
10
+ generateSkillSkeleton,
11
+ githubDirectAdapter,
12
+ openInEditor,
13
+ parseSkillRef,
14
+ validateSkillNameAvailable,
15
+ writeSkeletonSkill
16
+ } from "../chunk-SN2DQRL6.js";
17
+ import {
18
+ FOREIGN_SKILL_TOOLS,
19
+ resolveForeignToolIds,
20
+ resolveForeignToolIdsWithWarnings,
21
+ securityScoreToTier
22
+ } from "../chunk-JVKB2TBL.js";
23
+ import "../chunk-LHOA5DWA.js";
24
+ import {
25
+ SKILL_LIMITS
26
+ } from "../chunk-XEBHSJLT.js";
27
+ import "../chunk-7ZHZE5EO.js";
28
+ import {
29
+ isValidSkillNameFormat,
30
+ parseSkillFrontmatter,
31
+ stripFrontmatter,
32
+ validateSkillName
33
+ } from "../chunk-WEBMSQP2.js";
34
+ import "../chunk-WOEZV3Q4.js";
35
+ import "../chunk-TBRL2RCC.js";
36
+ import "../chunk-PFZO3C6H.js";
1580
37
  export {
1581
38
  DEFAULT_SKILLS_SH_URL,
1582
39
  FOREIGN_SKILL_TOOLS,