@mvoom/cli
The semantic decompiler for web motion, from your terminal.
The semantic decompiler for web motion, from your terminal. See what moves on a page, measure one piece, and keep every component you own up to date — without leaving the shell.
npm i -g @mvoom/cli # or: npx @mvoom/cli --help
api.mvoom.com opens, point MVOOM_API at a service you run yourself (npm run api in the mvoom repository). The package is published now so the install path is real from day one.mvoom auth # paste your key once mvoom capture https://example.com # what moves there — free mvoom capture https://example.com --piece .hero --save src/motion/hero mvoom sync # every fix, for everything you own, in place
What it is for
Four things, and only four.
| Command | What it does | What it costs |
|---|---|---|
auth | saves your API key, after checking it works | nothing |
capture <url> | lists what moves on that page — each piece, how much, a price range | nothing |
capture <url> --piece <sel> | measures one piece in a real browser, prints the brief | one read — it asks first |
sync | pulls the latest build of every component in mvoom.json | nothing under support |
budget | what your agent may spend on its own — you can only tighten it | nothing |
connect | pairs this folder with your account: buy at mvoom.cc, it lands here | nothing |
auth — the key never touches the command line
$ mvoom auth Paste your API key (it is not echoed back): ✓ Signed in as solo. Key saved to ~/.mvoom/credentials.
The key is read from a prompt, not from an argument: an argument ends up in your shell history, which is a plain file that sometimes syncs between machines. --key exists for a CI, where there is no history and the value comes from a secret — and that is the only place to use it.
It is checked against the service before it is saved. Saving a bad key would leave you with an auth that said "done" and a capture that said 401 ten minutes later, in another command, with no visible relation to the first.
MVOOM_API_KEY in the environment wins over the file. mvoom auth --forget removes it.
chmod 600 does nothing there, so the tool says so instead of pretending. The file lives in your profile — not in a project — because a file inside a repository ends up in the repository.capture — looking is free, measuring is not
$ mvoom capture https://example.com
Looking at https://example.com…
7 things move on that page. 2 worth naming:
01 hero 4 moving · $14.86–33.86
.hero
02 product cards 3 moving · $12.1–27.4
.cards article
Measure one with mvoom capture https://example.com --piece "<selector>". That spends one read.
Pieces marked as noise — a cookie banner, a chat bubble — are left out of the list. The total tells you how much actually moved.
$ mvoom capture https://example.com --piece .hero Measuring spends one read (79 left today). Continue? [y/N] y
Measuring opens the page in a real browser and runs the engine's passes. It spends one of your reads, so it asks first — unless you pass --yes, or there is no terminal, or you asked for --json. In a script, whoever launched it already decided.
What comes back is the brief: duration, easing, trigger, stagger, what could not be measured, and a paragraph to paste into your AI. It is free. The clean component is what costs, and it is bought elsewhere.
--save <path> notes the measured piece in mvoom.json so sync knows where it goes once it is bought. The path is yours: the service never decides where your files live.
sync — every fix, in place
$ mvoom sync = src/motion/hero up to date v1.0.0 ↑ src/motion/cards updated v1.1.0 · src/motion/footer measured, not bought
For every component in mvoom.json, sync asks the service to recompile it and writes the files where the manifest says. Under support, a recompile with the same engine build is not a recompile: nothing is generated, nothing is charged, and sync says "up to date". That is what lets you run it on every start.
--dry says what would change and writes nothing.
sync never writes outside the project. A manifest arrives by git clone, which means anyone can write one. A path with ../ in it, or a file name from the service that tries to escape, is refused with the reason — not resolved "as best it can".MVOOM.JSON
{
"version": 1,
"componentes": [
{ "purchaseId": "pur_…", "ruta": "src/motion/hero", "version": "1.0.0", "etiqueta": "hero" }
]
}
Plain JSON, committed with the project, and it never contains credentials. sync looks for it walking up from the current directory, the way git finds .git.
budget — you can only tighten it
$ mvoom budget --per-action 500 --monthly 5000 ✓ Saved. The service is enforcing these — and it only ever tightens. per autonomous purchase 500 cr $5.00 (plan allows up to 2,000) per month, total 5,000 cr $50.00 (plan allows up to 10,000)
These are the ceilings your agent may spend without asking you. The plan sets the hard ceiling; here you can only go down. Asking for more than the plan allows is trimmed to the ceiling and the tool prints what actually applies — printing what you asked for would make you believe in a limit that is not in force.
connect — buy there, it lands here
$ mvoom connect --dir src/motion Purchases from your account will be written into src/motion. Allow it? [y/N] y ● Connected. device MacBook-Pro since 9/12/2026, 1:48 PM folder /Users/you/project/src/motion Buy at mvoom.cc and it lands here. Ctrl-C disconnects. ↓ src/motion/HeroReveal v1.0.0 · 5 files
The same words — "Connected." — appear on the connect screen of your account, with the device, the time and the folder. Three rules, all of them enforced:
- The terminal opens the channel. It asks the service every few seconds whether anything new
was bought; nothing on your machine listens, and our servers never connect to it.
- The folder is decided here. It is sent to the service so you can see it in your account —
no path ever travels the other way, not even a relative one.
- One terminal, one folder, confirmed the first time (remembered in
~/.mvoom/connected-folders). Closing the terminal disconnects; so does the button on the web — the next question gets a 410 and the CLI stops.
What lands is noted in mvoom.json, so sync knows about it from then on.
--json — for scripts and agents
mvoom capture https://example.com --json | jq '.pieces[].selector'
--json is a mode, not a format. It changes where each thing goes: the result is one object on stdout, and everything else — progress, warnings, errors — goes to stderr, also as JSON. That is what lets you pipe the result and still see the progress, and what lets an agent read an error the same way it reads a success.
{"ok":false,"code":"BAD_KEY","message":"that key is not valid — it may have been rotated"}
Exit code is 0 on success and 1 on any failure. There is no third state.
What this package does not do
| Not included | Why |
|---|---|
| Buying | Needs your signature. Lives in the MCP connector and at mvoom.cc |
| Measuring locally | The engine runs on the service, in a real browser. This asks and waits |
| Logging the key | Never — not in an error, not in verbose mode, not truncated more than the first characters of auth --status |
| A config file in the project | Only mvoom.json, and it holds paths and versions, never credentials |
| Listening | connect polls; it opens no port and accepts no connection |
Zero dependencies
Node brings fetch, parseArgs and readline. Bringing an HTTP client and an argument parser for five commands would impose a dependency tree on whoever installs this — and what is here is exactly what you read in four short files.
Support
| Versioning | Semantic, with a hand-written changelog |
| Security | security@vorluno.dev — first response within 48 hours |
| Licence | MIT. The engine that produces the measurements is not — see ADR-MV016 |
<sub>Built and maintained by Vorluno Software, S.A. Every number this prints was measured in a real browser, never inferred from a screenshot.</sub>