@shnitzel/plugscout 0.3.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 (201) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +228 -0
  3. package/assets/cli/logo.txt +24 -0
  4. package/config/item-insights.json +316 -0
  5. package/config/providers.json +46 -0
  6. package/config/ranking-policy.json +14 -0
  7. package/config/recommendation-weights.json +7 -0
  8. package/config/registries.json +1423 -0
  9. package/config/security-policy.json +19 -0
  10. package/config/sources.json +30 -0
  11. package/data/catalog/items.json +182109 -0
  12. package/data/catalog/mcps.json +163843 -0
  13. package/data/catalog/skills.json +4768 -0
  14. package/data/catalog/sync-state.json +62 -0
  15. package/data/curated/mcps.json +78 -0
  16. package/data/curated/skills.json +174 -0
  17. package/data/quarantine/quarantined.json +3 -0
  18. package/data/raw/2024-05-15/mcps.json +20 -0
  19. package/data/raw/2024-05-20/skills.json +20 -0
  20. package/data/raw/2024-06-05/mcps.json +20 -0
  21. package/data/raw/2024-06-05/skills.json +29 -0
  22. package/data/security-reports/.gitkeep +0 -0
  23. package/data/security-reports/2026-02-06/report.json +8 -0
  24. package/data/security-reports/2026-02-10/report.json +9 -0
  25. package/data/security-reports/2026-02-11/report.json +9 -0
  26. package/data/security-reports/2026-02-12/report.json +9 -0
  27. package/data/security-reports/2026-02-13/report.json +8 -0
  28. package/data/security-reports/2026-02-14/report.json +8 -0
  29. package/data/security-reports/2026-02-23/report.json +8 -0
  30. package/data/security-reports/2026-02-25/report.json +8 -0
  31. package/data/security-reports/2026-02-26/report.json +8 -0
  32. package/data/security-reports/2026-03-10/report.json +8 -0
  33. package/data/security-reports/audits/.gitkeep +0 -0
  34. package/data/security-reports/audits/2026-02-06T10-17-33-872Z-mcp_remote-browser.json +8 -0
  35. package/data/security-reports/audits/2026-02-06T10-17-33-881Z-mcp_remote-browser.json +8 -0
  36. package/data/security-reports/audits/2026-02-10T20-22-24-474Z-mcp_remote-browser.json +8 -0
  37. package/data/security-reports/audits/2026-02-10T20-22-24-483Z-mcp_remote-browser.json +8 -0
  38. package/data/security-reports/audits/2026-02-10T20-42-12-305Z-mcp_remote-browser.json +8 -0
  39. package/data/security-reports/audits/2026-02-10T20-42-12-319Z-mcp_remote-browser.json +8 -0
  40. package/data/security-reports/audits/2026-02-10T20-43-15-728Z-mcp_remote-browser.json +8 -0
  41. package/data/security-reports/audits/2026-02-10T20-43-15-738Z-mcp_remote-browser.json +8 -0
  42. package/data/security-reports/audits/2026-02-10T21-22-14-047Z-mcp_remote-browser.json +8 -0
  43. package/data/security-reports/audits/2026-02-10T21-22-14-051Z-mcp_remote-browser.json +8 -0
  44. package/data/security-reports/audits/2026-02-10T21-29-59-237Z-mcp_remote-browser.json +8 -0
  45. package/data/security-reports/audits/2026-02-10T21-29-59-243Z-mcp_remote-browser.json +8 -0
  46. package/data/security-reports/audits/2026-02-11T20-21-51-074Z-mcp_remote-browser.json +8 -0
  47. package/data/security-reports/audits/2026-02-11T20-21-51-123Z-mcp_remote-browser.json +8 -0
  48. package/data/security-reports/audits/2026-02-11T20-28-33-021Z-mcp_remote-browser.json +8 -0
  49. package/data/security-reports/audits/2026-02-11T20-28-33-026Z-mcp_remote-browser.json +8 -0
  50. package/data/security-reports/audits/2026-02-11T20-34-43-623Z-mcp_remote-browser.json +8 -0
  51. package/data/security-reports/audits/2026-02-11T20-34-43-625Z-mcp_remote-browser.json +8 -0
  52. package/data/security-reports/audits/2026-02-11T21-06-33-281Z-mcp_remote-browser.json +8 -0
  53. package/data/security-reports/audits/2026-02-11T21-06-33-285Z-mcp_remote-browser.json +8 -0
  54. package/data/security-reports/audits/2026-02-11T21-08-58-836Z-mcp_remote-browser.json +8 -0
  55. package/data/security-reports/audits/2026-02-11T21-08-58-843Z-mcp_remote-browser.json +8 -0
  56. package/data/security-reports/audits/2026-02-12T12-26-07-150Z-mcp_remote-browser.json +8 -0
  57. package/data/security-reports/audits/2026-02-12T12-26-07-159Z-mcp_remote-browser.json +8 -0
  58. package/data/security-reports/audits/2026-02-12T14-37-36-565Z-mcp_remote-browser.json +8 -0
  59. package/data/security-reports/audits/2026-02-12T14-37-36-569Z-mcp_remote-browser.json +8 -0
  60. package/data/security-reports/audits/2026-02-12T14-47-32-103Z-mcp_remote-browser.json +8 -0
  61. package/data/security-reports/audits/2026-02-12T14-47-32-213Z-mcp_remote-browser.json +8 -0
  62. package/data/security-reports/audits/2026-02-12T14-47-47-769Z-mcp_filesystem.json +8 -0
  63. package/data/security-reports/audits/2026-02-12T15-05-49-085Z-mcp_remote-browser.json +8 -0
  64. package/data/security-reports/audits/2026-02-12T15-05-49-087Z-mcp_remote-browser.json +8 -0
  65. package/data/security-reports/audits/2026-02-12T16-37-42-204Z-mcp_remote-browser.json +8 -0
  66. package/data/security-reports/audits/2026-02-12T16-37-42-243Z-mcp_remote-browser.json +8 -0
  67. package/data/security-reports/audits/2026-02-12T16-47-16-589Z-mcp_remote-browser.json +8 -0
  68. package/data/security-reports/audits/2026-02-12T16-47-16-596Z-mcp_remote-browser.json +8 -0
  69. package/data/security-reports/audits/2026-02-12T17-38-24-899Z-mcp_remote-browser.json +8 -0
  70. package/data/security-reports/audits/2026-02-12T17-38-24-905Z-mcp_remote-browser.json +8 -0
  71. package/data/security-reports/audits/2026-02-12T17-56-00-835Z-mcp_remote-browser.json +8 -0
  72. package/data/security-reports/audits/2026-02-12T17-56-00-840Z-mcp_remote-browser.json +8 -0
  73. package/data/security-reports/audits/2026-02-12T18-19-26-005Z-mcp_remote-browser.json +8 -0
  74. package/data/security-reports/audits/2026-02-12T18-19-26-008Z-mcp_remote-browser.json +8 -0
  75. package/data/security-reports/audits/2026-02-12T18-34-38-642Z-mcp_remote-browser.json +8 -0
  76. package/data/security-reports/audits/2026-02-12T18-34-38-645Z-mcp_remote-browser.json +8 -0
  77. package/data/security-reports/audits/2026-02-13T05-44-27-648Z-mcp_remote-browser.json +8 -0
  78. package/data/security-reports/audits/2026-02-13T05-44-27-656Z-mcp_remote-browser.json +8 -0
  79. package/data/security-reports/audits/2026-02-13T05-48-50-827Z-mcp_remote-browser.json +8 -0
  80. package/data/security-reports/audits/2026-02-13T05-48-50-900Z-mcp_remote-browser.json +8 -0
  81. package/data/security-reports/audits/2026-02-13T10-53-33-850Z-mcp_remote-browser.json +8 -0
  82. package/data/security-reports/audits/2026-02-13T10-53-33-853Z-mcp_remote-browser.json +8 -0
  83. package/data/security-reports/audits/2026-02-14T17-51-27-279Z-mcp_remote-browser.json +8 -0
  84. package/data/security-reports/audits/2026-02-14T17-51-27-282Z-mcp_remote-browser.json +8 -0
  85. package/data/security-reports/audits/2026-02-14T19-43-39-991Z-mcp_remote-browser.json +8 -0
  86. package/data/security-reports/audits/2026-02-14T19-43-39-997Z-mcp_remote-browser.json +8 -0
  87. package/data/security-reports/audits/2026-02-23T19-24-43-515Z-mcp_remote-browser.json +8 -0
  88. package/data/security-reports/audits/2026-02-23T19-24-43-518Z-mcp_remote-browser.json +8 -0
  89. package/data/security-reports/audits/2026-02-25T14-45-02-763Z-mcp_remote-browser.json +8 -0
  90. package/data/security-reports/audits/2026-02-25T14-45-02-778Z-mcp_remote-browser.json +8 -0
  91. package/data/security-reports/audits/2026-02-25T14-46-58-957Z-mcp_remote-browser.json +8 -0
  92. package/data/security-reports/audits/2026-02-25T14-46-58-960Z-mcp_remote-browser.json +8 -0
  93. package/data/security-reports/audits/2026-02-25T14-57-37-133Z-mcp_remote-browser.json +8 -0
  94. package/data/security-reports/audits/2026-02-25T14-57-37-139Z-mcp_remote-browser.json +8 -0
  95. package/data/security-reports/audits/2026-02-25T15-03-23-507Z-mcp_remote-browser.json +8 -0
  96. package/data/security-reports/audits/2026-02-25T15-03-23-513Z-mcp_remote-browser.json +8 -0
  97. package/data/security-reports/audits/2026-02-25T15-03-41-157Z-mcp_remote-browser.json +8 -0
  98. package/data/security-reports/audits/2026-02-25T15-03-41-162Z-mcp_remote-browser.json +8 -0
  99. package/data/security-reports/audits/2026-02-25T15-05-18-042Z-mcp_remote-browser.json +8 -0
  100. package/data/security-reports/audits/2026-02-25T15-05-18-048Z-mcp_remote-browser.json +8 -0
  101. package/data/security-reports/audits/2026-02-25T15-39-08-519Z-mcp_remote-browser.json +8 -0
  102. package/data/security-reports/audits/2026-02-25T15-39-08-526Z-mcp_remote-browser.json +8 -0
  103. package/data/security-reports/audits/2026-02-25T18-35-54-463Z-mcp_remote-browser.json +8 -0
  104. package/data/security-reports/audits/2026-02-25T18-35-54-466Z-mcp_remote-browser.json +8 -0
  105. package/data/security-reports/audits/2026-02-26T05-52-21-092Z-mcp_remote-browser.json +8 -0
  106. package/data/security-reports/audits/2026-02-26T05-52-21-093Z-mcp_remote-browser.json +8 -0
  107. package/data/security-reports/audits/2026-02-26T05-52-27-076Z-mcp_remote-browser.json +8 -0
  108. package/data/security-reports/audits/2026-02-26T05-52-27-079Z-mcp_remote-browser.json +8 -0
  109. package/data/security-reports/audits/2026-02-26T05-52-27-084Z-mcp_remote-browser.json +8 -0
  110. package/data/security-reports/audits/2026-02-26T05-52-27-086Z-mcp_remote-browser.json +8 -0
  111. package/data/security-reports/audits/2026-02-26T05-52-37-249Z-mcp_remote-browser.json +8 -0
  112. package/data/security-reports/audits/2026-02-26T05-52-37-258Z-mcp_remote-browser.json +8 -0
  113. package/data/security-reports/audits/2026-02-26T05-52-37-259Z-mcp_remote-browser.json +8 -0
  114. package/data/security-reports/audits/2026-02-26T05-52-37-274Z-mcp_remote-browser.json +8 -0
  115. package/data/security-reports/audits/2026-02-26T05-53-28-389Z-mcp_remote-browser.json +8 -0
  116. package/data/security-reports/audits/2026-02-26T05-53-28-391Z-mcp_remote-browser.json +8 -0
  117. package/data/security-reports/audits/2026-02-26T05-53-33-868Z-mcp_remote-browser.json +8 -0
  118. package/data/security-reports/audits/2026-02-26T05-53-33-880Z-mcp_remote-browser.json +8 -0
  119. package/data/security-reports/audits/2026-02-26T05-53-33-892Z-mcp_remote-browser.json +8 -0
  120. package/data/security-reports/audits/2026-02-26T05-53-33-900Z-mcp_remote-browser.json +8 -0
  121. package/data/security-reports/audits/2026-02-26T05-53-43-064Z-mcp_remote-browser.json +8 -0
  122. package/data/security-reports/audits/2026-02-26T05-53-43-066Z-mcp_remote-browser.json +8 -0
  123. package/data/security-reports/audits/2026-02-26T05-53-43-068Z-mcp_remote-browser.json +8 -0
  124. package/data/security-reports/audits/2026-02-26T14-55-47-466Z-claude-plugin_workspace-ops.json +8 -0
  125. package/data/security-reports/audits/2026-02-26T14-55-47-468Z-copilot-extension_repo-security.json +8 -0
  126. package/data/security-reports/audits/2026-02-26T16-55-59-431Z-mcp_remote-browser.json +8 -0
  127. package/data/security-reports/audits/2026-02-26T16-55-59-432Z-mcp_remote-browser.json +8 -0
  128. package/data/security-reports/audits/2026-02-26T16-55-59-435Z-mcp_remote-browser.json +8 -0
  129. package/data/security-reports/audits/2026-02-26T16-55-59-439Z-mcp_remote-browser.json +8 -0
  130. package/data/security-reports/audits/2026-02-26T16-56-08-566Z-mcp_remote-browser.json +8 -0
  131. package/data/security-reports/audits/2026-02-26T16-56-08-570Z-mcp_remote-browser.json +8 -0
  132. package/data/security-reports/audits/2026-02-26T16-56-08-589Z-mcp_remote-browser.json +8 -0
  133. package/data/security-reports/audits/2026-02-26T16-56-08-591Z-mcp_remote-browser.json +8 -0
  134. package/data/security-reports/audits/2026-02-26T16-56-47-356Z-mcp_remote-browser.json +8 -0
  135. package/data/security-reports/audits/2026-02-26T16-56-47-358Z-mcp_remote-browser.json +8 -0
  136. package/data/security-reports/audits/2026-02-26T16-56-53-607Z-mcp_remote-browser.json +8 -0
  137. package/data/security-reports/audits/2026-02-26T16-56-53-612Z-mcp_remote-browser.json +8 -0
  138. package/data/security-reports/audits/2026-02-26T16-56-53-624Z-mcp_remote-browser.json +8 -0
  139. package/data/security-reports/audits/2026-02-26T16-56-53-628Z-mcp_remote-browser.json +8 -0
  140. package/data/security-reports/audits/2026-02-26T16-57-09-879Z-mcp_remote-browser.json +8 -0
  141. package/data/security-reports/audits/2026-02-26T16-57-09-881Z-mcp_remote-browser.json +8 -0
  142. package/data/security-reports/audits/2026-02-26T16-57-10-846Z-mcp_remote-browser.json +8 -0
  143. package/data/security-reports/audits/2026-02-26T16-57-10-848Z-mcp_remote-browser.json +8 -0
  144. package/data/security-reports/audits/2026-03-10T18-15-05-007Z-claude-plugin_playwright.json +8 -0
  145. package/data/security-reports/audits/2026-03-10T18-36-16-092Z-claude-plugin_playwright.json +8 -0
  146. package/data/whitelist/approved.json +5 -0
  147. package/dist/catalog/adapter.js +39 -0
  148. package/dist/catalog/adapters/claude-code-marketplace-v1.js +260 -0
  149. package/dist/catalog/adapters/claude-connectors-scrape-v1.js +107 -0
  150. package/dist/catalog/adapters/claude-plugins-scrape-v1.js +107 -0
  151. package/dist/catalog/adapters/claude-plugins-v0.1.js +48 -0
  152. package/dist/catalog/adapters/copilot-extensions-v0.1.js +48 -0
  153. package/dist/catalog/adapters/copilot-plugin-marketplace-v1.js +117 -0
  154. package/dist/catalog/adapters/mcp-registry-v0.1.js +211 -0
  155. package/dist/catalog/adapters/openai-skills-github-v1.js +100 -0
  156. package/dist/catalog/adapters/openai-skills-v1.js +48 -0
  157. package/dist/catalog/adapters/shared.js +94 -0
  158. package/dist/catalog/remote-registry.js +196 -0
  159. package/dist/catalog/repository.js +161 -0
  160. package/dist/catalog/sync-state.js +61 -0
  161. package/dist/catalog/sync.js +153 -0
  162. package/dist/cli.js +25 -0
  163. package/dist/commands/ExplainerVideo.js +225 -0
  164. package/dist/commands/ingest.js +11 -0
  165. package/dist/commands/validate-data.js +10 -0
  166. package/dist/config/runtime.js +51 -0
  167. package/dist/config/sources.js +21 -0
  168. package/dist/ingestion/mcps.js +77 -0
  169. package/dist/ingestion/skills.js +76 -0
  170. package/dist/install/dependencies.js +58 -0
  171. package/dist/install/review-state.js +70 -0
  172. package/dist/install/skillsh.js +245 -0
  173. package/dist/interfaces/cli/doctor.js +90 -0
  174. package/dist/interfaces/cli/formatters/colors.js +24 -0
  175. package/dist/interfaces/cli/formatters/csv.js +10 -0
  176. package/dist/interfaces/cli/formatters/json.js +3 -0
  177. package/dist/interfaces/cli/formatters/markdown.js +6 -0
  178. package/dist/interfaces/cli/formatters/table.js +82 -0
  179. package/dist/interfaces/cli/index.js +1277 -0
  180. package/dist/interfaces/cli/options.js +93 -0
  181. package/dist/interfaces/cli/output.js +9 -0
  182. package/dist/interfaces/cli/types.js +1 -0
  183. package/dist/interfaces/cli/ui/home.js +114 -0
  184. package/dist/interfaces/cli/ui/web-report.js +384 -0
  185. package/dist/interfaces/cli/update-check.js +180 -0
  186. package/dist/lib/json.js +11 -0
  187. package/dist/lib/logger.js +13 -0
  188. package/dist/lib/paths.js +18 -0
  189. package/dist/lib/validation/contracts.js +245 -0
  190. package/dist/mcps/normalize.js +38 -0
  191. package/dist/models/records.js +31 -0
  192. package/dist/recommendation/engine.js +135 -0
  193. package/dist/recommendation/project-analysis.js +231 -0
  194. package/dist/recommendation/requirements.js +58 -0
  195. package/dist/security/assessment.js +56 -0
  196. package/dist/security/whitelist.js +70 -0
  197. package/dist/skills/normalize.js +39 -0
  198. package/dist/validation/curated.js +72 -0
  199. package/dist/video/Root.js +6 -0
  200. package/dist/video/index.js +3 -0
  201. package/package.json +102 -0
