Skip to content

Commit 30f3830

Browse files
committed
docs(skills): gate blocked website captures
1 parent 9ae0007 commit 30f3830

5 files changed

Lines changed: 96 additions & 8 deletions

File tree

skills-manifest.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"files": 121
2727
},
2828
"hyperframes-cli": {
29-
"hash": "42b621ca580541b6",
29+
"hash": "3eab4c5fc1ae8e26",
3030
"files": 11
3131
},
3232
"hyperframes-core": {
@@ -50,7 +50,7 @@
5050
"files": 152
5151
},
5252
"motion-graphics": {
53-
"hash": "0212a19069119e72",
53+
"hash": "50db172cad89b1c7",
5454
"files": 23
5555
},
5656
"music-to-video": {
@@ -62,8 +62,8 @@
6262
"files": 30
6363
},
6464
"product-launch-video": {
65-
"hash": "a0f6b1f4c8131ed2",
66-
"files": 27
65+
"hash": "01fc75da8492f749",
66+
"files": 28
6767
},
6868
"remotion-to-hyperframes": {
6969
"hash": "3a0e6c2affb9f74e",

skills/hyperframes-cli/references/init-and-scaffold.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,32 @@ npx hyperframes capture https://stripe.com # scaffold from a we
3737
npx hyperframes capture https://linear.app -o linear-video # custom output directory
3838
npx hyperframes capture https://example.com --json # JSON output for agents
3939
npx hyperframes capture https://example.com --skip-assets # skip image/SVG download
40+
npx hyperframes capture https://example.com --skip-vision # skip optional AI captions
4041
npx hyperframes capture https://example.com --max-screenshots 12
4142
npx hyperframes capture https://example.com --timeout 60000 # page-load timeout in ms
43+
npx hyperframes capture https://example.com --capture-budget 90000 # post-navigation budget
4244
```
4345

4446
Captures a live URL as an editable HyperFrames project: screenshots become layered scenes, assets are downloaded locally, and the result is a normal project you can `lint` / `preview` / `render`. Use this when the user supplies a URL as the starting point for a video.
4547

48+
`--timeout` bounds page navigation; `--capture-budget` is the separate cooperative budget for work
49+
after navigation (fonts, assets, vision, and contact sheets). The latter is not a hard wall-clock
50+
watchdog and cannot interrupt native work already in flight. An outer caller deadline is therefore a
51+
third, distinct timeout. An outer caller timeout leaves the capture result unknown; it does not prove
52+
HyperFrames hung or that the navigation timeout should be increased. Preserve the last phase and
53+
classify the boundary that fired. `--skip-vision` disables only optional AI image captioning.
54+
55+
For agents, use `--json`. The result includes `ok`, warnings, and `lastPhase`. The command also emits
56+
stable `HYPERFRAMES_CAPTURE_PHASE` records so a watchdog can report the last started, completed, or
57+
degraded phase without retaining sensitive payloads.
58+
59+
Treat a non-zero exit, JSON `ok: false`, or an output `BLOCKED.md` as a **hard stop**. Do not render,
60+
build, or infer brand/design data from partial files in a blocked capture. A successful capture may
61+
degrade an optional phase within budget, but its structural output still has to satisfy the owning
62+
workflow's gate. Exit zero and file existence alone are not semantic success: require the current
63+
invocation's JSON `ok: true`, no `BLOCKED.md`, and artifacts usable for that workflow. Run each retry
64+
into a fresh output directory; never merge or reuse a blocked attempt's partial output.
65+
4666
## skills
4767

4868
```bash

skills/motion-graphics/categories/webpage/module.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,16 @@
44

55
## Source (Step 2)
66

7-
Fetch the page via `hyperframes capture` (DOM + screenshots) or a provided screenshot → frozen project-local image(s)/DOM. `asset_needs`: `{ kind: web, source: <url>, treatment: none }`.
7+
Fetch the page via `hyperframes capture --json` (DOM + screenshots) or a provided screenshot → frozen project-local image(s)/DOM. `asset_needs`: `{ kind: web, source: <url>, treatment: none }`.
8+
9+
Inspect the capture result before building. If `BLOCKED.md` exists, JSON reports `ok: false`, or the
10+
command exits non-zero, stop the URL-capture path and report the reason. Continue with a provided
11+
screenshot only when it was an explicit source from the user (or the user explicitly chooses it
12+
after the failure). Do not animate a blocked partial capture, invent missing DOM, or eyeball element
13+
coordinates from protection/challenge pages.
14+
15+
This web need cannot use the generic asset-free fallback: it requires a usable captured screenshot
16+
or DOM, or a provided screenshot explicitly selected by the user. If none is available, stop.
817

918
## Vocabulary / leans on
1019

skills/product-launch-video/SKILL.md

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,35 @@ Goal: Collect the source material, brand signals, and usable assets for the vide
5252

5353
Classify the input and choose the path. Explicit URL -> capture it and use the site for narration and assets. Pasted script/brief -> save verbatim as `user_script.txt`; `VO_MODE` (verbatim or restructured) comes from `BRIEF.md` — the intent layer asks it when a script arrives (ask once here only if the brief somehow lacks it). Then resolve capture target: URL in text -> use it; brand name only -> `WebSearch`, confirm URL in one line, then crawl; no URL/site (or the brief says don't scrape) -> no-capture path.
5454

55-
Run capture with: `npx hyperframes capture "<URL>" -o ./capture`
55+
Run capture with: `npx hyperframes capture "<URL>" -o ./capture --json`. Keep the default
56+
post-navigation budget unless the caller owns a smaller deadline; then pass a positive
57+
`--capture-budget <milliseconds>` that leaves time for downstream work. `--timeout` controls page
58+
navigation only. Use `--skip-vision` only when optional image captioning is intentionally disabled.
5659

57-
For a site tour or show-it-as-is brief, the captured page is the visual source of truth. Use the real screenshot instead of rebuilding the full website in HTML. If the shot needs internal movement, keep the screenshot as the base and overlay real captured assets at measured positions, or rebuild only the one component that moves. For a scroll shot, animate the viewport over `capture/screenshots/full-page.png` — the 1x plate of the whole document, pixel-exact for a 1920-wide viewport travelling down it. It is absent when the page was too tall to capture in one piece; fall back to the overlapping scroll-position shots in the same directory. Pushing in past 1:1 wants its own 2x capture of that region instead, since the plate has no headroom above 1x. Recreate the whole page only when the user explicitly asks for a stylized interpretation or the capture is unusable.
60+
Inspect the command result and output directory immediately. A non-zero exit, JSON `ok: false`, or
61+
`capture/BLOCKED.md` is a **hard stop** for the capture path: report the recorded reason and do not
62+
consume partial screenshots, DOM, tokens, or assets. Do not manufacture a synthetic no-capture
63+
fallback after a failed URL capture. Continue through the no-capture path only when the original
64+
brief supplied the source material, or when the user explicitly switches to a provided screenshot
65+
or brief after the failure.
66+
67+
Warnings such as `very little text content` together with an empty asset catalog are not proof of a
68+
usable page. For a site tour or show-it-as-is brief, require trustworthy captured structure or a
69+
provided screenshot; if neither exists, stop. Do not invent or rebuild the page merely because the
70+
capture is unusable.
71+
72+
For a site tour or show-it-as-is brief, the captured page is the visual source of truth. Use the real screenshot instead of rebuilding the full website in HTML. If the shot needs internal movement, keep the screenshot as the base and overlay real captured assets at measured positions, or rebuild only the one component that moves. For a scroll shot, animate the viewport over `capture/screenshots/full-page.png` — the 1x plate of the whole document, pixel-exact for a 1920-wide viewport travelling down it. It is absent when the page was too tall to capture in one piece; fall back to the overlapping scroll-position shots in the same directory. Pushing in past 1:1 wants its own 2x capture of that region instead, since the plate has no headroom above 1x. Recreate the whole page only when the user explicitly asks for a stylized interpretation; an unusable capture alone is not authorization.
5873

5974
If `GEMINI_API_KEY`, `GOOGLE_API_KEY`, or an OpenRouter key exists, capture auto-captions assets into `capture/extracted/asset-descriptions.md`. This is not a review gate. Without a vision key, use DOM context and continue.
6075

6176
No-capture path: create `capture/extracted/tokens.json`, `capture/extracted/visible-text.txt`, `capture/extracted/asset-descriptions.md`, and `capture/assets/` by hand. `tokens.json` should be `{ "title": "", "description": "", "colors": [], "fonts": [] }`; fill title/description from the brief when possible. `visible-text.txt` contains the full brief or script. `asset-descriptions.md` should say no assets were captured unless the user gave asset notes.
6277

63-
**Gate:** `capture/extracted/tokens.json`, `capture/extracted/visible-text.txt`, `capture/extracted/asset-descriptions.md`, and `capture/assets/` exist; you can state the brand in one clear sentence. Treat `asset-descriptions.md` as the main asset inventory. If it is missing after real capture, stop and report capture incomplete. If `capture/BLOCKED.md` exists, follow it.
78+
**Gate:** capture JSON reported `ok: true`; `capture/BLOCKED.md` does not exist;
79+
`capture/extracted/tokens.json`, `capture/extracted/visible-text.txt`,
80+
`capture/extracted/asset-descriptions.md`, and `capture/assets/` exist; and you can state the brand in
81+
one clear sentence. Treat `asset-descriptions.md` as the main asset inventory. If it is missing after
82+
real capture, stop and report capture incomplete. Warnings about a degraded optional phase are
83+
acceptable only when this structural gate still passes.
6484

6585
---
6686

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import assert from "node:assert/strict";
2+
import { readFileSync } from "node:fs";
3+
import test from "node:test";
4+
import { fileURLToPath } from "node:url";
5+
6+
function read(relativePath) {
7+
return readFileSync(fileURLToPath(new URL(relativePath, import.meta.url)), "utf8");
8+
}
9+
10+
test("product launch capture treats blocked output as a hard gate", () => {
11+
const skill = read("../SKILL.md");
12+
13+
assert.match(skill, /hyperframes capture[^\n]+--json/);
14+
assert.match(skill, /capture\/BLOCKED\.md[^\n]+hard stop/i);
15+
assert.match(skill, /do not[\s\S]{0,120}synthetic[\s\S]{0,120}fallback/i);
16+
assert.match(skill, /very little text[\s\S]{0,180}empty asset/i);
17+
assert.match(skill, /show-it-as-is[\s\S]{0,240}provided screenshot/i);
18+
});
19+
20+
test("CLI capture reference documents the two budgets and machine diagnostics", () => {
21+
const reference = read("../../hyperframes-cli/references/init-and-scaffold.md");
22+
23+
assert.match(reference, /--capture-budget/);
24+
assert.match(reference, /--skip-vision/);
25+
assert.match(reference, /HYPERFRAMES_CAPTURE_PHASE/);
26+
assert.match(reference, /BLOCKED\.md[^\n]+hard stop/i);
27+
assert.match(reference, /--timeout[^\n]+navigation[^\n]+--capture-budget/i);
28+
assert.match(reference, /fresh output directory/i);
29+
assert.match(reference, /outer caller timeout[\s\S]{0,180}does not prove/i);
30+
});
31+
32+
test("webpage motion workflow refuses blocked capture artifacts", () => {
33+
const module = read("../../motion-graphics/categories/webpage/module.md");
34+
35+
assert.match(module, /BLOCKED\.md[\s\S]{0,120}stop/i);
36+
assert.match(module, /provided[\s\S]{0,120}screenshot[\s\S]{0,120}explicit/i);
37+
assert.match(module, /do not[^\n]+blocked[^\n]+capture/i);
38+
assert.match(module, /asset-free fallback/i);
39+
});

0 commit comments

Comments
 (0)