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.
- package/README.md +78 -2
- package/agents/section-builder.md +37 -0
- package/commands/multiclone.md +15 -0
- package/dist/adapters/cli.js +129 -0
- package/dist/adapters/cli.js.map +1 -0
- package/dist/adapters/cliArgs.js +63 -0
- package/dist/adapters/cliArgs.js.map +1 -0
- package/dist/adapters/mcp.js +83 -0
- package/dist/adapters/mcp.js.map +1 -0
- package/dist/analyze/analyze.js +85 -0
- package/dist/analyze/analyze.js.map +1 -0
- package/dist/analyze/animations.js +340 -0
- package/dist/analyze/animations.js.map +1 -0
- package/dist/analyze/ast.js +182 -0
- package/dist/analyze/ast.js.map +1 -0
- package/dist/analyze/chunks.js +160 -0
- package/dist/analyze/chunks.js.map +1 -0
- package/dist/analyze/jobs/code.js +117 -0
- package/dist/analyze/jobs/code.js.map +1 -0
- package/dist/analyze/jobs/sections.js +356 -0
- package/dist/analyze/jobs/sections.js.map +1 -0
- package/dist/analyze/pretty.js +20 -0
- package/dist/analyze/pretty.js.map +1 -0
- package/dist/analyze/report.js +101 -0
- package/dist/analyze/report.js.map +1 -0
- package/dist/analyze/worker.js +71 -0
- package/dist/analyze/worker.js.map +1 -0
- package/dist/capture/capture.js +340 -0
- package/dist/capture/capture.js.map +1 -0
- package/dist/capture/collectors/a11y.js +92 -0
- package/dist/capture/collectors/a11y.js.map +1 -0
- package/dist/capture/collectors/animations.js +113 -0
- package/dist/capture/collectors/animations.js.map +1 -0
- package/dist/capture/collectors/breakpoints.js +31 -0
- package/dist/capture/collectors/breakpoints.js.map +1 -0
- package/dist/capture/collectors/consent.js +39 -0
- package/dist/capture/collectors/consent.js.map +1 -0
- package/dist/capture/collectors/coverage.js +121 -0
- package/dist/capture/collectors/coverage.js.map +1 -0
- package/dist/capture/collectors/dom.js +16 -0
- package/dist/capture/collectors/dom.js.map +1 -0
- package/dist/capture/collectors/fonts.js +154 -0
- package/dist/capture/collectors/fonts.js.map +1 -0
- package/dist/capture/collectors/frames.js +93 -0
- package/dist/capture/collectors/frames.js.map +1 -0
- package/dist/capture/collectors/index.js +61 -0
- package/dist/capture/collectors/index.js.map +1 -0
- package/dist/capture/collectors/interactions.js +207 -0
- package/dist/capture/collectors/interactions.js.map +1 -0
- package/dist/capture/collectors/intro.js +84 -0
- package/dist/capture/collectors/intro.js.map +1 -0
- package/dist/capture/collectors/listeners.js +84 -0
- package/dist/capture/collectors/listeners.js.map +1 -0
- package/dist/capture/collectors/motion.js +91 -0
- package/dist/capture/collectors/motion.js.map +1 -0
- package/dist/capture/collectors/network.js +288 -0
- package/dist/capture/collectors/network.js.map +1 -0
- package/dist/capture/collectors/probes.js +34 -0
- package/dist/capture/collectors/probes.js.map +1 -0
- package/dist/capture/collectors/runtime.js +38 -0
- package/dist/capture/collectors/runtime.js.map +1 -0
- package/dist/capture/collectors/scripts.js +98 -0
- package/dist/capture/collectors/scripts.js.map +1 -0
- package/dist/capture/collectors/sections.js +17 -0
- package/dist/capture/collectors/sections.js.map +1 -0
- package/dist/capture/collectors/shots.js +58 -0
- package/dist/capture/collectors/shots.js.map +1 -0
- package/dist/capture/collectors/snapshot.js +47 -0
- package/dist/capture/collectors/snapshot.js.map +1 -0
- package/dist/capture/collectors/sourcemaps.js +153 -0
- package/dist/capture/collectors/sourcemaps.js.map +1 -0
- package/dist/capture/collectors/storage.js +72 -0
- package/dist/capture/collectors/storage.js.map +1 -0
- package/dist/capture/context.js +40 -0
- package/dist/capture/context.js.map +1 -0
- package/dist/capture/hooks/candidates.js +104 -0
- package/dist/capture/hooks/candidates.js.map +1 -0
- package/dist/capture/hooks/consent.js +82 -0
- package/dist/capture/hooks/consent.js.map +1 -0
- package/dist/capture/hooks/recorders.js +507 -0
- package/dist/capture/hooks/recorders.js.map +1 -0
- package/dist/capture/hooks/sections.js +62 -0
- package/dist/capture/hooks/sections.js.map +1 -0
- package/dist/capture/hooks/styles.js +86 -0
- package/dist/capture/hooks/styles.js.map +1 -0
- package/dist/capture/probes.js +491 -0
- package/dist/capture/probes.js.map +1 -0
- package/dist/capture/report.js +115 -0
- package/dist/capture/report.js.map +1 -0
- package/dist/compare/compare.js +391 -0
- package/dist/compare/compare.js.map +1 -0
- package/dist/compare/image.js +105 -0
- package/dist/compare/image.js.map +1 -0
- package/dist/compare/probes.js +129 -0
- package/dist/compare/probes.js.map +1 -0
- package/dist/compare/report.js +99 -0
- package/dist/compare/report.js.map +1 -0
- package/dist/compare/session.js +139 -0
- package/dist/compare/session.js.map +1 -0
- package/dist/compare/shoot.js +62 -0
- package/dist/compare/shoot.js.map +1 -0
- package/dist/contracts/catalog.js +29 -0
- package/dist/contracts/catalog.js.map +1 -0
- package/dist/contracts/context.js +19 -0
- package/dist/contracts/context.js.map +1 -0
- package/dist/contracts/fields.js +16 -0
- package/dist/contracts/fields.js.map +1 -0
- package/dist/contracts/tools/analyze.js +38 -0
- package/dist/contracts/tools/analyze.js.map +1 -0
- package/dist/contracts/tools/capture.js +87 -0
- package/dist/contracts/tools/capture.js.map +1 -0
- package/dist/contracts/tools/compare.js +142 -0
- package/dist/contracts/tools/compare.js.map +1 -0
- package/dist/contracts/tools/doctor.js +23 -0
- package/dist/contracts/tools/doctor.js.map +1 -0
- package/dist/contracts/tools/inspect.js +41 -0
- package/dist/contracts/tools/inspect.js.map +1 -0
- package/dist/contracts/tools/method.js +60 -0
- package/dist/contracts/tools/method.js.map +1 -0
- package/dist/contracts/tools/mirror.js +53 -0
- package/dist/contracts/tools/mirror.js.map +1 -0
- package/dist/contracts/tools/project.js +61 -0
- package/dist/contracts/tools/project.js.map +1 -0
- package/dist/contracts/tools/session.js +195 -0
- package/dist/contracts/tools/session.js.map +1 -0
- package/dist/contracts/types.js +20 -0
- package/dist/contracts/types.js.map +1 -0
- package/dist/core/cdp.js +146 -0
- package/dist/core/cdp.js.map +1 -0
- package/dist/core/chrome.js +185 -0
- package/dist/core/chrome.js.map +1 -0
- package/dist/core/files.js +31 -0
- package/dist/core/files.js.map +1 -0
- package/dist/core/inpage.js +24 -0
- package/dist/core/inpage.js.map +1 -0
- package/dist/core/log.js +10 -0
- package/dist/core/log.js.map +1 -0
- package/dist/core/page.js +269 -0
- package/dist/core/page.js.map +1 -0
- package/dist/doctor.js +37 -0
- package/dist/doctor.js.map +1 -0
- package/dist/domain/breakpoints.js +47 -0
- package/dist/domain/breakpoints.js.map +1 -0
- package/dist/domain/budget.js +38 -0
- package/dist/domain/budget.js.map +1 -0
- package/dist/domain/compare.js +138 -0
- package/dist/domain/compare.js.map +1 -0
- package/dist/domain/coverage.js +96 -0
- package/dist/domain/coverage.js.map +1 -0
- package/dist/domain/motion.js +142 -0
- package/dist/domain/motion.js.map +1 -0
- package/dist/domain/paths.js +55 -0
- package/dist/domain/paths.js.map +1 -0
- package/dist/domain/result.js +38 -0
- package/dist/domain/result.js.map +1 -0
- package/dist/domain/routes.js +160 -0
- package/dist/domain/routes.js.map +1 -0
- package/dist/domain/sections.js +94 -0
- package/dist/domain/sections.js.map +1 -0
- package/dist/domain/snapshot.js +268 -0
- package/dist/domain/snapshot.js.map +1 -0
- package/dist/domain/sourcemap.js +95 -0
- package/dist/domain/sourcemap.js.map +1 -0
- package/dist/domain/stack.js +342 -0
- package/dist/domain/stack.js.map +1 -0
- package/dist/domain/tokens.js +183 -0
- package/dist/domain/tokens.js.map +1 -0
- package/dist/domain/types.js +2 -0
- package/dist/domain/types.js.map +1 -0
- package/dist/domain/viewports.js +45 -0
- package/dist/domain/viewports.js.map +1 -0
- package/dist/mirror/rewrite.js +56 -0
- package/dist/mirror/rewrite.js.map +1 -0
- package/dist/mirror/server.js +191 -0
- package/dist/mirror/server.js.map +1 -0
- package/dist/mirror/store.js +86 -0
- package/dist/mirror/store.js.map +1 -0
- package/dist/project/assets.js +69 -0
- package/dist/project/assets.js.map +1 -0
- package/dist/project/draft.js +860 -0
- package/dist/project/draft.js.map +1 -0
- package/dist/project/method.js +154 -0
- package/dist/project/method.js.map +1 -0
- package/dist/project/scaffold.js +142 -0
- package/dist/project/scaffold.js.map +1 -0
- package/dist/session/discover.js +53 -0
- package/dist/session/discover.js.map +1 -0
- package/dist/session/proc.js +113 -0
- package/dist/session/proc.js.map +1 -0
- package/dist/session/progress.js +83 -0
- package/dist/session/progress.js.map +1 -0
- package/dist/session/session.js +450 -0
- package/dist/session/session.js.map +1 -0
- package/dist/session/state.js +47 -0
- package/dist/session/state.js.map +1 -0
- package/dist/store/inspect.js +212 -0
- package/dist/store/inspect.js.map +1 -0
- package/dist/store/run.js +112 -0
- package/dist/store/run.js.map +1 -0
- package/dist/store/summary.js +69 -0
- package/dist/store/summary.js.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/install.sh +29 -0
- package/package.json +67 -4
- package/skills/multiclone/SKILL.md +123 -0
- package/skills/multiclone/references/capture-format.md +73 -0
- package/skills/multiclone/references/fidelity.md +54 -0
- package/skills/multiclone/references/quality.md +47 -0
- package/skills/multiclone/references/stacks.md +156 -0
- package/templates/react-vite/.prettierignore +5 -0
- package/templates/react-vite/.prettierrc.json +6 -0
- package/templates/react-vite/eslint.config.js +38 -0
- package/templates/react-vite/gitignore +4 -0
- package/templates/react-vite/index.html +12 -0
- package/templates/react-vite/package.json +36 -0
- package/templates/react-vite/src/app/App.tsx +15 -0
- package/templates/react-vite/src/app/Page.tsx +11 -0
- package/templates/react-vite/src/app/routes.ts +7 -0
- package/templates/react-vite/src/components/README.md +7 -0
- package/templates/react-vite/src/content/README.md +2 -0
- package/templates/react-vite/src/hooks/useActiveSection.ts +37 -0
- package/templates/react-vite/src/hooks/useInView.ts +45 -0
- package/templates/react-vite/src/hooks/useMediaQuery.ts +15 -0
- package/templates/react-vite/src/lib/cn.ts +4 -0
- package/templates/react-vite/src/lib/math.ts +21 -0
- package/templates/react-vite/src/lib/mc.ts +13 -0
- package/templates/react-vite/src/lib/media.ts +4 -0
- package/templates/react-vite/src/main.tsx +13 -0
- package/templates/react-vite/src/providers/SmoothScroll.tsx +24 -0
- package/templates/react-vite/src/styles/base.css +42 -0
- package/templates/react-vite/src/styles/draft.css +1 -0
- package/templates/react-vite/src/styles/fonts.css +1 -0
- package/templates/react-vite/src/styles/index.css +4 -0
- package/templates/react-vite/src/styles/tokens.css +3 -0
- package/templates/react-vite/src/vite-env.d.ts +1 -0
- package/templates/react-vite/tsconfig.app.json +29 -0
- package/templates/react-vite/tsconfig.json +4 -0
- package/templates/react-vite/tsconfig.node.json +19 -0
- package/templates/react-vite/vite.config.ts +37 -0
package/README.md
CHANGED
|
@@ -1,3 +1,79 @@
|
|
|
1
|
-
#
|
|
1
|
+
# multiclone
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Clone any website from a link. multiclone captures the page, plays it back offline, rebuilds it as a clean React + Vite + TypeScript project with the same design and animations, and scores how close the rebuild is.
|
|
4
|
+
|
|
5
|
+
It runs on your own machine: your Chrome, your CPU and your Claude Code. Nothing is uploaded anywhere.
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
You need Node 22.12 or newer and Google Chrome (Chromium, Edge or Brave also work).
|
|
10
|
+
|
|
11
|
+
1. Install the CLI once: `npm i -g multiclone` (or download `install.sh` from this repo, read it, and run `sh install.sh`; it also checks Chrome).
|
|
12
|
+
2. In the folder where you want the clone: `multiclone setup`. It teaches your agent the method (Claude Code skill and `/multiclone` command, a Cursor rule, and a block in `AGENTS.md` for every other agent). Add `--mcp` if you also want the MCP server; the CLI works without it.
|
|
13
|
+
3. Ask your agent: "clone https://example.com". Or run it yourself: `multiclone start https://example.com`.
|
|
14
|
+
|
|
15
|
+
No global install? Put `npx` in front of every command.
|
|
16
|
+
|
|
17
|
+
## Use it
|
|
18
|
+
|
|
19
|
+
1. The agent asks one question: the landing page only, specific pages, or the whole site.
|
|
20
|
+
2. `multiclone start <url> --scope …` returns in seconds with a **progress link** (for example `http://127.0.0.1:4300/`). Open it: it shows each step live, then the clone itself.
|
|
21
|
+
3. In about 2–3 minutes the first draft is live: a quick capture (desktop and mobile at the same time), a React project with the real fonts, colours and media, and one component per section with the real text, links, images and video. The clone runs on localhost next to an offline copy of the original.
|
|
22
|
+
4. While the agent polishes the draft, multiclone captures the other pages and a deep pass (scroll motion, hovers, clicks, the code that animates) in the background. `multiclone status` shows where it is.
|
|
23
|
+
5. The agent compares the clone with the original per section, motion track and hover, fixes the worst first, and finishes with a localhost link and a score.
|
|
24
|
+
|
|
25
|
+
Run `start` again after a crash or restart: finished captures are reused and drafted pages are never overwritten. `multiclone stop` ends the servers; the files stay.
|
|
26
|
+
|
|
27
|
+
## What's inside
|
|
28
|
+
|
|
29
|
+
- **Capture:** your Chrome (headless or headed, with an optional persistent profile) scrolls the page with real wheel events, so Lenis and GSAP sites move. It dismisses consent banners and records:
|
|
30
|
+
- every response, redirect and WebSocket, plus every script and its source maps;
|
|
31
|
+
- a computed-style DOM snapshot, the hydrated DOM, screenshots and an intro filmstrip per viewport;
|
|
32
|
+
- fonts, storage, CSS breakpoints and hydration data (Next, Remix, Nuxt and others);
|
|
33
|
+
- the scroll motion map (transform, opacity, clip, filter, colours, size, text) with exact curves;
|
|
34
|
+
- live animation state: GSAP tweens and ScrollTriggers, Lenis, WAAPI, IntersectionObservers, WebGL shaders, Lottie and Rive;
|
|
35
|
+
- event listeners, the code that runs while things move, and hover, focus and click states.
|
|
36
|
+
- **Analyze:** in an isolated worker it splits the page into section bundles, extracts design tokens, prettifies and splits the code into app and vendor, finds every animation call with file and line, and writes `REPORT.md`.
|
|
37
|
+
- **Inspect:** pages through any part of a capture so big captures never flood the chat.
|
|
38
|
+
- **Mirror:** an offline copy on localhost. `live` mode runs the original scripts with storage seeded; captured API calls become a mock API.
|
|
39
|
+
- **Scaffold:** a strict React 19 + Vite + TypeScript starter (ESLint strictTypeChecked, Prettier, `@` alias, Lenis provider, hooks, `check` script). From a capture it also brings the fonts, `tokens.css`, the media and a dev proxy to the mock API.
|
|
40
|
+
- **Shoot:** screenshots at exact scroll offsets, plus a JavaScript probe for measuring.
|
|
41
|
+
- **Compare:** scores frames, sections (aligned by `data-mc-section`), structure, scroll motion (`data-mc-track`), hover states (`data-mc-hover`) and the intro, with an HTML report and the worst sections to fix next. The score stays below 100 while anything could not be compared.
|
|
42
|
+
- **The skill and agent:** the method Claude follows, recipes for each stack, a fidelity playbook, the capture format, and a `section-builder` agent that rebuilds one section from its bundle.
|
|
43
|
+
|
|
44
|
+
## CLI
|
|
45
|
+
|
|
46
|
+
`multiclone <command>` (or `npx multiclone <command>`). Flags are the tool fields in kebab case; `multiclone <command> --help` lists them.
|
|
47
|
+
|
|
48
|
+
| Command | What it does |
|
|
49
|
+
| ---------------------------------------------------------------------------------------------- | ------------------------------------------ |
|
|
50
|
+
| `multiclone start <url> [--scope landing\|pages\|site] [--pages /a,/b] [--dir folder]` | Capture, draft and serve in the background |
|
|
51
|
+
| `multiclone status [dir] [--wait draft\|pages\|deep]` | Where the session is, with its links |
|
|
52
|
+
| `multiclone stop [dir]` | Stop the session's servers |
|
|
53
|
+
| `multiclone setup [dir] [--agents claude,cursor,agents] [--mcp]` | Teach your agent the method |
|
|
54
|
+
| `multiclone guide [topic]` | Print the method |
|
|
55
|
+
| `multiclone capture <url> [--depth quick\|full] [--viewports desktop,mobile\|auto] [--headed]` | Record and analyse the page |
|
|
56
|
+
| `multiclone draft <capture-dir> <project-dir> [--route /pricing]` | Write section components from a capture |
|
|
57
|
+
| `multiclone analyze <capture-dir>` | Rebuild the derived files |
|
|
58
|
+
| `multiclone inspect <capture-dir> section [03-pricing]` | Page through a capture |
|
|
59
|
+
| `multiclone serve <capture-dir> [--port 4400] [--mode live\|snapshot]` | Offline mirror and mock API |
|
|
60
|
+
| `multiclone scaffold <dir> [--capture capture-dir] [--port 5180]` | New React project |
|
|
61
|
+
| `multiclone assets <capture-dir> <project-dir>` | Copy media in |
|
|
62
|
+
| `multiclone shoot <url> [--positions 0,900] [--probe js]` | Screenshots and measurements |
|
|
63
|
+
| `multiclone compare <capture-dir\|url> <clone-url> [--intro] [--positions]` | Score the clone |
|
|
64
|
+
| `multiclone doctor` | Check the setup |
|
|
65
|
+
| `multiclone mcp` | Optional MCP server with the same tools |
|
|
66
|
+
|
|
67
|
+
Add `--json` for machine-readable output. Set `MULTICLONE_CHROME` to use a specific browser.
|
|
68
|
+
|
|
69
|
+
## Known limits
|
|
70
|
+
|
|
71
|
+
- Server features (logins, search, carts, live data) are rebuilt as static visuals; the mock API only replays what was recorded.
|
|
72
|
+
- WebGL and heavy canvas work need the most iteration. Shaders are captured, but matching them takes rounds of comparing.
|
|
73
|
+
- Some sites block automated browsers. Use `--headed` with `--profile` and pass the check once.
|
|
74
|
+
- Video, random and time-based effects never match pixel for pixel, so judge those frames by eye.
|
|
75
|
+
|
|
76
|
+
## Development
|
|
77
|
+
|
|
78
|
+
- `npm run check`: typecheck, lint, format check, unit tests and build
|
|
79
|
+
- `npm run e2e`: fixture-site capture → mirror → compare
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: section-builder
|
|
3
|
+
description: Rebuilds one section of a multiclone capture as a React component in an existing clone project. Give it the capture folder, the section id and the project folder. Use for pages with many independent sections after the shell (fonts, tokens, header, footer, smooth scroll) is in place.
|
|
4
|
+
tools: Read, Write, Edit, Glob, Grep, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You rebuild exactly one section of a captured website inside an existing React + Vite + TypeScript clone project. You get three things: the capture folder, the section id (for example `03-pricing`) and the project folder.
|
|
8
|
+
|
|
9
|
+
## Read first
|
|
10
|
+
|
|
11
|
+
1. `<capture>/sections/<id>/section.md`: the brief. Follow it.
|
|
12
|
+
2. The crops `<capture>/sections/<id>/<viewport>.png` for every viewport. Look at them.
|
|
13
|
+
3. `dom-<viewport>.txt` and `styles-<viewport>.json` for structure, boxes and exact CSS values; `tokens-<viewport>.json` for local values.
|
|
14
|
+
4. When present: `motion.json` (scroll curves with track ids), `animations.json` and `animations-code.json` (keyframes, library calls with file and line), `interactions.json` (hover, focus and click states with ids), `assets.json` (media used), `hot-code.json` (code that ran while it moved).
|
|
15
|
+
5. `<project>/src/styles/tokens.css`, `src/lib/mc.ts` and existing components, so you reuse what the shell already has.
|
|
16
|
+
|
|
17
|
+
Use the multiclone `inspect` tool (`facet: "section"`, `selector: "<id>"`) for anything too big to read whole.
|
|
18
|
+
|
|
19
|
+
## Build
|
|
20
|
+
|
|
21
|
+
- One component in `<project>/src/components/sections/<Name>.tsx`, plus typed content in `src/content/` when the section has copy or lists. Copy text from the capture; never retype or invent it.
|
|
22
|
+
- Root element: `<section {...mcSection('<id>')}>`.
|
|
23
|
+
- Every element in `motion.json` gets `{...mcTrack(id)}`; every element in `interactions.json` gets `{...mcHover(id)}`.
|
|
24
|
+
- Match the captured values: sizes, spacing, colours, fonts, radii, shadows and the motion curves (start, end, easing). Prefer the existing tokens when a value matches one.
|
|
25
|
+
- Media paths come from `src/content/asset-map.json`.
|
|
26
|
+
- Semantic, accessible markup; respect `prefers-reduced-motion`. Heavy parts load lazily and clean up on unmount.
|
|
27
|
+
- Strict TypeScript: no `any`, no non-null `!`, named exports.
|
|
28
|
+
|
|
29
|
+
## Do not
|
|
30
|
+
|
|
31
|
+
- Touch other sections, the shell, global styles or config, except to add your component to the page when asked.
|
|
32
|
+
- Install packages. If the section needs one that is missing, say so in your report.
|
|
33
|
+
- Run compare or the dev server unless asked.
|
|
34
|
+
|
|
35
|
+
## Finish
|
|
36
|
+
|
|
37
|
+
Run `npm run typecheck` and `npm run lint` in the project and fix what you introduced. Report in a few plain lines: the files you wrote, the tracks and hovers you tagged, and anything approximated.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Clone a website from a link into a React + Vite + TypeScript project with the same design and animations
|
|
3
|
+
argument-hint: <url> [project folder]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Clone this website with the multiclone method: $ARGUMENTS
|
|
7
|
+
|
|
8
|
+
1. Ask the user one short question first: clone just the landing page, specific pages (which ones), or the whole site?
|
|
9
|
+
2. Run `multiclone start <url> --scope <answer>` (add `--pages /a,/b` for specific pages, `--dir <folder>` if a project folder was given). Give the user the progress link it prints right away.
|
|
10
|
+
3. Run `multiclone status --wait draft`. When the first draft is live, give the user the clone link.
|
|
11
|
+
4. Follow the `multiclone` skill (or `multiclone guide`) to polish the draft section by section, then compare and fix.
|
|
12
|
+
|
|
13
|
+
If `multiclone` isn't on the PATH, use `npx multiclone` instead.
|
|
14
|
+
|
|
15
|
+
Keep progress updates short and in plain words. Finish with the localhost link, the final compare score, and an honest list of what is approximated.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { CATALOG } from '../contracts/catalog.js';
|
|
4
|
+
import { MirrorRegistry } from '../contracts/context.js';
|
|
5
|
+
import { setQuiet } from '../core/log.js';
|
|
6
|
+
import { toMcError } from '../domain/result.js';
|
|
7
|
+
import { VERSION } from '../version.js';
|
|
8
|
+
import { coerce, toolFields } from './cliArgs.js';
|
|
9
|
+
const GLOBAL_HELP = ` --json Print the result as JSON
|
|
10
|
+
--quiet No progress output
|
|
11
|
+
-h, --help Help for a command`;
|
|
12
|
+
function fieldHelp(field) {
|
|
13
|
+
const value = field.kind === 'boolean'
|
|
14
|
+
? ''
|
|
15
|
+
: field.kind === 'enum'
|
|
16
|
+
? ` <${field.options.join('|')}>`
|
|
17
|
+
: field.kind.endsWith('list')
|
|
18
|
+
? ' <a,b,c>'
|
|
19
|
+
: ` <${field.kind}>`;
|
|
20
|
+
const fallback = field.defaultValue === undefined || field.kind === 'boolean'
|
|
21
|
+
? ''
|
|
22
|
+
: ` (default ${JSON.stringify(field.defaultValue)})`;
|
|
23
|
+
const negate = field.kind === 'boolean' && field.defaultValue === true ? ` [--no-${field.flag}]` : '';
|
|
24
|
+
return ` --${field.flag}${value}${negate} ${field.description}${fallback}`;
|
|
25
|
+
}
|
|
26
|
+
function usage(tool) {
|
|
27
|
+
const binding = tool.cli;
|
|
28
|
+
if (!binding)
|
|
29
|
+
return '';
|
|
30
|
+
const positionals = binding.positionals.map((key) => `<${key}>`).join(' ');
|
|
31
|
+
const options = toolFields(tool)
|
|
32
|
+
.filter((field) => !binding.positionals.includes(field.key))
|
|
33
|
+
.map(fieldHelp)
|
|
34
|
+
.join('\n');
|
|
35
|
+
return ` multiclone ${binding.command} ${positionals}\n ${tool.title}\n${options}`;
|
|
36
|
+
}
|
|
37
|
+
function help() {
|
|
38
|
+
const tools = CATALOG.filter((tool) => tool.cli !== null).map(usage);
|
|
39
|
+
return `multiclone ${VERSION} — clone any website from a link\n\nStart: multiclone start <url> --scope landing|pages|site (prints a live progress link), then multiclone status --wait draft. Read the method: multiclone guide.\n\nUsage\n${tools.join('\n')}\n multiclone mcp\n Run the MCP server (for Claude Code, Cursor or any MCP agent)\n\nGlobal\n${GLOBAL_HELP}\n`;
|
|
40
|
+
}
|
|
41
|
+
function fail(message) {
|
|
42
|
+
process.stderr.write(`multiclone: ${message}\n`);
|
|
43
|
+
process.exit(2);
|
|
44
|
+
}
|
|
45
|
+
async function runTool(tool, argv) {
|
|
46
|
+
const binding = tool.cli;
|
|
47
|
+
if (!binding)
|
|
48
|
+
fail(`${tool.name} has no CLI command`);
|
|
49
|
+
const fields = toolFields(tool);
|
|
50
|
+
const options = {
|
|
51
|
+
json: { type: 'boolean' },
|
|
52
|
+
quiet: { type: 'boolean' },
|
|
53
|
+
help: { type: 'boolean', short: 'h' },
|
|
54
|
+
};
|
|
55
|
+
for (const field of fields) {
|
|
56
|
+
options[field.flag] = { type: field.kind === 'boolean' ? 'boolean' : 'string' };
|
|
57
|
+
if (field.kind === 'boolean')
|
|
58
|
+
options[`no-${field.flag}`] = { type: 'boolean' };
|
|
59
|
+
}
|
|
60
|
+
const { values, positionals } = parseArgs({ args: argv, options, allowPositionals: true });
|
|
61
|
+
if (values.help === true)
|
|
62
|
+
return void process.stdout.write(`${usage(tool)}\n`);
|
|
63
|
+
if (values.quiet === true)
|
|
64
|
+
setQuiet(true);
|
|
65
|
+
const input = {};
|
|
66
|
+
binding.positionals.forEach((key, index) => {
|
|
67
|
+
const value = positionals[index];
|
|
68
|
+
if (value !== undefined)
|
|
69
|
+
input[key] = value;
|
|
70
|
+
});
|
|
71
|
+
for (const field of fields) {
|
|
72
|
+
const raw = values[field.flag];
|
|
73
|
+
if (typeof raw === 'string' || typeof raw === 'boolean')
|
|
74
|
+
input[field.key] = coerce(field, raw);
|
|
75
|
+
if (field.kind === 'boolean' && values[`no-${field.flag}`] === true)
|
|
76
|
+
input[field.key] = false;
|
|
77
|
+
}
|
|
78
|
+
const mirrors = new MirrorRegistry();
|
|
79
|
+
const controller = new AbortController();
|
|
80
|
+
const stop = () => {
|
|
81
|
+
controller.abort();
|
|
82
|
+
void mirrors.stop().then(() => process.exit(130));
|
|
83
|
+
};
|
|
84
|
+
process.on('SIGINT', stop);
|
|
85
|
+
process.on('SIGTERM', stop);
|
|
86
|
+
const context = { mirrors, signal: controller.signal, surface: 'cli' };
|
|
87
|
+
const result = await tool.run(input, context);
|
|
88
|
+
if (!result.ok) {
|
|
89
|
+
const hint = result.error.hint === undefined ? '' : `\nHint: ${result.error.hint}`;
|
|
90
|
+
fail(`[${result.error.tag}] ${result.error.message}${hint}`);
|
|
91
|
+
}
|
|
92
|
+
const output = result.value;
|
|
93
|
+
process.stdout.write(values.json === true ? `${JSON.stringify(output.data, null, 2)}\n` : `${output.text}\n`);
|
|
94
|
+
if (binding.longRunning)
|
|
95
|
+
process.stderr.write('Running. Ctrl+C to stop.\n');
|
|
96
|
+
}
|
|
97
|
+
async function main() {
|
|
98
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
99
|
+
if (command === '--version' || command === '-v')
|
|
100
|
+
return void process.stdout.write(`${VERSION}\n`);
|
|
101
|
+
if (command === undefined || command === 'help' || command === '--help' || command === '-h') {
|
|
102
|
+
return void process.stdout.write(help());
|
|
103
|
+
}
|
|
104
|
+
if (command === '__session') {
|
|
105
|
+
// Background process behind `multiclone start`; not a public command.
|
|
106
|
+
const { runSessionDaemon } = await import('../session/session.js');
|
|
107
|
+
const workspace = rest[0];
|
|
108
|
+
if (workspace === undefined)
|
|
109
|
+
fail('__session needs a workspace');
|
|
110
|
+
await runSessionDaemon(workspace);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (command === 'mcp') {
|
|
114
|
+
setQuiet(true);
|
|
115
|
+
const { runMcpServer } = await import('./mcp.js');
|
|
116
|
+
await runMcpServer();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const tool = CATALOG.find((entry) => entry.cli?.command === command);
|
|
120
|
+
if (!tool)
|
|
121
|
+
fail(`unknown command "${command}"\n\n${help()}`);
|
|
122
|
+
await runTool(tool, rest);
|
|
123
|
+
}
|
|
124
|
+
main().catch((error) => {
|
|
125
|
+
const mapped = toMcError(error);
|
|
126
|
+
process.stderr.write(`multiclone: [${mapped.tag}] ${mapped.message}\n`);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
});
|
|
129
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/adapters/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAkC,MAAM,WAAW,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAoB,MAAM,yBAAyB,CAAC;AAE3E,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAiB,MAAM,cAAc,CAAC;AAEjE,MAAM,WAAW,GAAG;;mCAEe,CAAC;AAEpC,SAAS,SAAS,CAAC,KAAe;IAChC,MAAM,KAAK,GACT,KAAK,CAAC,IAAI,KAAK,SAAS;QACtB,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM;YACrB,CAAC,CAAC,KAAK,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG;YACjC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAC3B,CAAC,CAAC,UAAU;gBACZ,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,GAAG,CAAC;IAC7B,MAAM,QAAQ,GACZ,KAAK,CAAC,YAAY,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAC1D,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,aAAa,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC;IACzD,MAAM,MAAM,GACV,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,WAAW,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1F,OAAO,WAAW,KAAK,CAAC,IAAI,GAAG,KAAK,GAAG,MAAM,KAAK,KAAK,CAAC,WAAW,GAAG,QAAQ,EAAE,CAAC;AACnF,CAAC;AAED,SAAS,KAAK,CAAC,IAAiB;IAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC;IACzB,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IACxB,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3E,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC;SAC7B,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;SAC3D,GAAG,CAAC,SAAS,CAAC;SACd,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,OAAO,gBAAgB,OAAO,CAAC,OAAO,IAAI,WAAW,WAAW,IAAI,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC;AAC3F,CAAC;AAED,SAAS,IAAI;IACX,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACrE,OAAO,cAAc,OAAO,iNAAiN,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,sGAAsG,WAAW,IAAI,CAAC;AACrX,CAAC;AAED,SAAS,IAAI,CAAC,OAAe;IAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,eAAe,OAAO,IAAI,CAAC,CAAC;IACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,KAAK,UAAU,OAAO,CAAC,IAAiB,EAAE,IAAc;IACtD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC;IACzB,IAAI,CAAC,OAAO;QAAE,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,qBAAqB,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,OAAO,GAA8C;QACzD,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;QACzB,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;QAC1B,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;KACtC,CAAC;IACF,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAChF,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;YAAE,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAClF,CAAC;IACD,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3F,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI;QAAE,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/E,IAAI,MAAM,CAAC,KAAK,KAAK,IAAI;QAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;IAE1C,MAAM,KAAK,GAA4B,EAAE,CAAC;IAC1C,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;QACzC,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;QACjC,IAAI,KAAK,KAAK,SAAS;YAAE,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IAC9C,CAAC,CAAC,CAAC;IACH,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,OAAO,GAAG,KAAK,SAAS;YAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAC/F,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI;YAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IAChG,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,cAAc,EAAE,CAAC;IACrC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,IAAI,GAAG,GAAS,EAAE;QACtB,UAAU,CAAC,KAAK,EAAE,CAAC;QACnB,KAAK,OAAO,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACpD,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC3B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC5B,MAAM,OAAO,GAAgB,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IACpF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC9C,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QACnF,IAAI,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,EAAE,CAAC,CAAC;IAC/D,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC;IAC5B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,MAAM,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,IAAI,CACxF,CAAC;IACF,IAAI,OAAO,CAAC,WAAW;QAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;AAC9E,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACjD,IAAI,OAAO,KAAK,WAAW,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;IAClG,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QAC5F,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC;IACD,IAAI,OAAO,KAAK,WAAW,EAAE,CAAC;QAC5B,sEAAsE;QACtE,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC;QACnE,MAAM,SAAS,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1B,IAAI,SAAS,KAAK,SAAS;YAAE,IAAI,CAAC,6BAA6B,CAAC,CAAC;QACjE,MAAM,gBAAgB,CAAC,SAAS,CAAC,CAAC;QAClC,OAAO;IACT,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;QACtB,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;QAClD,MAAM,YAAY,EAAE,CAAC;QACrB,OAAO;IACT,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC;IACrE,IAAI,CAAC,IAAI;QAAE,IAAI,CAAC,oBAAoB,OAAO,QAAQ,IAAI,EAAE,EAAE,CAAC,CAAC;IAC7D,MAAM,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC5B,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IAChC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,MAAM,CAAC,GAAG,KAAK,MAAM,CAAC,OAAO,IAAI,CAAC,CAAC;IACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
export const kebab = (key) => key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
3
|
+
/** Reads one zod field down to its base type, keeping the outer description and default. */
|
|
4
|
+
export function describeField(key, schema) {
|
|
5
|
+
let current = schema;
|
|
6
|
+
let description = schema.description ?? '';
|
|
7
|
+
let defaultValue = undefined;
|
|
8
|
+
for (;;) {
|
|
9
|
+
const def = current.def;
|
|
10
|
+
if (def.type === 'optional' || def.type === 'nullable') {
|
|
11
|
+
current = current.unwrap();
|
|
12
|
+
}
|
|
13
|
+
else if (def.type === 'default') {
|
|
14
|
+
const wrapped = current;
|
|
15
|
+
defaultValue = wrapped.def.defaultValue;
|
|
16
|
+
current = wrapped.unwrap();
|
|
17
|
+
}
|
|
18
|
+
else
|
|
19
|
+
break;
|
|
20
|
+
description ||= current.description ?? '';
|
|
21
|
+
}
|
|
22
|
+
const base = current.def.type;
|
|
23
|
+
let kind = 'string';
|
|
24
|
+
let options = [];
|
|
25
|
+
if (base === 'number')
|
|
26
|
+
kind = 'number';
|
|
27
|
+
else if (base === 'boolean')
|
|
28
|
+
kind = 'boolean';
|
|
29
|
+
else if (base === 'enum') {
|
|
30
|
+
kind = 'enum';
|
|
31
|
+
options = current.options.map(String);
|
|
32
|
+
}
|
|
33
|
+
else if (base === 'array') {
|
|
34
|
+
const element = current.element.def.type;
|
|
35
|
+
kind = element === 'number' ? 'number-list' : 'string-list';
|
|
36
|
+
}
|
|
37
|
+
return { key, flag: kebab(key), kind, description, options, defaultValue };
|
|
38
|
+
}
|
|
39
|
+
export function toolFields(tool) {
|
|
40
|
+
return Object.entries(tool.input.shape).map(([key, schema]) => describeField(key, schema));
|
|
41
|
+
}
|
|
42
|
+
/** Converts parsed CLI strings to the shapes the tool schema expects; zod validates the rest. */
|
|
43
|
+
export function coerce(field, raw) {
|
|
44
|
+
if (field.kind === 'boolean')
|
|
45
|
+
return raw;
|
|
46
|
+
const text = String(raw);
|
|
47
|
+
if (field.kind === 'number')
|
|
48
|
+
return Number(text);
|
|
49
|
+
if (field.kind === 'number-list')
|
|
50
|
+
return text.split(',').filter(Boolean).map(Number);
|
|
51
|
+
if (field.kind === 'string-list')
|
|
52
|
+
return text.split(',').filter(Boolean);
|
|
53
|
+
if (field.key === 'probe' && text.length < 400 && !text.includes('(')) {
|
|
54
|
+
try {
|
|
55
|
+
return readFileSync(text, 'utf8');
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return text;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return text;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=cliArgs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cliArgs.js","sourceRoot":"","sources":["../../src/adapters/cliArgs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { CATALOG } from '../contracts/catalog.js';
|
|
4
|
+
import { MirrorRegistry } from '../contracts/context.js';
|
|
5
|
+
import { toMcError } from '../domain/result.js';
|
|
6
|
+
import { readGuide, TOPICS } from '../project/method.js';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { VERSION } from '../version.js';
|
|
9
|
+
const render = (output) => ({
|
|
10
|
+
content: [
|
|
11
|
+
{ type: 'text', text: output.text },
|
|
12
|
+
...(output.images ?? []).map((image) => ({
|
|
13
|
+
type: 'image',
|
|
14
|
+
data: image.data.toString('base64'),
|
|
15
|
+
mimeType: image.mimeType,
|
|
16
|
+
})),
|
|
17
|
+
],
|
|
18
|
+
});
|
|
19
|
+
const failure = (message) => ({
|
|
20
|
+
content: [{ type: 'text', text: message }],
|
|
21
|
+
isError: true,
|
|
22
|
+
});
|
|
23
|
+
const INSTRUCTIONS = [
|
|
24
|
+
'multiclone clones a website from a link into a React + Vite + TypeScript project with the same design, animations and interactions.',
|
|
25
|
+
'Before a clone, call the `guide` tool (topic "method") and follow it step by step.',
|
|
26
|
+
'Flow: doctor, capture (also analyses into section bundles), read REPORT.md and sections/<id>/section.md, inspect for anything big, scaffold with capture, rebuild section by section with data-mc-section / data-mc-track / data-mc-hover markers, then serve_mirror and compare against the capture folder until the worst sections are fixed.',
|
|
27
|
+
].join(' ');
|
|
28
|
+
/** MCP server generated from the tool catalog. */
|
|
29
|
+
export async function runMcpServer() {
|
|
30
|
+
const server = new McpServer({ name: 'multiclone', version: VERSION }, { instructions: INSTRUCTIONS });
|
|
31
|
+
const mirrors = new MirrorRegistry();
|
|
32
|
+
for (const tool of CATALOG) {
|
|
33
|
+
server.registerTool(tool.name, {
|
|
34
|
+
title: tool.title,
|
|
35
|
+
description: tool.description,
|
|
36
|
+
inputSchema: tool.input,
|
|
37
|
+
annotations: { readOnlyHint: tool.readOnly, openWorldHint: tool.openWorld },
|
|
38
|
+
}, async (args, extra) => {
|
|
39
|
+
const context = { mirrors, signal: extra.signal, surface: 'mcp' };
|
|
40
|
+
try {
|
|
41
|
+
const result = await tool.run(args, context);
|
|
42
|
+
if (result.ok)
|
|
43
|
+
return render(result.value);
|
|
44
|
+
const hint = result.error.hint === undefined ? '' : `\nHint: ${result.error.hint}`;
|
|
45
|
+
return failure(`[${result.error.tag}] ${result.error.message}${hint}`);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
const mapped = toMcError(error);
|
|
49
|
+
return failure(`[${mapped.tag}] ${mapped.message}`);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
for (const topic of TOPICS) {
|
|
54
|
+
server.registerResource(`guide-${topic}`, `multiclone://guide/${topic}`, { title: `multiclone guide: ${topic}`, mimeType: 'text/markdown' }, async (uri) => ({
|
|
55
|
+
contents: [{ uri: uri.href, mimeType: 'text/markdown', text: await readGuide(topic) }],
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
server.registerPrompt('clone', {
|
|
59
|
+
title: 'Clone a website',
|
|
60
|
+
description: 'Clone a website from a link with the multiclone method.',
|
|
61
|
+
argsSchema: {
|
|
62
|
+
url: z.string().describe('Page to clone.'),
|
|
63
|
+
dir: z.string().optional().describe('Project folder for the rebuild.'),
|
|
64
|
+
},
|
|
65
|
+
}, async ({ url, dir }) => ({
|
|
66
|
+
messages: [
|
|
67
|
+
{
|
|
68
|
+
role: 'user',
|
|
69
|
+
content: {
|
|
70
|
+
type: 'text',
|
|
71
|
+
text: `Clone ${url} with multiclone${dir === undefined ? '' : ` into ${dir}`}. Follow this method step by step; read the other guides with the \`guide\` tool when it points to them.\n\n${await readGuide('method')}`,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
}));
|
|
76
|
+
const close = async () => {
|
|
77
|
+
await mirrors.stop();
|
|
78
|
+
};
|
|
79
|
+
process.on('SIGINT', () => void close().then(() => process.exit(0)));
|
|
80
|
+
process.on('SIGTERM', () => void close().then(() => process.exit(0)));
|
|
81
|
+
await server.connect(new StdioServerTransport());
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=mcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../src/adapters/mcp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { log } from '../core/log.js';
|
|
5
|
+
import { err, mcError, ok } from '../domain/result.js';
|
|
6
|
+
/**
|
|
7
|
+
* Runs the analysis in an isolated Node child process with heap and time limits, so a huge or
|
|
8
|
+
* hostile bundle can't take the capture/MCP process down. Messages flow back as NDJSON.
|
|
9
|
+
*/
|
|
10
|
+
export function analyze(options) {
|
|
11
|
+
const timeoutMs = options.timeoutMs ?? 300_000;
|
|
12
|
+
const heap = options.maxHeapMb ?? 2048;
|
|
13
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const worker = join(here, `worker${import.meta.url.endsWith('.ts') ? '.ts' : '.js'}`);
|
|
15
|
+
const execArgv = import.meta.url.endsWith('.ts') ? ['--import', 'tsx'] : [];
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
const child = spawn(process.execPath, [
|
|
18
|
+
...execArgv,
|
|
19
|
+
`--max-old-space-size=${String(heap)}`,
|
|
20
|
+
worker,
|
|
21
|
+
options.dir,
|
|
22
|
+
String(Date.now() + timeoutMs - 10_000),
|
|
23
|
+
], {
|
|
24
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
25
|
+
});
|
|
26
|
+
let buffer = '';
|
|
27
|
+
let stderr = '';
|
|
28
|
+
let summary = null;
|
|
29
|
+
let failure = null;
|
|
30
|
+
const timer = setTimeout(() => {
|
|
31
|
+
failure = `analysis exceeded ${String(timeoutMs)}ms`;
|
|
32
|
+
child.kill('SIGKILL');
|
|
33
|
+
}, timeoutMs);
|
|
34
|
+
const onAbort = () => {
|
|
35
|
+
failure = 'analysis cancelled';
|
|
36
|
+
child.kill('SIGKILL');
|
|
37
|
+
};
|
|
38
|
+
options.signal?.addEventListener('abort', onAbort, { once: true });
|
|
39
|
+
child.stdout.setEncoding('utf8');
|
|
40
|
+
child.stdout.on('data', (chunk) => {
|
|
41
|
+
buffer += chunk;
|
|
42
|
+
let newline = buffer.indexOf('\n');
|
|
43
|
+
while (newline >= 0) {
|
|
44
|
+
const line = buffer.slice(0, newline).trim();
|
|
45
|
+
buffer = buffer.slice(newline + 1);
|
|
46
|
+
newline = buffer.indexOf('\n');
|
|
47
|
+
if (!line)
|
|
48
|
+
continue;
|
|
49
|
+
let message;
|
|
50
|
+
try {
|
|
51
|
+
message = JSON.parse(line);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (message.type === 'progress')
|
|
57
|
+
log(` analysis: ${message.step}`);
|
|
58
|
+
else if (message.type === 'warning')
|
|
59
|
+
log(` ! ${message.message}`);
|
|
60
|
+
else if (message.type === 'done')
|
|
61
|
+
summary = message.summary;
|
|
62
|
+
else
|
|
63
|
+
failure = message.message;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
child.stderr.setEncoding('utf8');
|
|
67
|
+
child.stderr.on('data', (chunk) => {
|
|
68
|
+
stderr = (stderr + chunk).slice(-4000);
|
|
69
|
+
});
|
|
70
|
+
child.on('close', (code) => {
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
options.signal?.removeEventListener('abort', onAbort);
|
|
73
|
+
if (summary) {
|
|
74
|
+
resolve(ok(summary));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const reason = failure ??
|
|
78
|
+
(/heap out of memory/i.test(stderr)
|
|
79
|
+
? `analysis ran out of memory (${String(heap)} MiB)`
|
|
80
|
+
: `analysis worker exited with code ${String(code)}: ${stderr.trim().slice(-400)}`);
|
|
81
|
+
resolve(err(mcError(failure?.includes('exceeded') ? 'timeout' : 'worker', reason)));
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=analyze.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../src/analyze/analyze.ts"],"names":[],"mappings":"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"}
|