@@ -0,0 +1,231 @@
1
+ import path from 'node:path';
2
+ import fsNative from 'node:fs/promises';
3
+ import fs from 'fs-extra';
4
+ export async function detectProjectSignals(projectPath, options = {}) {
5
+ const root = path.resolve(projectPath);
6
+ const stack = new Set();
7
+ const compatibilityTags = new Set();
8
+ const inferredCapabilities = new Set();
9
+ const scanEvidence = new Set();
10
+ if (await fs.pathExists(path.join(root, 'package.json'))) {
11
+ stack.add('node');
12
+ compatibilityTags.add('node');
13
+ const pkg = await fs.readJson(path.join(root, 'package.json'));
14
+ const dependencies = {
15
+ ...(pkg.dependencies ?? {}),
16
+ ...(pkg.devDependencies ?? {})
17
+ };
18
+ if ('react' in dependencies || 'next' in dependencies || 'next.js' in dependencies) {
19
+ stack.add('react');
20
+ compatibilityTags.add('react');
21
+ }
22
+ if ('typescript' in dependencies) {
23
+ stack.add('typescript');
24
+ compatibilityTags.add('typescript');
25
+ }
26
+ inferFromPackageJson(pkg, inferredCapabilities, scanEvidence);
27
+ }
28
+ if ((await fs.pathExists(path.join(root, 'pyproject.toml'))) ||
29
+ (await fs.pathExists(path.join(root, 'requirements.txt')))) {
30
+ stack.add('python');
31
+ compatibilityTags.add('python');
32
+ }
33
+ if (await fs.pathExists(path.join(root, 'Dockerfile'))) {
34
+ compatibilityTags.add('container');
35
+ inferredCapabilities.add('automation');
36
+ scanEvidence.add('Dockerfile found');
37
+ }
38
+ if ((await fs.pathExists(path.join(root, 'pom.xml'))) || (await fs.pathExists(path.join(root, 'build.gradle')))) {
39
+ stack.add('java');
40
+ compatibilityTags.add('java');
41
+ }
42
+ if (await fs.pathExists(path.join(root, 'go.mod'))) {
43
+ stack.add('go');
44
+ compatibilityTags.add('go');
45
+ }
46
+ if (await fs.pathExists(path.join(root, 'Cargo.toml'))) {
47
+ stack.add('rust');
48
+ compatibilityTags.add('rust');
49
+ }
50
+ if (await fs.pathExists(path.join(root, 'Gemfile'))) {
51
+ stack.add('ruby');
52
+ compatibilityTags.add('ruby');
53
+ }
54
+ const files = await listRepositoryFiles(root, 6, 2000);
55
+ const archetypeVotes = new Map();
56
+ inferFromRepositoryFiles(files, inferredCapabilities, scanEvidence, archetypeVotes);
57
+ if (options.llm) {
58
+ applyOptionalLlmHinting(inferredCapabilities, scanEvidence);
59
+ }
60
+ if (stack.size === 0) {
61
+ stack.add('unknown');
62
+ compatibilityTags.add('general');
63
+ }
64
+ const archetypeScores = toSortedArchetypeScores(archetypeVotes);
65
+ const inferredArchetype = archetypeScores[0]?.name ?? 'general-project';
66
+ const inferenceConfidence = Math.min(100, Math.max(0, Math.round((archetypeScores[0]?.score ?? 20) * 8)));
67
+ return {
68
+ stack: Array.from(stack).sort((a, b) => a.localeCompare(b)),
69
+ compatibilityTags: Array.from(compatibilityTags).sort((a, b) => a.localeCompare(b)),
70
+ inferredCapabilities: Array.from(inferredCapabilities).sort((a, b) => a.localeCompare(b)),
71
+ scanEvidence: Array.from(scanEvidence).sort((a, b) => a.localeCompare(b)),
72
+ inferredArchetype,
73
+ inferenceConfidence,
74
+ archetypeScores
75
+ };
76
+ }
77
+ function inferFromPackageJson(pkg, inferredCapabilities, scanEvidence) {
78
+ const dependencies = {
79
+ ...(pkg.dependencies ?? {}),
80
+ ...(pkg.devDependencies ?? {})
81
+ };
82
+ const scripts = pkg.scripts ?? {};
83
+ const depNames = Object.keys(dependencies);
84
+ const hasDep = (name) => depNames.includes(name);
85
+ const hasScriptLike = (needle) => Object.keys(scripts).some((key) => key.toLowerCase().includes(needle));
86
+ if (hasDep('zod') || hasDep('yup') || hasDep('joi')) {
87
+ inferredCapabilities.add('guardrails');
88
+ inferredCapabilities.add('security');
89
+ scanEvidence.add('validation library detected (zod/yup/joi)');
90
+ }
91
+ if (hasDep('playwright') ||
92
+ hasDep('puppeteer') ||
93
+ hasDep('@playwright/test') ||
94
+ hasDep('selenium-webdriver')) {
95
+ inferredCapabilities.add('browser-control');
96
+ inferredCapabilities.add('automation');
97
+ scanEvidence.add('browser automation dependency detected');
98
+ }
99
+ if (hasDep('openai') ||
100
+ hasDep('@anthropic-ai/sdk') ||
101
+ hasDep('langchain') ||
102
+ hasDep('@langchain/core')) {
103
+ inferredCapabilities.add('prompting');
104
+ inferredCapabilities.add('search');
105
+ scanEvidence.add('LLM SDK dependency detected');
106
+ }
107
+ if (hasDep('axios') || hasDep('got') || hasDep('node-fetch') || hasDep('undici')) {
108
+ inferredCapabilities.add('automation');
109
+ scanEvidence.add('HTTP client dependency detected');
110
+ }
111
+ if (hasScriptLike('lint') || hasScriptLike('audit') || hasScriptLike('security')) {
112
+ inferredCapabilities.add('security');
113
+ scanEvidence.add('security/lint scripts detected in package.json');
114
+ }
115
+ }
116
+ async function listRepositoryFiles(root, maxDepth, maxFiles) {
117
+ const ignore = new Set(['.git', 'node_modules', 'dist', 'coverage', '.next', 'out', 'build', '.turbo']);
118
+ const collected = [];
119
+ async function walk(current, depth) {
120
+ if (depth > maxDepth || collected.length >= maxFiles) {
121
+ return;
122
+ }
123
+ let entries;
124
+ try {
125
+ entries = await fsNative.readdir(current, { withFileTypes: true });
126
+ }
127
+ catch {
128
+ return;
129
+ }
130
+ for (const entry of entries) {
131
+ if (collected.length >= maxFiles) {
132
+ return;
133
+ }
134
+ const full = path.join(current, entry.name);
135
+ const relative = path.relative(root, full);
136
+ if (!relative) {
137
+ continue;
138
+ }
139
+ if (entry.isDirectory()) {
140
+ if (ignore.has(entry.name)) {
141
+ continue;
142
+ }
143
+ await walk(full, depth + 1);
144
+ continue;
145
+ }
146
+ collected.push(relative);
147
+ }
148
+ }
149
+ await walk(root, 0);
150
+ return collected;
151
+ }
152
+ function inferFromRepositoryFiles(files, inferredCapabilities, scanEvidence, archetypeVotes) {
153
+ const lowerFiles = files.map((file) => file.toLowerCase());
154
+ const vote = (archetype, points) => {
155
+ archetypeVotes.set(archetype, (archetypeVotes.get(archetype) ?? 0) + points);
156
+ };
157
+ if (lowerFiles.some((file) => file.endsWith('package.json'))) {
158
+ vote('node-service', 3);
159
+ }
160
+ if (lowerFiles.some((file) => file.includes('next.config') || file.includes('/pages/') || file.includes('/app/'))) {
161
+ vote('frontend-web-app', 4);
162
+ }
163
+ if (lowerFiles.some((file) => file.includes('src/video/') || file.includes('remotion'))) {
164
+ vote('media-automation', 4);
165
+ }
166
+ if (lowerFiles.some((file) => file.includes('src/commands/') || file.includes('src/interfaces/cli'))) {
167
+ vote('cli-platform-tooling', 5);
168
+ }
169
+ if (lowerFiles.some((file) => file.includes('dockerfile') || file.includes('k8s') || file.includes('helm'))) {
170
+ vote('devops-automation', 4);
171
+ }
172
+ if (lowerFiles.some((file) => file.includes('src/security/') || file.includes('whitelist') || file.includes('quarantine'))) {
173
+ vote('security-governance-tooling', 6);
174
+ }
175
+ if (lowerFiles.some((file) => file.startsWith('.github/workflows/'))) {
176
+ inferredCapabilities.add('automation');
177
+ scanEvidence.add('GitHub Actions workflows detected');
178
+ vote('devops-automation', 3);
179
+ }
180
+ if (lowerFiles.some((file) => file.includes('codeql') || file.includes('trivy') || file.includes('gitleaks'))) {
181
+ inferredCapabilities.add('code-scanning');
182
+ inferredCapabilities.add('dependency-audit');
183
+ inferredCapabilities.add('security');
184
+ scanEvidence.add('security scanning workflows/configs detected');
185
+ vote('security-governance-tooling', 5);
186
+ }
187
+ if (lowerFiles.some((file) => file.includes('dockerfile') || file.includes('compose'))) {
188
+ inferredCapabilities.add('automation');
189
+ scanEvidence.add('container/deployment files detected');
190
+ vote('devops-automation', 2);
191
+ }
192
+ if (lowerFiles.some((file) => file.includes('tests/') || file.endsWith('.spec.ts') || file.endsWith('.test.ts'))) {
193
+ inferredCapabilities.add('guardrails');
194
+ scanEvidence.add('automated tests detected');
195
+ vote('quality-platform', 2);
196
+ }
197
+ if (lowerFiles.some((file) => file.includes('playwright') || file.includes('browser'))) {
198
+ inferredCapabilities.add('browser-control');
199
+ inferredCapabilities.add('automation');
200
+ scanEvidence.add('browser workflow files detected');
201
+ vote('qa-automation', 3);
202
+ }
203
+ if (lowerFiles.includes('skill.lock') || lowerFiles.includes('.skills-mcps.json')) {
204
+ inferredCapabilities.add('automation');
205
+ scanEvidence.add('local skills/mcps tooling config detected');
206
+ vote('cli-platform-tooling', 2);
207
+ }
208
+ if (lowerFiles.some((file) => file.includes('src/catalog/') || file.includes('config/registries'))) {
209
+ inferredCapabilities.add('search');
210
+ inferredCapabilities.add('automation');
211
+ scanEvidence.add('catalog and registry pipeline files detected');
212
+ vote('catalog-intelligence-platform', 5);
213
+ }
214
+ }
215
+ function toSortedArchetypeScores(votes) {
216
+ if (votes.size === 0) {
217
+ return [{ name: 'general-project', score: 3 }];
218
+ }
219
+ return Array.from(votes.entries())
220
+ .map(([name, score]) => ({ name, score }))
221
+ .sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
222
+ }
223
+ function applyOptionalLlmHinting(inferredCapabilities, scanEvidence) {
224
+ if (!process.env.OPENAI_API_KEY) {
225
+ scanEvidence.add('LLM enrichment requested but OPENAI_API_KEY is not set; using deterministic scan only');
226
+ return;
227
+ }
228
+ inferredCapabilities.add('prompting');
229
+ inferredCapabilities.add('agent-orchestration');
230
+ scanEvidence.add('LLM enrichment enabled (heuristic augmentation active)');
231
+ }
@@ -0,0 +1,58 @@
1
+ import path from 'node:path';
2
+ import fs from 'fs-extra';
3
+ import { RequirementsProfileSchema } from '../lib/validation/contracts.js';
4
+ export async function loadRequirementsProfile(filePath) {
5
+ if (!filePath) {
6
+ return RequirementsProfileSchema.parse({});
7
+ }
8
+ const fullPath = path.resolve(filePath);
9
+ const raw = await fs.readFile(fullPath, 'utf8');
10
+ if (filePath.endsWith('.json')) {
11
+ return RequirementsProfileSchema.parse(JSON.parse(raw));
12
+ }
13
+ if (filePath.endsWith('.yml') || filePath.endsWith('.yaml')) {
14
+ return RequirementsProfileSchema.parse(parseSimpleYaml(raw));
15
+ }
16
+ throw new Error(`Unsupported requirements format: ${filePath}`);
17
+ }
18
+ function parseSimpleYaml(content) {
19
+ const result = {};
20
+ let activeArrayKey = null;
21
+ const lines = content
22
+ .split('\n')
23
+ .map((line) => line.trimEnd())
24
+ .filter((line) => line.trim() !== '' && !line.trim().startsWith('#'));
25
+ for (const line of lines) {
26
+ const trimmed = line.trim();
27
+ if (trimmed.startsWith('- ')) {
28
+ if (!activeArrayKey) {
29
+ throw new Error('Invalid YAML: array item without key');
30
+ }
31
+ const value = trimmed.slice(2).trim();
32
+ const current = result[activeArrayKey] ?? [];
33
+ current.push(stripQuotes(value));
34
+ result[activeArrayKey] = current;
35
+ continue;
36
+ }
37
+ const separator = trimmed.indexOf(':');
38
+ if (separator === -1) {
39
+ throw new Error(`Invalid YAML line: ${line}`);
40
+ }
41
+ const key = trimmed.slice(0, separator).trim();
42
+ const rawValue = trimmed.slice(separator + 1).trim();
43
+ if (rawValue === '') {
44
+ result[key] = [];
45
+ activeArrayKey = key;
46
+ continue;
47
+ }
48
+ activeArrayKey = null;
49
+ result[key] = stripQuotes(rawValue);
50
+ }
51
+ return result;
52
+ }
53
+ function stripQuotes(value) {
54
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
55
+ return value.slice(1, -1);
56
+ }
57
+ return value;
58
+ }
@@ -0,0 +1,56 @@
1
+ import { loadSecurityPolicy } from '../config/runtime.js';
2
+ import { RiskAssessmentSchema } from '../lib/validation/contracts.js';
3
+ export async function assessRisk(record) {
4
+ const policy = await loadSecurityPolicy();
5
+ return buildAssessment(record, policy);
6
+ }
7
+ export function buildAssessment(record, policy) {
8
+ const signals = record.securitySignals;
9
+ const scoring = policy.scoring;
10
+ const vulnerabilityPoints = signals.knownVulnerabilities * scoring.vulnerabilityWeight;
11
+ const suspiciousPoints = signals.suspiciousPatterns * scoring.suspiciousWeight;
12
+ const injectionPoints = signals.injectionFindings * scoring.injectionWeight;
13
+ const exfiltrationPoints = signals.exfiltrationSignals * scoring.exfiltrationWeight;
14
+ const integrityPoints = signals.integrityAlerts * scoring.integrityWeight;
15
+ const riskScore = Math.min(100, vulnerabilityPoints + suspiciousPoints + injectionPoints + exfiltrationPoints + integrityPoints);
16
+ const riskTier = mapRiskTier(riskScore, policy);
17
+ const reasons = [
18
+ `Integrity alerts: ${signals.integrityAlerts}`,
19
+ `Known vulnerabilities: ${signals.knownVulnerabilities}`,
20
+ `Suspicious patterns: ${signals.suspiciousPatterns}`,
21
+ `Injection findings: ${signals.injectionFindings}`,
22
+ `Exfiltration signals: ${signals.exfiltrationSignals}`
23
+ ];
24
+ return RiskAssessmentSchema.parse({
25
+ id: record.id,
26
+ riskScore,
27
+ riskTier,
28
+ reasons,
29
+ scannerResults: {
30
+ packageIntegrity: { findings: signals.integrityAlerts },
31
+ vulnerabilityIntel: { findings: signals.knownVulnerabilities },
32
+ permissionPatterns: { findings: signals.suspiciousPatterns },
33
+ injectionTests: { findings: signals.injectionFindings },
34
+ exfiltrationHeuristics: { findings: signals.exfiltrationSignals }
35
+ },
36
+ assessedAt: new Date().toISOString()
37
+ });
38
+ }
39
+ export function mapRiskTier(score, policy) {
40
+ if (score <= policy.thresholds.lowMax) {
41
+ return 'low';
42
+ }
43
+ if (score <= policy.thresholds.mediumMax) {
44
+ return 'medium';
45
+ }
46
+ if (score <= policy.thresholds.highMax) {
47
+ return 'high';
48
+ }
49
+ return 'critical';
50
+ }
51
+ export function isBlockedTier(tier, policy) {
52
+ return policy.installGate.blockTiers.includes(tier);
53
+ }
54
+ export function isWarnTier(tier, policy) {
55
+ return policy.installGate.warnTiers.includes(tier);
56
+ }
@@ -0,0 +1,70 @@
1
+ import path from 'node:path';
2
+ import fs from 'fs-extra';
3
+ import { loadSecurityPolicy } from '../config/runtime.js';
4
+ import { loadCatalogItemById, loadWhitelist, saveQuarantine, saveWhitelist } from '../catalog/repository.js';
5
+ import { getStaleRegistries, loadSyncState } from '../catalog/sync-state.js';
6
+ import { writeJsonFile, readJsonFile } from '../lib/json.js';
7
+ import { logger } from '../lib/logger.js';
8
+ import { getStatePath } from '../lib/paths.js';
9
+ import { SecurityReportSchema } from '../lib/validation/contracts.js';
10
+ import { buildAssessment, isBlockedTier } from './assessment.js';
11
+ export async function verifyWhitelist() {
12
+ const [whitelist, policy, syncState] = await Promise.all([loadWhitelist(), loadSecurityPolicy(), loadSyncState()]);
13
+ const passed = [];
14
+ const failed = [];
15
+ for (const id of whitelist) {
16
+ const record = await loadCatalogItemById(id);
17
+ if (!record) {
18
+ failed.push({
19
+ id,
20
+ riskTier: 'critical',
21
+ riskScore: 100,
22
+ reasons: ['Catalog item missing']
23
+ });
24
+ continue;
25
+ }
26
+ const assessment = buildAssessment(record, policy);
27
+ if (isBlockedTier(assessment.riskTier, policy)) {
28
+ failed.push({
29
+ id,
30
+ riskTier: assessment.riskTier,
31
+ riskScore: assessment.riskScore,
32
+ reasons: assessment.reasons
33
+ });
34
+ continue;
35
+ }
36
+ passed.push(id);
37
+ }
38
+ const report = SecurityReportSchema.parse({
39
+ generatedAt: new Date().toISOString(),
40
+ staleRegistries: getStaleRegistries(syncState),
41
+ passed,
42
+ failed
43
+ });
44
+ const date = report.generatedAt.slice(0, 10);
45
+ const reportPath = getStatePath(`data/security-reports/${date}/report.json`);
46
+ await fs.ensureDir(path.dirname(reportPath));
47
+ await writeJsonFile(reportPath, report);
48
+ logger.info(`Whitelist verification report written: ${reportPath}`);
49
+ return { reportPath, report };
50
+ }
51
+ export async function applyQuarantineFromReport(reportFilePath) {
52
+ const raw = await readJsonFile(reportFilePath);
53
+ const report = SecurityReportSchema.parse(raw);
54
+ const whitelist = await loadWhitelist();
55
+ const removedFromWhitelist = [];
56
+ report.failed.forEach((failure) => {
57
+ if (whitelist.delete(failure.id)) {
58
+ removedFromWhitelist.push(failure.id);
59
+ }
60
+ });
61
+ await saveWhitelist(whitelist);
62
+ const now = new Date().toISOString();
63
+ const quarantined = report.failed.map((failure) => ({
64
+ id: failure.id,
65
+ reason: failure.reasons.join(' | '),
66
+ quarantinedAt: now
67
+ }));
68
+ await saveQuarantine(quarantined);
69
+ return { removedFromWhitelist, quarantined };
70
+ }
@@ -0,0 +1,39 @@
1
+ import { CatalogSkillSchema } from '../lib/validation/contracts.js';
2
+ export function normalizeSkills(records, sourceId, today) {
3
+ return records
4
+ .map((entry) => CatalogSkillSchema.parse({
5
+ ...ensureObject(entry),
6
+ source: ensureObject(entry).source ?? sourceId,
7
+ lastSeenAt: ensureObject(entry).lastSeenAt ?? today
8
+ }))
9
+ .sort((a, b) => a.id.localeCompare(b.id));
10
+ }
11
+ function ensureObject(value) {
12
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
13
+ throw new Error('Invalid skill registry entry: expected object');
14
+ }
15
+ return value;
16
+ }
17
+ export function mergeSkillsById(skills) {
18
+ const state = new Map();
19
+ for (const skill of skills) {
20
+ const existing = state.get(skill.id);
21
+ if (!existing) {
22
+ state.set(skill.id, skill);
23
+ continue;
24
+ }
25
+ state.set(skill.id, {
26
+ ...existing,
27
+ description: skill.description.length > existing.description.length ? skill.description : existing.description,
28
+ capabilities: dedupe([...existing.capabilities, ...skill.capabilities]),
29
+ compatibility: dedupe([...existing.compatibility, ...skill.compatibility]),
30
+ maintenanceSignal: Math.max(existing.maintenanceSignal, skill.maintenanceSignal),
31
+ adoptionSignal: Math.max(existing.adoptionSignal, skill.adoptionSignal),
32
+ lastSeenAt: skill.lastSeenAt >= existing.lastSeenAt ? skill.lastSeenAt : existing.lastSeenAt
33
+ });
34
+ }
35
+ return Array.from(state.values()).sort((a, b) => a.id.localeCompare(b.id));
36
+ }
37
+ function dedupe(values) {
38
+ return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
39
+ }
@@ -0,0 +1,72 @@
1
+ import fs from 'fs-extra';
2
+ import { readJsonFile } from '../lib/json.js';
3
+ import { getPackagePath, getStatePath } from '../lib/paths.js';
4
+ import { McpSchema, SkillSchema } from '../models/records.js';
5
+ export async function validateCuratedSkills() {
6
+ try {
7
+ const raw = await loadCuratedArray('data/curated/skills.json');
8
+ const parsed = raw.map((entry, index) => {
9
+ try {
10
+ return SkillSchema.parse(entry);
11
+ }
12
+ catch (error) {
13
+ throw new Error(`skills[${index}] ${error}`);
14
+ }
15
+ });
16
+ return {
17
+ dataset: 'skills',
18
+ valid: true,
19
+ count: parsed.length,
20
+ errors: [],
21
+ records: parsed
22
+ };
23
+ }
24
+ catch (error) {
25
+ return {
26
+ dataset: 'skills',
27
+ valid: false,
28
+ count: 0,
29
+ errors: [error instanceof Error ? error.message : String(error)]
30
+ };
31
+ }
32
+ }
33
+ export async function validateCuratedMcps(skillIds) {
34
+ try {
35
+ const raw = await loadCuratedArray('data/curated/mcps.json');
36
+ const parsed = raw.map((entry, index) => {
37
+ try {
38
+ const record = McpSchema.parse(entry);
39
+ const missingLinks = record.skillLinks.filter((link) => !skillIds.has(link));
40
+ if (missingLinks.length) {
41
+ throw new Error(`references missing skills: ${missingLinks.join(', ')}`);
42
+ }
43
+ return record;
44
+ }
45
+ catch (error) {
46
+ throw new Error(`mcps[${index}] ${error}`);
47
+ }
48
+ });
49
+ return {
50
+ dataset: 'mcps',
51
+ valid: true,
52
+ count: parsed.length,
53
+ errors: [],
54
+ records: parsed
55
+ };
56
+ }
57
+ catch (error) {
58
+ return {
59
+ dataset: 'mcps',
60
+ valid: false,
61
+ count: 0,
62
+ errors: [error instanceof Error ? error.message : String(error)]
63
+ };
64
+ }
65
+ }
66
+ async function loadCuratedArray(relativePath) {
67
+ const statePath = getStatePath(relativePath);
68
+ if (await fs.pathExists(statePath)) {
69
+ return readJsonFile(statePath);
70
+ }
71
+ return readJsonFile(getPackagePath(relativePath));
72
+ }
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Composition } from 'remotion';
3
+ import { ExplainerVideo, walkthroughDurationInFrames } from '../commands/ExplainerVideo.js';
4
+ export const RemotionRoot = () => {
5
+ return (_jsx(_Fragment, { children: _jsx(Composition, { id: "FrameworkWalkthrough", component: ExplainerVideo, durationInFrames: walkthroughDurationInFrames, fps: 30, width: 1920, height: 1080 }) }));
6
+ };
@@ -0,0 +1,3 @@
1
+ import { registerRoot } from 'remotion';
2
+ import { RemotionRoot } from './Root.js';
3
+ registerRoot(RemotionRoot);
package/package.json ADDED
@@ -0,0 +1,102 @@
1
+ {
2
+ "name": "@shnitzel/plugscout",
3
+ "version": "0.3.1",
4
+ "description": "Claude plugins + Claude connectors + Copilot extensions + Skills + MCP security intelligence framework",
5
+ "private": false,
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "author": "Amit Rintzler",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/amitrintzler/skills-and-mcps.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/amitrintzler/skills-and-mcps/issues"
15
+ },
16
+ "homepage": "https://github.com/amitrintzler/skills-and-mcps#readme",
17
+ "keywords": [
18
+ "cli",
19
+ "skills",
20
+ "mcp",
21
+ "claude-plugin",
22
+ "copilot-extension",
23
+ "security",
24
+ "recommendation"
25
+ ],
26
+ "overrides": {
27
+ "terser-webpack-plugin": "5.3.17"
28
+ },
29
+ "bin": {
30
+ "plugscout": "dist/cli.js"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "config",
35
+ "data",
36
+ "assets/cli",
37
+ "README.md",
38
+ "LICENSE*"
39
+ ],
40
+ "engines": {
41
+ "node": ">=18.17"
42
+ },
43
+ "scripts": {
44
+ "dev": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/cli.ts",
45
+ "setup": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/cli.ts setup",
46
+ "build": "tsc -p tsconfig.json",
47
+ "lint": "eslint . --ext .ts",
48
+ "test": "vitest run",
49
+ "verify:claims": "vitest run --maxWorkers=1 tests/integration/functionality-claims.spec.ts tests/unit/workflow-contracts.spec.ts tests/integration/cli-flow.spec.ts tests/unit/recommendation.spec.ts tests/unit/project-analysis.spec.ts",
50
+ "smoke:pack": "node ./scripts/smoke-pack.mjs",
51
+ "prepush:local": "npm run lint && npm run build && npm run verify:claims && npm run smoke:pack",
52
+ "test:watch": "vitest",
53
+ "validate:data": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/commands/validate-data.ts",
54
+ "ingest:skills": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/commands/ingest.ts --kind skill",
55
+ "ingest:mcps": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/commands/ingest.ts --kind mcp",
56
+ "ingest:claude-plugins": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/commands/ingest.ts --kind claude-plugin",
57
+ "ingest:copilot-extensions": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/commands/ingest.ts --kind copilot-extension",
58
+ "ingest:all": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/commands/ingest.ts",
59
+ "sync": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/cli.ts sync",
60
+ "about": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/cli.ts about",
61
+ "init": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/cli.ts init",
62
+ "doctor": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/cli.ts doctor",
63
+ "status": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/cli.ts status",
64
+ "list": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/cli.ts list",
65
+ "search": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/cli.ts search",
66
+ "explain": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/cli.ts explain",
67
+ "scan": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/cli.ts scan",
68
+ "show": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/cli.ts show",
69
+ "top": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/cli.ts top",
70
+ "web": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/cli.ts web",
71
+ "video:preview": "remotion studio src/video/index.ts",
72
+ "video:render": "remotion render src/video/index.ts FrameworkWalkthrough out/framework-walkthrough.mp4",
73
+ "recommend": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/cli.ts recommend",
74
+ "assess": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/cli.ts assess",
75
+ "install:item": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/cli.ts install",
76
+ "whitelist:verify": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/cli.ts whitelist verify",
77
+ "quarantine:apply": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm src/cli.ts quarantine apply"
78
+ },
79
+ "dependencies": {
80
+ "fs-extra": "^11.2.0",
81
+ "semver": "^7.7.4",
82
+ "zod": "^3.23.8"
83
+ },
84
+ "devDependencies": {
85
+ "@types/node": "^25.3.0",
86
+ "@types/react": "^19.2.14",
87
+ "@remotion/cli": "4.0.427",
88
+ "@types/semver": "^7.7.1",
89
+ "@typescript-eslint/eslint-plugin": "^7.10.0",
90
+ "@typescript-eslint/parser": "^7.10.0",
91
+ "eslint": "^8.57.0",
92
+ "eslint-config-prettier": "^9.1.0",
93
+ "prettier": "^3.2.5",
94
+ "react": "^19.2.4",
95
+ "react-dom": "^19.2.4",
96
+ "remotion": "4.0.427",
97
+ "ts-node": "^10.9.2",
98
+ "tsx": "^4.7.1",
99
+ "typescript": "^5.4.5",
100
+ "vitest": "3.2.4"
101
+ }
102
+ }