@skillsmith/mcp-server 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 (306) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/src/__tests__/get-skill.test.d.ts +6 -0
  3. package/dist/src/__tests__/get-skill.test.d.ts.map +1 -0
  4. package/dist/src/__tests__/get-skill.test.js +88 -0
  5. package/dist/src/__tests__/get-skill.test.js.map +1 -0
  6. package/dist/src/__tests__/middleware/errorFormatter.test.d.ts +7 -0
  7. package/dist/src/__tests__/middleware/errorFormatter.test.d.ts.map +1 -0
  8. package/dist/src/__tests__/middleware/errorFormatter.test.js +304 -0
  9. package/dist/src/__tests__/middleware/errorFormatter.test.js.map +1 -0
  10. package/dist/src/__tests__/middleware/license.test.d.ts +7 -0
  11. package/dist/src/__tests__/middleware/license.test.d.ts.map +1 -0
  12. package/dist/src/__tests__/middleware/license.test.js +500 -0
  13. package/dist/src/__tests__/middleware/license.test.js.map +1 -0
  14. package/dist/src/__tests__/search.test.d.ts +6 -0
  15. package/dist/src/__tests__/search.test.d.ts.map +1 -0
  16. package/dist/src/__tests__/search.test.js +86 -0
  17. package/dist/src/__tests__/search.test.js.map +1 -0
  18. package/dist/src/__tests__/test-utils.d.ts +19 -0
  19. package/dist/src/__tests__/test-utils.d.ts.map +1 -0
  20. package/dist/src/__tests__/test-utils.js +87 -0
  21. package/dist/src/__tests__/test-utils.js.map +1 -0
  22. package/dist/src/context/index.d.ts +19 -0
  23. package/dist/src/context/index.d.ts.map +1 -0
  24. package/dist/src/context/index.js +25 -0
  25. package/dist/src/context/index.js.map +1 -0
  26. package/dist/src/context/project-detector.d.ts +145 -0
  27. package/dist/src/context/project-detector.d.ts.map +1 -0
  28. package/dist/src/context/project-detector.js +321 -0
  29. package/dist/src/context/project-detector.js.map +1 -0
  30. package/dist/src/context.d.ts +100 -0
  31. package/dist/src/context.d.ts.map +1 -0
  32. package/dist/src/context.js +157 -0
  33. package/dist/src/context.js.map +1 -0
  34. package/dist/src/core-shim.d.ts +7 -0
  35. package/dist/src/core-shim.d.ts.map +1 -0
  36. package/dist/src/core-shim.js +9 -0
  37. package/dist/src/core-shim.js.map +1 -0
  38. package/dist/src/health/healthCheck.d.ts +88 -0
  39. package/dist/src/health/healthCheck.d.ts.map +1 -0
  40. package/dist/src/health/healthCheck.js +117 -0
  41. package/dist/src/health/healthCheck.js.map +1 -0
  42. package/dist/src/health/index.d.ts +21 -0
  43. package/dist/src/health/index.d.ts.map +1 -0
  44. package/dist/src/health/index.js +21 -0
  45. package/dist/src/health/index.js.map +1 -0
  46. package/dist/src/health/readinessCheck.d.ts +139 -0
  47. package/dist/src/health/readinessCheck.d.ts.map +1 -0
  48. package/dist/src/health/readinessCheck.js +266 -0
  49. package/dist/src/health/readinessCheck.js.map +1 -0
  50. package/dist/src/index.d.ts +8 -0
  51. package/dist/src/index.d.ts.map +1 -0
  52. package/dist/src/index.js +178 -0
  53. package/dist/src/index.js.map +1 -0
  54. package/dist/src/index.test.d.ts +2 -0
  55. package/dist/src/index.test.d.ts.map +1 -0
  56. package/dist/src/index.test.js +43 -0
  57. package/dist/src/index.test.js.map +1 -0
  58. package/dist/src/logger.d.ts +26 -0
  59. package/dist/src/logger.d.ts.map +1 -0
  60. package/dist/src/logger.js +179 -0
  61. package/dist/src/logger.js.map +1 -0
  62. package/dist/src/middleware/__tests__/csp.test.d.ts +2 -0
  63. package/dist/src/middleware/__tests__/csp.test.d.ts.map +1 -0
  64. package/dist/src/middleware/__tests__/csp.test.js +389 -0
  65. package/dist/src/middleware/__tests__/csp.test.js.map +1 -0
  66. package/dist/src/middleware/csp.d.ts +87 -0
  67. package/dist/src/middleware/csp.d.ts.map +1 -0
  68. package/dist/src/middleware/csp.js +273 -0
  69. package/dist/src/middleware/csp.js.map +1 -0
  70. package/dist/src/middleware/degradation.d.ts +99 -0
  71. package/dist/src/middleware/degradation.d.ts.map +1 -0
  72. package/dist/src/middleware/degradation.js +315 -0
  73. package/dist/src/middleware/degradation.js.map +1 -0
  74. package/dist/src/middleware/errorFormatter.d.ts +119 -0
  75. package/dist/src/middleware/errorFormatter.d.ts.map +1 -0
  76. package/dist/src/middleware/errorFormatter.js +294 -0
  77. package/dist/src/middleware/errorFormatter.js.map +1 -0
  78. package/dist/src/middleware/index.d.ts +10 -0
  79. package/dist/src/middleware/index.d.ts.map +1 -0
  80. package/dist/src/middleware/index.js +14 -0
  81. package/dist/src/middleware/index.js.map +1 -0
  82. package/dist/src/middleware/license.d.ts +161 -0
  83. package/dist/src/middleware/license.d.ts.map +1 -0
  84. package/dist/src/middleware/license.js +281 -0
  85. package/dist/src/middleware/license.js.map +1 -0
  86. package/dist/src/middleware/toolFeatureMapping.d.ts +36 -0
  87. package/dist/src/middleware/toolFeatureMapping.d.ts.map +1 -0
  88. package/dist/src/middleware/toolFeatureMapping.js +90 -0
  89. package/dist/src/middleware/toolFeatureMapping.js.map +1 -0
  90. package/dist/src/onboarding/first-run.d.ts +64 -0
  91. package/dist/src/onboarding/first-run.d.ts.map +1 -0
  92. package/dist/src/onboarding/first-run.js +77 -0
  93. package/dist/src/onboarding/first-run.js.map +1 -0
  94. package/dist/src/onboarding/index.d.ts +7 -0
  95. package/dist/src/onboarding/index.d.ts.map +1 -0
  96. package/dist/src/onboarding/index.js +7 -0
  97. package/dist/src/onboarding/index.js.map +1 -0
  98. package/dist/src/suggestions/index.d.ts +21 -0
  99. package/dist/src/suggestions/index.d.ts.map +1 -0
  100. package/dist/src/suggestions/index.js +20 -0
  101. package/dist/src/suggestions/index.js.map +1 -0
  102. package/dist/src/suggestions/suggestion-engine.d.ts +185 -0
  103. package/dist/src/suggestions/suggestion-engine.d.ts.map +1 -0
  104. package/dist/src/suggestions/suggestion-engine.js +352 -0
  105. package/dist/src/suggestions/suggestion-engine.js.map +1 -0
  106. package/dist/src/suggestions/types.d.ts +88 -0
  107. package/dist/src/suggestions/types.d.ts.map +1 -0
  108. package/dist/src/suggestions/types.js +21 -0
  109. package/dist/src/suggestions/types.js.map +1 -0
  110. package/dist/src/tools/analyze.d.ts +151 -0
  111. package/dist/src/tools/analyze.d.ts.map +1 -0
  112. package/dist/src/tools/analyze.js +205 -0
  113. package/dist/src/tools/analyze.js.map +1 -0
  114. package/dist/src/tools/compare.d.ts +149 -0
  115. package/dist/src/tools/compare.d.ts.map +1 -0
  116. package/dist/src/tools/compare.js +464 -0
  117. package/dist/src/tools/compare.js.map +1 -0
  118. package/dist/src/tools/get-skill.d.ts +116 -0
  119. package/dist/src/tools/get-skill.d.ts.map +1 -0
  120. package/dist/src/tools/get-skill.js +224 -0
  121. package/dist/src/tools/get-skill.js.map +1 -0
  122. package/dist/src/tools/index.d.ts +20 -0
  123. package/dist/src/tools/index.d.ts.map +1 -0
  124. package/dist/src/tools/index.js +20 -0
  125. package/dist/src/tools/index.js.map +1 -0
  126. package/dist/src/tools/install.d.ts +122 -0
  127. package/dist/src/tools/install.d.ts.map +1 -0
  128. package/dist/src/tools/install.js +314 -0
  129. package/dist/src/tools/install.js.map +1 -0
  130. package/dist/src/tools/recommend.d.ts +171 -0
  131. package/dist/src/tools/recommend.d.ts.map +1 -0
  132. package/dist/src/tools/recommend.js +325 -0
  133. package/dist/src/tools/recommend.js.map +1 -0
  134. package/dist/src/tools/search.d.ts +121 -0
  135. package/dist/src/tools/search.d.ts.map +1 -0
  136. package/dist/src/tools/search.js +249 -0
  137. package/dist/src/tools/search.js.map +1 -0
  138. package/dist/src/tools/suggest.d.ts +181 -0
  139. package/dist/src/tools/suggest.d.ts.map +1 -0
  140. package/dist/src/tools/suggest.js +342 -0
  141. package/dist/src/tools/suggest.js.map +1 -0
  142. package/dist/src/tools/uninstall.d.ts +123 -0
  143. package/dist/src/tools/uninstall.d.ts.map +1 -0
  144. package/dist/src/tools/uninstall.js +250 -0
  145. package/dist/src/tools/uninstall.js.map +1 -0
  146. package/dist/src/tools/validate.d.ts +122 -0
  147. package/dist/src/tools/validate.d.ts.map +1 -0
  148. package/dist/src/tools/validate.js +497 -0
  149. package/dist/src/tools/validate.js.map +1 -0
  150. package/dist/src/utils/installed-skills.d.ts +101 -0
  151. package/dist/src/utils/installed-skills.d.ts.map +1 -0
  152. package/dist/src/utils/installed-skills.js +220 -0
  153. package/dist/src/utils/installed-skills.js.map +1 -0
  154. package/dist/src/utils/validation.d.ts +76 -0
  155. package/dist/src/utils/validation.d.ts.map +1 -0
  156. package/dist/src/utils/validation.js +153 -0
  157. package/dist/src/utils/validation.js.map +1 -0
  158. package/dist/src/webhooks/index.d.ts +8 -0
  159. package/dist/src/webhooks/index.d.ts.map +1 -0
  160. package/dist/src/webhooks/index.js +9 -0
  161. package/dist/src/webhooks/index.js.map +1 -0
  162. package/dist/src/webhooks/webhook-endpoint.d.ts +149 -0
  163. package/dist/src/webhooks/webhook-endpoint.d.ts.map +1 -0
  164. package/dist/src/webhooks/webhook-endpoint.js +339 -0
  165. package/dist/src/webhooks/webhook-endpoint.js.map +1 -0
  166. package/dist/tests/compare.test.d.ts +6 -0
  167. package/dist/tests/compare.test.d.ts.map +1 -0
  168. package/dist/tests/compare.test.js +225 -0
  169. package/dist/tests/compare.test.js.map +1 -0
  170. package/dist/tests/context/project-detector.test.d.ts +6 -0
  171. package/dist/tests/context/project-detector.test.d.ts.map +1 -0
  172. package/dist/tests/context/project-detector.test.js +719 -0
  173. package/dist/tests/context/project-detector.test.js.map +1 -0
  174. package/dist/tests/e2e/compare.e2e.test.d.ts +10 -0
  175. package/dist/tests/e2e/compare.e2e.test.d.ts.map +1 -0
  176. package/dist/tests/e2e/compare.e2e.test.js +286 -0
  177. package/dist/tests/e2e/compare.e2e.test.js.map +1 -0
  178. package/dist/tests/e2e/install-flow.e2e.test.d.ts +10 -0
  179. package/dist/tests/e2e/install-flow.e2e.test.d.ts.map +1 -0
  180. package/dist/tests/e2e/install-flow.e2e.test.js +209 -0
  181. package/dist/tests/e2e/install-flow.e2e.test.js.map +1 -0
  182. package/dist/tests/e2e/recommend.e2e.test.d.ts +12 -0
  183. package/dist/tests/e2e/recommend.e2e.test.d.ts.map +1 -0
  184. package/dist/tests/e2e/recommend.e2e.test.js +347 -0
  185. package/dist/tests/e2e/recommend.e2e.test.js.map +1 -0
  186. package/dist/tests/e2e/skill-flow.e2e.test.d.ts +10 -0
  187. package/dist/tests/e2e/skill-flow.e2e.test.d.ts.map +1 -0
  188. package/dist/tests/e2e/skill-flow.e2e.test.js +280 -0
  189. package/dist/tests/e2e/skill-flow.e2e.test.js.map +1 -0
  190. package/dist/tests/e2e/suggest.e2e.test.d.ts +13 -0
  191. package/dist/tests/e2e/suggest.e2e.test.d.ts.map +1 -0
  192. package/dist/tests/e2e/suggest.e2e.test.js +347 -0
  193. package/dist/tests/e2e/suggest.e2e.test.js.map +1 -0
  194. package/dist/tests/e2e/utils/baseline-collector.d.ts +107 -0
  195. package/dist/tests/e2e/utils/baseline-collector.d.ts.map +1 -0
  196. package/dist/tests/e2e/utils/baseline-collector.js +211 -0
  197. package/dist/tests/e2e/utils/baseline-collector.js.map +1 -0
  198. package/dist/tests/e2e/utils/hardcoded-detector.d.ts +46 -0
  199. package/dist/tests/e2e/utils/hardcoded-detector.d.ts.map +1 -0
  200. package/dist/tests/e2e/utils/hardcoded-detector.js +255 -0
  201. package/dist/tests/e2e/utils/hardcoded-detector.js.map +1 -0
  202. package/dist/tests/e2e/utils/index.d.ts +7 -0
  203. package/dist/tests/e2e/utils/index.d.ts.map +1 -0
  204. package/dist/tests/e2e/utils/index.js +7 -0
  205. package/dist/tests/e2e/utils/index.js.map +1 -0
  206. package/dist/tests/e2e/utils/linear-reporter.d.ts +60 -0
  207. package/dist/tests/e2e/utils/linear-reporter.d.ts.map +1 -0
  208. package/dist/tests/e2e/utils/linear-reporter.js +232 -0
  209. package/dist/tests/e2e/utils/linear-reporter.js.map +1 -0
  210. package/dist/tests/health.test.d.ts +9 -0
  211. package/dist/tests/health.test.d.ts.map +1 -0
  212. package/dist/tests/health.test.js +308 -0
  213. package/dist/tests/health.test.js.map +1 -0
  214. package/dist/tests/integration/analyze.integration.test.d.ts +2 -0
  215. package/dist/tests/integration/analyze.integration.test.d.ts.map +1 -0
  216. package/dist/tests/integration/analyze.integration.test.js +244 -0
  217. package/dist/tests/integration/analyze.integration.test.js.map +1 -0
  218. package/dist/tests/integration/compare.integration.test.d.ts +2 -0
  219. package/dist/tests/integration/compare.integration.test.d.ts.map +1 -0
  220. package/dist/tests/integration/compare.integration.test.js +120 -0
  221. package/dist/tests/integration/compare.integration.test.js.map +1 -0
  222. package/dist/tests/integration/fixtures/test-skills.d.ts +62 -0
  223. package/dist/tests/integration/fixtures/test-skills.d.ts.map +1 -0
  224. package/dist/tests/integration/fixtures/test-skills.js +644 -0
  225. package/dist/tests/integration/fixtures/test-skills.js.map +1 -0
  226. package/dist/tests/integration/get-skill.integration.test.d.ts +6 -0
  227. package/dist/tests/integration/get-skill.integration.test.d.ts.map +1 -0
  228. package/dist/tests/integration/get-skill.integration.test.js +203 -0
  229. package/dist/tests/integration/get-skill.integration.test.js.map +1 -0
  230. package/dist/tests/integration/github-api.integration.test.d.ts +14 -0
  231. package/dist/tests/integration/github-api.integration.test.d.ts.map +1 -0
  232. package/dist/tests/integration/github-api.integration.test.js +190 -0
  233. package/dist/tests/integration/github-api.integration.test.js.map +1 -0
  234. package/dist/tests/integration/install.integration.test.d.ts +6 -0
  235. package/dist/tests/integration/install.integration.test.d.ts.map +1 -0
  236. package/dist/tests/integration/install.integration.test.js +282 -0
  237. package/dist/tests/integration/install.integration.test.js.map +1 -0
  238. package/dist/tests/integration/recommend.integration.test.d.ts +2 -0
  239. package/dist/tests/integration/recommend.integration.test.d.ts.map +1 -0
  240. package/dist/tests/integration/recommend.integration.test.js +215 -0
  241. package/dist/tests/integration/recommend.integration.test.js.map +1 -0
  242. package/dist/tests/integration/search.integration.test.d.ts +6 -0
  243. package/dist/tests/integration/search.integration.test.d.ts.map +1 -0
  244. package/dist/tests/integration/search.integration.test.js +229 -0
  245. package/dist/tests/integration/search.integration.test.js.map +1 -0
  246. package/dist/tests/integration/setup.d.ts +71 -0
  247. package/dist/tests/integration/setup.d.ts.map +1 -0
  248. package/dist/tests/integration/setup.js +124 -0
  249. package/dist/tests/integration/setup.js.map +1 -0
  250. package/dist/tests/integration/uninstall.integration.test.d.ts +6 -0
  251. package/dist/tests/integration/uninstall.integration.test.d.ts.map +1 -0
  252. package/dist/tests/integration/uninstall.integration.test.js +296 -0
  253. package/dist/tests/integration/uninstall.integration.test.js.map +1 -0
  254. package/dist/tests/integration/validate.integration.test.d.ts +2 -0
  255. package/dist/tests/integration/validate.integration.test.d.ts.map +1 -0
  256. package/dist/tests/integration/validate.integration.test.js +181 -0
  257. package/dist/tests/integration/validate.integration.test.js.map +1 -0
  258. package/dist/tests/onboarding/first-run.test.d.ts +7 -0
  259. package/dist/tests/onboarding/first-run.test.d.ts.map +1 -0
  260. package/dist/tests/onboarding/first-run.test.js +258 -0
  261. package/dist/tests/onboarding/first-run.test.js.map +1 -0
  262. package/dist/tests/performance/search-performance.test.d.ts +10 -0
  263. package/dist/tests/performance/search-performance.test.d.ts.map +1 -0
  264. package/dist/tests/performance/search-performance.test.js +218 -0
  265. package/dist/tests/performance/search-performance.test.js.map +1 -0
  266. package/dist/tests/recommend.test.d.ts +6 -0
  267. package/dist/tests/recommend.test.d.ts.map +1 -0
  268. package/dist/tests/recommend.test.js +208 -0
  269. package/dist/tests/recommend.test.js.map +1 -0
  270. package/dist/tests/suggestions/suggestion-engine.test.d.ts +6 -0
  271. package/dist/tests/suggestions/suggestion-engine.test.d.ts.map +1 -0
  272. package/dist/tests/suggestions/suggestion-engine.test.js +448 -0
  273. package/dist/tests/suggestions/suggestion-engine.test.js.map +1 -0
  274. package/dist/tests/test-utils.d.ts +74 -0
  275. package/dist/tests/test-utils.d.ts.map +1 -0
  276. package/dist/tests/test-utils.js +98 -0
  277. package/dist/tests/test-utils.js.map +1 -0
  278. package/dist/tests/tools.test.d.ts +5 -0
  279. package/dist/tests/tools.test.d.ts.map +1 -0
  280. package/dist/tests/tools.test.js +138 -0
  281. package/dist/tests/tools.test.js.map +1 -0
  282. package/dist/tests/unit/installed-skills.test.d.ts +6 -0
  283. package/dist/tests/unit/installed-skills.test.d.ts.map +1 -0
  284. package/dist/tests/unit/installed-skills.test.js +285 -0
  285. package/dist/tests/unit/installed-skills.test.js.map +1 -0
  286. package/dist/tests/unit/logger.test.d.ts +6 -0
  287. package/dist/tests/unit/logger.test.d.ts.map +1 -0
  288. package/dist/tests/unit/logger.test.js +281 -0
  289. package/dist/tests/unit/logger.test.js.map +1 -0
  290. package/dist/tests/validate.test.d.ts +5 -0
  291. package/dist/tests/validate.test.d.ts.map +1 -0
  292. package/dist/tests/validate.test.js +303 -0
  293. package/dist/tests/validate.test.js.map +1 -0
  294. package/dist/tests/webhooks/proxy-trust.security.test.d.ts +8 -0
  295. package/dist/tests/webhooks/proxy-trust.security.test.d.ts.map +1 -0
  296. package/dist/tests/webhooks/proxy-trust.security.test.js +145 -0
  297. package/dist/tests/webhooks/proxy-trust.security.test.js.map +1 -0
  298. package/dist/tests/webhooks/rate-limiter.security.test.d.ts +8 -0
  299. package/dist/tests/webhooks/rate-limiter.security.test.d.ts.map +1 -0
  300. package/dist/tests/webhooks/rate-limiter.security.test.js +122 -0
  301. package/dist/tests/webhooks/rate-limiter.security.test.js.map +1 -0
  302. package/dist/vitest.config.d.ts +6 -0
  303. package/dist/vitest.config.d.ts.map +1 -0
  304. package/dist/vitest.config.js +13 -0
  305. package/dist/vitest.config.js.map +1 -0
  306. package/package.json +63 -0
