multiclone 0.0.0-stage → 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 (223) hide show
  1. package/README.md +89 -2
  2. package/agents/section-builder.md +37 -0
  3. package/commands/multiclone.md +20 -0
  4. package/dist/adapters/cli.js +120 -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 +231 -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 +119 -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 +50 -0
  47. package/dist/capture/collectors/index.js.map +1 -0
  48. package/dist/capture/collectors/interactions.js +208 -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 +86 -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 +33 -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 +38 -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 +35 -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 +133 -0
  99. package/dist/compare/session.js.map +1 -0
  100. package/dist/compare/shoot.js +44 -0
  101. package/dist/compare/shoot.js.map +1 -0
  102. package/dist/contracts/catalog.js +24 -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 +71 -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 +56 -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/types.js +20 -0
  125. package/dist/contracts/types.js.map +1 -0
  126. package/dist/core/cdp.js +146 -0
  127. package/dist/core/cdp.js.map +1 -0
  128. package/dist/core/chrome.js +185 -0
  129. package/dist/core/chrome.js.map +1 -0
  130. package/dist/core/files.js +31 -0
  131. package/dist/core/files.js.map +1 -0
  132. package/dist/core/inpage.js +24 -0
  133. package/dist/core/inpage.js.map +1 -0
  134. package/dist/core/log.js +10 -0
  135. package/dist/core/log.js.map +1 -0
  136. package/dist/core/page.js +248 -0
  137. package/dist/core/page.js.map +1 -0
  138. package/dist/doctor.js +37 -0
  139. package/dist/doctor.js.map +1 -0
  140. package/dist/domain/breakpoints.js +47 -0
  141. package/dist/domain/breakpoints.js.map +1 -0
  142. package/dist/domain/budget.js +38 -0
  143. package/dist/domain/budget.js.map +1 -0
  144. package/dist/domain/compare.js +138 -0
  145. package/dist/domain/compare.js.map +1 -0
  146. package/dist/domain/coverage.js +96 -0
  147. package/dist/domain/coverage.js.map +1 -0
  148. package/dist/domain/motion.js +142 -0
  149. package/dist/domain/motion.js.map +1 -0
  150. package/dist/domain/paths.js +55 -0
  151. package/dist/domain/paths.js.map +1 -0
  152. package/dist/domain/result.js +38 -0
  153. package/dist/domain/result.js.map +1 -0
  154. package/dist/domain/routes.js +160 -0
  155. package/dist/domain/routes.js.map +1 -0
  156. package/dist/domain/sections.js +94 -0
  157. package/dist/domain/sections.js.map +1 -0
  158. package/dist/domain/snapshot.js +268 -0
  159. package/dist/domain/snapshot.js.map +1 -0
  160. package/dist/domain/sourcemap.js +95 -0
  161. package/dist/domain/sourcemap.js.map +1 -0
  162. package/dist/domain/stack.js +342 -0
  163. package/dist/domain/stack.js.map +1 -0
  164. package/dist/domain/tokens.js +183 -0
  165. package/dist/domain/tokens.js.map +1 -0
  166. package/dist/domain/types.js +2 -0
  167. package/dist/domain/types.js.map +1 -0
  168. package/dist/domain/viewports.js +45 -0
  169. package/dist/domain/viewports.js.map +1 -0
  170. package/dist/mirror/rewrite.js +56 -0
  171. package/dist/mirror/rewrite.js.map +1 -0
  172. package/dist/mirror/server.js +191 -0
  173. package/dist/mirror/server.js.map +1 -0
  174. package/dist/mirror/store.js +86 -0
  175. package/dist/mirror/store.js.map +1 -0
  176. package/dist/project/assets.js +48 -0
  177. package/dist/project/assets.js.map +1 -0
  178. package/dist/project/method.js +152 -0
  179. package/dist/project/method.js.map +1 -0
  180. package/dist/project/scaffold.js +140 -0
  181. package/dist/project/scaffold.js.map +1 -0
  182. package/dist/store/inspect.js +212 -0
  183. package/dist/store/inspect.js.map +1 -0
  184. package/dist/store/run.js +112 -0
  185. package/dist/store/run.js.map +1 -0
  186. package/dist/store/summary.js +69 -0
  187. package/dist/store/summary.js.map +1 -0
  188. package/dist/version.js +2 -0
  189. package/dist/version.js.map +1 -0
  190. package/package.json +66 -4
  191. package/skills/multiclone/SKILL.md +102 -0
  192. package/skills/multiclone/references/capture-format.md +73 -0
  193. package/skills/multiclone/references/fidelity.md +54 -0
  194. package/skills/multiclone/references/quality.md +47 -0
  195. package/skills/multiclone/references/stacks.md +156 -0
  196. package/templates/react-vite/.prettierignore +5 -0
  197. package/templates/react-vite/.prettierrc.json +6 -0
  198. package/templates/react-vite/eslint.config.js +38 -0
  199. package/templates/react-vite/gitignore +4 -0
  200. package/templates/react-vite/index.html +12 -0
  201. package/templates/react-vite/package.json +36 -0
  202. package/templates/react-vite/src/app/App.tsx +10 -0
  203. package/templates/react-vite/src/app/Page.tsx +4 -0
  204. package/templates/react-vite/src/components/README.md +7 -0
  205. package/templates/react-vite/src/content/README.md +2 -0
  206. package/templates/react-vite/src/hooks/useActiveSection.ts +37 -0
  207. package/templates/react-vite/src/hooks/useInView.ts +45 -0
  208. package/templates/react-vite/src/hooks/useMediaQuery.ts +15 -0
  209. package/templates/react-vite/src/lib/cn.ts +4 -0
  210. package/templates/react-vite/src/lib/math.ts +21 -0
  211. package/templates/react-vite/src/lib/mc.ts +13 -0
  212. package/templates/react-vite/src/lib/media.ts +4 -0
  213. package/templates/react-vite/src/main.tsx +13 -0
  214. package/templates/react-vite/src/providers/SmoothScroll.tsx +24 -0
  215. package/templates/react-vite/src/styles/base.css +42 -0
  216. package/templates/react-vite/src/styles/fonts.css +1 -0
  217. package/templates/react-vite/src/styles/index.css +3 -0
  218. package/templates/react-vite/src/styles/tokens.css +3 -0
  219. package/templates/react-vite/src/vite-env.d.ts +1 -0
  220. package/templates/react-vite/tsconfig.app.json +29 -0
  221. package/templates/react-vite/tsconfig.json +4 -0
  222. package/templates/react-vite/tsconfig.node.json +19 -0
  223. package/templates/react-vite/vite.config.ts +37 -0
