@impractical-ai/motion 0.1.6 → 0.1.9

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.
@@ -10,7 +10,7 @@ build, the render, and the studio the user tweaks in — you provide the
10
10
  direction. Everything runs through the **Impractical MCP server**; the video's
11
11
  source code lives in the cloud, never in the user's project directory.
12
12
 
13
- ## 0. Setup check (once per session)
13
+ ## Setup check (once per session)
14
14
 
15
15
  Call the `account_status` tool (`whoami` is its legacy alias). If the
16
16
  Impractical tools are not available, the current coding agent is not connected.
@@ -19,222 +19,41 @@ Tell the user to run:
19
19
  npx --yes @impractical-ai/motion setup --agent
20
20
 
21
21
  Then ask them to restart or reload their coding agent and type `continue`.
22
- Claude Code users who specifically want the full plugin can run the same command
23
- with `--claude` instead. If the weekly quota is exhausted, call `upgrade` to give
24
- the user the authenticated billing page.
25
-
26
- ## 1. Read the engine before you write anything
27
-
28
- Call **`engine_types`** first. It returns the TypeScript surface a scene is
29
- written against: the `ctx` object, every named text/logo/transition effect with
30
- its options, the named eases, and the determinism rules. Writing a direction
31
- without reading it produces slop.
32
-
33
- ## 2. Make a video
34
-
35
- Before scaffolding, call **`motion_context`** with the user's brief — plus
36
- `brandSlug` and `contentType` when you know them. It returns the current
37
- versioned house skills, the best matching production references, available
38
- template schemas, and rendered reference frames. Study those frames and sources
39
- before choosing a template. The library is live: do not rely on a remembered
40
- catalog from an earlier session.
41
-
42
- When the workspace has personalization enabled, the result also carries the
43
- user's resolved motion profile: `personalStyleSkill` (confirmed preferences —
44
- follow it like house guidance), `acceptedExamples`, a reranked
45
- style/template order, and a `contextId`. Pass that `contextId` to
46
- `create_video` as `motionContextId` so the draft is pinned to the
47
- recommendations that produced it.
48
-
49
- Gather from the user (ask only for what's missing):
50
-
51
- - **Value prop** (required): one or two sentences on what ships and why it matters.
52
- - Optional: their product's front-end code, a brand URL, screen recordings.
53
-
54
- If they gave a brand URL and no kit exists yet:
55
-
56
- brand_list
57
- brand_extract { url: "https://example.com" }
58
- brand_extract { jobId: "<returned run id>", wait: true }
59
-
60
- Then scaffold the project:
61
-
62
- create_video { valueProp: "...", brandSlug?: "example-com", appCode?: "...", templateId?: "...", templateParams?: {...} }
63
-
64
- This creates the project and a draft with starter files. It deliberately does
65
- **not** run a server-side director — you are the director.
66
-
67
- ## 3. Direct it
68
-
69
- A scene is two files, and both must exist for the preview to build:
70
-
71
- - **`App.tsx`** — the stage markup. Plain React and CSS; this is the product UI
72
- on screen.
73
- - **`direction.ts`** — the choreography. `export default (ctx) => { ... }`
74
- against one paused GSAP timeline.
75
-
76
- Write them with `write_draft_files` (whole files) or `edit_draft_file` (exact
77
- string replacement — prefer this for revisions). Every save rebuilds the
78
- preview and returns the committed revision plus its build job id immediately.
79
- Follow that id with `job_status { jobIds: ["..."], wait: true }`; that result is
80
- your compile loop, so read and fix any build error rather than guessing.
81
-
82
- Rules that matter:
83
-
84
- - `brand.json` is law for colors and fonts. Read it; do not invent a palette.
85
- - Never edit `project.json`.
86
- - The timeline is rendered by seeking frame by frame. Anything that is not a
87
- pure function of timeline position — `Date.now()`, `Math.random()`,
88
- `setTimeout`, CSS animations, autoplaying `<video>` — desynchronises the
89
- export. Use `ctx.video()` for footage.
90
- - Only `.ts/.tsx/.js/.jsx/.css/.json` files sync, max 100 per draft.
91
-
92
- Craft, briefly — the full language is `guidance.motionDirection` in the
93
- `motion_context` result. That copy is live and versioned; this summary is a
94
- snapshot, so when the two disagree, the tool result wins.
95
-
96
- - **Beat it out before writing code.** Every beat needs a time, a thing on
97
- screen, and a move. A beat with no move is not a beat.
98
- - **Text enters on `rise`** — `text.reveal(el, 'rise')` on `promo-out`, lines
99
- ~0.2s apart. Do *not* default type to `'blur'`: word-by-word de-blur reads as
100
- out-of-focus, and across a stagger it turns to mush. Blur is for whole-frame
101
- moves — `fx.spike(el, 'blur')` alongside a whip or dive, where it hides the
102
- cut. Never animate blur above 20px.
103
- - **Every ease is a named house curve.** `ease-out-*` for entrances,
104
- `ease-in-out-*` for travel and every camera move, `ease-in-*` for exits and
105
- dives only. Never leave an ease unspecified; never put `ease-in-*` on an
106
- entrance. The promo subset: `promo-out`, `promo-soft`, `promo-inout`,
107
- `promo-in`, `launch-out`.
108
- - **Fast**: entrances 0.4–0.6s, exits shorter, staggers ~0.2s apart, nothing
109
- over 1s but a deliberate hold drift or slow push.
110
- - **Nothing is ever static.** Every hold carries a slow drift or camera push.
111
- - Hold long enough to read. A beat that looks legible scrubbing the timeline is
112
- usually still too fast on screen.
113
- - Cut hard between sections; crossfading backgrounds reads as cheap.
114
- - One idea per beat. If two things move for different reasons at once, split them.
115
-
116
- ### The five moves that separate a real video from a generated one
117
-
118
- These are the engine's `ctx.fx` and `ctx.mask` kits. Reaching for them is most
119
- of the gap between output that looks authored and output that looks scripted.
120
- Full signatures are in the engine types; the rules of thumb are:
121
-
122
- 1. **Hide every whip cut with one spike, never two tweens.**
123
- ```ts
124
- camera.move(camera.framing('[data-next]'), { duration: 0.28, ease: 'promo-inout' }, 'whip')
125
- fx.spike('[data-panel]', 'blur', { amount: 14 }, 'whip') // ONE call
126
- ```
127
- Hand-writing `tl.to(stage, { filter: 'blur(12px)' })` up and back is the
128
- single most common way this looks amateur: the two tweens drift out of sync
129
- and the user gets two unlinked controls. `fx.spike` is one tween, in and
130
- out, locked to the move.
131
-
132
- 2. **Feather full-frame reveals.** `mask.reveal(el, 'feather', { direction, softness: 0.3 })`
133
- — a hard edge across a whole frame reads cheap, and `clip-path` cannot make
134
- a soft one at all. Use `wipe`/`iris`/`slit`/`barn` when you want the hard
135
- edge deliberately; `radial` and `blinds` for softer variety.
136
-
137
- 3. **Land on light, not on opacity.** One `fx.spike(el, 'bloom')` as a beat
138
- settles reads as a light source. Fading opacity reads as a slideshow.
139
-
140
- 4. **Put colour inside the letters.** `mask.fill(el, { gradient, drift })` uses
141
- `background-clip: text`, so the gradient lives in the glyphs and moves
142
- through them. Far stronger than a coloured headline, and one call.
143
-
144
- 5. **Composite light instead of shipping a PNG.**
145
- `mask.blend(disc, 'screen')` behind type gives glow with no asset.
146
- `multiply` for ink on cream, `luminosity` for duotone.
147
-
148
- Two things to get right or the effect misfires:
149
-
150
- - **Grain must be continuous.** Apply it once to a full-frame plate that sits
151
- above every beat and is never cut (`fx.apply(plate, 'grain', { amount: 0.2 })`),
152
- or it visibly blinks at each hard cut.
153
- - **Effects are seasoning.** One or two per beat. `displace`, `chroma`, and
154
- `threshold` are accents for a single hit, not a look to hold.
155
-
156
- Ask `motion_context` for the **`optical`** style to see all of this composed in
157
- one reference, or the **`whip-cut`**, **`soft-reveal`**, and **`gradient-type`**
158
- templates for a working scene you can adapt.
159
-
160
- ## 4. Look at your work
161
-
162
- get_frames { draftId: "...", times: [0, 1, 2, 3] }
163
-
164
- This renders real frames on the user's machine and returns them as images. Ask
165
- for several times in one call — once the page is open the extra frames are
166
- nearly free.
167
-
168
- **Always look at frames before telling the user a change is done.** If a frame
169
- shows text clipped, an element off-frame, or a blank region, fix it and look
170
- again. This is the difference between shipping a video and shipping a guess.
171
-
172
- ## 5. Export and hand off
173
-
174
- export_video { draftId: "..." }
175
- job_status { jobIds: ["<returned job id>"], wait: true }
176
- studio_link { projectId: "...", slug: "..." }
177
-
178
- Always give the user the studio link — that is where they watch the cuts and
179
- tweak visually. The export URL expires, so only export once they are happy.
22
+ Claude Code users who specifically want the full plugin can run the same
23
+ command with `--claude` instead. If the weekly quota is exhausted, call
24
+ `upgrade` to give the user the authenticated billing page.
25
+
26
+ ## Get the playbook, then follow it
27
+
28
+ The full, current director playbook is SERVED, not bundled: call **`motion_context`**
29
+ with the user's brief (plus `brandSlug` and `contentType`
30
+ when known). Its `guidance.director` field carries the complete house playbook
31
+ for this library version — workflow, review discipline, export rules. Read it
32
+ and follow it before writing any scene code. `engine_types` returns the
33
+ TypeScript contract a scene is written against; read it before writing a
34
+ direction.
35
+
36
+ ## The tool loop (the shape; the served playbook has the craft)
37
+
38
+ Create with `create_video`, then write `App.tsx` and `direction.ts` with
39
+ `write_draft_files` (whole files) or `edit_draft_file` (exact string
40
+ replacement — prefer it for revisions). Every save rebuilds the preview and
41
+ returns the committed revision plus its build job id immediately; follow that
42
+ id with `job_status` and fix any build error it returns. Call `install_kits`
43
+ once right after `create_video` — the kit sources are served into the draft.
44
+ `brand_extract` builds a brand kit from a URL. Call `get_frames` to SEE the
45
+ result before telling the user it is done, then `export_video`, then hand the
46
+ user the `studio_link`.
180
47
 