@@ -0,0 +1,220 @@
1
+ /**
2
+ * @fileoverview Utility for auto-detecting installed skills from ~/.claude/skills/
3
+ * @module @skillsmith/mcp-server/utils/installed-skills
4
+ * @see SMI-906: Auto-detect installed skills from ~/.claude/skills/
5
+ *
6
+ * Scans the user's skills directory and extracts skill IDs from SKILL.md files.
7
+ * Falls back to folder name if no SKILL.md or no ID found in frontmatter.
8
+ *
9
+ * @example
10
+ * const skills = await getInstalledSkills();
11
+ * // Returns: ["docker", "linear", "varlock"]
12
+ */
13
+ import * as fs from 'node:fs';
14
+ import * as path from 'node:path';
15
+ import * as os from 'node:os';
16
+ /**
17
+ * Default skills directory path
18
+ */
19
+ const DEFAULT_SKILLS_DIR = path.join(os.homedir(), '.claude', 'skills');
20
+ /**
21
+ * Parse SKILL.md frontmatter to extract skill metadata.
22
+ *
23
+ * Extracts the `name` field from YAML frontmatter in SKILL.md files.
24
+ * Frontmatter is delimited by `---` lines at the start of the file.
25
+ *
26
+ * @param content - Content of the SKILL.md file
27
+ * @returns Parsed skill metadata, or null values if parsing fails
28
+ *
29
+ * @example
30
+ * const content = `---
31
+ * name: docker
32
+ * description: Docker skill
33
+ * ---
34
+ * # Docker Skill`;
35
+ * parseSkillMd(content); // { id: "docker", name: "docker", description: "Docker skill" }
36
+ */
37
+ export function parseSkillMd(content) {
38
+ const result = {
39
+ id: null,
40
+ name: null,
41
+ description: null,
42
+ };
43
+ // Check for frontmatter (starts with ---)
44
+ if (!content.startsWith('---')) {
45
+ return result;
46
+ }
47
+ // Find the closing --- delimiter
48
+ const secondDelimiterIndex = content.indexOf('---', 3);
49
+ if (secondDelimiterIndex === -1) {
50
+ return result;
51
+ }
52
+ // Extract frontmatter content
53
+ const frontmatter = content.substring(3, secondDelimiterIndex).trim();
54
+ // Parse YAML-like frontmatter (simple key: value parsing)
55
+ const lines = frontmatter.split('\n');
56
+ for (const line of lines) {
57
+ const colonIndex = line.indexOf(':');
58
+ if (colonIndex === -1)
59
+ continue;
60
+ const key = line.substring(0, colonIndex).trim().toLowerCase();
61
+ const value = line.substring(colonIndex + 1).trim();
62
+ if (key === 'name' && value) {
63
+ result.name = value;
64
+ result.id = value; // Use name as the skill ID
65
+ }
66
+ else if (key === 'description' && value) {
67
+ result.description = value;
68
+ }
69
+ }
70
+ return result;
71
+ }
72
+ /**
73
+ * Get the skill ID from a skill directory.
74
+ *
75
+ * Looks for SKILL.md in the directory and extracts the ID from frontmatter.
76
+ * Falls back to the directory name if no SKILL.md or no ID found.
77
+ *
78
+ * @param skillDir - Path to the skill directory
79
+ * @param dirName - Name of the directory (used as fallback)
80
+ * @returns Skill ID
81
+ */
82
+ export function getSkillIdFromDir(skillDir, dirName) {
83
+ const skillMdPath = path.join(skillDir, 'SKILL.md');
84
+ try {
85
+ if (fs.existsSync(skillMdPath)) {
86
+ const content = fs.readFileSync(skillMdPath, 'utf-8');
87
+ const parsed = parseSkillMd(content);
88
+ if (parsed.id) {
89
+ return parsed.id;
90
+ }
91
+ }
92
+ }
93
+ catch (error) {
94
+ console.warn('[installed-skills] Failed to parse SKILL.md:', skillMdPath, error instanceof Error ? error.message : String(error));
95
+ }
96
+ return dirName;
97
+ }
98
+ /**
99
+ * Auto-detect installed skills from ~/.claude/skills/ directory.
100
+ *
101
+ * Scans the skills directory for subdirectories containing SKILL.md files.
102
+ * Extracts the skill ID from the `name` field in SKILL.md frontmatter.
103
+ * Falls back to directory name if no SKILL.md or no name field found.
104
+ *
105
+ * @param skillsDir - Optional custom skills directory path (defaults to ~/.claude/skills/)
106
+ * @returns Promise resolving to array of skill IDs
107
+ *
108
+ * @example
109
+ * const skills = await getInstalledSkills();
110
+ * // Returns: ["docker", "linear", "varlock"]
111
+ *
112
+ * @example
113
+ * // With custom directory
114
+ * const skills = await getInstalledSkills('/path/to/custom/skills');
115
+ */
116
+ export async function getInstalledSkills(skillsDir) {
117
+ const dir = skillsDir || DEFAULT_SKILLS_DIR;
118
+ // Check if directory exists
119
+ if (!fs.existsSync(dir)) {
120
+ return [];
121
+ }
122
+ try {
123
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
124
+ const skillIds = [];
125
+ for (const entry of entries) {
126
+ // Skip non-directories and hidden directories
127
+ if (!entry.isDirectory() || entry.name.startsWith('.')) {
128
+ continue;
129
+ }
130
+ const skillDir = path.join(dir, entry.name);
131
+ const skillId = getSkillIdFromDir(skillDir, entry.name);
132
+ skillIds.push(skillId);
133
+ }
134
+ return skillIds.sort();
135
+ }
136
+ catch (error) {
137
+ console.warn('[installed-skills] Failed to read skills directory:', dir, error instanceof Error ? error.message : String(error));
138
+ return [];
139
+ }
140
+ }
141
+ /**
142
+ * Synchronous version of getInstalledSkills for use in non-async contexts.
143
+ *
144
+ * @param skillsDir - Optional custom skills directory path (defaults to ~/.claude/skills/)
145
+ * @returns Array of skill IDs
146
+ */
147
+ export function getInstalledSkillsSync(skillsDir) {
148
+ const dir = skillsDir || DEFAULT_SKILLS_DIR;
149
+ // Check if directory exists
150
+ if (!fs.existsSync(dir)) {
151
+ return [];
152
+ }
153
+ try {
154
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
155
+ const skillIds = [];
156
+ for (const entry of entries) {
157
+ // Skip non-directories and hidden directories
158
+ if (!entry.isDirectory() || entry.name.startsWith('.')) {
159
+ continue;
160
+ }
161
+ const skillDir = path.join(dir, entry.name);
162
+ const skillId = getSkillIdFromDir(skillDir, entry.name);
163
+ skillIds.push(skillId);
164
+ }
165
+ return skillIds.sort();
166
+ }
167
+ catch (error) {
168
+ console.warn('[installed-skills] Failed to read skills directory:', dir, error instanceof Error ? error.message : String(error));
169
+ return [];
170
+ }
171
+ }
172
+ export async function getInstalledSkillsDetailed(skillsDir) {
173
+ const dir = skillsDir || DEFAULT_SKILLS_DIR;
174
+ // Check if directory exists
175
+ if (!fs.existsSync(dir)) {
176
+ return [];
177
+ }
178
+ try {
179
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
180
+ const skills = [];
181
+ for (const entry of entries) {
182
+ // Skip non-directories and hidden directories
183
+ if (!entry.isDirectory() || entry.name.startsWith('.')) {
184
+ continue;
185
+ }
186
+ const skillDir = path.join(dir, entry.name);
187
+ const skillMdPath = path.join(skillDir, 'SKILL.md');
188
+ let hasSkillMd = false;
189
+ let description = null;
190
+ let id = entry.name;
191
+ try {
192
+ if (fs.existsSync(skillMdPath)) {
193
+ hasSkillMd = true;
194
+ const content = fs.readFileSync(skillMdPath, 'utf-8');
195
+ const parsed = parseSkillMd(content);
196
+ if (parsed.id) {
197
+ id = parsed.id;
198
+ }
199
+ description = parsed.description;
200
+ }
201
+ }
202
+ catch (error) {
203
+ console.warn('[installed-skills] Failed to parse SKILL.md for detailed info:', skillMdPath, error instanceof Error ? error.message : String(error));
204
+ }
205
+ skills.push({
206
+ id,
207
+ directory: entry.name,
208
+ path: skillDir,
209
+ hasSkillMd,
210
+ description,
211
+ });
212
+ }
213
+ return skills.sort((a, b) => a.id.localeCompare(b.id));
214
+ }
215
+ catch (error) {
216
+ console.warn('[installed-skills] Failed to get detailed skills:', dir, error instanceof Error ? error.message : String(error));
217
+ return [];
218
+ }
219
+ }
220
+ //# sourceMappingURL=installed-skills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installed-skills.js","sourceRoot":"","sources":["../../../src/utils/installed-skills.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * @fileoverview Shared validation utilities for MCP tools
3
+ * @module @skillsmith/mcp-server/utils/validation
4
+ * @see SMI-810: Create shared validation utility
5
+ *
6
+ * Provides common validation functions used across MCP tools:
7
+ * - Skill ID format validation
8
+ * - Skill ID parsing
9
+ * - Trust tier mapping between MCP and database types
10
+ */
11
+ import type { TrustTier as DBTrustTier, MCPTrustTier, SkillCategory } from '@skillsmith/core';
12
+ /**
13
+ * Validate skill ID format.
14
+ *
15
+ * Accepts two formats:
16
+ * - Author/name format: `anthropic/commit`, `community/jest-helper`
17
+ * - UUID format: `550e8400-e29b-41d4-a716-446655440000`
18
+ *
19
+ * @param id - Skill ID to validate
20
+ * @returns True if ID matches valid format
21
+ *
22
+ * @example
23
+ * isValidSkillId('anthropic/commit') // true
24
+ * isValidSkillId('invalid-format') // false
25
+ */
26
+ export declare function isValidSkillId(id: string): boolean;
27
+ /**
28
+ * Parse a skill ID into author and name components.
29
+ *
30
+ * @param id - Skill ID in author/name format
31
+ * @returns Object with author and name, or null if invalid format
32
+ *
33
+ * @example
34
+ * parseSkillId('anthropic/commit') // { author: 'anthropic', name: 'commit' }
35
+ * parseSkillId('invalid') // null
36
+ */
37
+ export declare function parseSkillId(id: string): {
38
+ author: string;
39
+ name: string;
40
+ } | null;
41
+ /**
42
+ * Map MCP trust tier to database trust tier.
43
+ *
44
+ * MCP types: verified, community, standard, unverified
45
+ * DB types: verified, community, experimental, unknown
46
+ *
47
+ * @param mcpTier - MCP trust tier
48
+ * @returns Database trust tier
49
+ */
50
+ export declare function mapTrustTierToDb(mcpTier: MCPTrustTier): DBTrustTier;
51
+ /**
52
+ * Map database trust tier to MCP trust tier.
53
+ *
54
+ * DB types: verified, community, experimental, unknown
55
+ * MCP types: verified, community, standard, unverified
56
+ *
57
+ * @param dbTier - Database trust tier
58
+ * @returns MCP trust tier
59
+ */
60
+ export declare function mapTrustTierFromDb(dbTier: DBTrustTier): MCPTrustTier;
61
+ /**
62
+ * Extract skill category from tags array.
63
+ *
64
+ * Searches through tags to find the first valid category match.
65
+ * Handles case-insensitive matching and common aliases.
66
+ *
67
+ * @param tags - Array of skill tags
68
+ * @returns Valid SkillCategory, defaults to 'other' if no match
69
+ *
70
+ * @example
71
+ * extractCategoryFromTags(['git', 'testing', 'jest']) // 'testing'
72
+ * extractCategoryFromTags(['react', 'frontend']) // 'development'
73
+ * extractCategoryFromTags(['random', 'tags']) // 'other'
74
+ */
75
+ export declare function extractCategoryFromTags(tags: string[] | undefined | null): SkillCategory;
76
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/utils/validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,IAAI,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAkB7F;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAMlD;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAMhF;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,YAAY,GAAG,WAAW,CAWnE;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,YAAY,CAWpE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAS,GAAG,IAAI,GAAG,aAAa,CAsCxF"}
@@ -0,0 +1,153 @@
1
+ /**
2
+ * @fileoverview Shared validation utilities for MCP tools
3
+ * @module @skillsmith/mcp-server/utils/validation
4
+ * @see SMI-810: Create shared validation utility
5
+ *
6
+ * Provides common validation functions used across MCP tools:
7
+ * - Skill ID format validation
8
+ * - Skill ID parsing
9
+ * - Trust tier mapping between MCP and database types
10
+ */
11
+ /**
12
+ * Valid skill categories for mapping
13
+ */
14
+ const VALID_CATEGORIES = [
15
+ 'development',
16
+ 'testing',
17
+ 'documentation',
18
+ 'devops',
19
+ 'database',
20
+ 'security',
21
+ 'productivity',
22
+ 'integration',
23
+ 'ai-ml',
24
+ 'other',
25
+ ];
26
+ /**
27
+ * Validate skill ID format.
28
+ *
29
+ * Accepts two formats:
30
+ * - Author/name format: `anthropic/commit`, `community/jest-helper`
31
+ * - UUID format: `550e8400-e29b-41d4-a716-446655440000`
32
+ *
33
+ * @param id - Skill ID to validate
34
+ * @returns True if ID matches valid format
35
+ *
36
+ * @example
37
+ * isValidSkillId('anthropic/commit') // true
38
+ * isValidSkillId('invalid-format') // false
39
+ */
40
+ export function isValidSkillId(id) {
41
+ // Format: author/skill-name or UUID
42
+ const authorSlashName = /^[a-z0-9-]+\/[a-z0-9-]+$/i;
43
+ const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
44
+ return authorSlashName.test(id) || uuid.test(id);
45
+ }
46
+ /**
47
+ * Parse a skill ID into author and name components.
48
+ *
49
+ * @param id - Skill ID in author/name format
50
+ * @returns Object with author and name, or null if invalid format
51
+ *
52
+ * @example
53
+ * parseSkillId('anthropic/commit') // { author: 'anthropic', name: 'commit' }
54
+ * parseSkillId('invalid') // null
55
+ */
56
+ export function parseSkillId(id) {
57
+ const parts = id.split('/');
58
+ if (parts.length !== 2) {
59
+ return null;
60
+ }
61
+ return { author: parts[0], name: parts[1] };
62
+ }
63
+ /**
64
+ * Map MCP trust tier to database trust tier.
65
+ *
66
+ * MCP types: verified, community, standard, unverified
67
+ * DB types: verified, community, experimental, unknown
68
+ *
69
+ * @param mcpTier - MCP trust tier
70
+ * @returns Database trust tier
71
+ */
72
+ export function mapTrustTierToDb(mcpTier) {
73
+ switch (mcpTier) {
74
+ case 'verified':
75
+ return 'verified';
76
+ case 'community':
77
+ return 'community';
78
+ case 'standard':
79
+ return 'experimental';
80
+ case 'unverified':
81
+ return 'unknown';
82
+ }
83
+ }
84
+ /**
85
+ * Map database trust tier to MCP trust tier.
86
+ *
87
+ * DB types: verified, community, experimental, unknown
88
+ * MCP types: verified, community, standard, unverified
89
+ *
90
+ * @param dbTier - Database trust tier
91
+ * @returns MCP trust tier
92
+ */
93
+ export function mapTrustTierFromDb(dbTier) {
94
+ switch (dbTier) {
95
+ case 'verified':
96
+ return 'verified';
97
+ case 'community':
98
+ return 'community';
99
+ case 'experimental':
100
+ return 'standard';
101
+ case 'unknown':
102
+ return 'unverified';
103
+ }
104
+ }
105
+ /**
106
+ * Extract skill category from tags array.
107
+ *
108
+ * Searches through tags to find the first valid category match.
109
+ * Handles case-insensitive matching and common aliases.
110
+ *
111
+ * @param tags - Array of skill tags
112
+ * @returns Valid SkillCategory, defaults to 'other' if no match
113
+ *
114
+ * @example
115
+ * extractCategoryFromTags(['git', 'testing', 'jest']) // 'testing'
116
+ * extractCategoryFromTags(['react', 'frontend']) // 'development'
117
+ * extractCategoryFromTags(['random', 'tags']) // 'other'
118
+ */
119
+ export function extractCategoryFromTags(tags) {
120
+ if (!tags || tags.length === 0) {
121
+ return 'other';
122
+ }
123
+ // Normalize tags to lowercase for matching
124
+ const normalizedTags = tags.map((tag) => tag.toLowerCase());
125
+ // First pass: direct category match
126
+ for (const tag of normalizedTags) {
127
+ if (VALID_CATEGORIES.includes(tag)) {
128
+ return tag;
129
+ }
130
+ }
131
+ // Second pass: keyword-based category inference
132
+ const categoryKeywords = {
133
+ development: ['dev', 'code', 'coding', 'programming', 'frontend', 'backend', 'fullstack'],
134
+ testing: ['test', 'tests', 'jest', 'vitest', 'mocha', 'cypress', 'playwright', 'e2e', 'unit'],
135
+ documentation: ['docs', 'doc', 'readme', 'markdown', 'jsdoc', 'typedoc'],
136
+ devops: ['ci', 'cd', 'cicd', 'docker', 'kubernetes', 'k8s', 'deploy', 'deployment', 'infra'],
137
+ database: ['db', 'sql', 'postgres', 'mysql', 'mongodb', 'redis', 'sqlite'],
138
+ security: ['auth', 'authentication', 'authorization', 'oauth', 'jwt', 'encryption'],
139
+ productivity: ['workflow', 'automation', 'tools', 'utility', 'helper'],
140
+ integration: ['api', 'rest', 'graphql', 'webhook', 'sync'],
141
+ 'ai-ml': ['ai', 'ml', 'machine-learning', 'llm', 'gpt', 'claude', 'openai', 'neural'],
142
+ other: [],
143
+ };
144
+ for (const tag of normalizedTags) {
145
+ for (const [category, keywords] of Object.entries(categoryKeywords)) {
146
+ if (keywords.includes(tag)) {
147
+ return category;
148
+ }
149
+ }
150
+ }
151
+ return 'other';
152
+ }
153
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../../../src/utils/validation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * SMI-645: Webhook module for MCP server
3
+ *
4
+ * Re-exports webhook functionality from core and provides HTTP server integration.
5
+ */
6
+ export { createWebhookServer, startWebhookServer, stopWebhookServer, type WebhookServerOptions, type ServerStartOptions, type WebhookServer, } from './webhook-endpoint.js';
7
+ export { WebhookHandler, WebhookQueue, isSkillFile, extractSkillChanges, parseWebhookPayload, type WebhookEventType, type SkillFileChange, type WebhookHandleResult, type WebhookQueueItem, type QueueStats, } from '@skillsmith/core';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/webhooks/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACvB,KAAK,aAAa,GACnB,MAAM,uBAAuB,CAAA;AAG9B,OAAO,EACL,cAAc,EACd,YAAY,EACZ,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,KAAK,UAAU,GAChB,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * SMI-645: Webhook module for MCP server
3
+ *
4
+ * Re-exports webhook functionality from core and provides HTTP server integration.
5
+ */
6
+ export { createWebhookServer, startWebhookServer, stopWebhookServer, } from './webhook-endpoint.js';
7
+ // Re-export core webhook types for convenience
8
+ export { WebhookHandler, WebhookQueue, isSkillFile, extractSkillChanges, parseWebhookPayload, } from '@skillsmith/core';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/webhooks/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GAIlB,MAAM,uBAAuB,CAAA;AAE9B,+CAA+C;AAC/C,OAAO,EACL,cAAc,EACd,YAAY,EACZ,WAAW,EACX,mBAAmB,EACnB,mBAAmB,GAMpB,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,149 @@
1
+ /**
2
+ * SMI-645: Webhook Endpoint - HTTP server for GitHub webhooks
3
+ *
4
+ * Provides:
5
+ * - Express/Node.js HTTP server for receiving webhooks
6
+ * - Signature validation middleware
7
+ * - Rate limiting for security
8
+ * - Event routing to WebhookHandler
9
+ *
10
+ * Usage:
11
+ * import { createWebhookServer, startWebhookServer } from './webhooks/webhook-endpoint.js';
12
+ *
13
+ * const server = createWebhookServer({
14
+ * secret: process.env.GITHUB_WEBHOOK_SECRET,
15
+ * onIndexUpdate: (repoUrl, filePath) => { ... },
16
+ * });
17
+ *
18
+ * startWebhookServer(server, { port: 3000 });
19
+ */
20
+ import { IncomingMessage, Server } from 'http';
21
+ import { WebhookHandler, WebhookQueue, type WebhookQueueItem } from '@skillsmith/core';
22
+ /**
23
+ * Webhook server configuration (SMI-682: Added trust proxy options)
24
+ */
25
+ export interface WebhookServerConfig {
26
+ /**
27
+ * GitHub webhook secret for signature verification
28
+ */
29
+ secret: string;
30
+ /**
31
+ * Whether to trust X-Forwarded-For headers (default: false)
32
+ * SMI-682: Must be explicitly enabled for security
33
+ */
34
+ trustProxy?: boolean;
35
+ /**
36
+ * List of trusted proxy IPs (optional, for enhanced security)
37
+ * SMI-682: When set, X-Forwarded-For is only trusted from these IPs
38
+ */
39
+ trustedProxies?: string[];
40
+ }
41
+ /**
42
+ * Webhook server options
43
+ */
44
+ export interface WebhookServerOptions extends WebhookServerConfig {
45
+ /**
46
+ * Maximum request body size in bytes (default: 1MB)
47
+ */
48
+ maxBodySize?: number;
49
+ /**
50
+ * Rate limit: max requests per window (default: 100)
51
+ */
52
+ rateLimit?: number;
53
+ /**
54
+ * Rate limit window in ms (default: 60000 = 1 minute)
55
+ */
56
+ rateLimitWindow?: number;
57
+ /**
58
+ * Callback when a skill needs to be indexed/updated
59
+ */
60
+ onIndexUpdate?: (item: WebhookQueueItem) => Promise<void>;
61
+ /**
62
+ * Callback for logging
63
+ */
64
+ onLog?: (level: 'info' | 'warn' | 'error', message: string, data?: unknown) => void;
65
+ /**
66
+ * Queue options for debouncing and retry
67
+ */
68
+ queueOptions?: {
69
+ debounceMs?: number;
70
+ maxRetries?: number;
71
+ retryDelayMs?: number;
72
+ };
73
+ }
74
+ /**
75
+ * Server startup options
76
+ */
77
+ export interface ServerStartOptions {
78
+ /**
79
+ * Port to listen on (default: 3000)
80
+ */
81
+ port?: number;
82
+ /**
83
+ * Host to bind to (default: '0.0.0.0')
84
+ */
85
+ host?: string;
86
+ }
87
+ /**
88
+ * Rate limiter state (SMI-681: Added cleanup timer for memory leak prevention)
89
+ */
90
+ export interface RateLimiterState {
91
+ requests: Map<string, number[]>;
92
+ limit: number;
93
+ window: number;
94
+ cleanupTimer?: ReturnType<typeof setInterval>;
95
+ }
96
+ /**
97
+ * Create rate limiter with automatic cleanup (SMI-681)
98
+ * @param limit - Maximum requests per window
99
+ * @param windowMs - Window duration in milliseconds
100
+ */
101
+ export declare function createRateLimiter(limit: number, windowMs: number): RateLimiterState;
102
+ /**
103
+ * Destroy rate limiter and clean up resources (SMI-681)
104
+ */
105
+ export declare function destroyRateLimiter(state: RateLimiterState): void;
106
+ /**
107
+ * Check if request is rate limited
108
+ */
109
+ export declare function isRateLimited(limiter: RateLimiterState, ip: string): boolean;
110
+ /**
111
+ * Get client IP from request (SMI-682: Added trusted proxy validation)
112
+ * @param req - Incoming HTTP request
113
+ * @param config - Server configuration with trust proxy settings
114
+ */
115
+ export declare function getClientIp(req: IncomingMessage, config: WebhookServerConfig): string;
116
+ /**
117
+ * Webhook server instance
118
+ */
119
+ export interface WebhookServer {
120
+ /**
121
+ * The underlying HTTP server
122
+ */
123
+ server: Server;
124
+ /**
125
+ * The webhook handler
126
+ */
127
+ handler: WebhookHandler;
128
+ /**
129
+ * The webhook queue
130
+ */
131
+ queue: WebhookQueue;
132
+ }
133
+ /**
134
+ * Create a webhook server
135
+ */
136
+ export declare function createWebhookServer(options: WebhookServerOptions): WebhookServer;
137
+ /**
138
+ * Start the webhook server
139
+ */
140
+ export declare function startWebhookServer(webhookServer: WebhookServer, options?: ServerStartOptions): Promise<void>;
141
+ /**
142
+ * Stop the webhook server
143
+ */
144
+ export declare function stopWebhookServer(webhookServer: WebhookServer): Promise<void>;
145
+ /**
146
+ * Main entry point for standalone webhook server
147
+ */
148
+ export declare function main(): Promise<void>;
149
+ //# sourceMappingURL=webhook-endpoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-endpoint.d.ts","sourceRoot":"","sources":["../../../src/webhooks/webhook-endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAgB,eAAe,EAAkB,MAAM,EAAE,MAAM,MAAM,CAAA;AAC5E,OAAO,EACL,cAAc,EACd,YAAY,EACZ,KAAK,gBAAgB,EAEtB,MAAM,kBAAkB,CAAA;AAEzB;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAA;IAEd;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IAEpB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,oBAAqB,SAAQ,mBAAmB;IAC/D;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IAExB;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAEzD;;OAEG;IACH,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IAEnF;;OAEG;IACH,YAAY,CAAC,EAAE;QACb,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,YAAY,CAAC,EAAE,MAAM,CAAA;KACtB,CAAA;CACF;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IAEb;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,CAAC,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAA;CAC9C;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CA4BnF;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAMhE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAoB5E;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAmCrF;AAoCD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,MAAM,EAAE,MAAM,CAAA;IAEd;;OAEG;IACH,OAAO,EAAE,cAAc,CAAA;IAEvB;;OAEG;IACH,KAAK,EAAE,YAAY,CAAA;CACpB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CA0IhF;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,aAAa,EAAE,aAAa,EAC5B,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAW7E;AAED;;GAEG;AACH,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAuC1C"}