@vosjs/cli 0.40.0 → 0.41.1
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 +6 -3
- package/dist/{chunk-7TDL3N24.js → chunk-4QVEV3HX.js} +252 -27
- package/dist/chunk-4QVEV3HX.js.map +1 -0
- package/dist/cli.js +17 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +57 -0
- package/dist/index.js +1 -1
- package/dist/{run-SDPKU6AV.js → run-ZIOIU3SD.js} +6 -4
- package/package.json +6 -6
- package/schema/digest.schema.json +13 -1
- package/dist/chunk-7TDL3N24.js.map +0 -1
- /package/dist/{run-SDPKU6AV.js.map → run-ZIOIU3SD.js.map} +0 -0
package/README.md
CHANGED
|
@@ -80,7 +80,7 @@ vos plan take --reuse # re-time that cut onto
|
|
|
80
80
|
|
|
81
81
|
| Verb | Flags |
|
|
82
82
|
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
83
|
-
| `record` | `--actions <file>` (or positional) `--url` `--out take` `--strict` `--dry-run` `--keep-frames` `--storage-state <file>` `--browser-arg=<switch>`... `--max-duration <s>` `--background <slug\|url\|none>`
|
|
83
|
+
| `record` | `--actions <file>` (or positional) `--url` `--out take` `--strict` `--dry-run` `--allow-wall` `--keep-frames` `--storage-state <file>` `--browser-arg=<switch>`... `--max-duration <s>` `--background <slug\|url\|none>` |
|
|
84
84
|
| `create` | The `record` flags plus the render flags (`--width` `--height` `--fps` `--format` `--parallel` `--draft` `--frame` `--set`), no `--range`. With `--strict` an incomplete recording exits 2 before anything is rendered |
|
|
85
85
|
| `plan` | `--fresh` (discard the current plan) `--reuse` `--from <doc.json>` (defaults to `<take>/doc.prev.json`) `--style <doc.json\|take\|vosId>` `--with <doc.json\|take\|vosId>[@end\|@start\|@step:<id>\|@<seconds>]` (a template, repeatable) `--background` `--motion` (re-propose the motion) `--headline` `--kicker` `--launch` `--brand` `--music` `--entrance` `--transitions slide\|fade\|scale\|none` `--end-card on\|none\|<ref>` `--captions` `--clicks` `--release` |
|
|
86
86
|
| `digest` | `--out <take>/digest` `--full 960` `--crop 640` (image long edges, the token budget) `--no-frames` `--transcript <file>` (Whisper-shaped segments merged as `said`) `--style <ref>` (report a reference document's style fields) |
|
|
@@ -88,7 +88,9 @@ vos plan take --reuse # re-time that cut onto
|
|
|
88
88
|
| `render` | `--width` `--height` `--fps` `--format webm\|mp4` `--parallel N` (1..16 sessions) `--range a..b` (output seconds; keeps its audio) `--draft` `--frame <kind>` `--background` `--set …`; `out` defaults to `<take>/out.<format>` |
|
|
89
89
|
| `open` | `--studio http://localhost:6060` `--print` (print the URL, do not launch a browser) |
|
|
90
90
|
|
|
91
|
-
**
|
|
91
|
+
**The wall check.** Once the first navigation settles, and before a frame is captured, `record`, `create` and `--dry-run` ask whether the recorder landed where it was sent. A take that met a sign-in instead is refused with exit 4 and a sentence (`asked for /dashboard, landed on /login: no session for app.acme.com`): the asked URL answered 401 or 403, the recorder was sent to an identity provider or a sign-in path, or the page is a sign-in form (one password field, or a one-time-code field) rendered in place. A redirect somewhere else with no sign-in in sight, which is what a site that shows strangers a public page looks like, is refused under `--strict` and in a rehearsal, and said as a warning otherwise. The check runs before a re-record clears anything, so a session that expired since the last take never costs the footage it failed to replace. The way past a wall is a session: `--storage-state <file>`, minted from the test auth the project already has wherever that exists. `--allow-wall` records the page anyway (a video OF a sign-in page is a legitimate take), and the take's `meta.wall` and its digest then say so.
|
|
92
|
+
|
|
93
|
+
**Rehearse before you record.** `vos record … --dry-run` runs every step against the real page, in order, because a later selector usually exists only after an earlier click. Nothing is captured and nothing is written: the pointer lands instead of travelling, every pause is cut to a beat, and the take directory beside it keeps its footage, its cut and its script exactly as they were (a real re-record moves `doc.json` aside; a rehearsal does not). Selector lookups keep their whole timeout, so a miss here is a miss in the take. It prints each step with the rect it resolved, in capture px, which are the rects a pin or `vos callout --step` reads, and exits 2 on any miss or a first load that never settled. Add `--dry-run` to the exact command you were about to run; `--storage-state` and `--browser-arg=` apply to it too, and the `Next:` line it prints carries them, so the command it hands you records what it rehearsed.
|
|
92
94
|
|
|
93
95
|
**Digest first.** `vos digest <take>` is how an agent sees a recording without reading the video. It writes `digest/digest.json`: one moment per thing the cursor track says mattered (click clusters, typing sessions, scroll runs, dwells, idle gaps, head, tail, and frame-diff scene changes), each with source and output extents, a normalized `focus` and `rect` you can copy into a zoom span, per-second `activity`, and the planners' `proposed` span ids; plus one footage frame and a crop around the target per moment, and `sheet.png`, the contact sheet. Read the JSON, then the sheet, then a crop only where you must decide. `vos validate` then warns when a zoom does not contain what was clicked under it, and `vos frames --at-moments` renders the composed output at every moment so a still and its footage crop share an id.
|
|
94
96
|
|
|
@@ -254,7 +256,8 @@ vos push /tmp/take --label "$TAG launch" --yes --json # VOS_API_KEY as a r
|
|
|
254
256
|
## For scripts and agents
|
|
255
257
|
|
|
256
258
|
- **Output.** Logs on stderr, results on stdout. `--json` turns every verb into NDJSON events ending with `{"event":"done",…}`.
|
|
257
|
-
-
|
|
259
|
+
- **`vos <verb> --help`** prints that verb's usage lines and exits 0 (it used to be a usage error).
|
|
260
|
+
- **Exit codes.** 0 ok, 1 error, 2 usage (including `--strict` failures), 3 no browser found, 4 the recorder met a wall (a sign-in, or under `--strict` a redirect away from the asked page) and nothing was recorded or cleared. `--json` carries the verdict as a `wall` event (`kind`, `level`, `asked`, `landed`, `refused`).
|
|
258
261
|
- **`--strict`** on `record` and `create` (agents: always): a skipped selector, a page that never reaches network idle, or a take that hit `--max-duration` exits 2 and lists `skipped[]` (and `capped`) in the done event. The default is lenient (exit 0, `skipped[]` still reported) for exploratory runs.
|
|
259
262
|
- **`--max-duration <s>`** on `record` and `create` defaults to the hosted recording cap, read live from `GET /api/limits` (2 s, fail-open to 30 min when the origin is unreachable): the capture stops there and the done event says so. Cut the flow rather than raising the cap; the platform refuses a longer take.
|
|
260
263
|
- **`create`** is the one-shot verb: record, auto-plan and render in one command and one browser session. The take directory still lands on disk, so the full loop (frames, edit `doc.json`, re-render) stays open afterwards.
|
|
@@ -79,6 +79,7 @@ var EXIT_OK = 0;
|
|
|
79
79
|
var EXIT_ERROR = 1;
|
|
80
80
|
var EXIT_USAGE = 2;
|
|
81
81
|
var EXIT_NO_BROWSER = 3;
|
|
82
|
+
var EXIT_WALL = 4;
|
|
82
83
|
function createReporter(json) {
|
|
83
84
|
return {
|
|
84
85
|
json,
|
|
@@ -6628,6 +6629,135 @@ function paceLine(r) {
|
|
|
6628
6629
|
return `pace: the script asked ${s(r.askedMs)}, the gestures added ${s(r.gestureMs)}, the take ran ${s(r.wallMs)} (${r.overheadPct} % overhead${slow})`;
|
|
6629
6630
|
}
|
|
6630
6631
|
|
|
6632
|
+
// src/plugin/wall.ts
|
|
6633
|
+
var IDP_HOSTS = [
|
|
6634
|
+
"accounts.google.com",
|
|
6635
|
+
"login.microsoftonline.com",
|
|
6636
|
+
"login.live.com",
|
|
6637
|
+
"appleid.apple.com",
|
|
6638
|
+
"auth0.com",
|
|
6639
|
+
"okta.com",
|
|
6640
|
+
"onelogin.com",
|
|
6641
|
+
"clerk.accounts.dev",
|
|
6642
|
+
"accounts.dev",
|
|
6643
|
+
"b2clogin.com",
|
|
6644
|
+
"amazoncognito.com",
|
|
6645
|
+
"workos.com",
|
|
6646
|
+
"stytch.com"
|
|
6647
|
+
];
|
|
6648
|
+
var SIGNIN_SEGMENTS = /* @__PURE__ */ new Set([
|
|
6649
|
+
"login",
|
|
6650
|
+
"log-in",
|
|
6651
|
+
"signin",
|
|
6652
|
+
"sign-in",
|
|
6653
|
+
"sign_in",
|
|
6654
|
+
"sso",
|
|
6655
|
+
"auth",
|
|
6656
|
+
"authenticate",
|
|
6657
|
+
"authorize",
|
|
6658
|
+
"oauth",
|
|
6659
|
+
"oauth2",
|
|
6660
|
+
"saml"
|
|
6661
|
+
]);
|
|
6662
|
+
function parse(url) {
|
|
6663
|
+
try {
|
|
6664
|
+
return new URL(url);
|
|
6665
|
+
} catch {
|
|
6666
|
+
return null;
|
|
6667
|
+
}
|
|
6668
|
+
}
|
|
6669
|
+
var bareHost = (h) => h.replace(/^www\./, "").toLowerCase();
|
|
6670
|
+
var trimSlash = (p) => p.length > 1 ? p.replace(/\/+$/, "") : p;
|
|
6671
|
+
var place = (u) => `${u.origin}${trimSlash(u.pathname)}`;
|
|
6672
|
+
function isIdpHost(host) {
|
|
6673
|
+
const h = host.toLowerCase();
|
|
6674
|
+
if (h.startsWith("clerk.")) return true;
|
|
6675
|
+
return IDP_HOSTS.some((idp) => h === idp || h.endsWith(`.${idp}`));
|
|
6676
|
+
}
|
|
6677
|
+
function isSignInPath(pathname) {
|
|
6678
|
+
const segs = pathname.toLowerCase().split("/").filter(Boolean);
|
|
6679
|
+
return segs.some((s) => SIGNIN_SEGMENTS.has(s));
|
|
6680
|
+
}
|
|
6681
|
+
function redirectedAway(asked, landed) {
|
|
6682
|
+
if (bareHost(asked.hostname) !== bareHost(landed.hostname)) return true;
|
|
6683
|
+
const a = trimSlash(asked.pathname).toLowerCase();
|
|
6684
|
+
const l = trimSlash(landed.pathname).toLowerCase();
|
|
6685
|
+
if (a === l || a === "/") return false;
|
|
6686
|
+
if (l.startsWith(`${a}/`)) return false;
|
|
6687
|
+
if (l.endsWith(a)) return false;
|
|
6688
|
+
return true;
|
|
6689
|
+
}
|
|
6690
|
+
var LADDER = 'This is a missing or expired session, not a script bug. Ways past, cheapest first: mint a session from the test auth the project already has; script the sign-in off camera and save the storage state; or have a person sign in once (npx playwright open --channel chrome --save-storage=<file> <url>). Then pass --storage-state <file>. The ladder: https://vos.so/llms-full.txt, "Sessions". A take OF this page is --allow-wall.';
|
|
6691
|
+
function wallVerdict(a) {
|
|
6692
|
+
const asked = parse(a.askedUrl);
|
|
6693
|
+
const landed = parse(a.landedUrl);
|
|
6694
|
+
if (!asked || !landed) return null;
|
|
6695
|
+
if (!/^https?:$/.test(asked.protocol)) return null;
|
|
6696
|
+
const away = redirectedAway(asked, landed);
|
|
6697
|
+
const where = { asked: place(asked), landed: place(landed) };
|
|
6698
|
+
const host = bareHost(asked.hostname);
|
|
6699
|
+
if (a.status === 401 || a.status === 403) {
|
|
6700
|
+
return {
|
|
6701
|
+
kind: "status",
|
|
6702
|
+
level: "hard",
|
|
6703
|
+
...where,
|
|
6704
|
+
message: `asked for ${asked.pathname}, answered ${a.status}: no session for ${host}. ${LADDER}`
|
|
6705
|
+
};
|
|
6706
|
+
}
|
|
6707
|
+
const askedForSignIn = isSignInPath(asked.pathname) || isIdpHost(asked.hostname);
|
|
6708
|
+
if (askedForSignIn && !away) return null;
|
|
6709
|
+
if (away && isIdpHost(landed.hostname) && !isIdpHost(asked.hostname)) {
|
|
6710
|
+
return {
|
|
6711
|
+
kind: "idp",
|
|
6712
|
+
level: "hard",
|
|
6713
|
+
...where,
|
|
6714
|
+
message: `asked for ${asked.pathname}, landed on ${landed.hostname}: no session for ${host}. ${LADDER}`
|
|
6715
|
+
};
|
|
6716
|
+
}
|
|
6717
|
+
const signInForm = a.passwordFields === 1 && !a.newPasswordField || a.oneTimeCodeField;
|
|
6718
|
+
if (away && isSignInPath(landed.pathname) || signInForm && !askedForSignIn) {
|
|
6719
|
+
const seen = away ? `landed on ${landed.pathname}` : "the page is a sign-in form";
|
|
6720
|
+
return {
|
|
6721
|
+
kind: "signin",
|
|
6722
|
+
level: "hard",
|
|
6723
|
+
...where,
|
|
6724
|
+
message: `asked for ${asked.pathname}, ${seen}: no session for ${host}. ${LADDER}`
|
|
6725
|
+
};
|
|
6726
|
+
}
|
|
6727
|
+
if (away) {
|
|
6728
|
+
return {
|
|
6729
|
+
kind: "redirect",
|
|
6730
|
+
level: "soft",
|
|
6731
|
+
...where,
|
|
6732
|
+
message: `asked for ${asked.pathname}, landed on ${landed.pathname}: the site sent the recorder elsewhere, which is what a page behind a login does to a stranger. If that is the page you meant, record it by its own URL. ${LADDER}`
|
|
6733
|
+
};
|
|
6734
|
+
}
|
|
6735
|
+
return null;
|
|
6736
|
+
}
|
|
6737
|
+
var WallError = class extends Error {
|
|
6738
|
+
constructor(verdict) {
|
|
6739
|
+
super(verdict.message);
|
|
6740
|
+
this.verdict = verdict;
|
|
6741
|
+
this.name = "WallError";
|
|
6742
|
+
}
|
|
6743
|
+
verdict;
|
|
6744
|
+
};
|
|
6745
|
+
var WALL_PROBE = `(() => {
|
|
6746
|
+
const shown = (el) => {
|
|
6747
|
+
const r = el.getBoundingClientRect()
|
|
6748
|
+
if (r.width < 2 || r.height < 2) return false
|
|
6749
|
+
const s = getComputedStyle(el)
|
|
6750
|
+
return s.visibility !== 'hidden' && s.display !== 'none'
|
|
6751
|
+
}
|
|
6752
|
+
const pw = [...document.querySelectorAll('input[type="password"]')].filter(shown)
|
|
6753
|
+
const otp = [...document.querySelectorAll('input[autocomplete="one-time-code"]')].filter(shown)
|
|
6754
|
+
return {
|
|
6755
|
+
passwordFields: pw.length,
|
|
6756
|
+
newPasswordField: pw.some((el) => (el.getAttribute('autocomplete') || '').includes('new-password')),
|
|
6757
|
+
oneTimeCodeField: otp.length > 0,
|
|
6758
|
+
}
|
|
6759
|
+
})()`;
|
|
6760
|
+
|
|
6631
6761
|
// src/plugin/recorder.ts
|
|
6632
6762
|
var realSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
6633
6763
|
var DRY_PAUSE_MS = 120;
|
|
@@ -6661,11 +6791,30 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6661
6791
|
});
|
|
6662
6792
|
log(`goto ${url}`);
|
|
6663
6793
|
let navTimeout = false;
|
|
6664
|
-
await page.goto(url, { waitUntil: "networkidle", timeout: 45e3 }).catch(() => {
|
|
6794
|
+
const response = await page.goto(url, { waitUntil: "networkidle", timeout: 45e3 }).catch(() => {
|
|
6665
6795
|
navTimeout = true;
|
|
6666
6796
|
log(" (networkidle timeout \u2014 continuing)");
|
|
6797
|
+
return null;
|
|
6667
6798
|
});
|
|
6668
6799
|
await sleep(800);
|
|
6800
|
+
let wall = null;
|
|
6801
|
+
if (opts.onArrival) {
|
|
6802
|
+
const seen = await page.evaluate(WALL_PROBE).catch(() => null);
|
|
6803
|
+
try {
|
|
6804
|
+
wall = await opts.onArrival({
|
|
6805
|
+
askedUrl: url,
|
|
6806
|
+
landedUrl: page.url(),
|
|
6807
|
+
...response ? { status: response.status() } : {},
|
|
6808
|
+
passwordFields: seen?.passwordFields ?? 0,
|
|
6809
|
+
newPasswordField: seen?.newPasswordField ?? false,
|
|
6810
|
+
oneTimeCodeField: seen?.oneTimeCodeField ?? false
|
|
6811
|
+
}) ?? null;
|
|
6812
|
+
} catch (e) {
|
|
6813
|
+
await context.close().catch(() => {
|
|
6814
|
+
});
|
|
6815
|
+
throw e;
|
|
6816
|
+
}
|
|
6817
|
+
}
|
|
6669
6818
|
const cdp = await context.newCDPSession(page);
|
|
6670
6819
|
const frames = [];
|
|
6671
6820
|
let frameIdx = 0;
|
|
@@ -6954,7 +7103,8 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6954
7103
|
pageTitle,
|
|
6955
7104
|
platform: process.platform === "darwin" ? "mac" : process.platform === "win32" ? "windows" : "linux",
|
|
6956
7105
|
producer: "cli",
|
|
6957
|
-
steps: stepSpans
|
|
7106
|
+
steps: stepSpans,
|
|
7107
|
+
...wall ? { wall: { kind: wall.kind, asked: wall.asked, landed: wall.landed } } : {}
|
|
6958
7108
|
};
|
|
6959
7109
|
const freezes = [];
|
|
6960
7110
|
for (let i = 1; i < frames.length; i++) {
|
|
@@ -6989,6 +7139,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6989
7139
|
pace,
|
|
6990
7140
|
skipped,
|
|
6991
7141
|
navTimeout,
|
|
7142
|
+
wall,
|
|
6992
7143
|
freezes,
|
|
6993
7144
|
freezePct,
|
|
6994
7145
|
capped
|
|
@@ -10140,14 +10291,15 @@ var BOOLEAN_FLAGS5 = /* @__PURE__ */ new Set([
|
|
|
10140
10291
|
"motion",
|
|
10141
10292
|
"leader",
|
|
10142
10293
|
"keep-frames",
|
|
10143
|
-
"dry-run"
|
|
10294
|
+
"dry-run",
|
|
10295
|
+
"allow-wall"
|
|
10144
10296
|
]);
|
|
10145
10297
|
var MULTI_FLAGS2 = /* @__PURE__ */ new Set(["set", "override", "browser-arg"]);
|
|
10146
10298
|
var HELP = `vos \u2014 record a browser flow, plan effects, render a product video; sync with vos.so
|
|
10147
10299
|
|
|
10148
10300
|
Take pipeline
|
|
10149
|
-
vos create --actions actions.json [--url <url>] [--out take] [out.webm] [--strict] [--keep-frames] [--max-duration <s>] [--storage-state <file>] [--browser-arg=<switch>]... [--background <slug|url|none>] [render flags] [--json]
|
|
10150
|
-
vos record --actions actions.json [--url <url>] [--out take] [--strict] [--dry-run] [--keep-frames] [--max-duration <s>] [--storage-state <file>] [--browser-arg=<switch>]... [--background <slug|url|none>] [--json]
|
|
10301
|
+
vos create --actions actions.json [--url <url>] [--out take] [out.webm] [--strict] [--allow-wall] [--keep-frames] [--max-duration <s>] [--storage-state <file>] [--browser-arg=<switch>]... [--background <slug|url|none>] [render flags] [--json]
|
|
10302
|
+
vos record --actions actions.json [--url <url>] [--out take] [--strict] [--dry-run] [--allow-wall] [--keep-frames] [--max-duration <s>] [--storage-state <file>] [--browser-arg=<switch>]... [--background <slug|url|none>] [--json]
|
|
10151
10303
|
vos plan <take> [--fresh] [--reuse [--from <doc.json>]] [--style <doc.json|vosId>] [--with <doc.json|vosId>[@end|@start|@step:<id>|@<s>]]... [--background <slug|url|none>] [--motion] [--headline "\u2026"] [--kicker "\u2026"] [--launch LAUNCH.md] [--brand BRAND.md] [--music <slug|mood|none>] [--entrance tilt-in|pull-out|rise|fade|slide|none] [--transitions slide|fade|scale|none] [--end-card on|none|<doc.json|vosId>] [--captions none] [--clicks none] [--still <t>] [--release v2.1] [--json]
|
|
10152
10304
|
vos render <take> [out.webm] [--width] [--height] [--fps] [--format webm|mp4] [--parallel N] [--range a..b] [--draft] [--frame <kind>] [--background <url|slug>] [--set <path=value>]... [--json]
|
|
10153
10305
|
vos frames <take> [--times 0,25%,50%,75%,100%] [--frame <t>] [--at-zooms] [--at-moments] [--at-still] [--size WxH] [--out dir] [--background <url|slug>] [--set <path=value>]... [--json]
|
|
@@ -10456,6 +10608,24 @@ async function releaseFrames(paths, flags, log) {
|
|
|
10456
10608
|
`released ${Math.round(freed / 1e6)} MB of screencast frames (the recording holds them now; --keep-frames keeps them)`
|
|
10457
10609
|
);
|
|
10458
10610
|
}
|
|
10611
|
+
function wallGate(flags, r, opts) {
|
|
10612
|
+
const allow = flags["allow-wall"] === true;
|
|
10613
|
+
return async (arrival) => {
|
|
10614
|
+
const verdict = wallVerdict(arrival);
|
|
10615
|
+
if (verdict && !allow && (verdict.level === "hard" || opts.strict)) {
|
|
10616
|
+
r.event({ event: "wall", ...verdict, refused: true });
|
|
10617
|
+
throw new WallError(verdict);
|
|
10618
|
+
}
|
|
10619
|
+
if (verdict) {
|
|
10620
|
+
r.event({ event: "wall", ...verdict, refused: false });
|
|
10621
|
+
r.log(
|
|
10622
|
+
allow ? ` note: recording past the wall (--allow-wall): landed on ${verdict.landed}` : ` WARNING: ${verdict.message}`
|
|
10623
|
+
);
|
|
10624
|
+
}
|
|
10625
|
+
await opts.prepare?.();
|
|
10626
|
+
return verdict;
|
|
10627
|
+
};
|
|
10628
|
+
}
|
|
10459
10629
|
async function cmdRecord(argv) {
|
|
10460
10630
|
const { positionals, flags, multi } = parseArgs(
|
|
10461
10631
|
argv,
|
|
@@ -10491,7 +10661,12 @@ async function cmdRecord(argv) {
|
|
|
10491
10661
|
actions,
|
|
10492
10662
|
takePaths(outDir),
|
|
10493
10663
|
r.log,
|
|
10494
|
-
{
|
|
10664
|
+
{
|
|
10665
|
+
storageState,
|
|
10666
|
+
dryRun: true,
|
|
10667
|
+
// A rehearsal is always strict, and it touches no directory.
|
|
10668
|
+
onArrival: wallGate(flags, r, { strict: true })
|
|
10669
|
+
}
|
|
10495
10670
|
);
|
|
10496
10671
|
const steps = rec.meta.steps ?? [];
|
|
10497
10672
|
const lines = steps.map((s) => {
|
|
@@ -10500,6 +10675,12 @@ async function cmdRecord(argv) {
|
|
|
10500
10675
|
return ` ${s.skipped ? "\u2717" : "\u2713"} #${s.step}${s.id ? ` (${s.id})` : ""} ${s.do}${what}${s.skipped ? " NOT FOUND" : rect}${s.navigated ? " \u2192 navigated" : ""}`;
|
|
10501
10676
|
});
|
|
10502
10677
|
const failed2 = rec.skipped.length > 0 || rec.navTimeout;
|
|
10678
|
+
const carried = [
|
|
10679
|
+
...strFlag(flags, "storage-state") ? [`--storage-state ${strFlag(flags, "storage-state")}`] : [],
|
|
10680
|
+
...browserArgs.map((a) => `--browser-arg=${a}`),
|
|
10681
|
+
...flags["allow-wall"] === true ? ["--allow-wall"] : []
|
|
10682
|
+
].join(" ");
|
|
10683
|
+
const nextOut = strFlag(flags, "out") ?? "take";
|
|
10503
10684
|
r.done(
|
|
10504
10685
|
{
|
|
10505
10686
|
dryRun: true,
|
|
@@ -10511,28 +10692,35 @@ async function cmdRecord(argv) {
|
|
|
10511
10692
|
},
|
|
10512
10693
|
`${failed2 ? "REHEARSAL FAILED" : "Rehearsal passed"}: ${steps.length - rec.skipped.length}/${steps.length} steps resolved in ${((Date.now() - started) / 1e3).toFixed(1)}s${rec.navTimeout ? " (the first load never reached networkidle)" : ""}
|
|
10513
10694
|
${lines.join("\n")}
|
|
10514
|
-
${failed2 ? "Fix the script, rehearse again, then record." : `Rects are capture px (the step rects a pin or a callout reads). Next: vos record --actions ${actionsPath} --out ${
|
|
10695
|
+
${failed2 ? "Fix the script, rehearse again, then record." : `Rects are capture px (the step rects a pin or a callout reads). Next: vos record --actions ${actionsPath} --out ${nextOut}${carried ? ` ${carried}` : ""} --strict`}`
|
|
10515
10696
|
);
|
|
10516
10697
|
return failed2 ? EXIT_USAGE : EXIT_OK;
|
|
10517
10698
|
} finally {
|
|
10518
10699
|
await browser2.close();
|
|
10519
10700
|
}
|
|
10520
10701
|
}
|
|
10521
|
-
|
|
10522
|
-
|
|
10523
|
-
|
|
10524
|
-
|
|
10525
|
-
|
|
10526
|
-
|
|
10527
|
-
|
|
10528
|
-
|
|
10702
|
+
const paths = takePaths(outDir);
|
|
10703
|
+
const prepare = async () => {
|
|
10704
|
+
if (existsSync15(join18(outDir, "meta.json"))) {
|
|
10705
|
+
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
10706
|
+
r.log(
|
|
10707
|
+
`note: re-recording ${outDir} \u2014 kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? "; apply the previous cut to the new footage with: vos plan " + outDir + " --reuse" : "")
|
|
10708
|
+
);
|
|
10709
|
+
}
|
|
10710
|
+
await mkdir10(outDir, { recursive: true });
|
|
10711
|
+
await ensureTakeDir(outDir);
|
|
10712
|
+
};
|
|
10529
10713
|
const browser = await launchBrowser(browserArgs);
|
|
10530
10714
|
try {
|
|
10531
10715
|
r.log("recording\u2026");
|
|
10532
10716
|
r.event({ event: "phase", phase: "record" });
|
|
10533
10717
|
const rec = await recordTake(browser, url, actions, paths, r.log, {
|
|
10534
10718
|
maxDurationSeconds,
|
|
10535
|
-
storageState
|
|
10719
|
+
storageState,
|
|
10720
|
+
onArrival: wallGate(flags, r, {
|
|
10721
|
+
strict: flags.strict === true,
|
|
10722
|
+
prepare
|
|
10723
|
+
})
|
|
10536
10724
|
});
|
|
10537
10725
|
r.event({ event: "phase", phase: "encode" });
|
|
10538
10726
|
r.log("encoding\u2026");
|
|
@@ -10564,6 +10752,7 @@ ${lines.join("\n")}
|
|
|
10564
10752
|
freezePct: rec.freezePct,
|
|
10565
10753
|
capped: rec.capped,
|
|
10566
10754
|
pace: rec.pace,
|
|
10755
|
+
...rec.wall ? { wall: rec.wall } : {},
|
|
10567
10756
|
...strictFail ? { strictFailed: true } : {}
|
|
10568
10757
|
},
|
|
10569
10758
|
strictFail ? `STRICT: take recorded but incomplete \u2014 ${strictReason(rec)}; fix the flow and re-record.${skippedNote}` : `Take ready: ${outDir}
|
|
@@ -10606,21 +10795,28 @@ async function cmdCreate2(argv) {
|
|
|
10606
10795
|
const maxDurationSeconds = await maxDuration(flags, r);
|
|
10607
10796
|
const storageState = takeStorageState(flags);
|
|
10608
10797
|
const browserArgs = takeBrowserArgs(multi);
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
10615
|
-
|
|
10616
|
-
|
|
10798
|
+
const paths = takePaths(outDir);
|
|
10799
|
+
const prepare = async () => {
|
|
10800
|
+
if (existsSync15(join18(outDir, "meta.json"))) {
|
|
10801
|
+
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
10802
|
+
r.log(
|
|
10803
|
+
`note: re-recording ${outDir} \u2014 kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? "; apply the previous cut to the new footage with: vos plan " + outDir + " --reuse" : "")
|
|
10804
|
+
);
|
|
10805
|
+
}
|
|
10806
|
+
await mkdir10(outDir, { recursive: true });
|
|
10807
|
+
await ensureTakeDir(outDir);
|
|
10808
|
+
};
|
|
10617
10809
|
const browser = await launchBrowser(browserArgs);
|
|
10618
10810
|
try {
|
|
10619
10811
|
r.log("recording\u2026");
|
|
10620
10812
|
r.event({ event: "phase", phase: "record" });
|
|
10621
10813
|
const rec = await recordTake(browser, url, actions, paths, r.log, {
|
|
10622
10814
|
maxDurationSeconds,
|
|
10623
|
-
storageState
|
|
10815
|
+
storageState,
|
|
10816
|
+
onArrival: wallGate(flags, r, {
|
|
10817
|
+
strict: flags.strict === true,
|
|
10818
|
+
prepare
|
|
10819
|
+
})
|
|
10624
10820
|
});
|
|
10625
10821
|
r.event({ event: "phase", phase: "encode" });
|
|
10626
10822
|
r.log("encoding\u2026");
|
|
@@ -11243,12 +11439,14 @@ async function cmdDigest(argv) {
|
|
|
11243
11439
|
sourceDuration: d.take.sourceDuration,
|
|
11244
11440
|
outputDuration: d.take.outputDuration,
|
|
11245
11441
|
hasCursor: d.take.hasCursor,
|
|
11442
|
+
...d.take.wall ? { wall: d.take.wall } : {},
|
|
11246
11443
|
tokensEstimate: d.images.tokensEstimate,
|
|
11247
11444
|
bytes: result.bytes
|
|
11248
11445
|
},
|
|
11249
11446
|
`Wrote ${result.file} \u2014 ${d.moments.length} moments (${Object.entries(
|
|
11250
11447
|
kinds
|
|
11251
|
-
).map(([k, n]) => `${n} ${k}`).join(", ")}), ${d.images.full} frames + ${d.images.crop} crops` + (d.images.sheet ? `, sheet.png` : "") + ` \u2014 ~${d.images.tokensEstimate} image tokens to read them all` + (d.take.hasCursor ? "" : "\n no cursor track: moments are head/tail/scenes only \u2014 pace by activity, zoom only where the ask names a place") + `
|
|
11448
|
+
).map(([k, n]) => `${n} ${k}`).join(", ")}), ${d.images.full} frames + ${d.images.crop} crops` + (d.images.sheet ? `, sheet.png` : "") + ` \u2014 ~${d.images.tokensEstimate} image tokens to read them all` + (d.take.hasCursor ? "" : "\n no cursor track: moments are head/tail/scenes only \u2014 pace by activity, zoom only where the ask names a place") + (d.take.wall ? `
|
|
11449
|
+
WALL: asked for ${d.take.wall.asked}, recorded ${d.take.wall.landed} \u2014 this footage may be the wrong page; re-record with a session before cutting it` : "") + `
|
|
11252
11450
|
Read digest.json, then the sheet, then a crop only where you must decide.`
|
|
11253
11451
|
);
|
|
11254
11452
|
return EXIT_OK;
|
|
@@ -11650,6 +11848,23 @@ async function cmdPull2(argv) {
|
|
|
11650
11848
|
);
|
|
11651
11849
|
return EXIT_OK;
|
|
11652
11850
|
}
|
|
11851
|
+
function verbHelp(verb) {
|
|
11852
|
+
const lines = HELP.split("\n");
|
|
11853
|
+
const out = [];
|
|
11854
|
+
let taking = false;
|
|
11855
|
+
for (const line of lines) {
|
|
11856
|
+
if (/^ {2}vos /.test(line)) taking = line.startsWith(` vos ${verb} `);
|
|
11857
|
+
else if (taking && !/^ {3,}\S/.test(line)) taking = false;
|
|
11858
|
+
if (taking) out.push(line);
|
|
11859
|
+
}
|
|
11860
|
+
if (out.length === 0) return `vos ${verb}: no such verb here. Run: vos help
|
|
11861
|
+
`;
|
|
11862
|
+
return `${out.join("\n")}
|
|
11863
|
+
|
|
11864
|
+
The whole reference, with every flag explained: https://vos.so/llms-full.txt
|
|
11865
|
+
Exit codes: 0 ok, 1 error, 2 usage or --strict failure, 3 no browser, 4 the recorder met a sign-in instead of the page.
|
|
11866
|
+
`;
|
|
11867
|
+
}
|
|
11653
11868
|
async function run(argv) {
|
|
11654
11869
|
const [cmd, ...rest] = argv;
|
|
11655
11870
|
try {
|
|
@@ -11657,6 +11872,10 @@ async function run(argv) {
|
|
|
11657
11872
|
process.stdout.write(HELP);
|
|
11658
11873
|
return cmd ? EXIT_OK : EXIT_USAGE;
|
|
11659
11874
|
}
|
|
11875
|
+
if (rest.includes("--help") || rest.includes("-h")) {
|
|
11876
|
+
process.stdout.write(verbHelp(cmd));
|
|
11877
|
+
return EXIT_OK;
|
|
11878
|
+
}
|
|
11660
11879
|
switch (cmd) {
|
|
11661
11880
|
case "create":
|
|
11662
11881
|
return await cmdCreate2(rest);
|
|
@@ -11721,6 +11940,11 @@ async function run(argv) {
|
|
|
11721
11940
|
`);
|
|
11722
11941
|
return EXIT_USAGE;
|
|
11723
11942
|
}
|
|
11943
|
+
if (e instanceof WallError) {
|
|
11944
|
+
process.stderr.write(`wall: ${e.message}
|
|
11945
|
+
`);
|
|
11946
|
+
return EXIT_WALL;
|
|
11947
|
+
}
|
|
11724
11948
|
if (e instanceof BrowserUnavailableError) {
|
|
11725
11949
|
process.stderr.write(`${e.message}
|
|
11726
11950
|
`);
|
|
@@ -11762,6 +11986,7 @@ export {
|
|
|
11762
11986
|
MULTI_FLAGS2 as MULTI_FLAGS,
|
|
11763
11987
|
HELP,
|
|
11764
11988
|
takeBrowserArgs,
|
|
11989
|
+
verbHelp,
|
|
11765
11990
|
run
|
|
11766
11991
|
};
|
|
11767
|
-
//# sourceMappingURL=chunk-
|
|
11992
|
+
//# sourceMappingURL=chunk-4QVEV3HX.js.map
|