181
48
  ## Timeouts and lost acknowledgements
182
49
 
183
50
  Build, export, and brand-extraction calls return their job or run id before
184
- waiting. Continue build/export ids with `job_status`, and continue a brand run
185
- with `brand_extract { jobId, wait: true }` (never the URL again). If the MCP
186
- client kills any call and shows only a timeout, the mutation may still have
187
- committed even though its acknowledgement was lost.
188
- **Never blindly retry a mutating or metered call.** Verify state first:
189
-
190
- - After `write_draft_files` / `edit_draft_file`, use `list_draft_files` and
191
- `read_draft_file` to confirm the intended revision and source.
192
- - After an export or other project mutation, use `get_project` / `list_projects`
193
- to check whether it already started or completed.
194
- - After `brand_extract`, continue its returned id with `brand_extract { jobId, wait: true }`. If the
195
- start acknowledgement was lost, check `brand_list` / `brand_get` before
196
- starting another extraction, especially when `reextract: true`.
197
-
198
- Retry only when those reads prove the mutation did not commit. Otherwise
199
- continue from the state you observed; a duplicate edit or export wastes quota.
200
-
201
- ## The personal motion profile
202
-
203
- The `motion_profile` tool manages what Impractical remembers about this user's
204
- taste. The rules are strict because trust in the profile depends on them:
205
-
206
- - **Read before directing.** The personalized context from `motion_context`
207
- already includes the resolved profile; call `motion_profile { action: 'get' }`
208
- only when the user asks about their profile or you need rule keys.
209
- - **`remember` is for explicit requests only** — "remember that I like…",
210
- "always keep my videos under 15 seconds". Never call `remember` for a
211
- preference you merely inferred.
212
- - **Inferred preferences go through `propose`.** If the user's feedback during
213
- this session implies a durable preference, submit it as a structured rule
214
- with `propose`. It becomes a pending proposal that has zero effect until the
215
- user confirms it.
216
- - **Present at most one pending proposal, and only after a completed video.**
217
- If `motion_context` returned `pendingProposal`, mention it once after the
218
- export or when the user is clearly done — one sentence, e.g. "Want me to
219
- remember that you prefer faster pacing? (yes/no)". Call `confirm` or
220
- `reject` with their answer. Never present more than one, never mid-work, and
221
- **never silently confirm**.
222
- - The user can `pause`, `forget` a single rule, or `reset` everything; do these
223
- only when asked.
224
-
225
- ## Conflicts
226
-
227
- The web tweak chat edits the same draft. `edit_draft_file` reads, edits and
228
- writes in one call against the current revision, so ordinary races resolve
229
- themselves. If a tool reports the draft changed underneath your edit, re-read
230
- the file and re-apply your change on top of the new content — never paste stale
231
- content back.
232
-
233
- If a tool reports the draft is locked by a job, wait for that job with
234
- `job_status { wait: true }` and retry.
235
-
236
- ## Costs
237
-
238
- - Preview builds and exports run on Impractical's infrastructure; the direction
239
- thinking is yours, so iterate on the code freely.
240
- - Preview rebuilds take tens of seconds. Batch your edits, then look at frames.
51
+ waiting. If the MCP client kills a call and shows only a timeout, the mutation
52
+ may still have committed. **Never blindly retry a mutating or metered call.**
53
+ Verify state first: after a write, use `list_draft_files` and
54
+ `read_draft_file` to confirm the intended revision; after an export, check
55
+ `get_project`; after `brand_extract`, continue its returned id.
56
+ Retry only when those reads prove the mutation did not commit.
57
+
58
+ Before directing an existing draft, read `notes.json` — the user's queued
59
+ edits come first; mark each handled item `done`.
@@ -13,8 +13,9 @@ import {
13
13
  waitForJobs,
14
14
  withAdmissionReceipt,
15
15
  withMeteredCall
16
- } from "./chunk-KITRCE33.js";
17
- import "./chunk-LJ4YH3YJ.js";
16
+ } from "./chunk-KI5E2464.js";
17
+ import "./chunk-H5TR3QJN.js";
18
+ import "./chunk-77HVPD4G.js";
18
19
  export {
19
20
  ApiContractError,
20
21
  ApiError,
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
9
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
10
+ }) : x)(function(x) {
11
+ if (typeof require !== "undefined") return require.apply(this, arguments);
12
+ throw Error('Dynamic require of "' + x + '" is not supported');
13
+ });
14
+ var __commonJS = (cb, mod) => function __require2() {
15
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
16
+ };
17
+ var __export = (target, all) => {
18
+ for (var name in all)
19
+ __defProp(target, name, { get: all[name], enumerable: true });
20
+ };
21
+ var __copyProps = (to, from, except, desc) => {
22
+ if (from && typeof from === "object" || typeof from === "function") {
23
+ for (let key of __getOwnPropNames(from))
24
+ if (!__hasOwnProp.call(to, key) && key !== except)
25
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
26
+ }
27
+ return to;
28
+ };
29
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
30
+ // If the importer is in node compatibility mode or this is not an ESM
31
+ // file that has been converted to a CommonJS file using a Babel-
32
+ // compatible transform (i.e. "__esModule" has not been set), then set
33
+ // "default" to the CommonJS "module.exports" for node compatibility.
34
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
35
+ mod
36
+ ));
37
+
38
+ export {
39
+ __require,
40
+ __commonJS,
41
+ __export,
42
+ __toESM
43
+ };
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+
3
+ // package.json
4
+ var package_default = {
5
+ name: "@impractical-ai/motion",
6
+ version: "0.1.9",
7
+ private: false,
8
+ description: "Create and refine Impractical Motion videos from coding agents and the terminal.",
9
+ homepage: "https://github.com/Openpod/impractical-motion#readme",
10
+ repository: {
11
+ type: "git",
12
+ url: "git+https://github.com/Openpod/impractical-motion.git"
13
+ },
14
+ bugs: {
15
+ url: "https://github.com/Openpod/impractical-motion/issues"
16
+ },
17
+ license: "UNLICENSED",
18
+ type: "module",
19
+ files: [
20
+ "dist",
21
+ "assets",
22
+ "README.md"
23
+ ],
24
+ bin: {
25
+ impractical: "dist/index.js"
26
+ },
27
+ engines: {
28
+ node: ">=22"
29
+ },
30
+ scripts: {
31
+ build: 'node ../scripts/verify-locked-cli-install.mjs && node ../scripts/build-engine-dts.mjs --cli-only && rm -rf dist && ./node_modules/esbuild/bin/esbuild src/index.ts --define:__BUILD_COMMIT__=\\"$(git rev-parse --short HEAD 2>/dev/null || echo dev)\\" --bundle --platform=node --format=esm --target=node22 --splitting --outdir=dist --external:playwright-core --banner:js="#!/usr/bin/env node" && chmod +x dist/index.js',
32
+ typecheck: "tsc --noEmit",
33
+ test: "npm run typecheck && npm run build && esbuild tests/onboarding.test.ts tests/mcp-tools.test.ts tests/mcp-frames.test.ts tests/mcp-browser-pool.test.ts tests/mcp-motion.test.ts tests/mcp-craft.test.ts tests/mcp-depthkit.test.ts tests/mcp-motion-score.test.ts tests/mcp-install-font.test.ts tests/mcp-metric-catalog.test.ts tests/mcp-text-census.test.ts tests/mcp-phased-flow.test.ts tests/mcp-preflight.test.ts tests/engine-dts.test.ts tests/mcp-connect.test.ts tests/mcp-longpoll.test.ts tests/mcp-agent-timeout.test.ts tests/api-hardening.test.ts --bundle --platform=node --format=esm --packages=external --outdir=dist --out-extension:.js=.mjs && node --test dist/onboarding.test.mjs dist/mcp-tools.test.mjs dist/mcp-frames.test.mjs dist/mcp-browser-pool.test.mjs dist/mcp-motion.test.mjs dist/mcp-craft.test.mjs dist/mcp-depthkit.test.mjs dist/mcp-motion-score.test.mjs dist/mcp-install-font.test.mjs dist/mcp-metric-catalog.test.mjs dist/mcp-text-census.test.mjs dist/mcp-phased-flow.test.mjs dist/mcp-preflight.test.mjs dist/engine-dts.test.mjs dist/mcp-connect.test.mjs dist/mcp-longpoll.test.mjs dist/mcp-agent-timeout.test.mjs dist/api-hardening.test.mjs",
34
+ prepack: "npm run build"
35
+ },
36
+ publishConfig: {
37
+ access: "public"
38
+ },
39
+ devDependencies: {
40
+ "@types/node": "^26.1.0",
41
+ esbuild: "^0.25.0",
42
+ typescript: "^5.6.3"
43
+ },
44
+ dependencies: {
45
+ "@modelcontextprotocol/sdk": "^1.30.0",
46
+ "playwright-core": "^1.55.1",
47
+ zod: "^4.4.3"
48
+ }
49
+ };
50
+
51
+ // src/constants.ts
52
+ if (package_default.name !== "@impractical-ai/motion" || typeof package_default.version !== "string" || !package_default.version) {
53
+ throw new Error("The packaged CLI manifest is missing valid release metadata");
54
+ }
55
+ var PRODUCTION_APP_URL = "https://motion.impractical.ai";
56
+ var PRODUCTION_SUPABASE_URL = "https://uaxjweednprtbhbceify.supabase.co";
57
+ var PACKAGE_NAME = "@impractical-ai/motion";
58
+ var PACKAGE_VERSION = package_default.version;
59
+ var BUILD_COMMIT = "fd0fa24" ? "fd0fa24" : "dev";
60
+ var BUILD_ID = `${PACKAGE_VERSION}+${BUILD_COMMIT}`;
61
+ var PACKAGE_COMMAND = `npx --yes ${PACKAGE_NAME}`;
62
+
63
+ export {
64
+ PRODUCTION_APP_URL,
65
+ PRODUCTION_SUPABASE_URL,
66
+ PACKAGE_NAME,
67
+ PACKAGE_VERSION,
68
+ BUILD_COMMIT,
69
+ PACKAGE_COMMAND
70
+ };
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  PRODUCTION_APP_URL,
4
4
  PRODUCTION_SUPABASE_URL
