mcpmake 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (344) hide show
  1. package/README.md +691 -0
  2. package/bin/mcpmake.mjs +2 -0
  3. package/dist/analyzer/auth-detector.d.ts +12 -0
  4. package/dist/analyzer/auth-detector.js +142 -0
  5. package/dist/analyzer/dom-parser.d.ts +10 -0
  6. package/dist/analyzer/dom-parser.js +259 -0
  7. package/dist/analyzer/goal-crawler.d.ts +25 -0
  8. package/dist/analyzer/goal-crawler.js +177 -0
  9. package/dist/analyzer/hybrid-detector.d.ts +28 -0
  10. package/dist/analyzer/hybrid-detector.js +96 -0
  11. package/dist/analyzer/index.d.ts +12 -0
  12. package/dist/analyzer/index.js +8 -0
  13. package/dist/analyzer/screenshot-capture.d.ts +29 -0
  14. package/dist/analyzer/screenshot-capture.js +42 -0
  15. package/dist/analyzer/selector-builder.d.ts +19 -0
  16. package/dist/analyzer/selector-builder.js +199 -0
  17. package/dist/analyzer/semantic-analyzer.d.ts +13 -0
  18. package/dist/analyzer/semantic-analyzer.js +145 -0
  19. package/dist/analyzer/site-crawler.d.ts +38 -0
  20. package/dist/analyzer/site-crawler.js +235 -0
  21. package/dist/cloud/billing/billing-engine.d.ts +44 -0
  22. package/dist/cloud/billing/billing-engine.js +81 -0
  23. package/dist/cloud/billing/credit-store.d.ts +64 -0
  24. package/dist/cloud/billing/credit-store.js +168 -0
  25. package/dist/cloud/billing/index.d.ts +4 -0
  26. package/dist/cloud/billing/index.js +2 -0
  27. package/dist/cloud/billing/usage-store.d.ts +42 -0
  28. package/dist/cloud/billing/usage-store.js +85 -0
  29. package/dist/cloud/billing/usage-tracker.d.ts +38 -0
  30. package/dist/cloud/billing/usage-tracker.js +95 -0
  31. package/dist/cloud/build-pipeline.d.ts +39 -0
  32. package/dist/cloud/build-pipeline.js +310 -0
  33. package/dist/cloud/build-queue.d.ts +30 -0
  34. package/dist/cloud/build-queue.js +70 -0
  35. package/dist/cloud/caddy-manager.d.ts +18 -0
  36. package/dist/cloud/caddy-manager.js +97 -0
  37. package/dist/cloud/container-backend.d.ts +62 -0
  38. package/dist/cloud/container-backend.js +59 -0
  39. package/dist/cloud/container-manager.d.ts +64 -0
  40. package/dist/cloud/container-manager.js +301 -0
  41. package/dist/cloud/crypto.d.ts +27 -0
  42. package/dist/cloud/crypto.js +63 -0
  43. package/dist/cloud/db/index.d.ts +27 -0
  44. package/dist/cloud/db/index.js +53 -0
  45. package/dist/cloud/db/migrations.d.ts +12 -0
  46. package/dist/cloud/db/migrations.js +329 -0
  47. package/dist/cloud/db/pg-store.d.ts +45 -0
  48. package/dist/cloud/db/pg-store.js +336 -0
  49. package/dist/cloud/failure-tracker.d.ts +51 -0
  50. package/dist/cloud/failure-tracker.js +102 -0
  51. package/dist/cloud/idle-monitor.d.ts +30 -0
  52. package/dist/cloud/idle-monitor.js +70 -0
  53. package/dist/cloud/mailer.d.ts +21 -0
  54. package/dist/cloud/mailer.js +193 -0
  55. package/dist/cloud/mcp-proxy.d.ts +58 -0
  56. package/dist/cloud/mcp-proxy.js +203 -0
  57. package/dist/cloud/metric-samples.d.ts +43 -0
  58. package/dist/cloud/metric-samples.js +85 -0
  59. package/dist/cloud/metrics.d.ts +26 -0
  60. package/dist/cloud/metrics.js +59 -0
  61. package/dist/cloud/multipart.d.ts +26 -0
  62. package/dist/cloud/multipart.js +132 -0
  63. package/dist/cloud/observability.d.ts +27 -0
  64. package/dist/cloud/observability.js +98 -0
  65. package/dist/cloud/rate-limiter.d.ts +31 -0
  66. package/dist/cloud/rate-limiter.js +58 -0
  67. package/dist/cloud/request-security.d.ts +5 -0
  68. package/dist/cloud/request-security.js +74 -0
  69. package/dist/cloud/resource-monitor.d.ts +69 -0
  70. package/dist/cloud/resource-monitor.js +130 -0
  71. package/dist/cloud/secret-store.d.ts +38 -0
  72. package/dist/cloud/secret-store.js +103 -0
  73. package/dist/cloud/security.d.ts +26 -0
  74. package/dist/cloud/security.js +142 -0
  75. package/dist/cloud/server.d.ts +21 -0
  76. package/dist/cloud/server.js +1079 -0
  77. package/dist/cloud/shared-state.d.ts +72 -0
  78. package/dist/cloud/shared-state.js +159 -0
  79. package/dist/cloud/ssrf.d.ts +43 -0
  80. package/dist/cloud/ssrf.js +150 -0
  81. package/dist/cloud/store.d.ts +41 -0
  82. package/dist/cloud/store.js +75 -0
  83. package/dist/cloud/stripe.d.ts +78 -0
  84. package/dist/cloud/stripe.js +317 -0
  85. package/dist/cloud/telemetry-store.d.ts +53 -0
  86. package/dist/cloud/telemetry-store.js +108 -0
  87. package/dist/cloud/web/auth.d.ts +225 -0
  88. package/dist/cloud/web/auth.js +555 -0
  89. package/dist/cloud/web/charts.d.ts +70 -0
  90. package/dist/cloud/web/charts.js +178 -0
  91. package/dist/cloud/web/csrf.d.ts +14 -0
  92. package/dist/cloud/web/csrf.js +22 -0
  93. package/dist/cloud/web/docs.d.ts +40 -0
  94. package/dist/cloud/web/docs.js +174 -0
  95. package/dist/cloud/web/router.d.ts +25 -0
  96. package/dist/cloud/web/router.js +1921 -0
  97. package/dist/cloud/web/static/alpine.min.js +5 -0
  98. package/dist/cloud/web/static/favicon.svg +4 -0
  99. package/dist/cloud/web/static/htmx-sse.js +290 -0
  100. package/dist/cloud/web/static/htmx.min.js +1 -0
  101. package/dist/cloud/web/static/style.css +2683 -0
  102. package/dist/cloud/web/static-server.d.ts +13 -0
  103. package/dist/cloud/web/static-server.js +73 -0
  104. package/dist/cloud/web/template-engine.d.ts +27 -0
  105. package/dist/cloud/web/template-engine.js +146 -0
  106. package/dist/cloud/web/templates/layouts/admin.hbs +57 -0
  107. package/dist/cloud/web/templates/layouts/auth.hbs +138 -0
  108. package/dist/cloud/web/templates/layouts/base.hbs +16 -0
  109. package/dist/cloud/web/templates/layouts/dashboard.hbs +39 -0
  110. package/dist/cloud/web/templates/layouts/landing.hbs +82 -0
  111. package/dist/cloud/web/templates/pages/admin/overview.hbs +123 -0
  112. package/dist/cloud/web/templates/pages/admin/servers.hbs +129 -0
  113. package/dist/cloud/web/templates/pages/admin/telemetry.hbs +39 -0
  114. package/dist/cloud/web/templates/pages/admin/user-edit.hbs +91 -0
  115. package/dist/cloud/web/templates/pages/admin/users.hbs +179 -0
  116. package/dist/cloud/web/templates/pages/auth/forgot-password.hbs +25 -0
  117. package/dist/cloud/web/templates/pages/auth/login.hbs +33 -0
  118. package/dist/cloud/web/templates/pages/auth/register.hbs +32 -0
  119. package/dist/cloud/web/templates/pages/auth/reset-password.hbs +34 -0
  120. package/dist/cloud/web/templates/pages/dashboard/billing.hbs +140 -0
  121. package/dist/cloud/web/templates/pages/dashboard/create.hbs +173 -0
  122. package/dist/cloud/web/templates/pages/dashboard/index.hbs +8 -0
  123. package/dist/cloud/web/templates/pages/dashboard/server-detail.hbs +280 -0
  124. package/dist/cloud/web/templates/pages/dashboard/server-logs.hbs +35 -0
  125. package/dist/cloud/web/templates/pages/dashboard/server-metrics.hbs +63 -0
  126. package/dist/cloud/web/templates/pages/dashboard/servers-partial.hbs +21 -0
  127. package/dist/cloud/web/templates/pages/dashboard/servers.hbs +44 -0
  128. package/dist/cloud/web/templates/pages/docs/show.hbs +16 -0
  129. package/dist/cloud/web/templates/pages/errors/404.hbs +9 -0
  130. package/dist/cloud/web/templates/pages/errors/500.hbs +8 -0
  131. package/dist/cloud/web/templates/pages/landing/index.hbs +223 -0
  132. package/dist/cloud/web/templates/pages/legal/privacy.hbs +71 -0
  133. package/dist/cloud/web/templates/pages/legal/terms.hbs +73 -0
  134. package/dist/cloud/web/templates/partials/admin-stats.hbs +52 -0
  135. package/dist/cloud/web/templates/partials/flash-message.hbs +6 -0
  136. package/dist/cloud/web/templates/partials/pricing-table.hbs +103 -0
  137. package/dist/cloud/web/templates/partials/server-card.hbs +19 -0
  138. package/dist/cloud/web/templates/partials/status-badge.hbs +1 -0
  139. package/dist/commands/bundle.d.ts +18 -0
  140. package/dist/commands/bundle.js +82 -0
  141. package/dist/commands/ci.d.ts +25 -0
  142. package/dist/commands/ci.js +149 -0
  143. package/dist/commands/deploy.d.ts +24 -0
  144. package/dist/commands/deploy.js +145 -0
  145. package/dist/commands/diff.d.ts +18 -0
  146. package/dist/commands/diff.js +185 -0
  147. package/dist/commands/from/describe.d.ts +65 -0
  148. package/dist/commands/from/describe.js +173 -0
  149. package/dist/commands/from/har.d.ts +81 -0
  150. package/dist/commands/from/har.js +255 -0
  151. package/dist/commands/from/openapi.d.ts +105 -0
  152. package/dist/commands/from/openapi.js +302 -0
  153. package/dist/commands/from/postman.d.ts +51 -0
  154. package/dist/commands/from/postman.js +146 -0
  155. package/dist/commands/from/target-support.d.ts +11 -0
  156. package/dist/commands/from/target-support.js +33 -0
  157. package/dist/commands/from/url.d.ts +75 -0
  158. package/dist/commands/from/url.js +244 -0
  159. package/dist/commands/from/website.d.ts +75 -0
  160. package/dist/commands/from/website.js +284 -0
  161. package/dist/commands/lint.d.ts +24 -0
  162. package/dist/commands/lint.js +184 -0
  163. package/dist/commands/merge.d.ts +18 -0
  164. package/dist/commands/merge.js +161 -0
  165. package/dist/commands/publish.d.ts +27 -0
  166. package/dist/commands/publish.js +334 -0
  167. package/dist/commands/rescan.d.ts +40 -0
  168. package/dist/commands/rescan.js +255 -0
  169. package/dist/commands/update.d.ts +14 -0
  170. package/dist/commands/update.js +87 -0
  171. package/dist/commands/verify.d.ts +14 -0
  172. package/dist/commands/verify.js +71 -0
  173. package/dist/config/configurable-command.d.ts +13 -0
  174. package/dist/config/configurable-command.js +70 -0
  175. package/dist/config/mcpmake-config.d.ts +68 -0
  176. package/dist/config/mcpmake-config.js +207 -0
  177. package/dist/docs/cli.md +400 -0
  178. package/dist/docs/mcp-2026-07-28-migration.md +78 -0
  179. package/dist/docs/migrate-from-stainless.md +94 -0
  180. package/dist/docs/quickstart.md +166 -0
  181. package/dist/docs/show-hn.md +26 -0
  182. package/dist/docs/website-servers.md +169 -0
  183. package/dist/emitter/code-writer.d.ts +8 -0
  184. package/dist/emitter/code-writer.js +25 -0
  185. package/dist/emitter/index.d.ts +32 -0
  186. package/dist/emitter/index.js +280 -0
  187. package/dist/emitter/mcpb-bundler.d.ts +31 -0
  188. package/dist/emitter/mcpb-bundler.js +172 -0
  189. package/dist/emitter/project-scaffolder.d.ts +4 -0
  190. package/dist/emitter/project-scaffolder.js +89 -0
  191. package/dist/emitter/python-template-loader.d.ts +4 -0
  192. package/dist/emitter/python-template-loader.js +30 -0
  193. package/dist/emitter/python-templates/dockerfile.hbs +14 -0
  194. package/dist/emitter/python-templates/env.example.hbs +6 -0
  195. package/dist/emitter/python-templates/requirements.txt.hbs +4 -0
  196. package/dist/emitter/python-templates/server.py.hbs +77 -0
  197. package/dist/emitter/site-scaffolder.d.ts +13 -0
  198. package/dist/emitter/site-scaffolder.js +70 -0
  199. package/dist/emitter/site-template-loader.d.ts +5 -0
  200. package/dist/emitter/site-template-loader.js +47 -0
  201. package/dist/emitter/site-templates/browser-manager.ts.hbs +233 -0
  202. package/dist/emitter/site-templates/config.ts.hbs +28 -0
  203. package/dist/emitter/site-templates/dockerfile.hbs +31 -0
  204. package/dist/emitter/site-templates/env.example.hbs +19 -0
  205. package/dist/emitter/site-templates/package.json.hbs +26 -0
  206. package/dist/emitter/site-templates/server-main-http.ts.hbs +108 -0
  207. package/dist/emitter/site-templates/server-main.ts.hbs +23 -0
  208. package/dist/emitter/site-templates/tool-handler-action.ts.hbs +86 -0
  209. package/dist/emitter/site-templates/tool-handler-form.ts.hbs +116 -0
  210. package/dist/emitter/site-templates/tool-handler-lifecycle.ts.hbs +146 -0
  211. package/dist/emitter/site-templates/tool-index.ts.hbs +11 -0
  212. package/dist/emitter/template-loader.d.ts +1 -0
  213. package/dist/emitter/template-loader.js +27 -0
  214. package/dist/emitter/templates/auth-provider.ts.hbs +57 -0
  215. package/dist/emitter/templates/config.ts.hbs +63 -0
  216. package/dist/emitter/templates/discovery.ts.hbs +301 -0
  217. package/dist/emitter/templates/dockerfile.hbs +34 -0
  218. package/dist/emitter/templates/env.example.hbs +28 -0
  219. package/dist/emitter/templates/gitignore.hbs +5 -0
  220. package/dist/emitter/templates/http-executor.ts.hbs +117 -0
  221. package/dist/emitter/templates/oauth.ts.hbs +188 -0
  222. package/dist/emitter/templates/package.json.hbs +25 -0
  223. package/dist/emitter/templates/prompts.ts.hbs +22 -0
  224. package/dist/emitter/templates/readme.md.hbs +123 -0
  225. package/dist/emitter/templates/resources.ts.hbs +63 -0
  226. package/dist/emitter/templates/server-main-http.ts.hbs +407 -0
  227. package/dist/emitter/templates/server-main.ts.hbs +40 -0
  228. package/dist/emitter/templates/task-handlers.ts.hbs +189 -0
  229. package/dist/emitter/templates/task-manager.ts.hbs +139 -0
  230. package/dist/emitter/templates/task-sse.ts.hbs +105 -0
  231. package/dist/emitter/templates/tool-handler.ts.hbs +124 -0
  232. package/dist/emitter/templates/tool-index.ts.hbs +11 -0
  233. package/dist/emitter/templates/tool-test.ts.hbs +57 -0
  234. package/dist/emitter/templates/trace.ts.hbs +79 -0
  235. package/dist/emitter/templates/tsconfig.json.hbs +16 -0
  236. package/dist/emitter/templates/types.ts.hbs +5 -0
  237. package/dist/emitter/worker-template-loader.d.ts +5 -0
  238. package/dist/emitter/worker-template-loader.js +33 -0
  239. package/dist/emitter/worker-templates/config.ts.hbs +54 -0
  240. package/dist/emitter/worker-templates/dev-vars.example.hbs +10 -0
  241. package/dist/emitter/worker-templates/gitignore.hbs +6 -0
  242. package/dist/emitter/worker-templates/package.json.hbs +24 -0
  243. package/dist/emitter/worker-templates/readme.md.hbs +53 -0
  244. package/dist/emitter/worker-templates/server.test.ts.hbs +20 -0
  245. package/dist/emitter/worker-templates/tool-handler.ts.hbs +85 -0
  246. package/dist/emitter/worker-templates/tool-index.ts.hbs +28 -0
  247. package/dist/emitter/worker-templates/tsconfig.json.hbs +17 -0
  248. package/dist/emitter/worker-templates/worker.ts.hbs +242 -0
  249. package/dist/emitter/worker-templates/wrangler.toml.hbs +19 -0
  250. package/dist/generator/spec-generator.d.ts +6 -0
  251. package/dist/generator/spec-generator.js +50 -0
  252. package/dist/index.d.ts +1 -0
  253. package/dist/index.js +64 -0
  254. package/dist/parser/har-filter.d.ts +8 -0
  255. package/dist/parser/har-filter.js +71 -0
  256. package/dist/parser/har-loader.d.ts +2 -0
  257. package/dist/parser/har-loader.js +14 -0
  258. package/dist/parser/har-normalizer.d.ts +20 -0
  259. package/dist/parser/har-normalizer.js +78 -0
  260. package/dist/parser/index.d.ts +10 -0
  261. package/dist/parser/index.js +6 -0
  262. package/dist/parser/openapi-loader.d.ts +6 -0
  263. package/dist/parser/openapi-loader.js +308 -0
  264. package/dist/parser/operation-extractor.d.ts +13 -0
  265. package/dist/parser/operation-extractor.js +155 -0
  266. package/dist/parser/overlay-loader.d.ts +10 -0
  267. package/dist/parser/overlay-loader.js +184 -0
  268. package/dist/parser/postman-loader.d.ts +9 -0
  269. package/dist/parser/postman-loader.js +106 -0
  270. package/dist/parser/schema-converter.d.ts +12 -0
  271. package/dist/parser/schema-converter.js +117 -0
  272. package/dist/plugins/adapter.d.ts +40 -0
  273. package/dist/plugins/adapter.js +15 -0
  274. package/dist/plugins/loader.d.ts +25 -0
  275. package/dist/plugins/loader.js +58 -0
  276. package/dist/pricing.d.ts +55 -0
  277. package/dist/pricing.js +133 -0
  278. package/dist/providers/index.d.ts +15 -0
  279. package/dist/providers/index.js +56 -0
  280. package/dist/recorder/browser-recorder.d.ts +22 -0
  281. package/dist/recorder/browser-recorder.js +205 -0
  282. package/dist/registry/official-registry.d.ts +90 -0
  283. package/dist/registry/official-registry.js +129 -0
  284. package/dist/rescan/diff-engine.d.ts +5 -0
  285. package/dist/rescan/diff-engine.js +312 -0
  286. package/dist/rescan/index.d.ts +3 -0
  287. package/dist/rescan/index.js +2 -0
  288. package/dist/rescan/rescan-runner.d.ts +42 -0
  289. package/dist/rescan/rescan-runner.js +69 -0
  290. package/dist/rescan/rescan-scheduler.d.ts +41 -0
  291. package/dist/rescan/rescan-scheduler.js +179 -0
  292. package/dist/site-transformer/browser-tools.d.ts +10 -0
  293. package/dist/site-transformer/browser-tools.js +59 -0
  294. package/dist/site-transformer/index.d.ts +2 -0
  295. package/dist/site-transformer/index.js +2 -0
  296. package/dist/site-transformer/selector-healer.d.ts +8 -0
  297. package/dist/site-transformer/selector-healer.js +106 -0
  298. package/dist/site-transformer/tool-generator.d.ts +13 -0
  299. package/dist/site-transformer/tool-generator.js +245 -0
  300. package/dist/transformer/auth-detector.d.ts +13 -0
  301. package/dist/transformer/auth-detector.js +90 -0
  302. package/dist/transformer/catalog-builder.d.ts +18 -0
  303. package/dist/transformer/catalog-builder.js +56 -0
  304. package/dist/transformer/client-compat.d.ts +6 -0
  305. package/dist/transformer/client-compat.js +44 -0
  306. package/dist/transformer/har-clusterer.d.ts +9 -0
  307. package/dist/transformer/har-clusterer.js +27 -0
  308. package/dist/transformer/har-dedup.d.ts +10 -0
  309. package/dist/transformer/har-dedup.js +81 -0
  310. package/dist/transformer/har-schema-inferrer.d.ts +15 -0
  311. package/dist/transformer/har-schema-inferrer.js +90 -0
  312. package/dist/transformer/har-to-operations.d.ts +13 -0
  313. package/dist/transformer/har-to-operations.js +192 -0
  314. package/dist/transformer/index.d.ts +8 -0
  315. package/dist/transformer/index.js +6 -0
  316. package/dist/transformer/llm-namer.d.ts +6 -0
  317. package/dist/transformer/llm-namer.js +59 -0
  318. package/dist/transformer/naming.d.ts +4 -0
  319. package/dist/transformer/naming.js +30 -0
  320. package/dist/transformer/operation-filter.d.ts +13 -0
  321. package/dist/transformer/operation-filter.js +52 -0
  322. package/dist/transformer/resource-builder.d.ts +12 -0
  323. package/dist/transformer/resource-builder.js +80 -0
  324. package/dist/transformer/schema-merger.d.ts +14 -0
  325. package/dist/transformer/schema-merger.js +65 -0
  326. package/dist/transformer/tool-builder.d.ts +3 -0
  327. package/dist/transformer/tool-builder.js +114 -0
  328. package/dist/types/index.d.ts +131 -0
  329. package/dist/types/index.js +1 -0
  330. package/dist/types/site.d.ts +284 -0
  331. package/dist/types/site.js +8 -0
  332. package/dist/utils/fail.d.ts +48 -0
  333. package/dist/utils/fail.js +204 -0
  334. package/dist/utils/fs.d.ts +5 -0
  335. package/dist/utils/fs.js +28 -0
  336. package/dist/utils/interactive.d.ts +6 -0
  337. package/dist/utils/interactive.js +30 -0
  338. package/dist/utils/logger.d.ts +1 -0
  339. package/dist/utils/logger.js +2 -0
  340. package/dist/utils/sanitize.d.ts +28 -0
  341. package/dist/utils/sanitize.js +44 -0
  342. package/dist/utils/watcher.d.ts +11 -0
  343. package/dist/utils/watcher.js +36 -0
  344. package/package.json +65 -0
