@mcpmake/core 0.2.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 (323) hide show
  1. package/dist/analyzer/auth-detector.d.ts +13 -0
  2. package/dist/analyzer/auth-detector.d.ts.map +1 -0
  3. package/dist/analyzer/auth-detector.js +143 -0
  4. package/dist/analyzer/auth-detector.js.map +1 -0
  5. package/dist/analyzer/dom-parser.d.ts +11 -0
  6. package/dist/analyzer/dom-parser.d.ts.map +1 -0
  7. package/dist/analyzer/dom-parser.js +260 -0
  8. package/dist/analyzer/dom-parser.js.map +1 -0
  9. package/dist/analyzer/goal-crawler.d.ts +26 -0
  10. package/dist/analyzer/goal-crawler.d.ts.map +1 -0
  11. package/dist/analyzer/goal-crawler.js +178 -0
  12. package/dist/analyzer/goal-crawler.js.map +1 -0
  13. package/dist/analyzer/hybrid-detector.d.ts +29 -0
  14. package/dist/analyzer/hybrid-detector.d.ts.map +1 -0
  15. package/dist/analyzer/hybrid-detector.js +97 -0
  16. package/dist/analyzer/hybrid-detector.js.map +1 -0
  17. package/dist/analyzer/index.d.ts +13 -0
  18. package/dist/analyzer/index.d.ts.map +1 -0
  19. package/dist/analyzer/index.js +9 -0
  20. package/dist/analyzer/index.js.map +1 -0
  21. package/dist/analyzer/screenshot-capture.d.ts +30 -0
  22. package/dist/analyzer/screenshot-capture.d.ts.map +1 -0
  23. package/dist/analyzer/screenshot-capture.js +43 -0
  24. package/dist/analyzer/screenshot-capture.js.map +1 -0
  25. package/dist/analyzer/selector-builder.d.ts +20 -0
  26. package/dist/analyzer/selector-builder.d.ts.map +1 -0
  27. package/dist/analyzer/selector-builder.js +200 -0
  28. package/dist/analyzer/selector-builder.js.map +1 -0
  29. package/dist/analyzer/semantic-analyzer.d.ts +14 -0
  30. package/dist/analyzer/semantic-analyzer.d.ts.map +1 -0
  31. package/dist/analyzer/semantic-analyzer.js +146 -0
  32. package/dist/analyzer/semantic-analyzer.js.map +1 -0
  33. package/dist/analyzer/site-crawler.d.ts +39 -0
  34. package/dist/analyzer/site-crawler.d.ts.map +1 -0
  35. package/dist/analyzer/site-crawler.js +236 -0
  36. package/dist/analyzer/site-crawler.js.map +1 -0
  37. package/dist/config/configurable-command.d.ts +14 -0
  38. package/dist/config/configurable-command.d.ts.map +1 -0
  39. package/dist/config/configurable-command.js +71 -0
  40. package/dist/config/configurable-command.js.map +1 -0
  41. package/dist/config/mcpmake-config.d.ts +69 -0
  42. package/dist/config/mcpmake-config.d.ts.map +1 -0
  43. package/dist/config/mcpmake-config.js +209 -0
  44. package/dist/config/mcpmake-config.js.map +1 -0
  45. package/dist/emitter/code-writer.d.ts +9 -0
  46. package/dist/emitter/code-writer.d.ts.map +1 -0
  47. package/dist/emitter/code-writer.js +26 -0
  48. package/dist/emitter/code-writer.js.map +1 -0
  49. package/dist/emitter/index.d.ts +33 -0
  50. package/dist/emitter/index.d.ts.map +1 -0
  51. package/dist/emitter/index.js +297 -0
  52. package/dist/emitter/index.js.map +1 -0
  53. package/dist/emitter/mcpb-bundler.d.ts +32 -0
  54. package/dist/emitter/mcpb-bundler.d.ts.map +1 -0
  55. package/dist/emitter/mcpb-bundler.js +173 -0
  56. package/dist/emitter/mcpb-bundler.js.map +1 -0
  57. package/dist/emitter/project-scaffolder.d.ts +5 -0
  58. package/dist/emitter/project-scaffolder.d.ts.map +1 -0
  59. package/dist/emitter/project-scaffolder.js +101 -0
  60. package/dist/emitter/project-scaffolder.js.map +1 -0
  61. package/dist/emitter/python-template-loader.d.ts +5 -0
  62. package/dist/emitter/python-template-loader.d.ts.map +1 -0
  63. package/dist/emitter/python-template-loader.js +31 -0
  64. package/dist/emitter/python-template-loader.js.map +1 -0
  65. package/dist/emitter/python-templates/dockerfile.hbs +14 -0
  66. package/dist/emitter/python-templates/env.example.hbs +12 -0
  67. package/dist/emitter/python-templates/requirements.txt.hbs +4 -0
  68. package/dist/emitter/python-templates/server.py.hbs +93 -0
  69. package/dist/emitter/site-scaffolder.d.ts +14 -0
  70. package/dist/emitter/site-scaffolder.d.ts.map +1 -0
  71. package/dist/emitter/site-scaffolder.js +71 -0
  72. package/dist/emitter/site-scaffolder.js.map +1 -0
  73. package/dist/emitter/site-template-loader.d.ts +6 -0
  74. package/dist/emitter/site-template-loader.d.ts.map +1 -0
  75. package/dist/emitter/site-template-loader.js +48 -0
  76. package/dist/emitter/site-template-loader.js.map +1 -0
  77. package/dist/emitter/site-templates/browser-manager.ts.hbs +233 -0
  78. package/dist/emitter/site-templates/config.ts.hbs +28 -0
  79. package/dist/emitter/site-templates/dockerfile.hbs +31 -0
  80. package/dist/emitter/site-templates/env.example.hbs +19 -0
  81. package/dist/emitter/site-templates/package.json.hbs +26 -0
  82. package/dist/emitter/site-templates/server-main-http.ts.hbs +108 -0
  83. package/dist/emitter/site-templates/server-main.ts.hbs +23 -0
  84. package/dist/emitter/site-templates/tool-handler-action.ts.hbs +86 -0
  85. package/dist/emitter/site-templates/tool-handler-form.ts.hbs +116 -0
  86. package/dist/emitter/site-templates/tool-handler-lifecycle.ts.hbs +146 -0
  87. package/dist/emitter/site-templates/tool-index.ts.hbs +11 -0
  88. package/dist/emitter/template-loader.d.ts +2 -0
  89. package/dist/emitter/template-loader.d.ts.map +1 -0
  90. package/dist/emitter/template-loader.js +28 -0
  91. package/dist/emitter/template-loader.js.map +1 -0
  92. package/dist/emitter/templates/auth-provider.ts.hbs +57 -0
  93. package/dist/emitter/templates/config.ts.hbs +110 -0
  94. package/dist/emitter/templates/discovery.ts.hbs +320 -0
  95. package/dist/emitter/templates/dockerfile.hbs +34 -0
  96. package/dist/emitter/templates/env.example.hbs +52 -0
  97. package/dist/emitter/templates/gitignore.hbs +5 -0
  98. package/dist/emitter/templates/http-executor.ts.hbs +159 -0
  99. package/dist/emitter/templates/oauth.ts.hbs +188 -0
  100. package/dist/emitter/templates/package.json.hbs +25 -0
  101. package/dist/emitter/templates/prompts.ts.hbs +22 -0
  102. package/dist/emitter/templates/readme.md.hbs +156 -0
  103. package/dist/emitter/templates/resources.ts.hbs +63 -0
  104. package/dist/emitter/templates/response-filter.ts.hbs +128 -0
  105. package/dist/emitter/templates/server-main-http.ts.hbs +442 -0
  106. package/dist/emitter/templates/server-main.ts.hbs +40 -0
  107. package/dist/emitter/templates/task-handlers.ts.hbs +189 -0
  108. package/dist/emitter/templates/task-manager.ts.hbs +139 -0
  109. package/dist/emitter/templates/task-sse.ts.hbs +105 -0
  110. package/dist/emitter/templates/tool-handler.ts.hbs +142 -0
  111. package/dist/emitter/templates/tool-index.ts.hbs +42 -0
  112. package/dist/emitter/templates/tool-test.ts.hbs +57 -0
  113. package/dist/emitter/templates/trace.ts.hbs +79 -0
  114. package/dist/emitter/templates/tsconfig.json.hbs +16 -0
  115. package/dist/emitter/templates/types.ts.hbs +5 -0
  116. package/dist/emitter/worker-template-loader.d.ts +6 -0
  117. package/dist/emitter/worker-template-loader.d.ts.map +1 -0
  118. package/dist/emitter/worker-template-loader.js +34 -0
  119. package/dist/emitter/worker-template-loader.js.map +1 -0
  120. package/dist/emitter/worker-templates/config.ts.hbs +93 -0
  121. package/dist/emitter/worker-templates/dev-vars.example.hbs +27 -0
  122. package/dist/emitter/worker-templates/gitignore.hbs +6 -0
  123. package/dist/emitter/worker-templates/package.json.hbs +24 -0
  124. package/dist/emitter/worker-templates/readme.md.hbs +73 -0
  125. package/dist/emitter/worker-templates/server.test.ts.hbs +20 -0
  126. package/dist/emitter/worker-templates/tool-handler.ts.hbs +103 -0
  127. package/dist/emitter/worker-templates/tool-index.ts.hbs +28 -0
  128. package/dist/emitter/worker-templates/tsconfig.json.hbs +17 -0
  129. package/dist/emitter/worker-templates/worker.ts.hbs +286 -0
  130. package/dist/emitter/worker-templates/wrangler.toml.hbs +19 -0
  131. package/dist/generator/spec-generator.d.ts +7 -0
  132. package/dist/generator/spec-generator.d.ts.map +1 -0
  133. package/dist/generator/spec-generator.js +51 -0
  134. package/dist/generator/spec-generator.js.map +1 -0
  135. package/dist/index.d.ts +75 -0
  136. package/dist/index.d.ts.map +1 -0
  137. package/dist/index.js +101 -0
  138. package/dist/index.js.map +1 -0
  139. package/dist/parser/har-filter.d.ts +9 -0
  140. package/dist/parser/har-filter.d.ts.map +1 -0
  141. package/dist/parser/har-filter.js +72 -0
  142. package/dist/parser/har-filter.js.map +1 -0
  143. package/dist/parser/har-loader.d.ts +3 -0
  144. package/dist/parser/har-loader.d.ts.map +1 -0
  145. package/dist/parser/har-loader.js +15 -0
  146. package/dist/parser/har-loader.js.map +1 -0
  147. package/dist/parser/har-normalizer.d.ts +21 -0
  148. package/dist/parser/har-normalizer.d.ts.map +1 -0
  149. package/dist/parser/har-normalizer.js +79 -0
  150. package/dist/parser/har-normalizer.js.map +1 -0
  151. package/dist/parser/index.d.ts +11 -0
  152. package/dist/parser/index.d.ts.map +1 -0
  153. package/dist/parser/index.js +7 -0
  154. package/dist/parser/index.js.map +1 -0
  155. package/dist/parser/openapi-loader.d.ts +7 -0
  156. package/dist/parser/openapi-loader.d.ts.map +1 -0
  157. package/dist/parser/openapi-loader.js +309 -0
  158. package/dist/parser/openapi-loader.js.map +1 -0
  159. package/dist/parser/operation-extractor.d.ts +14 -0
  160. package/dist/parser/operation-extractor.d.ts.map +1 -0
  161. package/dist/parser/operation-extractor.js +156 -0
  162. package/dist/parser/operation-extractor.js.map +1 -0
  163. package/dist/parser/overlay-loader.d.ts +11 -0
  164. package/dist/parser/overlay-loader.d.ts.map +1 -0
  165. package/dist/parser/overlay-loader.js +185 -0
  166. package/dist/parser/overlay-loader.js.map +1 -0
  167. package/dist/parser/postman-loader.d.ts +10 -0
  168. package/dist/parser/postman-loader.d.ts.map +1 -0
  169. package/dist/parser/postman-loader.js +107 -0
  170. package/dist/parser/postman-loader.js.map +1 -0
  171. package/dist/parser/schema-converter.d.ts +13 -0
  172. package/dist/parser/schema-converter.d.ts.map +1 -0
  173. package/dist/parser/schema-converter.js +118 -0
  174. package/dist/parser/schema-converter.js.map +1 -0
  175. package/dist/plugins/adapter.d.ts +41 -0
  176. package/dist/plugins/adapter.d.ts.map +1 -0
  177. package/dist/plugins/adapter.js +16 -0
  178. package/dist/plugins/adapter.js.map +1 -0
  179. package/dist/plugins/loader.d.ts +26 -0
  180. package/dist/plugins/loader.d.ts.map +1 -0
  181. package/dist/plugins/loader.js +59 -0
  182. package/dist/plugins/loader.js.map +1 -0
  183. package/dist/pricing.d.ts +56 -0
  184. package/dist/pricing.d.ts.map +1 -0
  185. package/dist/pricing.js +134 -0
  186. package/dist/pricing.js.map +1 -0
  187. package/dist/providers/index.d.ts +16 -0
  188. package/dist/providers/index.d.ts.map +1 -0
  189. package/dist/providers/index.js +57 -0
  190. package/dist/providers/index.js.map +1 -0
  191. package/dist/recorder/browser-recorder.d.ts +23 -0
  192. package/dist/recorder/browser-recorder.d.ts.map +1 -0
  193. package/dist/recorder/browser-recorder.js +206 -0
  194. package/dist/recorder/browser-recorder.js.map +1 -0
  195. package/dist/rescan/diff-engine.d.ts +6 -0
  196. package/dist/rescan/diff-engine.d.ts.map +1 -0
  197. package/dist/rescan/diff-engine.js +313 -0
  198. package/dist/rescan/diff-engine.js.map +1 -0
  199. package/dist/rescan/index.d.ts +4 -0
  200. package/dist/rescan/index.d.ts.map +1 -0
  201. package/dist/rescan/index.js +3 -0
  202. package/dist/rescan/index.js.map +1 -0
  203. package/dist/rescan/rescan-runner.d.ts +43 -0
  204. package/dist/rescan/rescan-runner.d.ts.map +1 -0
  205. package/dist/rescan/rescan-runner.js +70 -0
  206. package/dist/rescan/rescan-runner.js.map +1 -0
  207. package/dist/rescan/rescan-scheduler.d.ts +42 -0
  208. package/dist/rescan/rescan-scheduler.d.ts.map +1 -0
  209. package/dist/rescan/rescan-scheduler.js +180 -0
  210. package/dist/rescan/rescan-scheduler.js.map +1 -0
  211. package/dist/site-transformer/browser-tools.d.ts +11 -0
  212. package/dist/site-transformer/browser-tools.d.ts.map +1 -0
  213. package/dist/site-transformer/browser-tools.js +60 -0
  214. package/dist/site-transformer/browser-tools.js.map +1 -0
  215. package/dist/site-transformer/index.d.ts +3 -0
  216. package/dist/site-transformer/index.d.ts.map +1 -0
  217. package/dist/site-transformer/index.js +3 -0
  218. package/dist/site-transformer/index.js.map +1 -0
  219. package/dist/site-transformer/selector-healer.d.ts +9 -0
  220. package/dist/site-transformer/selector-healer.d.ts.map +1 -0
  221. package/dist/site-transformer/selector-healer.js +107 -0
  222. package/dist/site-transformer/selector-healer.js.map +1 -0
  223. package/dist/site-transformer/tool-generator.d.ts +14 -0
  224. package/dist/site-transformer/tool-generator.d.ts.map +1 -0
  225. package/dist/site-transformer/tool-generator.js +246 -0
  226. package/dist/site-transformer/tool-generator.js.map +1 -0
  227. package/dist/transformer/auth-detector.d.ts +29 -0
  228. package/dist/transformer/auth-detector.d.ts.map +1 -0
  229. package/dist/transformer/auth-detector.js +104 -0
  230. package/dist/transformer/auth-detector.js.map +1 -0
  231. package/dist/transformer/catalog-builder.d.ts +19 -0
  232. package/dist/transformer/catalog-builder.d.ts.map +1 -0
  233. package/dist/transformer/catalog-builder.js +57 -0
  234. package/dist/transformer/catalog-builder.js.map +1 -0
  235. package/dist/transformer/client-compat.d.ts +7 -0
  236. package/dist/transformer/client-compat.d.ts.map +1 -0
  237. package/dist/transformer/client-compat.js +45 -0
  238. package/dist/transformer/client-compat.js.map +1 -0
  239. package/dist/transformer/har-clusterer.d.ts +10 -0
  240. package/dist/transformer/har-clusterer.d.ts.map +1 -0
  241. package/dist/transformer/har-clusterer.js +28 -0
  242. package/dist/transformer/har-clusterer.js.map +1 -0
  243. package/dist/transformer/har-dedup.d.ts +11 -0
  244. package/dist/transformer/har-dedup.d.ts.map +1 -0
  245. package/dist/transformer/har-dedup.js +82 -0
  246. package/dist/transformer/har-dedup.js.map +1 -0
  247. package/dist/transformer/har-schema-inferrer.d.ts +16 -0
  248. package/dist/transformer/har-schema-inferrer.d.ts.map +1 -0
  249. package/dist/transformer/har-schema-inferrer.js +91 -0
  250. package/dist/transformer/har-schema-inferrer.js.map +1 -0
  251. package/dist/transformer/har-to-operations.d.ts +14 -0
  252. package/dist/transformer/har-to-operations.d.ts.map +1 -0
  253. package/dist/transformer/har-to-operations.js +193 -0
  254. package/dist/transformer/har-to-operations.js.map +1 -0
  255. package/dist/transformer/index.d.ts +9 -0
  256. package/dist/transformer/index.d.ts.map +1 -0
  257. package/dist/transformer/index.js +7 -0
  258. package/dist/transformer/index.js.map +1 -0
  259. package/dist/transformer/llm-namer.d.ts +7 -0
  260. package/dist/transformer/llm-namer.d.ts.map +1 -0
  261. package/dist/transformer/llm-namer.js +60 -0
  262. package/dist/transformer/llm-namer.js.map +1 -0
  263. package/dist/transformer/naming.d.ts +5 -0
  264. package/dist/transformer/naming.d.ts.map +1 -0
  265. package/dist/transformer/naming.js +31 -0
  266. package/dist/transformer/naming.js.map +1 -0
  267. package/dist/transformer/operation-filter.d.ts +14 -0
  268. package/dist/transformer/operation-filter.d.ts.map +1 -0
  269. package/dist/transformer/operation-filter.js +53 -0
  270. package/dist/transformer/operation-filter.js.map +1 -0
  271. package/dist/transformer/resource-builder.d.ts +13 -0
  272. package/dist/transformer/resource-builder.d.ts.map +1 -0
  273. package/dist/transformer/resource-builder.js +87 -0
  274. package/dist/transformer/resource-builder.js.map +1 -0
  275. package/dist/transformer/schema-merger.d.ts +15 -0
  276. package/dist/transformer/schema-merger.d.ts.map +1 -0
  277. package/dist/transformer/schema-merger.js +66 -0
  278. package/dist/transformer/schema-merger.js.map +1 -0
  279. package/dist/transformer/stainless-config.d.ts +75 -0
  280. package/dist/transformer/stainless-config.d.ts.map +1 -0
  281. package/dist/transformer/stainless-config.js +59 -0
  282. package/dist/transformer/stainless-config.js.map +1 -0
  283. package/dist/transformer/stainless-translator.d.ts +61 -0
  284. package/dist/transformer/stainless-translator.d.ts.map +1 -0
  285. package/dist/transformer/stainless-translator.js +375 -0
  286. package/dist/transformer/stainless-translator.js.map +1 -0
  287. package/dist/transformer/tool-builder.d.ts +4 -0
  288. package/dist/transformer/tool-builder.d.ts.map +1 -0
  289. package/dist/transformer/tool-builder.js +119 -0
  290. package/dist/transformer/tool-builder.js.map +1 -0
  291. package/dist/types/index.d.ts +140 -0
  292. package/dist/types/index.d.ts.map +1 -0
  293. package/dist/types/index.js +2 -0
  294. package/dist/types/index.js.map +1 -0
  295. package/dist/types/site.d.ts +285 -0
  296. package/dist/types/site.d.ts.map +1 -0
  297. package/dist/types/site.js +9 -0
  298. package/dist/types/site.js.map +1 -0
  299. package/dist/utils/fail.d.ts +49 -0
  300. package/dist/utils/fail.d.ts.map +1 -0
  301. package/dist/utils/fail.js +223 -0
  302. package/dist/utils/fail.js.map +1 -0
  303. package/dist/utils/fs.d.ts +6 -0
  304. package/dist/utils/fs.d.ts.map +1 -0
  305. package/dist/utils/fs.js +29 -0
  306. package/dist/utils/fs.js.map +1 -0
  307. package/dist/utils/interactive.d.ts +7 -0
  308. package/dist/utils/interactive.d.ts.map +1 -0
  309. package/dist/utils/interactive.js +31 -0
  310. package/dist/utils/interactive.js.map +1 -0
  311. package/dist/utils/logger.d.ts +2 -0
  312. package/dist/utils/logger.d.ts.map +1 -0
  313. package/dist/utils/logger.js +3 -0
  314. package/dist/utils/logger.js.map +1 -0
  315. package/dist/utils/sanitize.d.ts +29 -0
  316. package/dist/utils/sanitize.d.ts.map +1 -0
  317. package/dist/utils/sanitize.js +45 -0
  318. package/dist/utils/sanitize.js.map +1 -0
  319. package/dist/utils/watcher.d.ts +12 -0
  320. package/dist/utils/watcher.d.ts.map +1 -0
  321. package/dist/utils/watcher.js +37 -0
  322. package/dist/utils/watcher.js.map +1 -0
  323. package/package.json +45 -0
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Builds multi-strategy selector sets for DOM elements.
3
+ *
4
+ * Generates selectors in priority order (most stable first):
5
+ * data-testid > id > aria-label > name > role > CSS path > XPath
6
+ */
7
+ /**
8
+ * Build a SelectorSet for a given element on the page.
9
+ * Tries multiple strategies and picks the most stable one as primary.
10
+ */
11
+ export async function buildSelectorSet(page, element) {
12
+ const candidates = [];
13
+ const attrs = await element.evaluate((node) => {
14
+ const el = node;
15
+ return {
16
+ tagName: el.tagName.toLowerCase(),
17
+ id: el.id || null,
18
+ name: el.getAttribute('name'),
19
+ type: el.getAttribute('type'),
20
+ testId: el.getAttribute('data-testid') || el.getAttribute('data-test-id'),
21
+ ariaLabel: el.getAttribute('aria-label'),
22
+ role: el.getAttribute('role') || null,
23
+ placeholder: el.getAttribute('placeholder'),
24
+ textContent: el.textContent?.trim().slice(0, 80) || null,
25
+ className: el.className || null,
26
+ };
27
+ });
28
+ // 1. data-testid (most stable — intentionally placed for testing)
29
+ if (attrs.testId) {
30
+ candidates.push({
31
+ selector: `[data-testid="${cssEscape(attrs.testId)}"]`,
32
+ strategy: 'data-testid',
33
+ confidence: 0.95,
34
+ });
35
+ }
36
+ // 2. id (stable if present, but may be auto-generated)
37
+ if (attrs.id && !looksAutoGenerated(attrs.id)) {
38
+ candidates.push({
39
+ selector: `#${cssEscape(attrs.id)}`,
40
+ strategy: 'id',
41
+ confidence: 0.9,
42
+ });
43
+ }
44
+ // 3. aria-label (stable for accessible sites)
45
+ if (attrs.ariaLabel) {
46
+ candidates.push({
47
+ selector: `${attrs.tagName}[aria-label="${cssEscape(attrs.ariaLabel)}"]`,
48
+ strategy: 'aria-label',
49
+ confidence: 0.85,
50
+ });
51
+ }
52
+ // 4. name attribute (stable for form elements)
53
+ if (attrs.name) {
54
+ const typeQualifier = attrs.type ? `[type="${attrs.type}"]` : '';
55
+ candidates.push({
56
+ selector: `${attrs.tagName}[name="${cssEscape(attrs.name)}"]${typeQualifier}`,
57
+ strategy: 'name',
58
+ confidence: 0.85,
59
+ });
60
+ }
61
+ // 5. role + text (for buttons/links with visible text)
62
+ if (attrs.role && attrs.textContent) {
63
+ const shortText = attrs.textContent.slice(0, 40);
64
+ candidates.push({
65
+ selector: `${attrs.tagName}[role="${attrs.role}"]:has-text("${cssEscape(shortText)}")`,
66
+ strategy: 'role',
67
+ confidence: 0.7,
68
+ });
69
+ }
70
+ // 6. CSS path (always available but fragile)
71
+ const cssPath = await buildCssPath(element);
72
+ if (cssPath) {
73
+ candidates.push({
74
+ selector: cssPath,
75
+ strategy: 'css-path',
76
+ confidence: 0.4,
77
+ });
78
+ }
79
+ // Sort by confidence descending
80
+ candidates.sort((a, b) => b.confidence - a.confidence);
81
+ if (candidates.length === 0) {
82
+ // Absolute fallback: XPath
83
+ const xpath = await buildXPath(element);
84
+ return {
85
+ primary: xpath,
86
+ fallbacks: [],
87
+ strategy: 'xpath',
88
+ confidence: 0.2,
89
+ };
90
+ }
91
+ const primary = candidates[0];
92
+ const fallbacks = candidates.slice(1).map((c) => c.selector);
93
+ return {
94
+ primary: primary.selector,
95
+ fallbacks,
96
+ strategy: primary.strategy,
97
+ confidence: primary.confidence,
98
+ };
99
+ }
100
+ /**
101
+ * Validate that a selector still resolves to an element on the page.
102
+ * Tries the primary selector first, then fallbacks.
103
+ * Returns the first working selector or null if all fail.
104
+ */
105
+ export async function validateSelector(page, selectorSet) {
106
+ const allSelectors = [selectorSet.primary, ...selectorSet.fallbacks];
107
+ for (const selector of allSelectors) {
108
+ try {
109
+ const el = await page.$(selector);
110
+ if (el)
111
+ return selector;
112
+ }
113
+ catch {
114
+ // Selector syntax may be invalid — skip
115
+ }
116
+ }
117
+ return null;
118
+ }
119
+ // ─── Internal helpers ───────────────────────────────────────────────
120
+ /** Build a CSS path from element to a unique ancestor. */
121
+ async function buildCssPath(element) {
122
+ try {
123
+ return await element.evaluate((node) => {
124
+ const parts = [];
125
+ let current = node;
126
+ const maxDepth = 5;
127
+ let depth = 0;
128
+ while (current && current !== document.body && depth < maxDepth) {
129
+ let part = current.tagName.toLowerCase();
130
+ if (current.id) {
131
+ // CSS.escape keeps the selector valid (and injection-safe) for ids
132
+ // containing quotes/brackets/etc. from untrusted crawled pages.
133
+ part = `#${CSS.escape(current.id)}`;
134
+ parts.unshift(part);
135
+ break;
136
+ }
137
+ const parent = current.parentElement;
138
+ if (parent) {
139
+ const siblings = Array.from(parent.children).filter((c) => c.tagName === current.tagName);
140
+ if (siblings.length > 1) {
141
+ const index = siblings.indexOf(current) + 1;
142
+ part += `:nth-of-type(${index})`;
143
+ }
144
+ }
145
+ parts.unshift(part);
146
+ current = parent;
147
+ depth++;
148
+ }
149
+ return parts.join(' > ');
150
+ });
151
+ }
152
+ catch {
153
+ return null;
154
+ }
155
+ }
156
+ /** Build an XPath for the element (absolute fallback). */
157
+ async function buildXPath(element) {
158
+ try {
159
+ return await element.evaluate((node) => {
160
+ const parts = [];
161
+ let current = node;
162
+ while (current && current.nodeType === Node.ELEMENT_NODE) {
163
+ let index = 1;
164
+ let sibling = current.previousElementSibling;
165
+ while (sibling) {
166
+ if (sibling.tagName === current.tagName)
167
+ index++;
168
+ sibling = sibling.previousElementSibling;
169
+ }
170
+ parts.unshift(`${current.tagName.toLowerCase()}[${index}]`);
171
+ current = current.parentElement;
172
+ }
173
+ return '/' + parts.join('/');
174
+ });
175
+ }
176
+ catch {
177
+ return '//body';
178
+ }
179
+ }
180
+ /** Check if an ID looks auto-generated (random hash, UUID-like, etc.) */
181
+ function looksAutoGenerated(id) {
182
+ // Random hex strings (8+ chars of only hex)
183
+ if (/^[a-f0-9]{8,}$/i.test(id))
184
+ return true;
185
+ // React-style IDs like :r0:, :r1a:
186
+ if (/^:r[a-z0-9]+:$/.test(id))
187
+ return true;
188
+ // Contains UUID-like segments
189
+ if (/[a-f0-9]{8}-[a-f0-9]{4}/.test(id))
190
+ return true;
191
+ // Very long IDs are usually auto-generated
192
+ if (id.length > 40)
193
+ return true;
194
+ return false;
195
+ }
196
+ /** Escape a string for use in CSS selectors. */
197
+ function cssEscape(str) {
198
+ return str.replace(/["'\\[\](){}:.,!#$%^&*+=|~<>?/;@]/g, '\\$&');
199
+ }
200
+ //# sourceMappingURL=selector-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector-builder.js","sourceRoot":"","sources":["../../src/analyzer/selector-builder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * LLM-powered semantic analysis of crawled pages.
3
+ *
4
+ * Sends page structure to Claude (Haiku for cost efficiency) and infers
5
+ * human-readable semantic names and descriptions for forms, buttons, and links.
6
+ */
7
+ import type { PageDescriptor } from '../types/site.js';
8
+ /**
9
+ * Enrich PageDescriptors with LLM-inferred semantic names and descriptions.
10
+ *
11
+ * When ANTHROPIC_API_KEY is not set, returns pages unchanged with a warning.
12
+ */
13
+ export declare function analyzeSemantics(pages: PageDescriptor[], _screenshots?: Map<string, Buffer>): Promise<PageDescriptor[]>;
14
+ //# sourceMappingURL=semantic-analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semantic-analyzer.d.ts","sourceRoot":"","sources":["../../src/analyzer/semantic-analyzer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AA6DvD;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,cAAc,EAAE,EACvB,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC,OAAO,CAAC,cAAc,EAAE,CAAC,CAgJ3B"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * LLM-powered semantic analysis of crawled pages.
3
+ *
4
+ * Sends page structure to Claude (Haiku for cost efficiency) and infers
5
+ * human-readable semantic names and descriptions for forms, buttons, and links.
6
+ */
7
+ import Anthropic from '@anthropic-ai/sdk';
8
+ import { logger } from '../utils/logger.js';
9
+ const MODEL = 'claude-haiku-4-5-20251001';
10
+ const MAX_TOKENS = 4096;
11
+ /**
12
+ * Enrich PageDescriptors with LLM-inferred semantic names and descriptions.
13
+ *
14
+ * When ANTHROPIC_API_KEY is not set, returns pages unchanged with a warning.
15
+ */
16
+ export async function analyzeSemantics(pages, _screenshots) {
17
+ const apiKey = process.env.ANTHROPIC_API_KEY;
18
+ if (!apiKey) {
19
+ logger.warn('ANTHROPIC_API_KEY not set — skipping semantic analysis');
20
+ return pages;
21
+ }
22
+ const client = new Anthropic({ apiKey });
23
+ // Build compact page summaries for the prompt
24
+ const summaries = pages.map((page, pageIndex) => ({
25
+ pageIndex,
26
+ url: page.url,
27
+ title: page.title,
28
+ forms: page.forms.map((form, formIndex) => ({
29
+ formIndex,
30
+ formId: form.formId,
31
+ method: form.method,
32
+ action: form.action,
33
+ fieldNames: form.fields.map((f) => f.name),
34
+ fieldTypes: form.fields.map((f) => f.fieldType),
35
+ labels: form.fields.map((f) => f.label ?? f.name),
36
+ submitText: form.submitButton?.humanLabel,
37
+ })),
38
+ buttons: page.buttons.map((btn, buttonIndex) => ({
39
+ buttonIndex,
40
+ buttonId: btn.buttonId,
41
+ text: btn.text,
42
+ ariaLabel: btn.ariaLabel,
43
+ type: btn.type,
44
+ href: btn.href,
45
+ })),
46
+ links: page.links.map((link, linkIndex) => ({
47
+ linkIndex,
48
+ linkId: link.linkId,
49
+ text: link.text,
50
+ href: link.href,
51
+ isNavigation: link.isNavigation,
52
+ })),
53
+ }));
54
+ const prompt = `You are analyzing a website's interactive elements to generate MCP tool names. Given the page structure below, infer semantic names and descriptions for each element.
55
+
56
+ Rules:
57
+ - Form semanticName: snake_case, e.g. "login_form", "search_form", "checkout_form"
58
+ - Button semanticAction: snake_case verb, e.g. "add_to_cart", "submit_order", "toggle_menu"
59
+ - Link semanticAction: snake_case, e.g. "navigate_to_checkout", "view_product_details"
60
+ - Page semanticName: snake_case, e.g. "login_page", "product_listing"
61
+ - Descriptions: one sentence explaining what the element does
62
+ - Only output valid JSON matching the schema, no explanation
63
+
64
+ Schema:
65
+ {
66
+ "pages": [{
67
+ "pageIndex": number,
68
+ "semanticName": string,
69
+ "description": string,
70
+ "forms": [{ "formIndex": number, "semanticName": string, "description": string }],
71
+ "buttons": [{ "buttonIndex": number, "semanticAction": string, "description": string }],
72
+ "links": [{ "linkIndex": number, "semanticAction": string }]
73
+ }]
74
+ }
75
+
76
+ Pages:
77
+ ${JSON.stringify(summaries, null, 2)}`;
78
+ try {
79
+ logger.info('Analyzing page semantics with LLM...');
80
+ const message = await client.messages.create({
81
+ model: MODEL,
82
+ max_tokens: MAX_TOKENS,
83
+ messages: [{ role: 'user', content: prompt }],
84
+ });
85
+ const content = message.content[0];
86
+ if (content.type !== 'text')
87
+ return pages;
88
+ let json = content.text.trim();
89
+ if (json.startsWith('```')) {
90
+ json = json.replace(/^```(?:json)?\n?/, '').replace(/\n?```$/, '');
91
+ }
92
+ const result = JSON.parse(json);
93
+ // Apply inferred names back to the page descriptors
94
+ const enriched = pages.map((page, pageIndex) => {
95
+ const pageResult = result.pages.find((p) => p.pageIndex === pageIndex);
96
+ if (!pageResult)
97
+ return page;
98
+ const updatedPage = {
99
+ ...page,
100
+ semanticName: pageResult.semanticName ?? page.semanticName,
101
+ description: pageResult.description ?? page.description,
102
+ };
103
+ updatedPage.forms = page.forms.map((form, formIndex) => {
104
+ const formResult = pageResult.forms.find((f) => f.formIndex === formIndex);
105
+ if (!formResult)
106
+ return form;
107
+ return {
108
+ ...form,
109
+ semanticName: formResult.semanticName,
110
+ description: formResult.description,
111
+ };
112
+ });
113
+ updatedPage.buttons = page.buttons.map((btn, buttonIndex) => {
114
+ const btnResult = pageResult.buttons.find((b) => b.buttonIndex === buttonIndex);
115
+ if (!btnResult)
116
+ return btn;
117
+ return {
118
+ ...btn,
119
+ semanticAction: btnResult.semanticAction,
120
+ description: btnResult.description,
121
+ };
122
+ });
123
+ updatedPage.links = page.links.map((link, linkIndex) => {
124
+ const linkResult = pageResult.links.find((l) => l.linkIndex === linkIndex);
125
+ if (!linkResult)
126
+ return link;
127
+ return {
128
+ ...link,
129
+ semanticAction: linkResult.semanticAction,
130
+ };
131
+ });
132
+ return updatedPage;
133
+ });
134
+ const totalNamed = enriched.reduce((n, p) => n +
135
+ p.forms.filter((f) => f.semanticName).length +
136
+ p.buttons.filter((b) => b.semanticAction).length +
137
+ p.links.filter((l) => l.semanticAction).length, 0);
138
+ logger.info(`Semantic analysis complete: ${totalNamed} elements named`);
139
+ return enriched;
140
+ }
141
+ catch (err) {
142
+ logger.warn(`Semantic analysis failed, using defaults: ${err instanceof Error ? err.message : err}`);
143
+ return pages;
144
+ }
145
+ }
146
+ //# sourceMappingURL=semantic-analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semantic-analyzer.js","sourceRoot":"","sources":["../../src/analyzer/semantic-analyzer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAE1C,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAyD5C,MAAM,KAAK,GAAG,2BAA2B,CAAC;AAC1C,MAAM,UAAU,GAAG,IAAI,CAAC;AAExB;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,KAAuB,EACvB,YAAkC;IAElC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IAC7C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,CAAC,IAAI,CAAC,wDAAwD,CAAC,CAAC;QACtE,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAEzC,8CAA8C;IAC9C,MAAM,SAAS,GAAkB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;QAC/D,SAAS;QACT,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;YAC1C,SAAS;YACT,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;YAC1C,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;YAC/C,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC;YACjD,UAAU,EAAE,IAAI,CAAC,YAAY,EAAE,UAAU;SAC1C,CAAC,CAAC;QACH,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC;YAC/C,WAAW;YACX,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,SAAS,EAAE,GAAG,CAAC,SAAS;YACxB,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,IAAI,EAAE,GAAG,CAAC,IAAI;SACf,CAAC,CAAC;QACH,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;YAC1C,SAAS;YACT,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,YAAY,EAAE,IAAI,CAAC,YAAY;SAChC,CAAC,CAAC;KACJ,CAAC,CAAC,CAAC;IAEJ,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;EAuBf,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;IAErC,IAAI,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC;QACpD,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC3C,KAAK,EAAE,KAAK;YACZ,UAAU,EAAE,UAAU;YACtB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;SAC9C,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACnC,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM;YAAE,OAAO,KAAK,CAAC;QAE1C,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/B,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;QACrE,CAAC;QAED,MAAM,MAAM,GAAmB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAEhD,oDAAoD;QACpD,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE;YAC7C,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC;YACvE,IAAI,CAAC,UAAU;gBAAE,OAAO,IAAI,CAAC;YAE7B,MAAM,WAAW,GAAmB;gBAClC,GAAG,IAAI;gBACP,YAAY,EAAE,UAAU,CAAC,YAAY,IAAI,IAAI,CAAC,YAAY;gBAC1D,WAAW,EAAE,UAAU,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW;aACxD,CAAC;YAEF,WAAW,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE;gBACrD,MAAM,UAAU,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC;gBAC3E,IAAI,CAAC,UAAU;oBAAE,OAAO,IAAI,CAAC;gBAC7B,OAAO;oBACL,GAAG,IAAI;oBACP,YAAY,EAAE,UAAU,CAAC,YAAY;oBACrC,WAAW,EAAE,UAAU,CAAC,WAAW;iBACpC,CAAC;YACJ,CAAC,CAAC,CAAC;YAEH,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,WAAW,EAAE,EAAE;gBAC1D,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,KAAK,WAAW,CAAC,CAAC;gBAChF,IAAI,CAAC,SAAS;oBAAE,OAAO,GAAG,CAAC;gBAC3B,OAAO;oBACL,GAAG,GAAG;oBACN,cAAc,EAAE,SAAS,CAAC,cAAc;oBACxC,WAAW,EAAE,SAAS,CAAC,WAAW;iBACnC,CAAC;YACJ,CAAC,CAAC,CAAC;YAEH,WAAW,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE;gBACrD,MAAM,UAAU,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC;gBAC3E,IAAI,CAAC,UAAU;oBAAE,OAAO,IAAI,CAAC;gBAC7B,OAAO;oBACL,GAAG,IAAI;oBACP,cAAc,EAAE,UAAU,CAAC,cAAc;iBAC1C,CAAC;YACJ,CAAC,CAAC,CAAC;YAEH,OAAO,WAAW,CAAC;QACrB,CAAC,CAAC,CAAC;QAEH,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAChC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACP,CAAC;YACD,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,MAAM;YAC5C,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,MAAM;YAChD,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,MAAM,EAChD,CAAC,CACF,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,+BAA+B,UAAU,iBAAiB,CAAC,CAAC;QAExE,OAAO,QAAQ,CAAC;IAClB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,IAAI,CACT,6CAA6C,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,CACxF,CAAC;QACF,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Crawls a website using Playwright, visiting pages breadth-first
3
+ * and extracting interactive elements from each page.
4
+ */
5
+ import type { Entry } from 'har-format';
6
+ import type { SiteDescriptor } from '../types/site.js';
7
+ export interface CrawlOptions {
8
+ /** Target URL to start crawling from */
9
+ url: string;
10
+ /** Maximum crawl depth (default: 2) */
11
+ depth?: number;
12
+ /** Maximum number of pages to visit (default: 20) */
13
+ maxPages?: number;
14
+ /** Idle timeout in ms before auto-closing (default: 5 min) */
15
+ timeout?: number;
16
+ /** Run browser in headless mode (default: false) */
17
+ headless?: boolean;
18
+ /** Viewport dimensions */
19
+ viewport?: {
20
+ width: number;
21
+ height: number;
22
+ };
23
+ /** Whether to capture screenshots during analysis (default: true) */
24
+ captureScreenshots?: boolean;
25
+ /** Whether to capture HAR entries during crawl (for hybrid mode) */
26
+ captureHar?: boolean;
27
+ }
28
+ export interface CrawlResult {
29
+ siteDescriptor: SiteDescriptor;
30
+ /** Screenshots keyed by pageId */
31
+ screenshots: Map<string, Buffer>;
32
+ /** HAR entries captured during the crawl (only when captureHar is true) */
33
+ harEntries?: Entry[];
34
+ }
35
+ /**
36
+ * Crawl a website and return a SiteDescriptor with all discovered pages.
37
+ */
38
+ export declare function crawlSite(options: CrawlOptions): Promise<CrawlResult>;
39
+ //# sourceMappingURL=site-crawler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"site-crawler.d.ts","sourceRoot":"","sources":["../../src/analyzer/site-crawler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,KAAK,EAAU,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,cAAc,EAAkB,MAAM,kBAAkB,CAAC;AAMvE,MAAM,WAAW,YAAY;IAC3B,wCAAwC;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,0BAA0B;IAC1B,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,qEAAqE;IACrE,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,oEAAoE;IACpE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,cAAc,CAAC;IAC/B,kCAAkC;IAClC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,2EAA2E;IAC3E,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC;CACtB;AAOD;;GAEG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA2I3E"}
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Crawls a website using Playwright, visiting pages breadth-first
3
+ * and extracting interactive elements from each page.
4
+ */
5
+ import { chromium } from 'playwright';
6
+ import { parsePage } from './dom-parser.js';
7
+ import { captureViewportScreenshot } from './screenshot-capture.js';
8
+ import { logger } from '../utils/logger.js';
9
+ import crypto from 'node:crypto';
10
+ const DEFAULT_DEPTH = 2;
11
+ const DEFAULT_MAX_PAGES = 20;
12
+ const DEFAULT_TIMEOUT = 5 * 60 * 1000;
13
+ const DEFAULT_VIEWPORT = { width: 1280, height: 720 };
14
+ /**
15
+ * Crawl a website and return a SiteDescriptor with all discovered pages.
16
+ */
17
+ export async function crawlSite(options) {
18
+ const depth = options.depth ?? DEFAULT_DEPTH;
19
+ const maxPages = options.maxPages ?? DEFAULT_MAX_PAGES;
20
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT;
21
+ const viewport = options.viewport ?? DEFAULT_VIEWPORT;
22
+ const captureScreenshots = options.captureScreenshots ?? true;
23
+ const parsedUrl = new URL(options.url);
24
+ if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
25
+ throw new Error('Only http/https URLs are supported');
26
+ }
27
+ const baseUrl = `${parsedUrl.protocol}//${parsedUrl.host}`;
28
+ const captureHar = options.captureHar ?? false;
29
+ const pages = [];
30
+ const screenshots = new Map();
31
+ const harEntries = [];
32
+ const pendingRequests = new Map();
33
+ const visited = new Set();
34
+ const queue = [
35
+ { url: options.url, currentDepth: 0 },
36
+ ];
37
+ let browser;
38
+ try {
39
+ browser = await chromium.launch({ headless: options.headless ?? false });
40
+ const context = await browser.newContext({ viewport });
41
+ const page = await context.newPage();
42
+ // Optionally capture network requests as HAR entries during crawl
43
+ if (captureHar) {
44
+ page.on('request', (request) => {
45
+ pendingRequests.set(request, { startTime: Date.now() });
46
+ });
47
+ page.on('response', async (response) => {
48
+ const request = response.request();
49
+ const pending = pendingRequests.get(request);
50
+ if (!pending)
51
+ return;
52
+ pendingRequests.delete(request);
53
+ try {
54
+ const entry = await buildCrawlHarEntry(request, response, pending.startTime);
55
+ harEntries.push(entry);
56
+ }
57
+ catch {
58
+ // Some responses can't be read (redirects, aborted)
59
+ }
60
+ });
61
+ }
62
+ logger.info(`Crawling site: ${options.url} (depth: ${depth}, max pages: ${maxPages})`);
63
+ let lastActivityTime = Date.now();
64
+ while (queue.length > 0 && pages.length < maxPages) {
65
+ const item = queue.shift();
66
+ const normalizedUrl = normalizeUrl(item.url);
67
+ // Skip if already visited or different origin
68
+ if (visited.has(normalizedUrl))
69
+ continue;
70
+ if (!item.url.startsWith(baseUrl))
71
+ continue;
72
+ visited.add(normalizedUrl);
73
+ // Check idle timeout
74
+ if (Date.now() - lastActivityTime > timeout) {
75
+ logger.warn('Idle timeout reached during crawl');
76
+ break;
77
+ }
78
+ try {
79
+ logger.info(`[${pages.length + 1}/${maxPages}] Visiting: ${item.url}`);
80
+ await page.goto(item.url, {
81
+ waitUntil: 'domcontentloaded',
82
+ timeout: 15_000,
83
+ });
84
+ // Wait briefly for dynamic content to render
85
+ await page.waitForTimeout(1000);
86
+ lastActivityTime = Date.now();
87
+ // Parse the page DOM
88
+ const pageDescriptor = await parsePage(page);
89
+ pageDescriptor.url = item.url;
90
+ // Capture screenshot if enabled
91
+ if (captureScreenshots) {
92
+ const screenshot = await captureViewportScreenshot(page);
93
+ pageDescriptor.screenshotHash = screenshot.hash;
94
+ screenshots.set(pageDescriptor.pageId, screenshot.data);
95
+ }
96
+ pages.push(pageDescriptor);
97
+ // Queue navigation links for further crawling
98
+ if (item.currentDepth < depth) {
99
+ for (const link of pageDescriptor.links) {
100
+ if (link.isNavigation && !visited.has(normalizeUrl(link.href))) {
101
+ queue.push({ url: link.href, currentDepth: item.currentDepth + 1 });
102
+ }
103
+ }
104
+ }
105
+ }
106
+ catch (err) {
107
+ logger.warn(`Failed to crawl ${item.url}: ${err}`);
108
+ }
109
+ }
110
+ // Close browser
111
+ await browser.close().catch(() => { });
112
+ browser = undefined;
113
+ }
114
+ catch (error) {
115
+ if (browser)
116
+ await browser.close().catch(() => { });
117
+ throw error;
118
+ }
119
+ logger.info(`Crawl complete: ${pages.length} pages discovered`);
120
+ // Extract site metadata from the first page
121
+ const metadata = {
122
+ title: pages[0]?.title,
123
+ description: undefined,
124
+ favicon: undefined,
125
+ };
126
+ const siteDescriptor = {
127
+ siteId: generateSiteId(baseUrl),
128
+ baseUrl,
129
+ pages,
130
+ analyzedAt: new Date().toISOString(),
131
+ version: 1,
132
+ crawlDepth: depth,
133
+ metadata,
134
+ };
135
+ return {
136
+ siteDescriptor,
137
+ screenshots,
138
+ ...(captureHar ? { harEntries } : {}),
139
+ };
140
+ }
141
+ // ─── HAR Capture (for hybrid mode) ─────────────────────────────────
142
+ const MAX_RESPONSE_BODY_BYTES = 5 * 1024 * 1024;
143
+ const SKIP_BODY_MIME_TYPES = ['image/', 'video/', 'audio/', 'font/', 'application/octet-stream'];
144
+ async function buildCrawlHarEntry(request, response, startTime) {
145
+ const elapsed = Date.now() - startTime;
146
+ const url = request.url();
147
+ const parsedUrl = new URL(url);
148
+ const requestHeaders = Object.entries(request.headers()).map(([name, value]) => ({
149
+ name,
150
+ value,
151
+ }));
152
+ const queryString = [...parsedUrl.searchParams.entries()].map(([name, value]) => ({
153
+ name,
154
+ value,
155
+ }));
156
+ const postData = request.postData();
157
+ const contentTypeHeader = request.headers()['content-type'] ?? '';
158
+ const responseHeaders = Object.entries(response.headers()).map(([name, value]) => ({
159
+ name,
160
+ value,
161
+ }));
162
+ let responseText;
163
+ const responseMimeType = response.headers()['content-type'] ?? '';
164
+ const skipBody = SKIP_BODY_MIME_TYPES.some((m) => responseMimeType.startsWith(m));
165
+ const contentLength = parseInt(response.headers()['content-length'] ?? '0', 10);
166
+ if (!skipBody && contentLength <= MAX_RESPONSE_BODY_BYTES) {
167
+ try {
168
+ const body = await response.body();
169
+ if (body.length <= MAX_RESPONSE_BODY_BYTES) {
170
+ responseText = body.toString('utf-8');
171
+ }
172
+ }
173
+ catch {
174
+ // Body may not be available
175
+ }
176
+ }
177
+ return {
178
+ startedDateTime: new Date(startTime).toISOString(),
179
+ time: elapsed,
180
+ request: {
181
+ method: request.method(),
182
+ url,
183
+ httpVersion: 'HTTP/1.1',
184
+ headers: requestHeaders,
185
+ queryString,
186
+ cookies: [],
187
+ headersSize: -1,
188
+ bodySize: postData ? Buffer.byteLength(postData) : 0,
189
+ ...(postData
190
+ ? {
191
+ postData: {
192
+ mimeType: contentTypeHeader.split(';')[0].trim() || 'application/octet-stream',
193
+ text: postData,
194
+ },
195
+ }
196
+ : {}),
197
+ },
198
+ response: {
199
+ status: response.status(),
200
+ statusText: response.statusText(),
201
+ httpVersion: 'HTTP/1.1',
202
+ headers: responseHeaders,
203
+ cookies: [],
204
+ content: {
205
+ size: responseText ? Buffer.byteLength(responseText) : 0,
206
+ mimeType: responseMimeType.split(';')[0].trim() || 'application/octet-stream',
207
+ ...(responseText ? { text: responseText } : {}),
208
+ },
209
+ redirectURL: '',
210
+ headersSize: -1,
211
+ bodySize: responseText ? Buffer.byteLength(responseText) : 0,
212
+ },
213
+ cache: {},
214
+ timings: {
215
+ send: 1,
216
+ wait: Math.max(1, elapsed - 2),
217
+ receive: 1,
218
+ },
219
+ };
220
+ }
221
+ // ─── Helpers ────────────────────────────────────────────────────────
222
+ function normalizeUrl(url) {
223
+ try {
224
+ const parsed = new URL(url);
225
+ // Remove trailing slash, fragment, and normalize
226
+ return `${parsed.origin}${parsed.pathname.replace(/\/$/, '')}${parsed.search}`;
227
+ }
228
+ catch {
229
+ return url;
230
+ }
231
+ }
232
+ function generateSiteId(baseUrl) {
233
+ const hash = crypto.createHash('sha256').update(baseUrl).digest('hex').slice(0, 12);
234
+ return `site_${hash}`;
235
+ }
236
+ //# sourceMappingURL=site-crawler.js.map