5
- } from "./chunk-LJ4YH3YJ.js";
5
+ } from "./chunk-H5TR3QJN.js";
6
6
 
7
7
  // src/config.ts
8
8
  import {
@@ -6,14 +6,14 @@ import {
6
6
  loadConfig,
7
7
  recoverCorruptConfig,
8
8
  saveConfig
9
- } from "./chunk-KITRCE33.js";
9
+ } from "./chunk-KI5E2464.js";
10
10
  import {
11
11
  PACKAGE_COMMAND,
12
12
  PACKAGE_NAME,
13
13
  PACKAGE_VERSION,
14
14
  PRODUCTION_APP_URL,
15
15
  PRODUCTION_SUPABASE_URL
16
- } from "./chunk-LJ4YH3YJ.js";
16
+ } from "./chunk-H5TR3QJN.js";
17
17
 
18
18
  // src/onboarding.ts
19
19
  import {
@@ -30,6 +30,7 @@ import { homedir } from "node:os";
30
30
  import { dirname, join } from "node:path";
31
31
  import { fileURLToPath } from "node:url";
32
32
  import { spawnSync } from "node:child_process";
33
+ import { createRequire } from "node:module";
33
34
  var CLAUDE_MARKETPLACE = "Openpod/impractical-agent-plugins";
34
35
  var CLAUDE_PLUGIN = "impractical-motion@impractical";
35
36
  var CLAUDE_MARKETPLACE_NAME = "impractical";
@@ -426,7 +427,7 @@ Official setup guide: ${CLAUDE_INSTALL_URL}`
426
427
  throw new Error(`${label} is required before setup can continue.`);
427
428
  }
428
429
  }
429
- function hasChrome() {
430
+ function hasSystemChrome() {
430
431
  const macPaths = [
431
432
  "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
432
433
  "/Applications/Chromium.app/Contents/MacOS/Chromium"
@@ -437,11 +438,62 @@ function hasChrome() {
437
438
  return !result.error && result.status === 0;
438
439
  });
439
440
  }
441
+ function playwrightCli() {
442
+ try {
443
+ const require_ = createRequire(import.meta.url);
444
+ const cli = join(dirname(require_.resolve("playwright-core/package.json")), "cli.js");
445
+ return existsSync(cli) ? cli : void 0;
446
+ } catch {
447
+ return void 0;
448
+ }
449
+ }
450
+ function managedBrowserPath() {
451
+ const cli = playwrightCli();
452
+ if (!cli) return void 0;
453
+ const probe = spawnSync(
454
+ process.execPath,
455
+ [cli, "install", "--dry-run", "chromium-headless-shell"],
456
+ { encoding: "utf8", windowsHide: true }
457
+ );
458
+ if (probe.error || probe.status !== 0) return void 0;
459
+ const match = /^\s*Install location:\s*(.+?)\s*$/m.exec(probe.stdout ?? "");
460
+ return match?.[1];
461
+ }
462
+ function hasManagedBrowser() {
463
+ const path = managedBrowserPath();
464
+ return Boolean(path && existsSync(path));
465
+ }
466
+ function ensureRenderBrowser(log = () => {
467
+ }) {
468
+ if (hasManagedBrowser()) return;
469
+ const cli = playwrightCli();
470
+ if (!cli) {
471
+ if (hasSystemChrome()) return;
472
+ throw new Error(
473
+ "A browser is required to inspect video frames, and the bundled installer could not be found. Install Google Chrome, or run `npx playwright-core install chromium-headless-shell`."
474
+ );
475
+ }
476
+ log("Installing a headless browser for frame capture (one time, a few hundred MB)...");
477
+ const result = spawnSync(process.execPath, [cli, "install", "chromium-headless-shell"], {
478
+ stdio: "inherit",
479
+ windowsHide: true
480
+ });
481
+ if (!result.error && result.status === 0) {
482
+ log("Headless browser ready.");
483
+ return;
484
+ }
485
+ if (hasSystemChrome()) {
486
+ log("Could not install the headless browser; falling back to your installed Chrome.");
487
+ return;
488
+ }
489
+ throw new Error(
490
+ "Could not install a browser for frame capture, and no Chrome or Chromium was found. Install Google Chrome, or run `npx playwright-core install chromium-headless-shell` and re-run setup."
491
+ );
492
+ }
440
493
  function verifyClaudePrerequisites() {
441
494
  const major = Number(process.versions.node.split(".")[0]);
442
495
  if (!Number.isInteger(major) || major < 22) throw new Error("Node.js 22 or newer is required.");
443
496
  requireCommand("claude", "Claude Code");
444
- if (!hasChrome()) throw new Error("Google Chrome or Chromium is required to inspect video frames.");
445
497
  }
446
498
  function runClaude(args, log, tolerateAlreadyInstalled = false) {
447
499
  const result = commandResult("claude", args);
@@ -618,6 +670,7 @@ async function setup(options) {
618
670
  if (options.installAgentMcp) {
619
671
  verifyAgentPrerequisites();
620
672
  }
673
+ ensureRenderBrowser(options.log);
621
674
  const corruptConfigBackup = recoverCorruptConfig();
622
675
  if (corruptConfigBackup) {
623
676
  options.log(`Backed up invalid configuration to ${corruptConfigBackup}; starting clean.`);
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  setup
4
- } from "./chunk-BUHNO2W2.js";
4
+ } from "./chunk-NG3Q3BYW.js";
5
5
  import {
6
6
  invoke,
7
7
  invokeAt,
@@ -11,8 +11,9 @@ import {
11
11
  saveConfig,
12
12
  studioLink,
13
13
  waitForJobs
14
- } from "./chunk-KITRCE33.js";
15
- import "./chunk-LJ4YH3YJ.js";
14
+ } from "./chunk-KI5E2464.js";
15
+ import "./chunk-H5TR3QJN.js";
16
+ import "./chunk-77HVPD4G.js";
16
17
 
17
18
  // src/commands.ts
18
19
  import { readFileSync as readFileSync2, writeFileSync } from "node:fs";
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  PACKAGE_VERSION
4
- } from "./chunk-LJ4YH3YJ.js";
4
+ } from "./chunk-H5TR3QJN.js";
5
+ import "./chunk-77HVPD4G.js";
5
6
 
6
7
  // src/cli-args.ts
7
8
  import { parseArgs } from "node:util";
@@ -139,11 +140,11 @@ async function runCli(args, deps) {
139
140
  // src/index.ts
140
141
  runCli(process.argv.slice(2), {
141
142
  writeLine: (line) => console.log(line),
142
- loadCommands: () => import("./commands-P5CDHPDP.js"),
143
- loadApi: () => import("./api-C7KJDYN4.js"),
144
- loadMcp: () => import("./stdio-GHDI4S7P.js")
143
+ loadCommands: () => import("./commands-56WH3ZVJ.js"),
144
+ loadApi: () => import("./api-NVZQJBVP.js"),
145
+ loadMcp: () => import("./stdio-MJ4LTXME.js")
145
146
  }).catch(async (error) => {
146
- const { ApiError } = await import("./api-C7KJDYN4.js");
147
+ const { ApiError } = await import("./api-NVZQJBVP.js");
147
148
  if (error instanceof ApiError) {
148
149
  console.error(`Error (${error.status} from ${error.fn}): ${error.message}`);
149
150
  if (error.status === 429) console.error("Weekly MCP allowance reached. Upgrade or wait for Monday UTC.");