@@ -0,0 +1,27 @@
1
+ declare const _default: import("citty").CommandDef<{
2
+ directory: {
3
+ type: "positional";
4
+ description: string;
5
+ required: true;
6
+ };
7
+ registry: {
8
+ type: "string";
9
+ alias: string;
10
+ description: string;
11
+ };
12
+ name: {
13
+ type: "string";
14
+ alias: string;
15
+ description: string;
16
+ };
17
+ 'remote-url': {
18
+ type: "string";
19
+ description: string;
20
+ };
21
+ push: {
22
+ type: "boolean";
23
+ description: string;
24
+ default: false;
25
+ };
26
+ }>;
27
+ export default _default;
@@ -0,0 +1,334 @@
1
+ import { defineCommand } from 'citty';
2
+ import { readFile, writeFile, readdir } from 'node:fs/promises';
3
+ import { resolve, join } from 'node:path';
4
+ import { execFile as execFileCb } from 'node:child_process';
5
+ import { promisify } from 'node:util';
6
+ import { stringify as yamlStringify } from 'yaml';
7
+ import { logger } from '../utils/logger.js';
8
+ import { fail } from '../utils/fail.js';
9
+ import { pathExists } from '../utils/fs.js';
10
+ import { buildServerJson, deriveServerName, parseEnvExample, validateServerName, OFFICIAL_REGISTRY_URL, } from '../registry/official-registry.js';
11
+ const execFile = promisify(execFileCb);
12
+ export default defineCommand({
13
+ meta: {
14
+ name: 'publish',
15
+ description: 'Generate registry manifests (Smithery/Glama) or publish to the official MCP Registry',
16
+ },
17
+ args: {
18
+ directory: {
19
+ type: 'positional',
20
+ description: 'Path to the generated MCP server project',
21
+ required: true,
22
+ },
23
+ registry: {
24
+ type: 'string',
25
+ alias: 'r',
26
+ description: 'Target registry: official, smithery, glama (default: smithery + glama)',
27
+ },
28
+ name: {
29
+ type: 'string',
30
+ alias: 'n',
31
+ description: 'official: registry name (e.g. io.github.you/my-server); default derived from the git remote',
32
+ },
33
+ 'remote-url': {
34
+ type: 'string',
35
+ description: 'official: hosted endpoint URL to advertise as a remote (streamable-http)',
36
+ },
37
+ push: {
38
+ type: 'boolean',
39
+ description: 'official: run `mcp-publisher publish` after generating server.json (requires login + npm publish first)',
40
+ default: false,
41
+ },
42
+ },
43
+ async run({ args }) {
44
+ const projectDir = resolve(args.directory);
45
+ const pkgPath = resolve(projectDir, 'package.json');
46
+ if (!(await pathExists(pkgPath))) {
47
+ await fail(`Not a valid project directory (no package.json): ${projectDir}`);
48
+ }
49
+ const registry = args.registry?.toLowerCase();
50
+ if (registry && !['official', 'smithery', 'glama'].includes(registry)) {
51
+ await fail('Invalid registry. Use "official", "smithery", "glama", or omit for both.');
52
+ }
53
+ // Read project metadata
54
+ const pkgJson = JSON.parse(await readFile(pkgPath, 'utf-8'));
55
+ // The official MCP Registry has a distinct flow (server.json + mcp-publisher).
56
+ if (registry === 'official') {
57
+ await publishOfficial(projectDir, pkgPath, pkgJson, {
58
+ name: args.name,
59
+ remoteUrl: args['remote-url'],
60
+ push: args.push ?? false,
61
+ });
62
+ return;
63
+ }
64
+ const name = pkgJson.name ?? 'mcp-server';
65
+ const version = pkgJson.version ?? '1.0.0';
66
+ const description = pkgJson.description ?? '';
67
+ // Detect transport mode from the project's main entry
68
+ const transport = await detectTransport(projectDir);
69
+ // Extract tool list
70
+ const tools = await extractTools(projectDir);
71
+ if (tools.length === 0) {
72
+ logger.warn('No tools found in the project. The manifest will have an empty tool list.');
73
+ }
74
+ else {
75
+ logger.info(`Found ${tools.length} tool(s)`);
76
+ }
77
+ const manifest = { name, version, description, transport, tools };
78
+ const generateSmithery = !registry || registry === 'smithery';
79
+ const generateGlama = !registry || registry === 'glama';
80
+ if (generateSmithery) {
81
+ await writeSmitheryManifest(projectDir, manifest);
82
+ }
83
+ if (generateGlama) {
84
+ await writeGlamaManifest(projectDir, manifest);
85
+ }
86
+ // Print next steps
87
+ logger.info('');
88
+ logger.success('Next steps:');
89
+ logger.info(' 1. Review the generated manifest file(s)');
90
+ logger.info(' 2. Commit and push to your GitHub repository');
91
+ if (generateSmithery) {
92
+ logger.info(' 3. Register your server at https://smithery.ai — Smithery discovers');
93
+ logger.info(' servers from GitHub repos containing a smithery.yaml file');
94
+ }
95
+ if (generateGlama) {
96
+ logger.info(` ${generateSmithery ? '4' : '3'}. Submit your server at https://glama.ai/mcp/servers — Glama discovers`);
97
+ logger.info(' servers from GitHub repos containing a glama.json file');
98
+ }
99
+ },
100
+ });
101
+ /**
102
+ * Detect whether the project uses stdio or HTTP transport by inspecting
103
+ * the main server entry file.
104
+ */
105
+ async function detectTransport(projectDir) {
106
+ const stdioEntry = resolve(projectDir, 'src/index.ts');
107
+ const httpEntry = resolve(projectDir, 'src/index.ts');
108
+ for (const entry of [stdioEntry, httpEntry]) {
109
+ if (await pathExists(entry)) {
110
+ try {
111
+ const content = await readFile(entry, 'utf-8');
112
+ if (content.includes('SSEServerTransport') ||
113
+ content.includes('StreamableHTTPServerTransport')) {
114
+ return 'http';
115
+ }
116
+ }
117
+ catch {
118
+ // ignore read errors
119
+ }
120
+ }
121
+ }
122
+ return 'stdio';
123
+ }
124
+ /**
125
+ * Extract tool names and descriptions from the generated project.
126
+ * Tries multiple sources in priority order:
127
+ * 1. tool-catalog.json (dynamic discovery mode)
128
+ * 2. Individual tool handler files in src/tools/
129
+ */
130
+ async function extractTools(projectDir) {
131
+ // Strategy 1: tool-catalog.json (dynamic discovery projects)
132
+ const catalogPath = resolve(projectDir, 'src/tool-catalog.json');
133
+ if (await pathExists(catalogPath)) {
134
+ try {
135
+ const catalog = JSON.parse(await readFile(catalogPath, 'utf-8'));
136
+ if (Array.isArray(catalog)) {
137
+ return catalog.map((entry) => ({
138
+ name: entry.name,
139
+ description: entry.description ?? entry.title ?? '',
140
+ }));
141
+ }
142
+ }
143
+ catch {
144
+ logger.warn('Failed to parse tool-catalog.json, falling back to tool file scanning');
145
+ }
146
+ }
147
+ // Strategy 2: Parse individual tool handler files in src/tools/
148
+ const toolsDir = resolve(projectDir, 'src/tools');
149
+ if (!(await pathExists(toolsDir))) {
150
+ return [];
151
+ }
152
+ const tools = [];
153
+ let entries;
154
+ try {
155
+ entries = await readdir(toolsDir);
156
+ }
157
+ catch {
158
+ return [];
159
+ }
160
+ for (const file of entries) {
161
+ if (file === 'index.ts' || !file.endsWith('.ts'))
162
+ continue;
163
+ try {
164
+ const content = await readFile(join(toolsDir, file), 'utf-8');
165
+ // Extract the tool name from server.registerTool('name', ...)
166
+ const nameMatch = content.match(/registerTool\(\s*['"]([^'"]+)['"]/);
167
+ if (!nameMatch)
168
+ continue;
169
+ const toolName = nameMatch[1];
170
+ // Extract the description from description: `...` or description: '...'
171
+ const descMatch = content.match(/description:\s*[`'"]([^`'"]*)[`'"]/);
172
+ const toolDescription = descMatch ? descMatch[1] : '';
173
+ tools.push({ name: toolName, description: toolDescription });
174
+ }
175
+ catch {
176
+ // skip files that can't be read
177
+ }
178
+ }
179
+ return tools;
180
+ }
181
+ async function writeSmitheryManifest(projectDir, manifest) {
182
+ const smitheryData = {
183
+ name: manifest.name,
184
+ description: manifest.description,
185
+ version: manifest.version,
186
+ transport: manifest.transport,
187
+ tools: manifest.tools.map((t) => ({
188
+ name: t.name,
189
+ description: t.description,
190
+ })),
191
+ };
192
+ const yamlContent = yamlStringify(smitheryData, { lineWidth: 120 });
193
+ const outputPath = resolve(projectDir, 'smithery.yaml');
194
+ await writeFile(outputPath, yamlContent, 'utf-8');
195
+ logger.success(`Generated ${outputPath}`);
196
+ }
197
+ async function writeGlamaManifest(projectDir, manifest) {
198
+ const glamaData = {
199
+ name: manifest.name,
200
+ version: manifest.version,
201
+ description: manifest.description,
202
+ transport: manifest.transport,
203
+ tools: manifest.tools.map((t) => ({
204
+ name: t.name,
205
+ description: t.description,
206
+ })),
207
+ };
208
+ const jsonContent = JSON.stringify(glamaData, null, 2) + '\n';
209
+ const outputPath = resolve(projectDir, 'glama.json');
210
+ await writeFile(outputPath, jsonContent, 'utf-8');
211
+ logger.success(`Generated ${outputPath}`);
212
+ }
213
+ /**
214
+ * Generate a schema-conformant `server.json`, set the npm `mcpName` validation
215
+ * marker, and either print the publish steps or (with --push) run the official
216
+ * `mcp-publisher` CLI. mcpmake never re-implements the registry auth flow.
217
+ */
218
+ async function publishOfficial(projectDir, pkgPath, pkgJson, opts) {
219
+ const packageName = typeof pkgJson.name === 'string' ? pkgJson.name : undefined;
220
+ if (!packageName) {
221
+ return await fail('package.json has no "name" — set it before publishing to the official registry.');
222
+ }
223
+ const version = typeof pkgJson.version === 'string' ? pkgJson.version : '1.0.0';
224
+ const description = typeof pkgJson.description === 'string' ? pkgJson.description : undefined;
225
+ // Repository URL: package.json `repository`, else the git `origin` remote.
226
+ const repositoryUrl = extractRepoUrl(pkgJson.repository) ?? (await gitRemoteUrl(projectDir));
227
+ const name = deriveServerName({ explicit: opts.name, repositoryUrl });
228
+ if (!name) {
229
+ logger.info('Pass --name (e.g. --name io.github.you/my-server) or add a GitHub "repository" to package.json.');
230
+ return await fail('Could not determine the registry name.');
231
+ }
232
+ const nameError = validateServerName(name);
233
+ if (nameError) {
234
+ return await fail(nameError);
235
+ }
236
+ // Environment variables from .env.example feed the server.json env spec.
237
+ let envVars = [];
238
+ const envPath = resolve(projectDir, '.env.example');
239
+ if (await pathExists(envPath)) {
240
+ envVars = parseEnvExample(await readFile(envPath, 'utf-8'));
241
+ }
242
+ const serverJson = buildServerJson({
243
+ name,
244
+ description,
245
+ version,
246
+ repositoryUrl,
247
+ packageIdentifier: packageName,
248
+ packageVersion: version,
249
+ environmentVariables: envVars,
250
+ remoteUrl: opts.remoteUrl,
251
+ });
252
+ const serverJsonPath = resolve(projectDir, 'server.json');
253
+ await writeFile(serverJsonPath, JSON.stringify(serverJson, null, 2) + '\n', 'utf-8');
254
+ logger.success(`Generated ${serverJsonPath}`);
255
+ // The registry validates that the npm package's `mcpName` matches the server
256
+ // name. Add/update it idempotently so `mcp-publisher publish` validates.
257
+ if (pkgJson.mcpName !== name) {
258
+ const updated = { ...pkgJson, mcpName: name };
259
+ await writeFile(pkgPath, JSON.stringify(updated, null, 2) + '\n', 'utf-8');
260
+ logger.info(`Set "mcpName": "${name}" in package.json (required for npm validation)`);
261
+ }
262
+ if (opts.push) {
263
+ await runMcpPublisher(projectDir);
264
+ return;
265
+ }
266
+ logger.info('');
267
+ logger.success(`Ready to publish ${name} to the official MCP Registry.`);
268
+ logger.info('Next steps:');
269
+ logger.info(' 1. Publish the package to npm (the registry hosts metadata, not artifacts):');
270
+ logger.info(' npm publish --access public');
271
+ logger.info(' 2. Install mcp-publisher: https://modelcontextprotocol.io/registry/quickstart');
272
+ logger.info(' 3. Authenticate (the namespace must match your GitHub login):');
273
+ logger.info(' mcp-publisher login github');
274
+ logger.info(' 4. Publish the server metadata:');
275
+ logger.info(` cd ${projectDir} && mcp-publisher publish`);
276
+ logger.info('');
277
+ logger.info(`Re-run with --push to run step 4 automatically. Registry: ${OFFICIAL_REGISTRY_URL}`);
278
+ }
279
+ /** Normalize a package.json `repository` field to a plain https URL. */
280
+ function extractRepoUrl(repository) {
281
+ let raw;
282
+ if (typeof repository === 'string') {
283
+ raw = repository;
284
+ }
285
+ else if (repository && typeof repository === 'object' && 'url' in repository) {
286
+ const u = repository.url;
287
+ if (typeof u === 'string')
288
+ raw = u;
289
+ }
290
+ if (!raw)
291
+ return undefined;
292
+ raw = raw.replace(/^git\+/, '');
293
+ const shorthand = raw.match(/^github:([^/]+\/[^/]+)$/);
294
+ if (shorthand)
295
+ raw = `https://github.com/${shorthand[1]}`;
296
+ return raw.replace(/\.git$/, '');
297
+ }
298
+ /** Best-effort `git remote get-url origin` for the project directory. */
299
+ async function gitRemoteUrl(projectDir) {
300
+ try {
301
+ const { stdout } = await execFile('git', ['-C', projectDir, 'remote', 'get-url', 'origin']);
302
+ const url = stdout.trim();
303
+ return url ? url.replace(/^git\+/, '').replace(/\.git$/, '') : undefined;
304
+ }
305
+ catch {
306
+ return undefined;
307
+ }
308
+ }
309
+ /** Run the official `mcp-publisher publish` CLI in the project directory. */
310
+ async function runMcpPublisher(projectDir) {
311
+ logger.info(`Publishing to ${OFFICIAL_REGISTRY_URL} via mcp-publisher...`);
312
+ try {
313
+ const { stdout, stderr } = await execFile('mcp-publisher', ['publish'], { cwd: projectDir });
314
+ if (stdout.trim())
315
+ logger.info(stdout.trim());
316
+ if (stderr.trim())
317
+ logger.info(stderr.trim());
318
+ logger.success('Published to the official MCP Registry.');
319
+ }
320
+ catch (err) {
321
+ const e = err;
322
+ if (e.code === 'ENOENT') {
323
+ logger.info('Install it: https://modelcontextprotocol.io/registry/quickstart');
324
+ await fail('mcp-publisher is not installed or not on PATH.', err);
325
+ }
326
+ else {
327
+ if (e.stdout?.trim())
328
+ logger.info(e.stdout.trim());
329
+ if (e.stderr?.trim())
330
+ logger.error(e.stderr.trim());
331
+ await fail('mcp-publisher publish failed. Ensure you ran `mcp-publisher login github` and published to npm first.', err);
332
+ }
333
+ }
334
+ }
@@ -0,0 +1,40 @@
1
+ declare const _default: import("citty").CommandDef<{
2
+ project: {
3
+ type: "positional";
4
+ description: string;
5
+ required: true;
6
+ };
7
+ url: {
8
+ type: "string";
9
+ description: string;
10
+ };
11
+ depth: {
12
+ type: "string";
13
+ description: string;
14
+ };
15
+ 'max-pages': {
16
+ type: "string";
17
+ description: string;
18
+ };
19
+ heal: {
20
+ type: "boolean";
21
+ description: string;
22
+ default: true;
23
+ };
24
+ write: {
25
+ type: "boolean";
26
+ description: string;
27
+ default: false;
28
+ };
29
+ headless: {
30
+ type: "boolean";
31
+ description: string;
32
+ default: true;
33
+ };
34
+ format: {
35
+ type: "string";
36
+ description: string;
37
+ default: string;
38
+ };
39
+ }>;
40
+ export default _default;
@@ -0,0 +1,255 @@
1
+ import { defineCommand } from 'citty';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { resolve } from 'node:path';
4
+ import { chromium } from 'playwright';
5
+ import { crawlSite } from '../analyzer/site-crawler.js';
6
+ import { validateSelector } from '../analyzer/selector-builder.js';
7
+ import { detectAuthFlow } from '../analyzer/auth-detector.js';
8
+ import { generateSiteTools } from '../site-transformer/tool-generator.js';
9
+ import { healBrokenSelector } from '../site-transformer/selector-healer.js';
10
+ import { diffSiteDescriptors } from '../rescan/diff-engine.js';
11
+ import { collectLowConfidenceSelectors, summarizeRescan } from '../rescan/rescan-runner.js';
12
+ import { emitSiteProject } from '../emitter/index.js';
13
+ import { logger } from '../utils/logger.js';
14
+ import { fail } from '../utils/fail.js';
15
+ import { pathExists } from '../utils/fs.js';
16
+ export default defineCommand({
17
+ meta: {
18
+ name: 'rescan',
19
+ description: "Re-crawl a generated website MCP server's target, diff against the embedded snapshot, heal broken selectors, and optionally regenerate",
20
+ },
21
+ args: {
22
+ project: {
23
+ type: 'positional',
24
+ description: 'Path to the generated site MCP server project',
25
+ required: true,
26
+ },
27
+ url: {
28
+ type: 'string',
29
+ description: 'Override the base URL to rescan (default: from the embedded descriptor)',
30
+ },
31
+ depth: {
32
+ type: 'string',
33
+ description: 'Crawl depth (default: from the embedded descriptor)',
34
+ },
35
+ 'max-pages': {
36
+ type: 'string',
37
+ description: 'Maximum pages to crawl (default: page count of the embedded descriptor)',
38
+ },
39
+ heal: {
40
+ type: 'boolean',
41
+ description: 'LLM-heal low-confidence selectors (requires ANTHROPIC_API_KEY)',
42
+ default: true,
43
+ },
44
+ write: {
45
+ type: 'boolean',
46
+ description: 'Regenerate the project in place from the new snapshot',
47
+ default: false,
48
+ },
49
+ headless: {
50
+ type: 'boolean',
51
+ description: 'Run the crawl/heal browser headless',
52
+ default: true,
53
+ },
54
+ format: {
55
+ type: 'string',
56
+ description: 'Output format: "text" (default) or "json"',
57
+ default: 'text',
58
+ },
59
+ },
60
+ async run({ args }) {
61
+ const projectDir = resolve(args.project);
62
+ const descriptorPath = resolve(projectDir, 'src/site-descriptor.json');
63
+ if (!(await pathExists(descriptorPath))) {
64
+ return await fail(`No site snapshot at ${descriptorPath}. Is this a 'mcpmake from website' project?`);
65
+ }
66
+ let oldSite;
67
+ try {
68
+ oldSite = JSON.parse(await readFile(descriptorPath, 'utf-8'));
69
+ }
70
+ catch (err) {
71
+ return await fail(`Failed to read site snapshot: ${descriptorPath}`, err);
72
+ }
73
+ const baseUrl = args.url ?? oldSite.baseUrl;
74
+ const depth = args.depth ? parseInt(args.depth, 10) : oldSite.crawlDepth || 2;
75
+ // Default with headroom so newly-added pages are still discovered (a flat
76
+ // cap at the old page count would make rescan blind to site growth).
77
+ const maxPages = args['max-pages']
78
+ ? parseInt(args['max-pages'], 10)
79
+ : Math.max(oldSite.pages.length * 2, oldSite.pages.length + 5, 1);
80
+ const headless = args.headless ?? true;
81
+ logger.info(`Rescanning ${baseUrl} (depth ${depth}, max ${maxPages} pages)`);
82
+ let newSite;
83
+ try {
84
+ const result = await crawlSite({ url: baseUrl, depth, maxPages, headless });
85
+ newSite = result.siteDescriptor;
86
+ }
87
+ catch (err) {
88
+ return await fail(`Rescan crawl failed: ${baseUrl}`, err);
89
+ }
90
+ if (newSite.pages.length === 0) {
91
+ return await fail('Rescan crawl found no pages. Check that the URL is reachable.');
92
+ }
93
+ // Diff bumps from the OLD snapshot version.
94
+ newSite.version = oldSite.version;
95
+ const result = diffSiteDescriptors(oldSite, newSite);
96
+ // Heal the brittle (low-confidence) selectors in the fresh snapshot so the
97
+ // regenerated server gets more stable selectors than a bare re-crawl found.
98
+ const lows = collectLowConfidenceSelectors(newSite);
99
+ let healedCount = 0;
100
+ const apiKey = process.env.ANTHROPIC_API_KEY;
101
+ if (args.heal && lows.length > 0 && apiKey) {
102
+ logger.info(`Healing ${lows.length} low-confidence selector(s)…`);
103
+ healedCount = await healLowConfidenceSelectors(lows, headless);
104
+ logger.info(`Healed ${healedCount}/${lows.length} selector(s)`);
105
+ }
106
+ else if (args.heal && lows.length > 0 && !apiKey) {
107
+ logger.warn(`${lows.length} low-confidence selector(s) found, but ANTHROPIC_API_KEY is not set — skipping healing`);
108
+ }
109
+ const summary = summarizeRescan(result);
110
+ if (args.format === 'json') {
111
+ process.stdout.write(JSON.stringify({
112
+ summary,
113
+ healed: healedCount,
114
+ lowConfidence: lows.length,
115
+ brokenSelectors: result.brokenSelectors,
116
+ changes: result.changes,
117
+ }, null, 2) + '\n');
118
+ }
119
+ else {
120
+ printTextReport(summary, healedCount, lows.length);
121
+ }
122
+ if (args.write) {
123
+ await regenerate(projectDir, result.newSiteDescriptor);
124
+ logger.success(`Regenerated ${projectDir} (snapshot v${summary.newVersion})`);
125
+ }
126
+ else if (summary.totalChanges > 0 || healedCount > 0) {
127
+ logger.info('');
128
+ logger.info('Re-run with --write to regenerate the project from this snapshot.');
129
+ }
130
+ },
131
+ });
132
+ /**
133
+ * Open a browser and try to heal each low-confidence selector by navigating to
134
+ * its page, snapshotting the accessibility tree, asking the LLM for a better
135
+ * selector, and applying it only if it actually resolves on the live page.
136
+ * Mutates the selector objects in place (they are live references into the
137
+ * descriptor). Returns the number of selectors healed.
138
+ */
139
+ async function healLowConfidenceSelectors(lows, headless) {
140
+ const byUrl = new Map();
141
+ for (const low of lows) {
142
+ const group = byUrl.get(low.pageUrl) ?? [];
143
+ group.push(low);
144
+ byUrl.set(low.pageUrl, group);
145
+ }
146
+ let healed = 0;
147
+ const browser = await chromium.launch({ headless });
148
+ try {
149
+ const context = await browser.newContext({ viewport: { width: 1280, height: 720 } });
150
+ const page = await context.newPage();
151
+ for (const [url, group] of byUrl) {
152
+ try {
153
+ await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 15_000 });
154
+ await page.waitForTimeout(1000);
155
+ }
156
+ catch (err) {
157
+ logger.warn(`Could not load ${url} for healing: ${err instanceof Error ? err.message : String(err)}`);
158
+ continue;
159
+ }
160
+ let tree = '';
161
+ try {
162
+ // Modern Playwright accessibility tree (YAML); compact and selector-friendly.
163
+ tree = await page.locator('body').ariaSnapshot();
164
+ }
165
+ catch {
166
+ // Fall back to an empty tree; the healer will likely return null.
167
+ }
168
+ for (const low of group) {
169
+ const candidate = await healBrokenSelector(tree, low.selector, low.description);
170
+ if (!candidate)
171
+ continue;
172
+ // Only apply selectors that actually resolve on the live page.
173
+ const working = await validateSelector(page, candidate);
174
+ if (working) {
175
+ Object.assign(low.selector, candidate);
176
+ healed += 1;
177
+ }
178
+ }
179
+ }
180
+ }
181
+ finally {
182
+ await browser.close().catch(() => { });
183
+ }
184
+ return healed;
185
+ }
186
+ /** Regenerate the project in place from a fresh (healed) descriptor. */
187
+ async function regenerate(projectDir, descriptor) {
188
+ const metaPath = resolve(projectDir, 'mcpmake.site.json');
189
+ let meta;
190
+ if (await pathExists(metaPath)) {
191
+ meta = JSON.parse(await readFile(metaPath, 'utf-8'));
192
+ }
193
+ else {
194
+ logger.warn('mcpmake.site.json not found — regenerating with default settings');
195
+ meta = {
196
+ serverName: hostToName(descriptor.baseUrl),
197
+ serverVersion: '1.0.0',
198
+ transport: 'stdio',
199
+ baseUrl: descriptor.baseUrl,
200
+ envVars: [
201
+ {
202
+ name: 'BASE_URL',
203
+ description: 'Target website URL',
204
+ required: true,
205
+ example: descriptor.baseUrl,
206
+ },
207
+ ],
208
+ browserConfig: {
209
+ headless: true,
210
+ idleTimeoutMs: 5 * 60 * 1000,
211
+ viewport: { width: 1280, height: 720 },
212
+ maxSessions: 10,
213
+ },
214
+ };
215
+ }
216
+ const authFlow = detectAuthFlow(descriptor.pages);
217
+ if (authFlow) {
218
+ descriptor.authFlow = authFlow;
219
+ }
220
+ const tools = generateSiteTools(descriptor);
221
+ const manifest = {
222
+ ...meta,
223
+ siteDescriptor: descriptor,
224
+ tools,
225
+ };
226
+ await emitSiteProject(manifest, { outputDir: projectDir, force: true, dryRun: false });
227
+ }
228
+ function hostToName(baseUrl) {
229
+ let host = 'site';
230
+ try {
231
+ host = new URL(baseUrl).hostname;
232
+ }
233
+ catch {
234
+ // keep default
235
+ }
236
+ return (host
237
+ .toLowerCase()
238
+ .replace(/[^a-z0-9]+/g, '-')
239
+ .replace(/^-|-$/g, '') || 'site');
240
+ }
241
+ function printTextReport(summary, healedCount, lowCount) {
242
+ const line = (label, c) => `${label}: pages ${c.page}, forms ${c.form}, fields ${c.field}, buttons ${c.button}, links ${c.link}`;
243
+ logger.info('');
244
+ logger.info(`Snapshot v${summary.previousVersion} → v${summary.newVersion}`);
245
+ logger.info(` ${line('Added ', summary.added)}`);
246
+ logger.info(` ${line('Removed ', summary.removed)}`);
247
+ logger.info(` ${line('Modified', summary.modified)}`);
248
+ logger.info(` Broken selectors (low-confidence change): ${summary.brokenSelectors}`);
249
+ if (lowCount > 0) {
250
+ logger.info(` Healed selectors: ${healedCount}/${lowCount}`);
251
+ }
252
+ if (summary.totalChanges === 0) {
253
+ logger.info(' No structural changes detected.');
254
+ }
255
+ }
@@ -0,0 +1,14 @@
1
+ declare const _default: import("citty").CommandDef<{
2
+ spec: {
3
+ type: "positional";
4
+ description: string;
5
+ required: true;
6
+ };
7
+ project: {
8
+ type: "string";
9
+ alias: string;
10
+ description: string;
11
+ required: true;
12
+ };
13
+ }>;
14
+ export default _default;