@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,107 @@
1
+ import { dedupe, sanitizeUrl, slugify, stripHtml } from './shared.js';
2
+ const TRUSTED_HOSTS = ['claude.com', 'www.claude.com', 'anthropic.com', 'www.anthropic.com'];
3
+ const CONNECTOR_PATH_PATTERN = /(?:https:\/\/claude\.com)?\/connectors\/([a-z0-9-]+)/gi;
4
+ export function adaptClaudeConnectorsScrapeEntries(sourceId, entries) {
5
+ const html = entries.find((entry) => typeof entry === 'string');
6
+ if (typeof html !== 'string' || html.trim().length === 0) {
7
+ return [];
8
+ }
9
+ const connectorSlugs = collectConnectorSlugs(html);
10
+ const mapped = [];
11
+ const seen = new Set();
12
+ for (const slug of connectorSlugs) {
13
+ const id = `claude-connector:${slug}`;
14
+ if (seen.has(id)) {
15
+ continue;
16
+ }
17
+ const snippet = extractAnchorSnippet(html, slug);
18
+ const label = stripHtml(snippet, 120);
19
+ const name = titleizeSlug(label.length >= 3 ? label : slug);
20
+ const link = sanitizeUrl(`https://claude.com/connectors/${slug}`, TRUSTED_HOSTS);
21
+ if (!link) {
22
+ continue;
23
+ }
24
+ seen.add(id);
25
+ mapped.push({
26
+ id,
27
+ kind: 'claude-connector',
28
+ provider: 'anthropic',
29
+ name: sanitizeText(name, 120),
30
+ description: sanitizeText(`Connector listed on Claude Connectors for ${name}. Verify connector permissions before enabling.`, 320),
31
+ capabilities: inferCapabilities(name, slug),
32
+ compatibility: ['claude', 'mcp'],
33
+ source: sourceId,
34
+ install: {
35
+ kind: 'manual',
36
+ instructions: 'Enable this connector from Claude Connectors.',
37
+ url: link
38
+ },
39
+ adoptionSignal: 52,
40
+ maintenanceSignal: 60,
41
+ provenanceSignal: 72,
42
+ freshnessSignal: 65,
43
+ securitySignals: {
44
+ knownVulnerabilities: 0,
45
+ suspiciousPatterns: 1,
46
+ injectionFindings: 0,
47
+ exfiltrationSignals: 0,
48
+ integrityAlerts: 0
49
+ },
50
+ metadata: {
51
+ catalogType: 'connector',
52
+ sourcePage: 'https://claude.com/connectors',
53
+ scrapedAt: new Date().toISOString(),
54
+ sourceConfidence: 'scraped'
55
+ }
56
+ });
57
+ }
58
+ return mapped;
59
+ }
60
+ function collectConnectorSlugs(html) {
61
+ const slugs = new Set();
62
+ let match;
63
+ while ((match = CONNECTOR_PATH_PATTERN.exec(html)) !== null) {
64
+ const slug = slugify(match[1] ?? '');
65
+ if (!slug) {
66
+ continue;
67
+ }
68
+ slugs.add(slug);
69
+ }
70
+ return Array.from(slugs).sort((a, b) => a.localeCompare(b));
71
+ }
72
+ function extractAnchorSnippet(html, slug) {
73
+ const pattern = new RegExp(`<a[^>]+href="(?:https:\\/\\/claude\\.com)?\\/connectors\\/${slug}"[^>]*>([\\s\\S]{0,500}?)<\\/a>`, 'i');
74
+ const match = html.match(pattern);
75
+ if (!match || typeof match[1] !== 'string') {
76
+ return '';
77
+ }
78
+ return match[1];
79
+ }
80
+ function sanitizeText(value, maxLength) {
81
+ return stripHtml(value, maxLength).slice(0, maxLength);
82
+ }
83
+ function titleizeSlug(value) {
84
+ return value
85
+ .split(/[^a-zA-Z0-9]+/g)
86
+ .filter((segment) => segment.length > 0)
87
+ .map((segment) => segment[0].toUpperCase() + segment.slice(1))
88
+ .join(' ');
89
+ }
90
+ function inferCapabilities(name, slug) {
91
+ const text = `${name} ${slug}`.toLowerCase();
92
+ const capabilities = [];
93
+ if (/(jira|linear|asana|clickup|trello|task)/.test(text)) {
94
+ capabilities.push('tickets');
95
+ }
96
+ if (/(github|gitlab|bitbucket|repo|code)/.test(text)) {
97
+ capabilities.push('code-scanning');
98
+ }
99
+ if (/(drive|dropbox|box|sharepoint|doc|notion|confluence)/.test(text)) {
100
+ capabilities.push('docs');
101
+ }
102
+ if (/(salesforce|hubspot|crm|zendesk|intercom)/.test(text)) {
103
+ capabilities.push('search');
104
+ }
105
+ capabilities.push('automation');
106
+ return dedupe(capabilities);
107
+ }
@@ -0,0 +1,107 @@
1
+ import { dedupe, sanitizeUrl, slugify, stripHtml } from './shared.js';
2
+ const TRUSTED_HOSTS = ['claude.com', 'www.claude.com', 'anthropic.com', 'www.anthropic.com'];
3
+ const PLUGIN_PATH_PATTERN = /(?:https:\/\/claude\.com)?\/plugins\/([a-z0-9-]+)/gi;
4
+ export function adaptClaudePluginsScrapeEntries(sourceId, entries) {
5
+ const html = entries.find((entry) => typeof entry === 'string');
6
+ if (typeof html !== 'string' || html.trim().length === 0) {
7
+ return [];
8
+ }
9
+ const pluginSlugs = collectPluginSlugs(html);
10
+ const mapped = [];
11
+ const seen = new Set();
12
+ for (const slug of pluginSlugs) {
13
+ const id = `claude-plugin:${slug}`;
14
+ if (seen.has(id)) {
15
+ continue;
16
+ }
17
+ const snippet = extractAnchorSnippet(html, slug);
18
+ const label = stripHtml(snippet, 140);
19
+ const name = titleizeSlug(label.length >= 3 ? label : slug);
20
+ const link = sanitizeUrl(`https://claude.com/plugins/${slug}`, TRUSTED_HOSTS);
21
+ if (!link) {
22
+ continue;
23
+ }
24
+ seen.add(id);
25
+ mapped.push({
26
+ id,
27
+ kind: 'claude-plugin',
28
+ provider: 'anthropic',
29
+ name: sanitizeText(name, 140),
30
+ description: sanitizeText(`Plugin listed on Claude Plugins for ${name}. Review plugin behavior and permissions before enabling.`, 320),
31
+ capabilities: inferCapabilities(name, slug),
32
+ compatibility: ['claude', 'claude-code', 'cowork'],
33
+ source: sourceId,
34
+ install: {
35
+ kind: 'manual',
36
+ instructions: 'Enable this plugin from Claude Plugins.',
37
+ url: link
38
+ },
39
+ adoptionSignal: 58,
40
+ maintenanceSignal: 68,
41
+ provenanceSignal: 88,
42
+ freshnessSignal: 72,
43
+ securitySignals: {
44
+ knownVulnerabilities: 0,
45
+ suspiciousPatterns: 0,
46
+ injectionFindings: 0,
47
+ exfiltrationSignals: 0,
48
+ integrityAlerts: 0
49
+ },
50
+ metadata: {
51
+ catalogType: 'plugin',
52
+ sourcePage: 'https://claude.com/plugins',
53
+ scrapedAt: new Date().toISOString(),
54
+ sourceConfidence: 'scraped'
55
+ }
56
+ });
57
+ }
58
+ return mapped;
59
+ }
60
+ function collectPluginSlugs(html) {
61
+ const slugs = new Set();
62
+ let match;
63
+ while ((match = PLUGIN_PATH_PATTERN.exec(html)) !== null) {
64
+ const slug = slugify(match[1] ?? '');
65
+ if (!slug) {
66
+ continue;
67
+ }
68
+ slugs.add(slug);
69
+ }
70
+ return Array.from(slugs).sort((a, b) => a.localeCompare(b));
71
+ }
72
+ function extractAnchorSnippet(html, slug) {
73
+ const pattern = new RegExp(`<a[^>]+href="(?:https:\\/\\/claude\\.com)?\\/plugins\\/${slug}"[^>]*>([\\s\\S]{0,700}?)<\\/a>`, 'i');
74
+ const match = html.match(pattern);
75
+ if (!match || typeof match[1] !== 'string') {
76
+ return '';
77
+ }
78
+ return match[1];
79
+ }
80
+ function sanitizeText(value, maxLength) {
81
+ return stripHtml(value, maxLength).slice(0, maxLength);
82
+ }
83
+ function titleizeSlug(value) {
84
+ return value
85
+ .split(/[^a-zA-Z0-9]+/g)
86
+ .filter((segment) => segment.length > 0)
87
+ .map((segment) => segment[0].toUpperCase() + segment.slice(1))
88
+ .join(' ');
89
+ }
90
+ function inferCapabilities(name, slug) {
91
+ const text = `${name} ${slug}`.toLowerCase();
92
+ const capabilities = [];
93
+ if (/(github|gitlab|repo|review|commit|code|playwright|lsp|semgrep|sentry|security)/.test(text)) {
94
+ capabilities.push('code-scanning');
95
+ }
96
+ if (/(asana|atlassian|linear|slack|notion|figma|product|operations|support)/.test(text)) {
97
+ capabilities.push('automation');
98
+ }
99
+ if (/(research|finance|legal|marketing|sales|enterprise-search|data)/.test(text)) {
100
+ capabilities.push('search');
101
+ }
102
+ if (/(design|brand|learning|output-style|creator|setup|management)/.test(text)) {
103
+ capabilities.push('prompting');
104
+ }
105
+ capabilities.push('automation');
106
+ return dedupe(capabilities);
107
+ }
@@ -0,0 +1,48 @@
1
+ import { dedupe, extractStringArray, readNestedString, readString, toCount, toScore } from './shared.js';
2
+ export function adaptClaudePluginsEntries(sourceId, entries) {
3
+ return entries
4
+ .map((entry) => mapClaudePluginEntry(sourceId, entry))
5
+ .filter((entry) => entry !== null);
6
+ }
7
+ function mapClaudePluginEntry(sourceId, entry) {
8
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
9
+ return null;
10
+ }
11
+ const record = entry;
12
+ const slug = readString(record, ['slug', 'id', 'name']);
13
+ if (!slug) {
14
+ return null;
15
+ }
16
+ const name = readString(record, ['title', 'name']) ?? slug;
17
+ const description = readString(record, ['description', 'summary']) ?? `Claude plugin ${name}`;
18
+ const capabilities = dedupe(extractStringArray(record, ['capabilities', 'tools']).concat(extractStringArray(record, ['tags'])));
19
+ const compatibility = dedupe(extractStringArray(record, ['compatibility', 'targets']).concat(['claude']));
20
+ const installUrl = readNestedString(record, ['install', 'url']) ?? readString(record, ['url']);
21
+ const instructions = readNestedString(record, ['install', 'instructions']) ?? 'Enable from Claude plugin catalog.';
22
+ return {
23
+ id: slug.startsWith('claude-plugin:') ? slug : `claude-plugin:${slug}`,
24
+ kind: 'claude-plugin',
25
+ provider: 'anthropic',
26
+ name,
27
+ description,
28
+ capabilities,
29
+ compatibility,
30
+ source: sourceId,
31
+ install: {
32
+ kind: 'manual',
33
+ instructions,
34
+ ...(installUrl ? { url: installUrl } : {})
35
+ },
36
+ adoptionSignal: toScore(record.adoptionSignal),
37
+ maintenanceSignal: toScore(record.maintenanceSignal),
38
+ provenanceSignal: toScore(record.provenanceSignal, 95),
39
+ freshnessSignal: toScore(record.freshnessSignal, 65),
40
+ securitySignals: {
41
+ knownVulnerabilities: toCount(record.knownVulnerabilities),
42
+ suspiciousPatterns: toCount(record.suspiciousPatterns),
43
+ injectionFindings: toCount(record.injectionFindings),
44
+ exfiltrationSignals: toCount(record.exfiltrationSignals),
45
+ integrityAlerts: toCount(record.integrityAlerts)
46
+ }
47
+ };
48
+ }
@@ -0,0 +1,48 @@
1
+ import { dedupe, extractStringArray, readNestedStringArray, readString, toCount, toScore } from './shared.js';
2
+ export function adaptCopilotExtensionsEntries(sourceId, entries) {
3
+ return entries
4
+ .map((entry) => mapCopilotExtensionEntry(sourceId, entry))
5
+ .filter((entry) => entry !== null);
6
+ }
7
+ function mapCopilotExtensionEntry(sourceId, entry) {
8
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
9
+ return null;
10
+ }
11
+ const record = entry;
12
+ const slug = readString(record, ['slug', 'id', 'name']);
13
+ if (!slug) {
14
+ return null;
15
+ }
16
+ const name = readString(record, ['title', 'name']) ?? slug;
17
+ const description = readString(record, ['description', 'summary']) ?? `Copilot extension ${name}`;
18
+ const capabilities = dedupe(extractStringArray(record, ['capabilities', 'tools']).concat(extractStringArray(record, ['tags'])));
19
+ const compatibility = dedupe(extractStringArray(record, ['compatibility', 'targets']).concat(['copilot', 'github']));
20
+ const installTarget = readString(record, ['installId', 'name']) ?? slug;
21
+ const installArgs = readNestedStringArray(record, ['install', 'args']);
22
+ return {
23
+ id: slug.startsWith('copilot-extension:') ? slug : `copilot-extension:${slug}`,
24
+ kind: 'copilot-extension',
25
+ provider: 'github',
26
+ name,
27
+ description,
28
+ capabilities,
29
+ compatibility,
30
+ source: sourceId,
31
+ install: {
32
+ kind: 'gh-cli',
33
+ target: 'copilot-extension',
34
+ args: installArgs.length > 0 ? installArgs : ['install', installTarget]
35
+ },
36
+ adoptionSignal: toScore(record.adoptionSignal),
37
+ maintenanceSignal: toScore(record.maintenanceSignal),
38
+ provenanceSignal: toScore(record.provenanceSignal, 96),
39
+ freshnessSignal: toScore(record.freshnessSignal, 70),
40
+ securitySignals: {
41
+ knownVulnerabilities: toCount(record.knownVulnerabilities),
42
+ suspiciousPatterns: toCount(record.suspiciousPatterns),
43
+ injectionFindings: toCount(record.injectionFindings),
44
+ exfiltrationSignals: toCount(record.exfiltrationSignals),
45
+ integrityAlerts: toCount(record.integrityAlerts)
46
+ }
47
+ };
48
+ }
@@ -0,0 +1,117 @@
1
+ import { dedupe, readString, sanitizeUrl, slugify, stripHtml, toCount, toScore } from './shared.js';
2
+ const TRUSTED_HOSTS = ['github.com', 'raw.githubusercontent.com'];
3
+ export function adaptCopilotPluginMarketplaceEntries(sourceId, entries) {
4
+ const seen = new Set();
5
+ const mapped = [];
6
+ for (const entry of entries) {
7
+ const candidate = mapCopilotMarketplaceEntry(sourceId, entry);
8
+ if (!candidate) {
9
+ continue;
10
+ }
11
+ const candidateId = candidate.id;
12
+ if (typeof candidateId !== 'string') {
13
+ continue;
14
+ }
15
+ if (seen.has(candidateId)) {
16
+ continue;
17
+ }
18
+ seen.add(candidateId);
19
+ mapped.push(candidate);
20
+ }
21
+ return mapped;
22
+ }
23
+ function mapCopilotMarketplaceEntry(sourceId, entry) {
24
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
25
+ return null;
26
+ }
27
+ const record = entry;
28
+ const rawName = readString(record, ['name', 'title', 'id']);
29
+ if (!rawName) {
30
+ return null;
31
+ }
32
+ const slug = slugify(rawName);
33
+ if (!slug) {
34
+ return null;
35
+ }
36
+ const description = stripHtml(readString(record, ['description']) ?? `Copilot extension plugin ${rawName}`, 320) ||
37
+ `Copilot extension plugin ${rawName}`;
38
+ const source = sanitizeUrl(readString(record, ['source', 'url', 'homepage']), TRUSTED_HOSTS);
39
+ const version = readString(record, ['version']);
40
+ const skills = extractSkills(record.skills);
41
+ const capabilities = dedupe(extractTags(record)
42
+ .concat(skills)
43
+ .concat(['automation']));
44
+ return {
45
+ id: `copilot-extension:${slug}`,
46
+ kind: 'copilot-extension',
47
+ provider: 'github',
48
+ name: rawName.trim().slice(0, 120),
49
+ description,
50
+ capabilities,
51
+ compatibility: ['copilot', 'github'],
52
+ source: sourceId,
53
+ install: {
54
+ kind: 'manual',
55
+ instructions: 'Enable this plugin from GitHub Copilot marketplace settings.',
56
+ ...(source ? { url: source } : {})
57
+ },
58
+ adoptionSignal: toScore(record.adoptionSignal, 62),
59
+ maintenanceSignal: toScore(record.maintenanceSignal, version ? 76 : 68),
60
+ provenanceSignal: toScore(record.provenanceSignal, 88),
61
+ freshnessSignal: toScore(record.freshnessSignal, version ? 74 : 64),
62
+ securitySignals: {
63
+ knownVulnerabilities: toCount(record.knownVulnerabilities),
64
+ suspiciousPatterns: toCount(record.suspiciousPatterns),
65
+ injectionFindings: toCount(record.injectionFindings),
66
+ exfiltrationSignals: toCount(record.exfiltrationSignals),
67
+ integrityAlerts: toCount(record.integrityAlerts)
68
+ },
69
+ metadata: {
70
+ catalogType: 'plugin',
71
+ sourceRepo: sourceId.includes('awesome') ? 'github/awesome-copilot' : 'github/copilot-plugins',
72
+ rawVersion: version ?? 'unknown',
73
+ sourceConfidence: sourceId.includes('awesome') ? 'vetted-curated' : 'official'
74
+ }
75
+ };
76
+ }
77
+ function extractTags(record) {
78
+ const tags = [];
79
+ const raw = record.tags;
80
+ if (Array.isArray(raw)) {
81
+ for (const tag of raw) {
82
+ if (typeof tag === 'string' && tag.trim().length > 0) {
83
+ tags.push(tag.trim().toLowerCase());
84
+ }
85
+ }
86
+ }
87
+ const category = readString(record, ['category']);
88
+ if (category) {
89
+ tags.push(category.toLowerCase());
90
+ }
91
+ return dedupe(tags);
92
+ }
93
+ function extractSkills(value) {
94
+ if (!Array.isArray(value)) {
95
+ return [];
96
+ }
97
+ const skills = [];
98
+ for (const item of value) {
99
+ if (!item || typeof item !== 'object' || Array.isArray(item)) {
100
+ continue;
101
+ }
102
+ const record = item;
103
+ const name = readString(record, ['name']);
104
+ if (name) {
105
+ skills.push(name.toLowerCase());
106
+ }
107
+ const commands = record.commands;
108
+ if (Array.isArray(commands)) {
109
+ for (const command of commands) {
110
+ if (typeof command === 'string' && command.trim().length > 0) {
111
+ skills.push(command.trim().toLowerCase());
112
+ }
113
+ }
114
+ }
115
+ }
116
+ return dedupe(skills);
117
+ }
@@ -0,0 +1,211 @@
1
+ import { dedupe, extractStringArray, readNestedString, readString } from './shared.js';
2
+ export function adaptMcpRegistryEntries(sourceId, entries) {
3
+ return entries
4
+ .map((entry) => mapMcpRegistryEntry(sourceId, entry))
5
+ .filter((entry) => entry !== null);
6
+ }
7
+ function mapMcpRegistryEntry(sourceId, entry) {
8
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
9
+ return null;
10
+ }
11
+ const record = entry;
12
+ const server = resolveServerRecord(record);
13
+ const name = readString(server, ['name', 'id']);
14
+ if (!name) {
15
+ return null;
16
+ }
17
+ const title = readString(server, ['title', 'displayName']) ?? name;
18
+ const description = readString(server, ['description']) ?? `MCP server ${name}`;
19
+ const packages = Array.isArray(server.packages) ? server.packages : [];
20
+ const remotes = Array.isArray(server.remotes) ? server.remotes : [];
21
+ const packageRecords = packages
22
+ .filter((pkg) => pkg && typeof pkg === 'object' && !Array.isArray(pkg))
23
+ .map((pkg) => pkg);
24
+ const remoteRecords = remotes
25
+ .filter((remote) => remote && typeof remote === 'object' && !Array.isArray(remote))
26
+ .map((remote) => remote);
27
+ const firstPackage = packageRecords[0] ?? {};
28
+ const firstRemote = remoteRecords[0] ?? {};
29
+ const transport = normalizeTransport(readNestedString(firstPackage, ['transport', 'type']) ?? readString(firstPackage, ['transport'])) ??
30
+ normalizeTransport(readString(firstRemote, ['type'])) ??
31
+ normalizeTransport(readString(server, ['transport'])) ??
32
+ 'stdio';
33
+ const authModel = normalizeAuthModel(readString(server, ['authModel', 'auth'])) ?? inferAuthModel(firstPackage, remoteRecords);
34
+ const compatibility = dedupe(packageRecords
35
+ .flatMap((pkg) => detectCompatibilityFromPackage(pkg))
36
+ .concat(detectCompatibilityFromRemotes(remoteRecords))
37
+ .concat(['general']));
38
+ const capabilities = dedupe(extractStringArray(server, ['capabilities', 'tools'])
39
+ .concat(extractStringArray(server, ['tags']))
40
+ .concat(extractStringArray(record, ['tags'])));
41
+ const target = readNestedString(firstPackage, ['identifier']) ??
42
+ readNestedString(firstPackage, ['name']) ??
43
+ readString(server, ['name']) ??
44
+ name;
45
+ const installUrl = readString(server, ['websiteUrl']) ?? readNestedString(server, ['repository', 'url']);
46
+ const install = packageRecords.length > 0
47
+ ? {
48
+ kind: 'skill.sh',
49
+ target,
50
+ args: []
51
+ }
52
+ : {
53
+ kind: 'manual',
54
+ instructions: 'Configure using the MCP server repository or hosted remote endpoint.',
55
+ url: installUrl
56
+ };
57
+ const publishedAt = readNestedString(record, ['_meta', 'io.modelcontextprotocol.registry/official', 'publishedAt']);
58
+ const freshnessSignal = scoreFreshness(publishedAt);
59
+ return {
60
+ id: `mcp:${name}`,
61
+ kind: 'mcp',
62
+ provider: 'mcp',
63
+ name: title,
64
+ description,
65
+ transport,
66
+ authModel,
67
+ capabilities,
68
+ compatibility,
69
+ source: sourceId,
70
+ install,
71
+ adoptionSignal: 50,
72
+ maintenanceSignal: 65,
73
+ provenanceSignal: 90,
74
+ freshnessSignal,
75
+ securitySignals: {
76
+ knownVulnerabilities: 0,
77
+ suspiciousPatterns: 0,
78
+ injectionFindings: 0,
79
+ exfiltrationSignals: 0,
80
+ integrityAlerts: 0
81
+ },
82
+ metadata: {
83
+ websiteUrl: readString(server, ['websiteUrl']),
84
+ repositoryUrl: readNestedString(server, ['repository', 'url']),
85
+ packageIdentifier: readNestedString(firstPackage, ['identifier']) ?? readNestedString(firstPackage, ['name']),
86
+ packageRegistryType: readString(firstPackage, ['registryType']),
87
+ packageRuntime: readString(firstPackage, ['runtime']),
88
+ version: readString(server, ['version']),
89
+ publishedAt
90
+ }
91
+ };
92
+ }
93
+ function resolveServerRecord(entry) {
94
+ const nested = entry.server;
95
+ if (nested && typeof nested === 'object' && !Array.isArray(nested)) {
96
+ return nested;
97
+ }
98
+ return entry;
99
+ }
100
+ function normalizeTransport(value) {
101
+ const normalized = value?.toLowerCase();
102
+ if (!normalized) {
103
+ return undefined;
104
+ }
105
+ if (normalized === 'http') {
106
+ return 'http';
107
+ }
108
+ if (normalized === 'streamable-http') {
109
+ return 'http';
110
+ }
111
+ if (normalized === 'sse') {
112
+ return 'sse';
113
+ }
114
+ if (normalized === 'websocket' || normalized === 'ws') {
115
+ return 'websocket';
116
+ }
117
+ if (normalized === 'stdio') {
118
+ return 'stdio';
119
+ }
120
+ return undefined;
121
+ }
122
+ function normalizeAuthModel(value) {
123
+ const normalized = value?.toLowerCase();
124
+ if (!normalized) {
125
+ return undefined;
126
+ }
127
+ if (normalized === 'none' || normalized === 'noauth') {
128
+ return 'none';
129
+ }
130
+ if (normalized === 'api_key' || normalized === 'apikey' || normalized === 'bearer') {
131
+ return 'api_key';
132
+ }
133
+ if (normalized === 'oauth' || normalized === 'oauth2') {
134
+ return 'oauth';
135
+ }
136
+ return undefined;
137
+ }
138
+ function inferAuthModel(firstPackage, remotes) {
139
+ const envVars = Array.isArray(firstPackage.environmentVariables) ? firstPackage.environmentVariables : [];
140
+ const hasSecretEnv = envVars.some((envVar) => {
141
+ if (!envVar || typeof envVar !== 'object' || Array.isArray(envVar)) {
142
+ return false;
143
+ }
144
+ return envVar.isSecret === true;
145
+ });
146
+ if (hasSecretEnv) {
147
+ return 'api_key';
148
+ }
149
+ if (remotes.length > 0) {
150
+ return 'custom';
151
+ }
152
+ return 'none';
153
+ }
154
+ function detectCompatibilityFromPackage(pkg) {
155
+ const words = [
156
+ readString(pkg, ['registryType']) ?? '',
157
+ readString(pkg, ['runtime']) ?? '',
158
+ readString(pkg, ['name']) ?? '',
159
+ readString(pkg, ['identifier']) ?? ''
160
+ ]
161
+ .join(' ')
162
+ .toLowerCase();
163
+ const tags = [];
164
+ if (words.includes('npm') || words.includes('node')) {
165
+ tags.push('node');
166
+ }
167
+ if (words.includes('pypi') || words.includes('python')) {
168
+ tags.push('python');
169
+ }
170
+ if (words.includes('docker') || words.includes('container')) {
171
+ tags.push('container');
172
+ }
173
+ return tags;
174
+ }
175
+ function detectCompatibilityFromRemotes(remotes) {
176
+ const tags = new Set();
177
+ remotes.forEach((remote) => {
178
+ const remoteType = readString(remote, ['type'])?.toLowerCase() ?? '';
179
+ if (remoteType.includes('http') || remoteType === 'sse') {
180
+ tags.add('network');
181
+ }
182
+ if (remoteType === 'websocket' || remoteType === 'ws') {
183
+ tags.add('network');
184
+ }
185
+ });
186
+ return Array.from(tags);
187
+ }
188
+ function scoreFreshness(publishedAt) {
189
+ if (!publishedAt) {
190
+ return 60;
191
+ }
192
+ const stamp = Date.parse(publishedAt);
193
+ if (!Number.isFinite(stamp)) {
194
+ return 60;
195
+ }
196
+ const ageMs = Date.now() - stamp;
197
+ const ageDays = ageMs / (24 * 60 * 60 * 1000);
198
+ if (ageDays <= 7) {
199
+ return 95;
200
+ }
201
+ if (ageDays <= 30) {
202
+ return 85;
203
+ }
204
+ if (ageDays <= 90) {
205
+ return 75;
206
+ }
207
+ if (ageDays <= 180) {
208
+ return 65;
209
+ }
210
+ return 55;
211
+ }