multiclone 0.0.0-stage → 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 (240) hide show
  1. package/README.md +78 -2
  2. package/agents/section-builder.md +37 -0
  3. package/commands/multiclone.md +15 -0
  4. package/dist/adapters/cli.js +129 -0
  5. package/dist/adapters/cli.js.map +1 -0
  6. package/dist/adapters/cliArgs.js +63 -0
  7. package/dist/adapters/cliArgs.js.map +1 -0
  8. package/dist/adapters/mcp.js +83 -0
  9. package/dist/adapters/mcp.js.map +1 -0
  10. package/dist/analyze/analyze.js +85 -0
  11. package/dist/analyze/analyze.js.map +1 -0
  12. package/dist/analyze/animations.js +340 -0
  13. package/dist/analyze/animations.js.map +1 -0
  14. package/dist/analyze/ast.js +182 -0
  15. package/dist/analyze/ast.js.map +1 -0
  16. package/dist/analyze/chunks.js +160 -0
  17. package/dist/analyze/chunks.js.map +1 -0
  18. package/dist/analyze/jobs/code.js +117 -0
  19. package/dist/analyze/jobs/code.js.map +1 -0
  20. package/dist/analyze/jobs/sections.js +356 -0
  21. package/dist/analyze/jobs/sections.js.map +1 -0
  22. package/dist/analyze/pretty.js +20 -0
  23. package/dist/analyze/pretty.js.map +1 -0
  24. package/dist/analyze/report.js +101 -0
  25. package/dist/analyze/report.js.map +1 -0
  26. package/dist/analyze/worker.js +71 -0
  27. package/dist/analyze/worker.js.map +1 -0
  28. package/dist/capture/capture.js +340 -0
  29. package/dist/capture/capture.js.map +1 -0
  30. package/dist/capture/collectors/a11y.js +92 -0
  31. package/dist/capture/collectors/a11y.js.map +1 -0
  32. package/dist/capture/collectors/animations.js +113 -0
  33. package/dist/capture/collectors/animations.js.map +1 -0
  34. package/dist/capture/collectors/breakpoints.js +31 -0
  35. package/dist/capture/collectors/breakpoints.js.map +1 -0
  36. package/dist/capture/collectors/consent.js +39 -0
  37. package/dist/capture/collectors/consent.js.map +1 -0
  38. package/dist/capture/collectors/coverage.js +121 -0
  39. package/dist/capture/collectors/coverage.js.map +1 -0
  40. package/dist/capture/collectors/dom.js +16 -0
  41. package/dist/capture/collectors/dom.js.map +1 -0
  42. package/dist/capture/collectors/fonts.js +154 -0
  43. package/dist/capture/collectors/fonts.js.map +1 -0
  44. package/dist/capture/collectors/frames.js +93 -0
  45. package/dist/capture/collectors/frames.js.map +1 -0
  46. package/dist/capture/collectors/index.js +61 -0
  47. package/dist/capture/collectors/index.js.map +1 -0
  48. package/dist/capture/collectors/interactions.js +207 -0
  49. package/dist/capture/collectors/interactions.js.map +1 -0
  50. package/dist/capture/collectors/intro.js +84 -0
  51. package/dist/capture/collectors/intro.js.map +1 -0
  52. package/dist/capture/collectors/listeners.js +84 -0
  53. package/dist/capture/collectors/listeners.js.map +1 -0
  54. package/dist/capture/collectors/motion.js +91 -0
  55. package/dist/capture/collectors/motion.js.map +1 -0
  56. package/dist/capture/collectors/network.js +288 -0
  57. package/dist/capture/collectors/network.js.map +1 -0
  58. package/dist/capture/collectors/probes.js +34 -0
  59. package/dist/capture/collectors/probes.js.map +1 -0
  60. package/dist/capture/collectors/runtime.js +38 -0
  61. package/dist/capture/collectors/runtime.js.map +1 -0
  62. package/dist/capture/collectors/scripts.js +98 -0
  63. package/dist/capture/collectors/scripts.js.map +1 -0
  64. package/dist/capture/collectors/sections.js +17 -0
  65. package/dist/capture/collectors/sections.js.map +1 -0
  66. package/dist/capture/collectors/shots.js +58 -0
  67. package/dist/capture/collectors/shots.js.map +1 -0
  68. package/dist/capture/collectors/snapshot.js +47 -0
  69. package/dist/capture/collectors/snapshot.js.map +1 -0
  70. package/dist/capture/collectors/sourcemaps.js +153 -0
  71. package/dist/capture/collectors/sourcemaps.js.map +1 -0
  72. package/dist/capture/collectors/storage.js +72 -0
  73. package/dist/capture/collectors/storage.js.map +1 -0
  74. package/dist/capture/context.js +40 -0
  75. package/dist/capture/context.js.map +1 -0
  76. package/dist/capture/hooks/candidates.js +104 -0
  77. package/dist/capture/hooks/candidates.js.map +1 -0
  78. package/dist/capture/hooks/consent.js +82 -0
  79. package/dist/capture/hooks/consent.js.map +1 -0
  80. package/dist/capture/hooks/recorders.js +507 -0
  81. package/dist/capture/hooks/recorders.js.map +1 -0
  82. package/dist/capture/hooks/sections.js +62 -0
  83. package/dist/capture/hooks/sections.js.map +1 -0
  84. package/dist/capture/hooks/styles.js +86 -0
  85. package/dist/capture/hooks/styles.js.map +1 -0
  86. package/dist/capture/probes.js +491 -0
  87. package/dist/capture/probes.js.map +1 -0
  88. package/dist/capture/report.js +115 -0
  89. package/dist/capture/report.js.map +1 -0
  90. package/dist/compare/compare.js +391 -0
  91. package/dist/compare/compare.js.map +1 -0
  92. package/dist/compare/image.js +105 -0
  93. package/dist/compare/image.js.map +1 -0
  94. package/dist/compare/probes.js +129 -0
  95. package/dist/compare/probes.js.map +1 -0
  96. package/dist/compare/report.js +99 -0
  97. package/dist/compare/report.js.map +1 -0
  98. package/dist/compare/session.js +139 -0
  99. package/dist/compare/session.js.map +1 -0
  100. package/dist/compare/shoot.js +62 -0
  101. package/dist/compare/shoot.js.map +1 -0
  102. package/dist/contracts/catalog.js +29 -0
  103. package/dist/contracts/catalog.js.map +1 -0
  104. package/dist/contracts/context.js +19 -0
  105. package/dist/contracts/context.js.map +1 -0
  106. package/dist/contracts/fields.js +16 -0
  107. package/dist/contracts/fields.js.map +1 -0
  108. package/dist/contracts/tools/analyze.js +38 -0
  109. package/dist/contracts/tools/analyze.js.map +1 -0
  110. package/dist/contracts/tools/capture.js +87 -0
  111. package/dist/contracts/tools/capture.js.map +1 -0
  112. package/dist/contracts/tools/compare.js +142 -0
  113. package/dist/contracts/tools/compare.js.map +1 -0
  114. package/dist/contracts/tools/doctor.js +23 -0
  115. package/dist/contracts/tools/doctor.js.map +1 -0
  116. package/dist/contracts/tools/inspect.js +41 -0
  117. package/dist/contracts/tools/inspect.js.map +1 -0
  118. package/dist/contracts/tools/method.js +60 -0
  119. package/dist/contracts/tools/method.js.map +1 -0
  120. package/dist/contracts/tools/mirror.js +53 -0
  121. package/dist/contracts/tools/mirror.js.map +1 -0
  122. package/dist/contracts/tools/project.js +61 -0
  123. package/dist/contracts/tools/project.js.map +1 -0
  124. package/dist/contracts/tools/session.js +195 -0
  125. package/dist/contracts/tools/session.js.map +1 -0
  126. package/dist/contracts/types.js +20 -0
  127. package/dist/contracts/types.js.map +1 -0
  128. package/dist/core/cdp.js +146 -0
  129. package/dist/core/cdp.js.map +1 -0
  130. package/dist/core/chrome.js +185 -0
  131. package/dist/core/chrome.js.map +1 -0
  132. package/dist/core/files.js +31 -0
  133. package/dist/core/files.js.map +1 -0
  134. package/dist/core/inpage.js +24 -0
  135. package/dist/core/inpage.js.map +1 -0
  136. package/dist/core/log.js +10 -0
  137. package/dist/core/log.js.map +1 -0
  138. package/dist/core/page.js +269 -0
  139. package/dist/core/page.js.map +1 -0
  140. package/dist/doctor.js +37 -0
  141. package/dist/doctor.js.map +1 -0
  142. package/dist/domain/breakpoints.js +47 -0
  143. package/dist/domain/breakpoints.js.map +1 -0
  144. package/dist/domain/budget.js +38 -0
  145. package/dist/domain/budget.js.map +1 -0
  146. package/dist/domain/compare.js +138 -0
  147. package/dist/domain/compare.js.map +1 -0
  148. package/dist/domain/coverage.js +96 -0
  149. package/dist/domain/coverage.js.map +1 -0
  150. package/dist/domain/motion.js +142 -0
  151. package/dist/domain/motion.js.map +1 -0
  152. package/dist/domain/paths.js +55 -0
  153. package/dist/domain/paths.js.map +1 -0
  154. package/dist/domain/result.js +38 -0
  155. package/dist/domain/result.js.map +1 -0
  156. package/dist/domain/routes.js +160 -0
  157. package/dist/domain/routes.js.map +1 -0
  158. package/dist/domain/sections.js +94 -0
  159. package/dist/domain/sections.js.map +1 -0
  160. package/dist/domain/snapshot.js +268 -0
  161. package/dist/domain/snapshot.js.map +1 -0
  162. package/dist/domain/sourcemap.js +95 -0
  163. package/dist/domain/sourcemap.js.map +1 -0
  164. package/dist/domain/stack.js +342 -0
  165. package/dist/domain/stack.js.map +1 -0
  166. package/dist/domain/tokens.js +183 -0
  167. package/dist/domain/tokens.js.map +1 -0
  168. package/dist/domain/types.js +2 -0
  169. package/dist/domain/types.js.map +1 -0
  170. package/dist/domain/viewports.js +45 -0
  171. package/dist/domain/viewports.js.map +1 -0
  172. package/dist/mirror/rewrite.js +56 -0
  173. package/dist/mirror/rewrite.js.map +1 -0
  174. package/dist/mirror/server.js +191 -0
  175. package/dist/mirror/server.js.map +1 -0
  176. package/dist/mirror/store.js +86 -0
  177. package/dist/mirror/store.js.map +1 -0
  178. package/dist/project/assets.js +69 -0
  179. package/dist/project/assets.js.map +1 -0
  180. package/dist/project/draft.js +860 -0
  181. package/dist/project/draft.js.map +1 -0
  182. package/dist/project/method.js +154 -0
  183. package/dist/project/method.js.map +1 -0
  184. package/dist/project/scaffold.js +142 -0
  185. package/dist/project/scaffold.js.map +1 -0
  186. package/dist/session/discover.js +53 -0
  187. package/dist/session/discover.js.map +1 -0
  188. package/dist/session/proc.js +113 -0
  189. package/dist/session/proc.js.map +1 -0
  190. package/dist/session/progress.js +83 -0
  191. package/dist/session/progress.js.map +1 -0
  192. package/dist/session/session.js +450 -0
  193. package/dist/session/session.js.map +1 -0
  194. package/dist/session/state.js +47 -0
  195. package/dist/session/state.js.map +1 -0
  196. package/dist/store/inspect.js +212 -0
  197. package/dist/store/inspect.js.map +1 -0
  198. package/dist/store/run.js +112 -0
  199. package/dist/store/run.js.map +1 -0
  200. package/dist/store/summary.js +69 -0
  201. package/dist/store/summary.js.map +1 -0
  202. package/dist/version.js +2 -0
  203. package/dist/version.js.map +1 -0
  204. package/install.sh +29 -0
  205. package/package.json +67 -4
  206. package/skills/multiclone/SKILL.md +123 -0
  207. package/skills/multiclone/references/capture-format.md +73 -0
  208. package/skills/multiclone/references/fidelity.md +54 -0
  209. package/skills/multiclone/references/quality.md +47 -0
  210. package/skills/multiclone/references/stacks.md +156 -0
  211. package/templates/react-vite/.prettierignore +5 -0
  212. package/templates/react-vite/.prettierrc.json +6 -0
  213. package/templates/react-vite/eslint.config.js +38 -0
  214. package/templates/react-vite/gitignore +4 -0
  215. package/templates/react-vite/index.html +12 -0
  216. package/templates/react-vite/package.json +36 -0
  217. package/templates/react-vite/src/app/App.tsx +15 -0
  218. package/templates/react-vite/src/app/Page.tsx +11 -0
  219. package/templates/react-vite/src/app/routes.ts +7 -0
  220. package/templates/react-vite/src/components/README.md +7 -0
  221. package/templates/react-vite/src/content/README.md +2 -0
  222. package/templates/react-vite/src/hooks/useActiveSection.ts +37 -0
  223. package/templates/react-vite/src/hooks/useInView.ts +45 -0
  224. package/templates/react-vite/src/hooks/useMediaQuery.ts +15 -0
  225. package/templates/react-vite/src/lib/cn.ts +4 -0
  226. package/templates/react-vite/src/lib/math.ts +21 -0
  227. package/templates/react-vite/src/lib/mc.ts +13 -0
  228. package/templates/react-vite/src/lib/media.ts +4 -0
  229. package/templates/react-vite/src/main.tsx +13 -0
  230. package/templates/react-vite/src/providers/SmoothScroll.tsx +24 -0
  231. package/templates/react-vite/src/styles/base.css +42 -0
  232. package/templates/react-vite/src/styles/draft.css +1 -0
  233. package/templates/react-vite/src/styles/fonts.css +1 -0
  234. package/templates/react-vite/src/styles/index.css +4 -0
  235. package/templates/react-vite/src/styles/tokens.css +3 -0
  236. package/templates/react-vite/src/vite-env.d.ts +1 -0
  237. package/templates/react-vite/tsconfig.app.json +29 -0
  238. package/templates/react-vite/tsconfig.json +4 -0
  239. package/templates/react-vite/tsconfig.node.json +19 -0
  240. package/templates/react-vite/vite.config.ts +37 -0