@@ -0,0 +1,56 @@
1
+ const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2
+ /**
3
+ * Points absolute URLs at the mirror: the page origin becomes the mirror root and every other
4
+ * captured host is served under `/__mc/<host>`. Handles plain, protocol-relative and
5
+ * JSON-escaped (`https:\/\/`) forms.
6
+ */
7
+ export function rewriteUrls(text, target) {
8
+ const mainHost = new URL(target.origin).host;
9
+ const base = new URL(target.base);
10
+ const hosts = [mainHost, ...target.hosts.filter((host) => host !== mainHost)];
11
+ if (!hosts.length)
12
+ return text;
13
+ const pattern = new RegExp(`(https?:)?(\\\\?/\\\\?/)(${hosts.map(escapeRegExp).join('|')})(?![\\w.-])`, 'g');
14
+ return text.replace(pattern, (_match, scheme, slashes, host) => {
15
+ const escaped = slashes.includes('\\');
16
+ const sep = escaped ? '\\/' : '/';
17
+ const prefix = scheme ? base.protocol : '';
18
+ const suffix = host === mainHost ? '' : `${sep}__mc${sep}${host}`;
19
+ return `${prefix}${slashes}${base.host}${suffix}`;
20
+ });
21
+ }
22
+ /** Mirror-specific clean-up of the main HTML document. */
23
+ export function prepareHtml(html, target, runtime) {
24
+ const cleaned = rewriteUrls(html, target)
25
+ // Subresource integrity hashes no longer match once URLs inside files are rewritten.
26
+ .replace(/\s+integrity=("[^"]*"|'[^']*')/gi, '')
27
+ .replace(/<meta[^>]+http-equiv=["']?content-security-policy["']?[^>]*>/gi, '');
28
+ const tag = `<script data-multiclone>${runtime}</script>`;
29
+ return /<head[^>]*>/i.test(cleaned)
30
+ ? cleaned.replace(/<head[^>]*>/i, (head) => `${head}${tag}`)
31
+ : `${tag}${cleaned}`;
32
+ }
33
+ /** Removes scripts so a hydrated DOM snapshot renders as a frozen, static page. */
34
+ export function stripScripts(html) {
35
+ return html
36
+ .replace(/<script\b(?![^>]*type=["']application\/(ld\+)?json["'])[\s\S]*?<\/script>/gi, '')
37
+ .replace(/<link[^>]+rel=["']?(modulepreload|preload)["']?[^>]*as=["']?script["']?[^>]*>/gi, '')
38
+ .replace(/<link[^>]+rel=["']?modulepreload["']?[^>]*>/gi, '');
39
+ }
40
+ /**
41
+ * Runs first in the mirrored page: routes runtime-built absolute URLs (fetch, XHR) to the
42
+ * mirror and stops service workers from caching the live site.
43
+ */
44
+ export function runtimeScript(target) {
45
+ const config = JSON.stringify({ origin: target.origin, hosts: target.hosts });
46
+ return `(()=>{const c=${config};const r=(u)=>{try{const x=new URL(u,location.href);if(x.origin===c.origin)return location.origin+x.pathname+x.search+x.hash;if(c.hosts.includes(x.host))return location.origin+'/__mc/'+x.host+x.pathname+x.search+x.hash}catch{}return u};const f=window.fetch;window.fetch=(i,o)=>f(typeof i==='string'||i instanceof URL?r(String(i)):new Request(r(i.url),i),o);const op=XMLHttpRequest.prototype.open;XMLHttpRequest.prototype.open=function(m,u,...a){return op.call(this,m,r(String(u)),...a)};if(navigator.serviceWorker){navigator.serviceWorker.register=()=>Promise.reject(new Error('disabled in multiclone mirror'))}})();`;
47
+ }
48
+ /**
49
+ * Seeds captured storage before the first page script, once per browser profile (so the page's
50
+ * own later writes win), keeping theme, consent and A/B state identical to the capture.
51
+ */
52
+ export function storageSeedScript(seed) {
53
+ const data = JSON.stringify(seed).replace(/</g, '\\u003c');
54
+ return `(()=>{try{const s=${data};const k='__mc_seeded';if(localStorage.getItem(k))return;for(const[a,b]of Object.entries(s.local))localStorage.setItem(a,b);for(const[a,b]of Object.entries(s.session))sessionStorage.setItem(a,b);for(const c of s.cookies)document.cookie=c.name+'='+c.value+';path='+(c.path||'/');localStorage.setItem(k,'1')}catch{}})();`;
55
+ }
56
+ //# sourceMappingURL=rewrite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rewrite.js","sourceRoot":"","sources":["../../src/mirror/rewrite.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,191 @@
1
+ import { createServer } from 'node:http';
2
+ import { log } from '../core/log.js';
3
+ import { prepareHtml, rewriteUrls, runtimeScript, storageSeedScript, stripScripts, } from './rewrite.js';
4
+ import { CaptureStore } from './store.js';
5
+ import { buildApiRoutes, matchRoute } from '../domain/routes.js';
6
+ import { readJson } from '../core/files.js';
7
+ import { join } from 'node:path';
8
+ const DROP_HEADERS = new Set([
9
+ 'content-encoding',
10
+ 'content-length',
11
+ 'transfer-encoding',
12
+ 'connection',
13
+ 'content-security-policy',
14
+ 'content-security-policy-report-only',
15
+ 'x-frame-options',
16
+ 'strict-transport-security',
17
+ 'set-cookie',
18
+ 'alt-svc',
19
+ 'report-to',
20
+ 'nel',
21
+ 'etag',
22
+ 'last-modified',
23
+ 'expires',
24
+ 'age',
25
+ 'cache-control',
26
+ 'vary',
27
+ 'cross-origin-embedder-policy',
28
+ 'cross-origin-opener-policy',
29
+ 'cross-origin-resource-policy',
30
+ 'link',
31
+ ]);
32
+ const TEXT_TYPES = /html|css|javascript|json|svg|xml|text\//;
33
+ /** Serves a capture folder as a working offline copy of the page. */
34
+ export async function startMirror(options) {
35
+ const store = await CaptureStore.load(options.dir);
36
+ const routes = buildApiRoutes(store.entries);
37
+ const seed = options.seedStorage === false ? null : await loadSeed(options.dir);
38
+ const host = options.host ?? '127.0.0.1';
39
+ const misses = new Set();
40
+ const mode = options.mode ?? 'live';
41
+ let target = { origin: store.origin, hosts: store.hosts, base: '' };
42
+ const server = createServer((request, response) => {
43
+ handle(request, response).catch((error) => {
44
+ response.writeHead(500, { 'content-type': 'text/plain' });
45
+ response.end(String(error));
46
+ });
47
+ });
48
+ async function handle(request, response) {
49
+ const path = request.url ?? '/';
50
+ if (path === '/__mc/api') {
51
+ response.writeHead(200, { 'content-type': 'application/json' });
52
+ response.end(JSON.stringify(routes, null, 2));
53
+ return;
54
+ }
55
+ if (request.method === 'OPTIONS') {
56
+ response.writeHead(204, {
57
+ 'access-control-allow-origin': '*',
58
+ 'access-control-allow-methods': '*',
59
+ 'access-control-allow-headers': '*',
60
+ });
61
+ response.end();
62
+ return;
63
+ }
64
+ if (path === '/__mc/misses') {
65
+ response.writeHead(200, { 'content-type': 'application/json' });
66
+ response.end(JSON.stringify([...misses], null, 2));
67
+ return;
68
+ }
69
+ const original = toOriginalUrl(path, store.origin);
70
+ const isDocument = original === store.manifest.finalUrl || path === '/';
71
+ if (isDocument && mode === 'snapshot') {
72
+ const dom = stripScripts(await store.readDom('desktop'));
73
+ send(response, 200, 'text/html; charset=utf-8', prepareHtml(dom, target, headScript()));
74
+ return;
75
+ }
76
+ const entry = isDocument
77
+ ? (store.lookup(store.manifest.finalUrl) ?? store.lookup(original))
78
+ : (store.lookup(original, request.method) ?? mockApi(original, request.method ?? 'GET'));
79
+ if (!entry) {
80
+ if (!misses.has(original))
81
+ log(` miss ${original}`);
82
+ misses.add(original);
83
+ response.writeHead(404, { 'content-type': 'text/plain', 'access-control-allow-origin': '*' });
84
+ response.end(`multiclone: not in capture: ${original}`);
85
+ return;
86
+ }
87
+ if (entry.redirectTo) {
88
+ response.writeHead(entry.status, { location: rewriteUrls(entry.redirectTo, target) });
89
+ response.end();
90
+ return;
91
+ }
92
+ await serveEntry(entry, response, isDocument);
93
+ }
94
+ function headScript() {
95
+ return `${runtimeScript(target)}${seed ? storageSeedScript(seed) : ''}`;
96
+ }
97
+ /** Serves an API route template from its closest recorded example (same template, any params). */
98
+ function mockApi(url, method) {
99
+ let parsed;
100
+ try {
101
+ parsed = new URL(url);
102
+ }
103
+ catch {
104
+ return undefined;
105
+ }
106
+ for (const route of routes) {
107
+ if (route.method !== method.toUpperCase() || route.origin !== parsed.origin)
108
+ continue;
109
+ if (matchRoute(route.template, parsed.pathname) === null)
110
+ continue;
111
+ for (const example of route.examples) {
112
+ const entry = store.entries[example.entry];
113
+ if (entry?.file)
114
+ return entry;
115
+ }
116
+ }
117
+ return undefined;
118
+ }
119
+ async function serveEntry(entry, response, isDocument) {
120
+ const body = await store.read(entry);
121
+ const type = entry.headers['content-type'] ?? (entry.mimeType || 'application/octet-stream');
122
+ const headers = {};
123
+ for (const [key, value] of Object.entries(entry.headers)) {
124
+ if (!DROP_HEADERS.has(key) && !key.startsWith(':'))
125
+ headers[key] = value;
126
+ }
127
+ headers['content-type'] = type;
128
+ headers['access-control-allow-origin'] = '*';
129
+ headers['cache-control'] = 'no-store';
130
+ const status = entry.status >= 200 && entry.status < 300 ? 200 : entry.status;
131
+ if (TEXT_TYPES.test(type)) {
132
+ const text = body.toString('utf8');
133
+ const output = isDocument
134
+ ? prepareHtml(text, target, headScript())
135
+ : rewriteUrls(text, target);
136
+ response.writeHead(status, headers);
137
+ response.end(output);
138
+ return;
139
+ }
140
+ response.writeHead(status, headers);
141
+ response.end(body);
142
+ }
143
+ await new Promise((resolve, reject) => {
144
+ server.once('error', reject);
145
+ server.listen(options.port ?? 4400, host, resolve);
146
+ });
147
+ const port = server.address().port;
148
+ const url = `http://${host}:${port}`;
149
+ target = { ...target, base: url };
150
+ return {
151
+ url: `${url}/`,
152
+ misses,
153
+ routes,
154
+ close: () => new Promise((resolve) => {
155
+ server.closeAllConnections();
156
+ server.close(() => {
157
+ resolve();
158
+ });
159
+ }),
160
+ };
161
+ }
162
+ function send(response, status, type, body) {
163
+ response.writeHead(status, {
164
+ 'content-type': type,
165
+ 'cache-control': 'no-store',
166
+ });
167
+ response.end(body);
168
+ }
169
+ /** Maps a mirror path back to the URL it was captured from. */
170
+ export function toOriginalUrl(path, origin) {
171
+ const match = /^\/__mc\/([^/]+)(\/.*)?$/.exec(path);
172
+ if (match?.[1])
173
+ return `https://${match[1]}${match[2] ?? '/'}`;
174
+ return new URL(path, origin).toString();
175
+ }
176
+ async function loadSeed(dir) {
177
+ try {
178
+ const storage = await readJson(join(dir, 'storage.json'));
179
+ return {
180
+ local: storage.local,
181
+ session: storage.session,
182
+ cookies: storage.cookies
183
+ .filter((cookie) => !cookie.httpOnly)
184
+ .map((cookie) => ({ name: cookie.name, value: cookie.value, path: cookie.path })),
185
+ };
186
+ }
187
+ catch {
188
+ return null;
189
+ }
190
+ }
191
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mirror/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { readJson } from '../core/files.js';
4
+ /** Captured responses indexed for replay, with forgiving fallbacks for cache-busting queries. */
5
+ export class CaptureStore {
6
+ dir;
7
+ manifest;
8
+ entries;
9
+ exact = new Map();
10
+ byPath = new Map();
11
+ byMethod = new Map();
12
+ hosts;
13
+ constructor(dir, manifest, entries) {
14
+ this.dir = dir;
15
+ this.manifest = manifest;
16
+ this.entries = entries;
17
+ const hosts = new Set();
18
+ for (const entry of entries) {
19
+ const parsed = new URL(entry.url);
20
+ hosts.add(parsed.host);
21
+ const usable = entry.file !== null || entry.redirectTo !== undefined;
22
+ if (!usable)
23
+ continue;
24
+ const methodKey = `${entry.method} ${entry.url}`;
25
+ if (!this.byMethod.has(methodKey))
26
+ this.byMethod.set(methodKey, entry);
27
+ if (!this.exact.has(entry.url) || this.exact.get(entry.url)?.method !== 'GET') {
28
+ if (entry.method === 'GET' || !this.exact.has(entry.url))
29
+ this.exact.set(entry.url, entry);
30
+ }
31
+ const pathKey = `${parsed.host}${parsed.pathname}`;
32
+ if (!this.byPath.has(pathKey))
33
+ this.byPath.set(pathKey, entry);
34
+ }
35
+ hosts.delete(new URL(manifest.origin).host);
36
+ this.hosts = [...hosts].sort((a, b) => b.length - a.length);
37
+ }
38
+ static async load(dir) {
39
+ const manifest = await readJson(join(dir, 'capture.json'));
40
+ const entries = await readJson(join(dir, 'network.json'));
41
+ return new CaptureStore(dir, manifest, entries);
42
+ }
43
+ get origin() {
44
+ return this.manifest.origin;
45
+ }
46
+ lookup(url, method) {
47
+ if (method !== undefined && method !== 'GET') {
48
+ const exact = this.byMethod.get(`${method} ${url}`);
49
+ if (exact)
50
+ return exact;
51
+ }
52
+ const hit = this.exact.get(url);
53
+ if (hit)
54
+ return hit;
55
+ const parsed = new URL(url);
56
+ for (const scheme of ['https:', 'http:']) {
57
+ const alternate = new URL(url);
58
+ alternate.protocol = scheme;
59
+ const entry = this.exact.get(alternate.toString());
60
+ if (entry)
61
+ return entry;
62
+ }
63
+ const path = parsed.pathname;
64
+ const candidates = [path, path.endsWith('/') ? path.slice(0, -1) : `${path}/`];
65
+ for (const candidate of candidates) {
66
+ const entry = this.byPath.get(`${parsed.host}${candidate}`);
67
+ if (entry)
68
+ return entry;
69
+ }
70
+ return undefined;
71
+ }
72
+ read(entry) {
73
+ if (!entry.file)
74
+ throw new Error(`No body recorded for ${entry.url}`);
75
+ return readFile(join(this.dir, entry.file));
76
+ }
77
+ readDom(viewport) {
78
+ const capture = this.manifest.viewports.find((item) => item.viewport === viewport);
79
+ const first = this.manifest.viewports[0];
80
+ const dom = capture?.dom ?? first?.dom;
81
+ if (!dom)
82
+ throw new Error('This capture has no DOM snapshot.');
83
+ return readFile(join(this.dir, dom), 'utf8');
84
+ }
85
+ }
86
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/mirror/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAG5C,iGAAiG;AACjG,MAAM,OAAO,YAAY;IAOZ;IACA;IACA;IARM,KAAK,GAAG,IAAI,GAAG,EAAwB,CAAC;IACxC,MAAM,GAAG,IAAI,GAAG,EAAwB,CAAC;IACzC,QAAQ,GAAG,IAAI,GAAG,EAAwB,CAAC;IACnD,KAAK,CAAoB;IAElC,YACW,GAAW,EACX,QAAyB,EACzB,OAAgC;QAFhC,QAAG,GAAH,GAAG,CAAQ;QACX,aAAQ,GAAR,QAAQ,CAAiB;QACzB,YAAO,GAAP,OAAO,CAAyB;QAEzC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;QAChC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAClC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACvB,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS,CAAC;YACrE,IAAI,CAAC,MAAM;gBAAE,SAAS;YACtB,MAAM,SAAS,GAAG,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,GAAG,EAAE,CAAC;YACjD,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC;gBAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;YACvE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,MAAM,KAAK,KAAK,EAAE,CAAC;gBAC9E,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC;oBAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAC7F,CAAC;YACD,MAAM,OAAO,GAAG,GAAG,MAAM,CAAC,IAAI,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;YACnD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACjE,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,CAAC,KAAK,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;IAC9D,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAW;QAC3B,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAkB,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC;QAC5E,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAiB,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC;QAC1E,OAAO,IAAI,YAAY,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAClD,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;IAC9B,CAAC;IAED,MAAM,CAAC,GAAW,EAAE,MAAe;QACjC,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;YAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC;YACpD,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAC;QAC1B,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,GAAG;YAAE,OAAO,GAAG,CAAC;QACpB,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,KAAK,MAAM,MAAM,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC;YACzC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;YAC/B,SAAS,CAAC,QAAQ,GAAG,MAAM,CAAC;YAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC;YACnD,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAC;QAC1B,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC;QAC7B,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC;QAC/E,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACnC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,GAAG,SAAS,EAAE,CAAC,CAAC;YAC5D,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAC;QAC1B,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,CAAC,KAAmB;QACtB,IAAI,CAAC,KAAK,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;QACtE,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9C,CAAC;IAED,OAAO,CAAC,QAAgB;QACtB,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;QACnF,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QACzC,MAAM,GAAG,GAAG,OAAO,EAAE,GAAG,IAAI,KAAK,EAAE,GAAG,CAAC;QACvC,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;QAC/D,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;IAC/C,CAAC;CACF"}
@@ -0,0 +1,48 @@
1
+ import { copyFile } from 'node:fs/promises';
2
+ import { extname, join, resolve } from 'node:path';
3
+ import { ensureDir, readJson, writeJson } from '../core/files.js';
4
+ import { log } from '../core/log.js';
5
+ import { CaptureStore } from '../mirror/store.js';
6
+ const MEDIA = /^(image|font|video|audio)\//;
7
+ const MEDIA_EXT = /\.(png|jpe?g|webp|avif|gif|svg|ico|woff2?|ttf|otf|mp4|webm|mov|mp3|riv|lottie|wasm)$/i;
8
+ /**
9
+ * Copies captured media (images, fonts, video, Rive/Lottie) into `public/media/<host>/<path>`
10
+ * and writes `src/content/asset-map.json` mapping each original URL to its local path.
11
+ */
12
+ export async function exportAssets(captureDir, projectDir) {
13
+ const store = await CaptureStore.load(resolve(captureDir));
14
+ const entries = await readJson(join(store.dir, 'network.json'));
15
+ const project = resolve(projectDir);
16
+ const map = {};
17
+ let bytes = 0;
18
+ for (const entry of entries) {
19
+ if (!entry.file)
20
+ continue;
21
+ const url = new URL(entry.url);
22
+ if (!MEDIA.test(entry.mimeType) && !MEDIA_EXT.test(url.pathname))
23
+ continue;
24
+ const safePath = url.pathname
25
+ .split('/')
26
+ .filter(Boolean)
27
+ .map((part) => decodeURIComponent(part)
28
+ .replace(/[^a-zA-Z0-9._-]+/g, '_')
29
+ .slice(0, 80))
30
+ .join('/');
31
+ const ext = extname(safePath) ? '' : extname(entry.file);
32
+ const local = `/media/${url.hostname}/${safePath || 'index'}${ext}`;
33
+ const suffix = url.search
34
+ ? `-${Buffer.from(url.search).toString('base64url').slice(0, 10)}`
35
+ : '';
36
+ const finalLocal = suffix ? local.replace(/(\.[a-z0-9]+)?$/i, `${suffix}$1`) : local;
37
+ const target = join(project, 'public', finalLocal);
38
+ await ensureDir(join(target, '..'));
39
+ await copyFile(join(store.dir, entry.file), target);
40
+ map[entry.url] = finalLocal;
41
+ bytes += entry.bytes;
42
+ }
43
+ const mapPath = join(project, 'src', 'content', 'asset-map.json');
44
+ await writeJson(mapPath, map);
45
+ log(`copied ${Object.keys(map).length} media files (${Math.round(bytes / 1024)} KB) → public/media`);
46
+ return { copied: Object.keys(map).length, bytes, map: mapPath };
47
+ }
48
+ //# sourceMappingURL=assets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assets.js","sourceRoot":"","sources":["../../src/project/assets.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,152 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { copyFile, readFile, readdir, writeFile } from 'node:fs/promises';
3
+ import { dirname, join, relative, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { ensureDir } from '../core/files.js';
6
+ import { err, mcError, ok } from '../domain/result.js';
7
+ /** Package root: the folder holding `skills/`, `agents/`, `commands/` and `templates/`. */
8
+ export const PACKAGE_ROOT = fileURLToPath(new URL('../../', import.meta.url));
9
+ const SKILL_DIR = join(PACKAGE_ROOT, 'skills', 'multiclone');
10
+ const AGENT_FILE = join(PACKAGE_ROOT, 'agents', 'section-builder.md');
11
+ const COMMAND_FILE = join(PACKAGE_ROOT, 'commands', 'multiclone.md');
12
+ export const TOPICS = [
13
+ 'method',
14
+ 'stacks',
15
+ 'fidelity',
16
+ 'quality',
17
+ 'capture-format',
18
+ 'section-builder',
19
+ ];
20
+ export const AGENTS = ['claude', 'cursor', 'agents'];
21
+ /** How agents start the server; npx keeps it working without a global install. */
22
+ export const MCP_ENTRY = { command: 'npx', args: ['-y', 'multiclone', 'mcp'] };
23
+ const START = '<!-- multiclone:start -->';
24
+ const END = '<!-- multiclone:end -->';
25
+ const stripFrontmatter = (text) => text.replace(/^---\n[\s\S]*?\n---\n+/, '').trimStart();
26
+ const topicFile = (topic) => topic === 'method'
27
+ ? join(SKILL_DIR, 'SKILL.md')
28
+ : topic === 'section-builder'
29
+ ? AGENT_FILE
30
+ : join(SKILL_DIR, 'references', `${topic}.md`);
31
+ /** One part of the method as plain markdown, with file links turned into guide topics. */
32
+ export async function readGuide(topic) {
33
+ const text = stripFrontmatter(await readFile(topicFile(topic), 'utf8'));
34
+ return text
35
+ .replace(/\[references\/([\w-]+)\.md\]\(references\/[\w-]+\.md\)/g, 'the `$1` guide')
36
+ .replace(/\[agents\/section-builder\.md\]\(\.\.\/\.\.\/agents\/section-builder\.md\)/g, 'the `section-builder` guide');
37
+ }
38
+ /**
39
+ * Installs the cloning method and the MCP server entry where each agent looks for them.
40
+ * Method files are replaced on every run; JSON configs are merged, never overwritten.
41
+ */
42
+ export async function setupProject(options) {
43
+ const dir = resolve(options.dir);
44
+ if (!existsSync(dir)) {
45
+ return err(mcError('not-found', `${dir} does not exist`, 'Create the folder first.'));
46
+ }
47
+ const agents = [...new Set(options.agents)].sort();
48
+ const written = [];
49
+ const skipped = [];
50
+ const put = async (path, text) => {
51
+ await ensureDir(dirname(path));
52
+ await writeFile(path, text);
53
+ written.push(relative(dir, path));
54
+ };
55
+ const copy = async (from, to) => {
56
+ await ensureDir(dirname(to));
57
+ await copyFile(from, to);
58
+ written.push(relative(dir, to));
59
+ };
60
+ const references = async (target) => {
61
+ for (const name of (await readdir(join(SKILL_DIR, 'references'))).sort()) {
62
+ if (name.endsWith('.md'))
63
+ await copy(join(SKILL_DIR, 'references', name), join(target, name));
64
+ }
65
+ };
66
+ const mcp = async (path) => {
67
+ const merged = await mergeMcpConfig(path);
68
+ if (merged === null)
69
+ skipped.push(relative(dir, path));
70
+ else
71
+ await put(path, merged);
72
+ };
73
+ for (const agent of agents) {
74
+ if (agent === 'claude') {
75
+ const skill = join(dir, '.claude', 'skills', 'multiclone');
76
+ await copy(join(SKILL_DIR, 'SKILL.md'), join(skill, 'SKILL.md'));
77
+ await references(join(skill, 'references'));
78
+ await copy(AGENT_FILE, join(dir, '.claude', 'agents', 'section-builder.md'));
79
+ await copy(COMMAND_FILE, join(dir, '.claude', 'commands', 'multiclone.md'));
80
+ await mcp(join(dir, '.mcp.json'));
81
+ }
82
+ else if (agent === 'cursor') {
83
+ const method = (await readFile(join(SKILL_DIR, 'SKILL.md'), 'utf8'))
84
+ .replace(/^---\n[\s\S]*?\n---\n+/, '')
85
+ .replace(/\]\(references\//g, '](../../.multiclone/references/')
86
+ .replace(/\]\(\.\.\/\.\.\/agents\//g, '](../../.multiclone/');
87
+ const rule = [
88
+ '---',
89
+ 'description: Clone a website from a link with multiclone (capture, rebuild in React, compare). Use when asked to clone, copy, recreate or replicate a site or its animations.',
90
+ 'alwaysApply: false',
91
+ '---',
92
+ '',
93
+ method,
94
+ ].join('\n');
95
+ await put(join(dir, '.cursor', 'rules', 'multiclone.mdc'), rule);
96
+ await references(join(dir, '.multiclone', 'references'));
97
+ await copy(AGENT_FILE, join(dir, '.multiclone', 'section-builder.md'));
98
+ await mcp(join(dir, '.cursor', 'mcp.json'));
99
+ }
100
+ else {
101
+ const guide = (await readFile(join(SKILL_DIR, 'SKILL.md'), 'utf8'))
102
+ .replace(/^---\n[\s\S]*?\n---\n+/, '')
103
+ .replace(/\]\(\.\.\/\.\.\/agents\//g, '](./');
104
+ await put(join(dir, '.multiclone', 'GUIDE.md'), guide);
105
+ await references(join(dir, '.multiclone', 'references'));
106
+ await copy(AGENT_FILE, join(dir, '.multiclone', 'section-builder.md'));
107
+ await put(join(dir, 'AGENTS.md'), await agentsBlock(join(dir, 'AGENTS.md')));
108
+ }
109
+ }
110
+ return ok({ dir, agents, written: [...new Set(written)], skipped });
111
+ }
112
+ /** Adds the multiclone server to an MCP config; null when the existing file is not a JSON object. */
113
+ async function mergeMcpConfig(path) {
114
+ let config = {};
115
+ if (existsSync(path)) {
116
+ try {
117
+ const parsed = JSON.parse(await readFile(path, 'utf8'));
118
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
119
+ return null;
120
+ config = parsed;
121
+ }
122
+ catch {
123
+ return null;
124
+ }
125
+ }
126
+ const current = config.mcpServers;
127
+ const servers = typeof current === 'object' && current !== null && !Array.isArray(current)
128
+ ? current
129
+ : {};
130
+ config.mcpServers = { ...servers, multiclone: MCP_ENTRY };
131
+ return `${JSON.stringify(config, null, 2)}\n`;
132
+ }
133
+ /** AGENTS.md with one marked multiclone block, replaced in place on later runs. */
134
+ async function agentsBlock(path) {
135
+ const block = [
136
+ START,
137
+ '## Cloning websites (multiclone)',
138
+ '',
139
+ 'When asked to clone, copy or recreate a website, follow `.multiclone/GUIDE.md` step by step. Its references are in `.multiclone/references/`.',
140
+ '',
141
+ 'Use the multiclone MCP tools when they are connected; otherwise run the same tools from the terminal with `npx multiclone <command>` (`npx multiclone --help` lists them).',
142
+ END,
143
+ ].join('\n');
144
+ const existing = existsSync(path) ? await readFile(path, 'utf8') : '';
145
+ const start = existing.indexOf(START);
146
+ const end = existing.indexOf(END);
147
+ if (start >= 0 && end > start) {
148
+ return `${existing.slice(0, start)}${block}${existing.slice(end + END.length)}`;
149
+ }
150
+ return existing.trim() ? `${existing.trimEnd()}\n\n${block}\n` : `${block}\n`;
151
+ }
152
+ //# sourceMappingURL=method.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"method.js","sourceRoot":"","sources":["../../src/project/method.ts"],"names":[],"mappings":"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"}