@@ -0,0 +1,87 @@
1
+ import { z } from 'zod';
2
+ import { capture } from '../../capture/capture.js';
3
+ import { ok } from '../../domain/result.js';
4
+ import { RunFolder } from '../../store/run.js';
5
+ import { renderSummary, runSummary } from '../../store/summary.js';
6
+ import { parseViewports, wantsAutoViewports } from '../../domain/viewports.js';
7
+ import { defineTool } from '../types.js';
8
+ import { positionsField, settleField, viewportsField } from '../fields.js';
9
+ export const captureTool = defineTool({
10
+ name: 'capture',
11
+ title: 'Capture a website',
12
+ description: 'Record a live page with Chrome: network, every frame, scripts and source maps, styled DOM snapshot, runtime animation data (GSAP/ScrollTrigger, WAAPI, WebGL shaders), intro filmstrip, scroll motion, hover/click states and fonts, then split it into per-section bundles. depth=quick (1–2 min) records what a first draft needs; depth=full (default, 3–8 min) adds motion, hovers, clicks, coverage and source maps. Prefer `start`, which does quick + draft + live link + deep pass for you. Returns a short summary; page through details with `inspect`.',
13
+ input: z.object({
14
+ url: z.url().describe('Page to clone.'),
15
+ out: z.string().optional().describe('Output folder; default multiclone-captures/<site>.'),
16
+ viewports: viewportsField,
17
+ positions: positionsField,
18
+ depth: z
19
+ .enum(['quick', 'full'])
20
+ .default('full')
21
+ .describe('quick: draft essentials in 1–2 min. full: everything.'),
22
+ shots: z
23
+ .number()
24
+ .int()
25
+ .min(1)
26
+ .max(40)
27
+ .optional()
28
+ .describe('Evenly spaced screenshots (default 4 quick, 8 full).'),
29
+ settleMs: settleField,
30
+ motion: z.boolean().optional().describe('Sample scroll-linked motion (default: full only).'),
31
+ parallel: z
32
+ .boolean()
33
+ .optional()
34
+ .describe('Capture extra viewports in a second browser at the same time (default: quick).'),
35
+ headed: z.boolean().default(false).describe('Show the browser window (helps with bot checks).'),
36
+ skip: z
37
+ .array(z.string())
38
+ .optional()
39
+ .describe('Collectors to skip: intro, consent, coverage, interactions, listeners, sourcemaps, runtime, animations, snapshot, a11y, fonts, storage, motion.'),
40
+ deep: z
41
+ .array(z.string())
42
+ .optional()
43
+ .describe('Viewports that get coverage, listener and interaction passes. Default the first.'),
44
+ analyze: z
45
+ .boolean()
46
+ .default(true)
47
+ .describe('Build section bundles, tokens, code and REPORT.md after capture.'),
48
+ profile: z
49
+ .string()
50
+ .optional()
51
+ .describe('Persistent Chrome profile folder: log in or pass a bot check once, then reuse.'),
52
+ }),
53
+ cli: { command: 'capture', positionals: ['url'] },
54
+ readOnly: false,
55
+ openWorld: true,
56
+ run: async (input, context) => {
57
+ const result = await capture({
58
+ url: input.url,
59
+ ...(input.out === undefined ? {} : { out: input.out }),
60
+ viewports: parseViewports(input.viewports),
61
+ autoViewports: wantsAutoViewports(input.viewports),
62
+ positions: input.positions ?? null,
63
+ depth: input.depth,
64
+ ...(input.shots === undefined ? {} : { shots: input.shots }),
65
+ ...(input.settleMs === undefined ? {} : { settleMs: input.settleMs }),
66
+ ...(input.motion === undefined ? {} : { motion: input.motion }),
67
+ ...(input.parallel === undefined ? {} : { parallel: input.parallel }),
68
+ headless: !input.headed,
69
+ ...(input.profile === undefined ? {} : { profile: input.profile }),
70
+ signal: context.signal,
71
+ ...(input.skip === undefined ? {} : { skip: input.skip }),
72
+ ...(input.deep === undefined ? {} : { deepViewports: input.deep }),
73
+ analyze: input.analyze,
74
+ });
75
+ const opened = await RunFolder.open(result.dir);
76
+ if (!opened.ok)
77
+ return opened;
78
+ const summary = await runSummary(opened.value);
79
+ const analysis = result.analysis;
80
+ const warnings = analysis && !analysis.ok
81
+ ? [...summary.warnings, `analysis: ${analysis.error.message}`]
82
+ : summary.warnings;
83
+ const data = { ...summary, warnings };
84
+ return ok({ data, text: renderSummary(data) });
85
+ },
86
+ });
87
+ //# sourceMappingURL=capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.js","sourceRoot":"","sources":["../../../src/contracts/tools/capture.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,142 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { z } from 'zod';
4
+ import { compare } from '../../compare/compare.js';
5
+ import { shoot } from '../../compare/shoot.js';
6
+ import { ok } from '../../domain/result.js';
7
+ import { parseViewports } from '../../domain/viewports.js';
8
+ import { defineTool } from '../types.js';
9
+ import { positionsField, settleField, viewportsField } from '../fields.js';
10
+ export const shootTool = defineTool({
11
+ name: 'shoot',
12
+ title: 'Screenshot at scroll offsets',
13
+ description: 'Screenshot a URL at fixed scroll offsets with real wheel scrolling (works with Lenis/GSAP). Optional `probe` JS expression runs after each scroll, e.g. to read element positions or CSS variables.',
14
+ input: z.object({
15
+ url: z.string(),
16
+ out: z.string().default('multiclone-shots'),
17
+ viewports: viewportsField,
18
+ positions: positionsField,
19
+ count: z.number().int().min(1).max(30).default(6),
20
+ settleMs: settleField,
21
+ probe: z.string().optional().describe('JS expression evaluated after each scroll.'),
22
+ inline: z.boolean().default(false).describe('Also return up to 4 screenshots inline (MCP).'),
23
+ }),
24
+ cli: { command: 'shoot', positionals: ['url'] },
25
+ readOnly: false,
26
+ openWorld: true,
27
+ run: async (input) => {
28
+ const sets = await shoot({
29
+ url: input.url,
30
+ viewports: parseViewports(input.viewports ?? 'desktop'),
31
+ positions: input.positions ?? null,
32
+ count: input.count,
33
+ settleMs: input.settleMs ?? 5000,
34
+ out: input.out,
35
+ ...(input.probe === undefined ? {} : { probe: input.probe }),
36
+ });
37
+ const summary = sets.map((set) => ({
38
+ viewport: set.viewport.name,
39
+ pageHeight: set.pageHeight,
40
+ shots: set.shots.map((shot) => ({
41
+ y: shot.y,
42
+ actualY: shot.actualY,
43
+ file: join(input.out, `shot-${set.viewport.name}-${String(shot.y)}.png`),
44
+ })),
45
+ probes: set.probes,
46
+ }));
47
+ const images = input.inline
48
+ ? sets
49
+ .flatMap((set) => set.shots)
50
+ .slice(0, 4)
51
+ .map((shot) => ({ data: shot.png, mimeType: 'image/png' }))
52
+ : [];
53
+ return ok({ data: summary, text: JSON.stringify(summary, null, 2), images });
54
+ },
55
+ });
56
+ export const compareTool = defineTool({
57
+ name: 'compare',
58
+ title: 'Compare original and clone',
59
+ description: 'Compare the clone with the original per viewport: viewport frames, sections aligned by `data-mc-section`, page structure, scroll motion (`data-mc-track`), hover states (`data-mc-hover`) and optionally the intro. `original` is a URL or a capture folder (a capture adds its sections, motion and hovers). Writes an HTML report and compare.json and scores out of 100; the score stays below 100 while anything could not be compared. Returns the worst sections and frames to fix first.',
60
+ input: z.object({
61
+ original: z.string().describe('Original URL, or a capture folder.'),
62
+ clone: z.string(),
63
+ out: z.string().optional(),
64
+ viewports: viewportsField,
65
+ positions: positionsField,
66
+ relative: z.boolean().default(false).describe('Compare at the same share of scroll.'),
67
+ count: z.number().int().min(1).max(30).default(6),
68
+ settleMs: settleField,
69
+ threshold: z.number().min(0).max(1).default(0.1),
70
+ intro: z.boolean().default(false).describe('Record and diff the first 4 s after load.'),
71
+ interactions: z.boolean().default(true).describe('Diff hover states (capture original).'),
72
+ inlineWorst: z.boolean().default(false).describe('Return the worst frame inline (MCP).'),
73
+ }),
74
+ cli: { command: 'compare', positionals: ['original', 'clone'] },
75
+ readOnly: false,
76
+ openWorld: true,
77
+ run: async (input, context) => {
78
+ const result = await compare({
79
+ original: input.original,
80
+ clone: input.clone,
81
+ ...(input.out === undefined ? {} : { out: input.out }),
82
+ viewports: parseViewports(input.viewports),
83
+ positions: input.positions ?? null,
84
+ relative: input.relative,
85
+ count: input.count,
86
+ settleMs: input.settleMs ?? 5000,
87
+ threshold: input.threshold,
88
+ intro: input.intro,
89
+ interactions: input.interactions,
90
+ signal: context.signal,
91
+ });
92
+ const worst = result.worst.map((frame) => ({ ...frame, file: join(result.dir, frame.file) }));
93
+ const worstSections = result.worstSections.map((section) => ({
94
+ ...section,
95
+ file: section.file === null ? null : join(result.dir, section.file),
96
+ }));
97
+ const images = [];
98
+ const first = worstSections.find((section) => section.file !== null)?.file ?? worst[0]?.file;
99
+ if (input.inlineWorst && first) {
100
+ images.push({ data: await readFile(first), mimeType: 'image/png' });
101
+ }
102
+ const pct = (value) => value === null ? 'n/a' : `${(value * 100).toFixed(1)}%`;
103
+ const parts = Object.entries(result.parts)
104
+ .map(([name, value]) => `${name} ${pct(value)}`)
105
+ .join(' · ');
106
+ const sectionLines = worstSections
107
+ .map((section) => ` ${section.viewport} ${section.label || section.key}: ${section.status === 'missing' ? 'missing' : `${String(section.score)}/100`}${section.file ? ` ${section.file}` : ''}`)
108
+ .join('\n');
109
+ const frameLines = worst
110
+ .map((frame) => ` ${frame.viewport} @ ${String(frame.y)}px: ${pct(1 - frame.mismatch)} ${frame.file}`)
111
+ .join('\n');
112
+ const untagged = result.motion.filter((track) => track.missing).length;
113
+ const c = result.completeness;
114
+ return ok({
115
+ data: {
116
+ score: result.score,
117
+ parts: result.parts,
118
+ completeness: result.completeness,
119
+ heights: result.heights,
120
+ worstSections,
121
+ worst,
122
+ untaggedTracks: untagged,
123
+ report: result.report,
124
+ json: join(result.dir, 'compare.json'),
125
+ },
126
+ text: [
127
+ `Score ${String(result.score)}/100 (${parts})`,
128
+ `Sections: ${String(c.sectionsPaired)} paired, ${String(c.sectionsMissing)} missing, ${String(c.sectionsExtra)} extra`,
129
+ untagged
130
+ ? `${String(untagged)} motion tracks have no data-mc-track element in the clone.`
131
+ : '',
132
+ `Worst sections:\n${sectionLines || ' none'}`,
133
+ `Worst frames:\n${frameLines || ' none'}`,
134
+ `Report: ${result.report}`,
135
+ ]
136
+ .filter(Boolean)
137
+ .join('\n'),
138
+ images,
139
+ });
140
+ },
141
+ });
142
+ //# sourceMappingURL=compare.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare.js","sourceRoot":"","sources":["../../../src/contracts/tools/compare.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { z } from 'zod';
2
+ import { doctor } from '../../doctor.js';
3
+ import { ok } from '../../domain/result.js';
4
+ import { defineTool } from '../types.js';
5
+ export const doctorTool = defineTool({
6
+ name: 'doctor',
7
+ title: 'Check the environment',
8
+ description: 'Check Node, Chrome and headless WebGL.',
9
+ input: z.object({}),
10
+ cli: { command: 'doctor', positionals: [] },
11
+ readOnly: true,
12
+ openWorld: false,
13
+ run: async () => {
14
+ const checks = await doctor();
15
+ return ok({
16
+ data: checks,
17
+ text: checks
18
+ .map((check) => `${check.ok ? '✔' : '✘'} ${check.name}: ${check.detail}`)
19
+ .join('\n'),
20
+ });
21
+ },
22
+ });
23
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../src/contracts/tools/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,EAAE,EAAE,MAAM,wBAAwB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,CAAC,MAAM,UAAU,GAAG,UAAU,CAAC;IACnC,IAAI,EAAE,QAAQ;IACd,KAAK,EAAE,uBAAuB;IAC9B,WAAW,EAAE,wCAAwC;IACrD,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;IACnB,GAAG,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,EAAE,EAAE;IAC3C,QAAQ,EAAE,IAAI;IACd,SAAS,EAAE,KAAK;IAChB,GAAG,EAAE,KAAK,IAAI,EAAE;QACd,MAAM,MAAM,GAAG,MAAM,MAAM,EAAE,CAAC;QAC9B,OAAO,EAAE,CAAC;YACR,IAAI,EAAE,MAAM;YACZ,IAAI,EAAE,MAAM;iBACT,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC;iBACxE,IAAI,CAAC,IAAI,CAAC;SACd,CAAC,CAAC;IACL,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { z } from 'zod';
2
+ import { ok } from '../../domain/result.js';
3
+ import { FACETS, inspect } from '../../store/inspect.js';
4
+ import { RunFolder } from '../../store/run.js';
5
+ import { defineTool } from '../types.js';
6
+ export const inspectTool = defineTool({
7
+ name: 'inspect',
8
+ title: 'Read part of a capture',
9
+ description: 'Page through one facet of a capture: summary, section (bundle brief + files), dom, styles, network, motion, animations, runtime, interactions, code, fonts, tokens. `selector` narrows it (section id like 03-pricing, element path prefix, runtime bucket, or a file to read for facet=code). Returns at most `limit` items and a nextOffset.',
10
+ input: z.object({
11
+ run: z.string().describe('Capture folder (the run id from capture).'),
12
+ facet: z.enum(FACETS),
13
+ selector: z.string().optional(),
14
+ viewport: z.string().optional(),
15
+ query: z.string().optional().describe('Case-insensitive text filter on rows.'),
16
+ offset: z.number().int().min(0).default(0),
17
+ limit: z.number().int().min(1).max(500).default(40),
18
+ }),
19
+ cli: { command: 'inspect', positionals: ['run', 'facet', 'selector'] },
20
+ readOnly: true,
21
+ openWorld: false,
22
+ run: async (input) => {
23
+ const opened = await RunFolder.open(input.run);
24
+ if (!opened.ok)
25
+ return opened;
26
+ const result = await inspect(opened.value, {
27
+ facet: input.facet,
28
+ offset: input.offset,
29
+ limit: input.limit,
30
+ ...(input.selector === undefined ? {} : { selector: input.selector }),
31
+ ...(input.viewport === undefined ? {} : { viewport: input.viewport }),
32
+ ...(input.query === undefined ? {} : { query: input.query }),
33
+ });
34
+ if (!result.ok)
35
+ return result;
36
+ const value = result.value;
37
+ const header = `${value.facet}: items ${String(value.offset)}–${String(value.offset + value.items.length)} of ${String(value.total)}${value.nextOffset === null ? '' : ` (next offset ${String(value.nextOffset)})`}${value.note ? `\n${value.note}` : ''}`;
38
+ return ok({ data: value, text: `${header}\n${JSON.stringify(value.items, null, 1)}` });
39
+ },
40
+ });
41
+ //# sourceMappingURL=inspect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspect.js","sourceRoot":"","sources":["../../../src/contracts/tools/inspect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ import { z } from 'zod';
2
+ import { ok } from '../../domain/result.js';
3
+ import { AGENTS, MCP_ENTRY, readGuide, setupProject, TOPICS } from '../../project/method.js';
4
+ import { defineTool } from '../types.js';
5
+ export const guideTool = defineTool({
6
+ name: 'guide',
7
+ title: 'Read the cloning method',
8
+ description: 'Read how to clone a website with multiclone. Call it with topic "method" before starting a clone and follow it step by step. Other topics: stacks (rebuild recipes per technology), fidelity (the compare-and-fix playbook), quality (project standards), capture-format (every file in a capture folder), section-builder (brief for rebuilding one section).',
9
+ input: z.object({
10
+ topic: z.enum(TOPICS).default('method'),
11
+ }),
12
+ cli: { command: 'guide', positionals: ['topic'] },
13
+ readOnly: true,
14
+ openWorld: false,
15
+ run: async (input) => {
16
+ const text = await readGuide(input.topic);
17
+ return ok({ data: { topic: input.topic, text }, text });
18
+ },
19
+ });
20
+ export const setupTool = defineTool({
21
+ name: 'setup',
22
+ title: 'Install multiclone into a project',
23
+ description: 'Install the cloning method into a project folder so the agent knows how to use the multiclone CLI: claude (.claude/skills, agents and the /multiclone command), cursor (.cursor/rules) and agents (.multiclone/GUIDE.md plus a block in AGENTS.md). With mcp it also registers the MCP server (.mcp.json, .cursor/mcp.json); the CLI works without it. Method files are refreshed on every run; existing MCP configs are merged.',
24
+ input: z.object({
25
+ dir: z.string().default('.').describe('Project folder (default: the current folder).'),
26
+ agents: z
27
+ .array(z.enum(AGENTS))
28
+ .default([...AGENTS])
29
+ .describe('Which agents to set up: claude, cursor, agents (default all).'),
30
+ mcp: z.boolean().default(false).describe('Also register the MCP server (optional).'),
31
+ }),
32
+ cli: { command: 'setup', positionals: ['dir'] },
33
+ readOnly: false,
34
+ openWorld: false,
35
+ run: async (input) => {
36
+ const result = await setupProject({ dir: input.dir, agents: input.agents, mcp: input.mcp });
37
+ if (!result.ok)
38
+ return result;
39
+ const { dir, agents, written, skipped } = result.value;
40
+ const entry = JSON.stringify({ mcpServers: { multiclone: MCP_ENTRY } });
41
+ const lines = [
42
+ `Set up multiclone in ${dir} for ${agents.join(', ')} (${String(written.length)} files).`,
43
+ ...skipped.map((file) => `Left ${file} alone: it is not valid JSON. Add this by hand: ${entry}`),
44
+ '',
45
+ 'Next:',
46
+ ' multiclone doctor check Chrome and WebGL',
47
+ ];
48
+ if (agents.includes('claude'))
49
+ lines.push(' Claude Code: start `claude` here and run /multiclone <url>.');
50
+ if (agents.includes('cursor'))
51
+ lines.push(' Cursor: open this folder and ask the agent to clone <url>.');
52
+ if (agents.includes('agents'))
53
+ lines.push(' Other agents: ask the agent to clone <url> (it reads AGENTS.md).');
54
+ lines.push(input.mcp
55
+ ? ' MCP server registered; agents can use the CLI or the MCP tools.'
56
+ : ' Agents drive the `multiclone` CLI. Add --mcp to also register the MCP server.');
57
+ return ok({ data: result.value, text: lines.join('\n') });
58
+ },
59
+ });
60
+ //# sourceMappingURL=method.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"method.js","sourceRoot":"","sources":["../../../src/contracts/tools/method.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ import { z } from 'zod';
2
+ import { ok } from '../../domain/result.js';
3
+ import { startMirror } from '../../mirror/server.js';
4
+ import { defineTool } from '../types.js';
5
+ export const serveMirrorTool = defineTool({
6
+ name: 'serve_mirror',
7
+ title: 'Serve an offline mirror',
8
+ description: 'Serve a capture folder as a working local copy of the page. mode=live replays the original scripts (animations run) with captured storage seeded and service workers blocked; mode=snapshot serves the frozen hydrated DOM without scripts. Captured API calls are also served as a mock grouped into route templates, so other param values get the closest recorded response.',
9
+ input: z.object({
10
+ dir: z.string().describe('Capture folder.'),
11
+ port: z.number().int().min(0).max(65535).default(4400),
12
+ mode: z.enum(['live', 'snapshot']).default('live'),
13
+ seedStorage: z
14
+ .boolean()
15
+ .default(true)
16
+ .describe('Seed captured localStorage, sessionStorage and cookies before page scripts.'),
17
+ }),
18
+ cli: { command: 'serve', positionals: ['dir'], longRunning: true },
19
+ readOnly: true,
20
+ openWorld: false,
21
+ run: async (input, context) => {
22
+ const mirror = await startMirror({
23
+ dir: input.dir,
24
+ port: input.port,
25
+ mode: input.mode,
26
+ seedStorage: input.seedStorage,
27
+ });
28
+ context.mirrors.add(mirror);
29
+ return ok({
30
+ data: {
31
+ url: mirror.url,
32
+ misses: `${mirror.url}__mc/misses`,
33
+ api: `${mirror.url}__mc/api`,
34
+ apiRoutes: mirror.routes.length,
35
+ },
36
+ text: `Mirror running at ${mirror.url}\n misses: ${mirror.url}__mc/misses\n mock API: ${String(mirror.routes.length)} route templates (${mirror.url}__mc/api)`,
37
+ });
38
+ },
39
+ });
40
+ export const stopMirrorTool = defineTool({
41
+ name: 'stop_mirror',
42
+ title: 'Stop mirrors',
43
+ description: 'Stop one mirror by URL, or every mirror started by this server.',
44
+ input: z.object({ url: z.string().optional() }),
45
+ cli: null,
46
+ readOnly: false,
47
+ openWorld: false,
48
+ run: async (input, context) => {
49
+ const stopped = await context.mirrors.stop(input.url);
50
+ return ok({ data: { stopped }, text: `Stopped ${String(stopped.length)} mirror(s).` });
51
+ },
52
+ });
53
+ //# sourceMappingURL=mirror.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mirror.js","sourceRoot":"","sources":["../../../src/contracts/tools/mirror.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ import { z } from 'zod';
2
+ import { ok } from '../../domain/result.js';
3
+ import { exportAssets } from '../../project/assets.js';
4
+ import { scaffold } from '../../project/scaffold.js';
5
+ import { defineTool } from '../types.js';
6
+ export const scaffoldTool = defineTool({
7
+ name: 'scaffold',
8
+ title: 'Create the React rebuild project',
9
+ description: 'Create a strict React + Vite + TypeScript project (ESLint strictTypeChecked, Prettier, path aliases, Lenis provider, hooks, check script, data-mc compare helpers). With `capture` it also installs the captured fonts (@font-face in src/styles/fonts.css), the capture tokens.css, the media files, and a dev proxy that sends same-origin API calls to the mirror mock API.',
10
+ input: z.object({
11
+ dir: z.string(),
12
+ capture: z
13
+ .string()
14
+ .optional()
15
+ .describe('Capture folder to seed fonts, tokens, media and the API proxy from.'),
16
+ title: z.string().optional(),
17
+ name: z.string().optional(),
18
+ port: z.number().int().min(1024).max(65535).optional(),
19
+ mirrorPort: z
20
+ .number()
21
+ .int()
22
+ .min(1)
23
+ .max(65535)
24
+ .optional()
25
+ .describe('Port of `multiclone serve` (default 4400).'),
26
+ }),
27
+ cli: { command: 'scaffold', positionals: ['dir'] },
28
+ readOnly: false,
29
+ openWorld: false,
30
+ run: async (input) => {
31
+ const result = await scaffold({
32
+ dir: input.dir,
33
+ ...(input.title === undefined ? {} : { title: input.title }),
34
+ ...(input.name === undefined ? {} : { name: input.name }),
35
+ ...(input.port === undefined ? {} : { port: input.port }),
36
+ ...(input.capture === undefined ? {} : { capture: input.capture }),
37
+ ...(input.mirrorPort === undefined ? {} : { mirrorPort: input.mirrorPort }),
38
+ });
39
+ const seeded = input.capture
40
+ ? `\nFrom the capture: ${String(result.fonts)} font faces, ${result.tokens ? 'tokens.css' : 'no tokens.css (run analyze)'}, ${String(result.media)} media files${result.proxied.length ? `, API proxy for ${result.proxied.join(', ')} (run \`multiclone serve\` alongside dev)` : ''}.`
41
+ : '';
42
+ return ok({
43
+ data: result,
44
+ text: `Created ${result.dir}${seeded}\nNext: cd "${result.dir}" && npm install && npm run dev`,
45
+ });
46
+ },
47
+ });
48
+ export const assetsTool = defineTool({
49
+ name: 'assets',
50
+ title: 'Copy captured media into the project',
51
+ description: 'Copy captured images, fonts, video, Rive and Lottie files into <project>/public/media and write src/content/asset-map.json (original URL → local path).',
52
+ input: z.object({ captureDir: z.string(), projectDir: z.string() }),
53
+ cli: { command: 'assets', positionals: ['captureDir', 'projectDir'] },
54
+ readOnly: false,
55
+ openWorld: false,
56
+ run: async (input) => {
57
+ const result = await exportAssets(input.captureDir, input.projectDir);
58
+ return ok({ data: result, text: `Copied ${String(result.copied)} files. Map: ${result.map}` });
59
+ },
60
+ });
61
+ //# sourceMappingURL=project.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project.js","sourceRoot":"","sources":["../../../src/contracts/tools/project.ts"],"names":[],"mappings":"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"}