@vosjs/cli 0.43.0 → 0.45.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 +18 -1
- package/dist/{chunk-6PJPGBKH.js → chunk-JRRM7P5G.js} +437 -183
- package/dist/chunk-JRRM7P5G.js.map +1 -0
- package/dist/chunk-RDQYYRWN.js +107 -0
- package/dist/chunk-RDQYYRWN.js.map +1 -0
- package/dist/cli.js +3 -3
- package/dist/index.d.ts +13 -1
- package/dist/index.js +2 -1
- package/dist/{run-ZYROWQ3T.js → run-3TY74GWE.js} +3 -2
- package/dist/shotList-FYDHO5AI.js +111 -0
- package/dist/shotList-FYDHO5AI.js.map +1 -0
- package/package.json +3 -3
- package/dist/chunk-6PJPGBKH.js.map +0 -1
- /package/dist/{run-ZYROWQ3T.js.map → run-3TY74GWE.js.map} +0 -0
|
@@ -6,11 +6,24 @@ import {
|
|
|
6
6
|
noteGivenFlag,
|
|
7
7
|
trackFlags
|
|
8
8
|
} from "./chunk-FACYY7VV.js";
|
|
9
|
+
import {
|
|
10
|
+
PRESS_HOLD_MS,
|
|
11
|
+
PRESS_LEAD_MS,
|
|
12
|
+
SCROLL_SETTLE_MS,
|
|
13
|
+
TRAILING_HOLD_MS,
|
|
14
|
+
askedMs,
|
|
15
|
+
clockMotion,
|
|
16
|
+
clockTyping,
|
|
17
|
+
paceLine,
|
|
18
|
+
paceReport,
|
|
19
|
+
pointerTravelMs,
|
|
20
|
+
settleMs
|
|
21
|
+
} from "./chunk-RDQYYRWN.js";
|
|
9
22
|
|
|
10
23
|
// src/plugin/run.ts
|
|
11
24
|
import { mkdir as mkdir10, readFile as readFile15, rm as rm5 } from "fs/promises";
|
|
12
|
-
import { existsSync as
|
|
13
|
-
import { join as
|
|
25
|
+
import { existsSync as existsSync16 } from "fs";
|
|
26
|
+
import { join as join19, resolve as resolve9 } from "path";
|
|
14
27
|
import {
|
|
15
28
|
END_CARD_FROM as END_CARD_FROM2,
|
|
16
29
|
docOutputDuration as docOutputDuration3,
|
|
@@ -6686,97 +6699,6 @@ function cappedLine(capSeconds) {
|
|
|
6686
6699
|
return `stopped at ${formatDurationCap(capSeconds)} (--max-duration ${capSeconds}); the remaining steps did not run`;
|
|
6687
6700
|
}
|
|
6688
6701
|
|
|
6689
|
-
// src/plugin/pace.ts
|
|
6690
|
-
var POINTER_MS_PER_PX = 0.7;
|
|
6691
|
-
var POINTER_MIN_MS = 250;
|
|
6692
|
-
var POINTER_MAX_MS = 800;
|
|
6693
|
-
var MOTION_TICK_MS = 16;
|
|
6694
|
-
var SETTLE_MS = {
|
|
6695
|
-
click: 150,
|
|
6696
|
-
type: 150,
|
|
6697
|
-
scroll: 200,
|
|
6698
|
-
drag: 80
|
|
6699
|
-
};
|
|
6700
|
-
var PRESS_LEAD_MS = 80;
|
|
6701
|
-
var PRESS_HOLD_MS = 70;
|
|
6702
|
-
var SCROLL_SETTLE_MS = 120;
|
|
6703
|
-
var TRAILING_HOLD_MS = 400;
|
|
6704
|
-
function pointerTravelMs(dist) {
|
|
6705
|
-
return Math.min(
|
|
6706
|
-
POINTER_MAX_MS,
|
|
6707
|
-
Math.max(POINTER_MIN_MS, Math.round(dist * POINTER_MS_PER_PX))
|
|
6708
|
-
);
|
|
6709
|
-
}
|
|
6710
|
-
function settleMs(step) {
|
|
6711
|
-
if (typeof step.ms === "number" && step.ms >= 0) return step.ms;
|
|
6712
|
-
const d = step.do;
|
|
6713
|
-
return SETTLE_MS[d] ?? 0;
|
|
6714
|
-
}
|
|
6715
|
-
var easeInOutCubic = (u) => u < 0.5 ? 4 * u * u * u : 1 - Math.pow(-2 * u + 2, 3) / 2;
|
|
6716
|
-
async function clockMotion(dur, at2, clock) {
|
|
6717
|
-
const start = clock.now();
|
|
6718
|
-
let samples = 0;
|
|
6719
|
-
for (; ; ) {
|
|
6720
|
-
const elapsed = clock.now() - start;
|
|
6721
|
-
if (elapsed >= dur) break;
|
|
6722
|
-
await at2(easeInOutCubic(Math.min(1, elapsed / dur)));
|
|
6723
|
-
samples++;
|
|
6724
|
-
const next = start + Math.ceil((clock.now() - start) / MOTION_TICK_MS) * MOTION_TICK_MS;
|
|
6725
|
-
const wait2 = Math.min(next, start + dur) - clock.now();
|
|
6726
|
-
if (wait2 > 0) await clock.sleep(wait2);
|
|
6727
|
-
}
|
|
6728
|
-
await at2(1);
|
|
6729
|
-
return samples + 1;
|
|
6730
|
-
}
|
|
6731
|
-
async function clockTyping(chars, delay, type, clock) {
|
|
6732
|
-
const start = clock.now();
|
|
6733
|
-
for (let i = 0; i < chars.length; i++) {
|
|
6734
|
-
await type(chars[i], i);
|
|
6735
|
-
const due = start + (i + 1) * delay;
|
|
6736
|
-
const wait2 = due - clock.now();
|
|
6737
|
-
if (wait2 > 0 && i < chars.length - 1) await clock.sleep(wait2);
|
|
6738
|
-
}
|
|
6739
|
-
}
|
|
6740
|
-
function askedMs(step) {
|
|
6741
|
-
switch (step.do) {
|
|
6742
|
-
case "wait":
|
|
6743
|
-
return step.ms ?? 0;
|
|
6744
|
-
case "hover":
|
|
6745
|
-
return step.ms ?? 700;
|
|
6746
|
-
case "drag":
|
|
6747
|
-
return step.ms ?? 700;
|
|
6748
|
-
case "type":
|
|
6749
|
-
return (step.text?.length ?? 0) * (step.delayMs ?? 40);
|
|
6750
|
-
default:
|
|
6751
|
-
return 0;
|
|
6752
|
-
}
|
|
6753
|
-
}
|
|
6754
|
-
function paceReport(steps) {
|
|
6755
|
-
const askedMs2 = steps.reduce((a, s) => a + s.askedMs, 0);
|
|
6756
|
-
const gestureMs = steps.reduce((a, s) => a + s.gestureMs, 0);
|
|
6757
|
-
const wallMs = steps.reduce((a, s) => a + s.wallMs, 0);
|
|
6758
|
-
const overheadMs = Math.max(0, wallMs - askedMs2 - gestureMs);
|
|
6759
|
-
const slow = steps.filter((s) => s.wallMs > (s.askedMs + s.gestureMs) * 1.34 + 60).map((s) => ({
|
|
6760
|
-
step: s.step,
|
|
6761
|
-
do: s.do,
|
|
6762
|
-
askedMs: s.askedMs,
|
|
6763
|
-
wallMs: s.wallMs
|
|
6764
|
-
}));
|
|
6765
|
-
return {
|
|
6766
|
-
askedMs: askedMs2,
|
|
6767
|
-
gestureMs,
|
|
6768
|
-
wallMs,
|
|
6769
|
-
overheadMs,
|
|
6770
|
-
overheadPct: wallMs > 0 ? Math.round(overheadMs / wallMs * 100) : 0,
|
|
6771
|
-
slow
|
|
6772
|
-
};
|
|
6773
|
-
}
|
|
6774
|
-
function paceLine(r) {
|
|
6775
|
-
const s = (ms) => `${(ms / 1e3).toFixed(1)} s`;
|
|
6776
|
-
const slow = r.slow.length ? `; slow: ${r.slow.map((x) => `#${x.step} ${x.do} ${s(x.wallMs)} for ${s(x.askedMs)} asked`).join(", ")}` : "";
|
|
6777
|
-
return `pace: the script asked ${s(r.askedMs)}, the gestures added ${s(r.gestureMs)}, the take ran ${s(r.wallMs)} (${r.overheadPct} % overhead${slow})`;
|
|
6778
|
-
}
|
|
6779
|
-
|
|
6780
6702
|
// src/plugin/wall.ts
|
|
6781
6703
|
var IDP_HOSTS = [
|
|
6782
6704
|
"accounts.google.com",
|
|
@@ -7121,12 +7043,14 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
7121
7043
|
const maxSeconds = dry ? Infinity : opts.maxDurationSeconds ?? Infinity;
|
|
7122
7044
|
const vw = actions.viewport?.width ?? 1280;
|
|
7123
7045
|
const vh = actions.viewport?.height ?? 720;
|
|
7124
|
-
const context = await browser.newContext({
|
|
7046
|
+
const context = opts.context ?? await browser.newContext({
|
|
7125
7047
|
viewport: { width: vw, height: vh },
|
|
7126
7048
|
deviceScaleFactor: 1,
|
|
7127
7049
|
...opts.storageState ? { storageState: opts.storageState } : {},
|
|
7128
7050
|
...opts.headers && Object.keys(opts.headers).length ? { extraHTTPHeaders: opts.headers } : {}
|
|
7129
7051
|
});
|
|
7052
|
+
if (opts.context && opts.headers && Object.keys(opts.headers).length)
|
|
7053
|
+
await context.setExtraHTTPHeaders(opts.headers);
|
|
7130
7054
|
const masks = actions.mask ?? [];
|
|
7131
7055
|
if (masks.length) await context.addInitScript(maskInitScript(masks));
|
|
7132
7056
|
const exposureLog = new ExposureLog(masks);
|
|
@@ -7515,6 +7439,148 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
7515
7439
|
};
|
|
7516
7440
|
}
|
|
7517
7441
|
|
|
7442
|
+
// src/plugin/session.ts
|
|
7443
|
+
import { spawn } from "child_process";
|
|
7444
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync2, readdirSync as readdirSync2, rmSync, statSync as statSync3 } from "fs";
|
|
7445
|
+
import { homedir as homedir2 } from "os";
|
|
7446
|
+
import { join as join14 } from "path";
|
|
7447
|
+
import { chromium } from "playwright";
|
|
7448
|
+
var SESSIONS_DIR = join14(homedir2(), ".config", "vos", "sessions");
|
|
7449
|
+
var NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
|
|
7450
|
+
function sessionNameVerdict(name) {
|
|
7451
|
+
if (!name) return "a session needs a name: --name <app>";
|
|
7452
|
+
if (!NAME.test(name) || name.includes(".."))
|
|
7453
|
+
return `"${name}" is not a session name: letters, digits, . _ - only, up to 64`;
|
|
7454
|
+
return null;
|
|
7455
|
+
}
|
|
7456
|
+
function sessionDir(name) {
|
|
7457
|
+
return join14(SESSIONS_DIR, name);
|
|
7458
|
+
}
|
|
7459
|
+
function systemChromePath() {
|
|
7460
|
+
const explicit = process.env.VOS_BROWSER_PATH;
|
|
7461
|
+
if (explicit) return explicit;
|
|
7462
|
+
const candidates = process.platform === "darwin" ? ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"] : process.platform === "win32" ? [
|
|
7463
|
+
join14(
|
|
7464
|
+
process.env["PROGRAMFILES"] ?? "C:\\Program Files",
|
|
7465
|
+
"Google",
|
|
7466
|
+
"Chrome",
|
|
7467
|
+
"Application",
|
|
7468
|
+
"chrome.exe"
|
|
7469
|
+
),
|
|
7470
|
+
join14(
|
|
7471
|
+
process.env["LOCALAPPDATA"] ?? "",
|
|
7472
|
+
"Google",
|
|
7473
|
+
"Chrome",
|
|
7474
|
+
"Application",
|
|
7475
|
+
"chrome.exe"
|
|
7476
|
+
)
|
|
7477
|
+
] : [
|
|
7478
|
+
"/usr/bin/google-chrome",
|
|
7479
|
+
"/usr/bin/google-chrome-stable",
|
|
7480
|
+
"/opt/google/chrome/chrome"
|
|
7481
|
+
];
|
|
7482
|
+
const found = candidates.find((c) => existsSync12(c));
|
|
7483
|
+
if (!found)
|
|
7484
|
+
throw new Error(
|
|
7485
|
+
"no Google Chrome found: a session needs the system Chrome (the bundled Chromium cannot read a profile Chrome wrote). Install Chrome, or set VOS_BROWSER_PATH"
|
|
7486
|
+
);
|
|
7487
|
+
return found;
|
|
7488
|
+
}
|
|
7489
|
+
async function openSession(name, url, opts = { log: () => {
|
|
7490
|
+
} }) {
|
|
7491
|
+
const dir = sessionDir(name);
|
|
7492
|
+
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
7493
|
+
const exe = systemChromePath();
|
|
7494
|
+
const args = [
|
|
7495
|
+
`--user-data-dir=${dir}`,
|
|
7496
|
+
"--no-first-run",
|
|
7497
|
+
"--no-default-browser-check",
|
|
7498
|
+
"--window-size=1100,800",
|
|
7499
|
+
url
|
|
7500
|
+
];
|
|
7501
|
+
opts.log(
|
|
7502
|
+
`a Chrome window opened on session "${name}": sign in to ${new URL(url).host}, then close the window`
|
|
7503
|
+
);
|
|
7504
|
+
return new Promise((resolve10, reject) => {
|
|
7505
|
+
const child = spawn(exe, args, { stdio: "ignore" });
|
|
7506
|
+
child.on("error", reject);
|
|
7507
|
+
child.on("exit", (code) => resolve10({ code }));
|
|
7508
|
+
});
|
|
7509
|
+
}
|
|
7510
|
+
async function withSession(name, fn, opts = {}) {
|
|
7511
|
+
const dir = sessionDir(name);
|
|
7512
|
+
if (!existsSync12(dir))
|
|
7513
|
+
throw new Error(`no session "${name}" (vos session list)`);
|
|
7514
|
+
const explicit = process.env.VOS_BROWSER_PATH;
|
|
7515
|
+
const ctx = await chromium.launchPersistentContext(dir, {
|
|
7516
|
+
headless: opts.headless ?? true,
|
|
7517
|
+
...explicit ? { executablePath: explicit } : { channel: "chrome" },
|
|
7518
|
+
// The real keychain: the whole reason a profile Chrome wrote can be read.
|
|
7519
|
+
ignoreDefaultArgs: ["--use-mock-keychain"],
|
|
7520
|
+
...opts.viewport ? { viewport: opts.viewport, deviceScaleFactor: 1 } : {}
|
|
7521
|
+
});
|
|
7522
|
+
try {
|
|
7523
|
+
return await fn(ctx);
|
|
7524
|
+
} finally {
|
|
7525
|
+
await ctx.close().catch(() => {
|
|
7526
|
+
});
|
|
7527
|
+
}
|
|
7528
|
+
}
|
|
7529
|
+
async function summarizeSession(name) {
|
|
7530
|
+
const dir = sessionDir(name);
|
|
7531
|
+
const created = statSync3(dir).birthtimeMs || statSync3(dir).mtimeMs;
|
|
7532
|
+
return withSession(name, async (ctx) => {
|
|
7533
|
+
const cookies = await ctx.cookies();
|
|
7534
|
+
const byHost = /* @__PURE__ */ new Map();
|
|
7535
|
+
let newest = 0;
|
|
7536
|
+
let sessionOnly = 0;
|
|
7537
|
+
for (const c of cookies) {
|
|
7538
|
+
const host = c.domain.replace(/^\./, "");
|
|
7539
|
+
byHost.set(host, (byHost.get(host) ?? 0) + 1);
|
|
7540
|
+
if (c.expires && c.expires > 0) newest = Math.max(newest, c.expires);
|
|
7541
|
+
else sessionOnly++;
|
|
7542
|
+
}
|
|
7543
|
+
return {
|
|
7544
|
+
name,
|
|
7545
|
+
origins: [...byHost.entries()].map(([host, n]) => ({ host, cookies: n })).sort((a, b) => b.cookies - a.cookies),
|
|
7546
|
+
newestExpires: newest ? new Date(newest * 1e3).toISOString() : null,
|
|
7547
|
+
sessionOnly,
|
|
7548
|
+
ageDays: Math.floor((Date.now() - created) / 864e5)
|
|
7549
|
+
};
|
|
7550
|
+
});
|
|
7551
|
+
}
|
|
7552
|
+
function summaryLine(s) {
|
|
7553
|
+
if (!s.origins.length)
|
|
7554
|
+
return `session "${s.name}" holds no cookies: the window closed before a sign-in landed, or the sign-in never happened`;
|
|
7555
|
+
const hosts = s.origins.slice(0, 4).map((o) => `${o.host} (${o.cookies})`).join(", ");
|
|
7556
|
+
const more = s.origins.length > 4 ? ` and ${s.origins.length - 4} more` : "";
|
|
7557
|
+
const exp = s.newestExpires ? `newest expires in ${Math.max(0, Math.round((new Date(s.newestExpires).getTime() - Date.now()) / 864e5))} days` : "every cookie dies with the window (the app set no expiry), so this session cannot be reused";
|
|
7558
|
+
return `session "${s.name}" holds cookies for ${hosts}${more}; ${exp}`;
|
|
7559
|
+
}
|
|
7560
|
+
function listSessions() {
|
|
7561
|
+
if (!existsSync12(SESSIONS_DIR)) return [];
|
|
7562
|
+
return readdirSync2(SESSIONS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && NAME.test(d.name)).map((d) => {
|
|
7563
|
+
const st = statSync3(join14(SESSIONS_DIR, d.name));
|
|
7564
|
+
return {
|
|
7565
|
+
name: d.name,
|
|
7566
|
+
ageDays: Math.floor(
|
|
7567
|
+
(Date.now() - (st.birthtimeMs || st.mtimeMs)) / 864e5
|
|
7568
|
+
)
|
|
7569
|
+
};
|
|
7570
|
+
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
7571
|
+
}
|
|
7572
|
+
function removeSession(name) {
|
|
7573
|
+
const dir = sessionDir(name);
|
|
7574
|
+
if (!existsSync12(dir)) return false;
|
|
7575
|
+
rmSync(dir, { recursive: true, force: true });
|
|
7576
|
+
return true;
|
|
7577
|
+
}
|
|
7578
|
+
function looksLikeStorageState(value) {
|
|
7579
|
+
if (typeof value !== "object" || value === null) return false;
|
|
7580
|
+
const v = value;
|
|
7581
|
+
return Array.isArray(v.cookies) && Array.isArray(v.origins);
|
|
7582
|
+
}
|
|
7583
|
+
|
|
7518
7584
|
// src/plugin/encode.ts
|
|
7519
7585
|
var MEDIABUNNY_URL2 = "https://esm.sh/mediabunny@1.55.7?target=es2022";
|
|
7520
7586
|
var ENCODE_HTML = `<!doctype html><html><head><meta charset="utf-8"></head><body>
|
|
@@ -7570,9 +7636,9 @@ async function encodeRecording(browser, takeDir, onProgress) {
|
|
|
7570
7636
|
}
|
|
7571
7637
|
|
|
7572
7638
|
// src/plugin/plan.ts
|
|
7573
|
-
import { existsSync as
|
|
7639
|
+
import { existsSync as existsSync13 } from "fs";
|
|
7574
7640
|
import { readFile as readFile12 } from "fs/promises";
|
|
7575
|
-
import { join as
|
|
7641
|
+
import { join as join15 } from "path";
|
|
7576
7642
|
import {
|
|
7577
7643
|
DEFAULT_FRAME_STYLE,
|
|
7578
7644
|
STYLE_FIELDS,
|
|
@@ -7794,8 +7860,8 @@ function retimeCut(prev, newSteps, newDurationMs) {
|
|
|
7794
7860
|
// src/plugin/plan.ts
|
|
7795
7861
|
var overlaps = (a, b) => a.in < b.out && b.in < a.out;
|
|
7796
7862
|
async function readDigestActivity(dir) {
|
|
7797
|
-
const file =
|
|
7798
|
-
if (!
|
|
7863
|
+
const file = join15(dir, "digest", "digest.json");
|
|
7864
|
+
if (!existsSync13(file)) return null;
|
|
7799
7865
|
try {
|
|
7800
7866
|
const d = JSON.parse(await readFile12(file, "utf8"));
|
|
7801
7867
|
return Array.isArray(d.activity) && d.activity.every((v) => typeof v === "number") ? d.activity : null;
|
|
@@ -8007,15 +8073,15 @@ async function planTake(dir, opts = {}) {
|
|
|
8007
8073
|
|
|
8008
8074
|
// src/plugin/sync.ts
|
|
8009
8075
|
import { createHash } from "crypto";
|
|
8010
|
-
import { existsSync as
|
|
8076
|
+
import { existsSync as existsSync14, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "fs";
|
|
8011
8077
|
import { readFile as readFile13 } from "fs/promises";
|
|
8012
8078
|
import { createInterface } from "readline/promises";
|
|
8013
|
-
import { basename as basename3, join as
|
|
8079
|
+
import { basename as basename3, join as join17 } from "path";
|
|
8014
8080
|
import { lowerToComposition as lowerToComposition3, migrateHostedDoc as migrateHostedDoc3 } from "@vosjs/studio-core";
|
|
8015
8081
|
|
|
8016
8082
|
// src/plugin/folder.ts
|
|
8017
8083
|
import { mkdir as mkdir8, writeFile as writeFile10 } from "fs/promises";
|
|
8018
|
-
import { join as
|
|
8084
|
+
import { join as join16 } from "path";
|
|
8019
8085
|
import { recipeHints } from "@vosjs/shared/frontmatter";
|
|
8020
8086
|
import { migrateHostedDoc as migrateHostedDoc2 } from "@vosjs/studio-core";
|
|
8021
8087
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "help", "media"]);
|
|
@@ -8191,18 +8257,18 @@ async function cmdPull(argv) {
|
|
|
8191
8257
|
throw new Error(apiError(`pull folder ${folder.slug}`, res));
|
|
8192
8258
|
const payload = res.body;
|
|
8193
8259
|
const out = strFlag(flags, "out") ?? folder.slug;
|
|
8194
|
-
await mkdir8(
|
|
8260
|
+
await mkdir8(join16(out, "recipes"), { recursive: true });
|
|
8195
8261
|
const lines = [];
|
|
8196
8262
|
const recipes = [];
|
|
8197
8263
|
for (const rec of payload.recipes) {
|
|
8198
|
-
const path =
|
|
8264
|
+
const path = join16(out, "recipes", safeName(rec.filename));
|
|
8199
8265
|
await writeFile10(path, rec.body ?? "");
|
|
8200
8266
|
recipes.push({ path, line: recipeLine(rec.filename, rec.body ?? "") });
|
|
8201
8267
|
}
|
|
8202
8268
|
for (const rec of payload.inheritedRecipes) {
|
|
8203
8269
|
const from = safeName(rec.folderSlug ?? "inherited");
|
|
8204
|
-
await mkdir8(
|
|
8205
|
-
const path =
|
|
8270
|
+
await mkdir8(join16(out, "recipes", "_inherited", from), { recursive: true });
|
|
8271
|
+
const path = join16(
|
|
8206
8272
|
out,
|
|
8207
8273
|
"recipes",
|
|
8208
8274
|
"_inherited",
|
|
@@ -8217,7 +8283,7 @@ async function cmdPull(argv) {
|
|
|
8217
8283
|
}
|
|
8218
8284
|
const members = [];
|
|
8219
8285
|
for (const v of payload.voses) {
|
|
8220
|
-
const dir =
|
|
8286
|
+
const dir = join16(out, "members", safeName(v.slug || v.title || v.id));
|
|
8221
8287
|
await mkdir8(dir, { recursive: true });
|
|
8222
8288
|
const cfg = await apiJson(origin, `/api/vos/${v.id}/config`, { key });
|
|
8223
8289
|
if (cfg.status !== 200) {
|
|
@@ -8225,7 +8291,7 @@ async function cmdPull(argv) {
|
|
|
8225
8291
|
continue;
|
|
8226
8292
|
}
|
|
8227
8293
|
await writeFile10(
|
|
8228
|
-
|
|
8294
|
+
join16(dir, "config.json"),
|
|
8229
8295
|
JSON.stringify(cfg.body.config, null, 2)
|
|
8230
8296
|
);
|
|
8231
8297
|
let take = false;
|
|
@@ -8237,7 +8303,7 @@ async function cmdPull(argv) {
|
|
|
8237
8303
|
);
|
|
8238
8304
|
if (doc.status === 200) {
|
|
8239
8305
|
await writeFile10(
|
|
8240
|
-
|
|
8306
|
+
join16(dir, "doc.json"),
|
|
8241
8307
|
JSON.stringify(doc.body, null, 2)
|
|
8242
8308
|
);
|
|
8243
8309
|
take = true;
|
|
@@ -8506,7 +8572,24 @@ function programPushTarget(ask) {
|
|
|
8506
8572
|
}
|
|
8507
8573
|
return { kind: "version", vosId: tracked, tracked: true };
|
|
8508
8574
|
}
|
|
8575
|
+
function storageStateInDir(dir) {
|
|
8576
|
+
for (const f of readdirSync3(dir)) {
|
|
8577
|
+
if (!f.endsWith(".json") || f === "doc.json" || f === "meta.json" || f === "actions.json" || f === "cursor.json" || f === "vos.json" || f === "doc.prev.json")
|
|
8578
|
+
continue;
|
|
8579
|
+
try {
|
|
8580
|
+
const v = JSON.parse(readFileSync4(join17(dir, f), "utf8"));
|
|
8581
|
+
if (looksLikeStorageState(v)) return join17(dir, f);
|
|
8582
|
+
} catch {
|
|
8583
|
+
}
|
|
8584
|
+
}
|
|
8585
|
+
return null;
|
|
8586
|
+
}
|
|
8509
8587
|
async function pushTake(dir, flags, r) {
|
|
8588
|
+
const stateFile = storageStateInDir(dir);
|
|
8589
|
+
if (stateFile)
|
|
8590
|
+
throw new Error(
|
|
8591
|
+
`${stateFile} looks like a Playwright storage state (cookies + origins): a live session in the take directory would be uploaded with it. Move it out of the take (a session belongs under ~/.config/vos/sessions or a temp dir) and push again`
|
|
8592
|
+
);
|
|
8510
8593
|
const take = await loadTake(dir);
|
|
8511
8594
|
if (!take.doc) {
|
|
8512
8595
|
throw new Error("no doc.json in this take \u2014 run `vos plan` first");
|
|
@@ -8516,7 +8599,7 @@ async function pushTake(dir, flags, r) {
|
|
|
8516
8599
|
throw new Error(`doc.json fails lint:
|
|
8517
8600
|
${lint.problems.join("\n ")}`);
|
|
8518
8601
|
}
|
|
8519
|
-
if (!
|
|
8602
|
+
if (!existsSync14(take.paths.recording)) {
|
|
8520
8603
|
throw new Error(
|
|
8521
8604
|
"no recording in this take \u2014 record it, or `vos pull --media` to bring it home"
|
|
8522
8605
|
);
|
|
@@ -8608,8 +8691,8 @@ async function pushTake(dir, flags, r) {
|
|
|
8608
8691
|
delete docForPush.source.micKey;
|
|
8609
8692
|
}
|
|
8610
8693
|
for (const ref of docMediaRefs(docForPush)) {
|
|
8611
|
-
const file =
|
|
8612
|
-
if (!
|
|
8694
|
+
const file = join17(dir, takeRelativeFile(ref.key));
|
|
8695
|
+
if (!existsSync14(file)) {
|
|
8613
8696
|
r.log(` ${ref.where}: ${ref.key} is not in the take \u2014 left as is`);
|
|
8614
8697
|
continue;
|
|
8615
8698
|
}
|
|
@@ -8753,9 +8836,9 @@ async function pullTake(dir, flags, r) {
|
|
|
8753
8836
|
};
|
|
8754
8837
|
}
|
|
8755
8838
|
let media2;
|
|
8756
|
-
if (flags.media &&
|
|
8839
|
+
if (flags.media && existsSync14(join17(dir, "doc.json"))) {
|
|
8757
8840
|
const local = JSON.parse(
|
|
8758
|
-
await readFile13(
|
|
8841
|
+
await readFile13(join17(dir, "doc.json"), "utf8")
|
|
8759
8842
|
);
|
|
8760
8843
|
media2 = await pullMedia(ctx, dir, local, r.log);
|
|
8761
8844
|
}
|
|
@@ -8816,10 +8899,10 @@ async function pullTake(dir, flags, r) {
|
|
|
8816
8899
|
const hostedDoc = migrateHostedDoc3(docRes.json);
|
|
8817
8900
|
const doc = hostedDoc;
|
|
8818
8901
|
const localRecording = takeMediaFile(dir, "recording", RECORDING_NAME);
|
|
8819
|
-
if (
|
|
8902
|
+
if (existsSync14(localRecording)) {
|
|
8820
8903
|
doc.source.videoKey = basename3(localRecording);
|
|
8821
8904
|
}
|
|
8822
|
-
await writeJson(
|
|
8905
|
+
await writeJson(join17(dir, "doc.json"), doc, true);
|
|
8823
8906
|
const media = flags.media ? await pullMedia(ctx, dir, doc, r.log) : void 0;
|
|
8824
8907
|
const versions = await api(ctx, `/vos/${vosId}/versions`);
|
|
8825
8908
|
const headRow = (versions.json.versions ?? []).find((v) => v.id === head);
|
|
@@ -8838,10 +8921,10 @@ async function pullTake(dir, flags, r) {
|
|
|
8838
8921
|
}
|
|
8839
8922
|
|
|
8840
8923
|
// src/plugin/program.ts
|
|
8841
|
-
import { existsSync as
|
|
8924
|
+
import { existsSync as existsSync15 } from "fs";
|
|
8842
8925
|
import { mkdir as mkdir9, readFile as readFile14, writeFile as writeFile11 } from "fs/promises";
|
|
8843
8926
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
8844
|
-
import { basename as basename4, dirname as dirname4, join as
|
|
8927
|
+
import { basename as basename4, dirname as dirname4, join as join18 } from "path";
|
|
8845
8928
|
import {
|
|
8846
8929
|
CURRENT_CONFIG_VERSION,
|
|
8847
8930
|
migrateConfig,
|
|
@@ -9011,8 +9094,8 @@ function preflightConfig(parsed) {
|
|
|
9011
9094
|
}
|
|
9012
9095
|
function resolveConfigPath(target) {
|
|
9013
9096
|
if (target.endsWith(".json")) return target;
|
|
9014
|
-
const inDir =
|
|
9015
|
-
if (
|
|
9097
|
+
const inDir = join18(target, "config.json");
|
|
9098
|
+
if (existsSync15(inDir)) return inDir;
|
|
9016
9099
|
throw new UsageError(
|
|
9017
9100
|
`${target} has no config.json (and is not a take \u2014 no doc.json)`
|
|
9018
9101
|
);
|
|
@@ -9040,7 +9123,7 @@ async function cmdFetch(argv) {
|
|
|
9040
9123
|
const out = strFlag(flags, "out") ?? slug;
|
|
9041
9124
|
await mkdir9(out, { recursive: true });
|
|
9042
9125
|
await writeFile11(
|
|
9043
|
-
|
|
9126
|
+
join18(out, "config.json"),
|
|
9044
9127
|
JSON.stringify(cfg.body.config, null, 2)
|
|
9045
9128
|
);
|
|
9046
9129
|
const title = typeof vosMeta.title === "string" ? vosMeta.title : "";
|
|
@@ -9065,12 +9148,12 @@ async function cmdFetch(argv) {
|
|
|
9065
9148
|
const hosted = migrateHostedDoc4(doc.body);
|
|
9066
9149
|
const { config: own } = await writeProgramDoc(out, hosted);
|
|
9067
9150
|
if (own) {
|
|
9068
|
-
await writeFile11(
|
|
9151
|
+
await writeFile11(join18(out, "config.json"), JSON.stringify(own, null, 2));
|
|
9069
9152
|
}
|
|
9070
9153
|
} else if (doc.status === 200) {
|
|
9071
9154
|
take = true;
|
|
9072
9155
|
const hosted = migrateHostedDoc4(doc.body);
|
|
9073
|
-
await writeFile11(
|
|
9156
|
+
await writeFile11(join18(out, "doc.json"), JSON.stringify(hosted, null, 2));
|
|
9074
9157
|
if (flags.media === true) {
|
|
9075
9158
|
const media = await pullMedia({ origin, key }, out, hosted, r.log);
|
|
9076
9159
|
mediaLine = media.downloaded.length ? ` + ${media.downloaded.map((m) => m.file).join(", ")}` : "";
|
|
@@ -9433,10 +9516,10 @@ async function cmdPullProgram(argv) {
|
|
|
9433
9516
|
const cfg = await apiJson(origin, `/api/vos/${vosId}/config`, { key });
|
|
9434
9517
|
if (cfg.status !== 200)
|
|
9435
9518
|
throw new Error(apiError(`fetch head config for ${vosId}`, cfg));
|
|
9436
|
-
const configPath =
|
|
9519
|
+
const configPath = join18(dir, "config.json");
|
|
9437
9520
|
let backedUp = false;
|
|
9438
|
-
if (
|
|
9439
|
-
await writeFile11(
|
|
9521
|
+
if (existsSync15(configPath)) {
|
|
9522
|
+
await writeFile11(join18(dir, "config.backup.json"), await readFile14(configPath));
|
|
9440
9523
|
backedUp = true;
|
|
9441
9524
|
}
|
|
9442
9525
|
let headConfig = cfg.body.config;
|
|
@@ -9469,7 +9552,7 @@ async function cmdPullProgram(argv) {
|
|
|
9469
9552
|
protected: protectedIds,
|
|
9470
9553
|
changes,
|
|
9471
9554
|
out: configPath,
|
|
9472
|
-
backup: backedUp ?
|
|
9555
|
+
backup: backedUp ? join18(dir, "config.backup.json") : null
|
|
9473
9556
|
},
|
|
9474
9557
|
`Pulled ${changes.length} version${changes.length === 1 ? "" : "s"} \u2192 ${configPath}` + (backedUp ? ` (previous copy: config.backup.json)` : "") + `
|
|
9475
9558
|
Re-apply your edit on the new head, then: vos push ${configPath} --vos ${vosId}`
|
|
@@ -9531,8 +9614,8 @@ function storedProgramConfig(config, programDoc) {
|
|
|
9531
9614
|
return lowerProgramDoc(doc).config;
|
|
9532
9615
|
}
|
|
9533
9616
|
async function readProgramDoc(dir, config) {
|
|
9534
|
-
const docPath =
|
|
9535
|
-
if (!
|
|
9617
|
+
const docPath = join18(dir, "doc.json");
|
|
9618
|
+
if (!existsSync15(docPath)) return null;
|
|
9536
9619
|
const parsed = JSON.parse(await readFile14(docPath, "utf8"));
|
|
9537
9620
|
if (typeof parsed !== "object" || parsed === null || "source" in parsed)
|
|
9538
9621
|
return null;
|
|
@@ -9544,14 +9627,14 @@ async function writeProgramDoc(dir, hosted) {
|
|
|
9544
9627
|
const program = hosted.program && typeof hosted.program === "object" ? hosted.program : {};
|
|
9545
9628
|
const { config, ...rest } = program;
|
|
9546
9629
|
const onDisk = { ...hosted, program: rest };
|
|
9547
|
-
await writeFile11(
|
|
9630
|
+
await writeFile11(join18(dir, "doc.json"), JSON.stringify(onDisk, null, 2));
|
|
9548
9631
|
return {
|
|
9549
9632
|
config: config && typeof config === "object" ? config : null
|
|
9550
9633
|
};
|
|
9551
9634
|
}
|
|
9552
9635
|
|
|
9553
9636
|
// src/plugin/asset.ts
|
|
9554
|
-
import { readFileSync as
|
|
9637
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
9555
9638
|
import { basename as basename5, extname as extname2 } from "path";
|
|
9556
9639
|
var BOOLEAN_FLAGS3 = /* @__PURE__ */ new Set(["json", "help"]);
|
|
9557
9640
|
var MIME_BY_EXT = {
|
|
@@ -9605,7 +9688,7 @@ async function cmdPush(argv) {
|
|
|
9605
9688
|
for (const file of positionals) {
|
|
9606
9689
|
const name = basename5(file);
|
|
9607
9690
|
const mime = MIME_BY_EXT[extname2(name).toLowerCase()] ?? "application/octet-stream";
|
|
9608
|
-
const body =
|
|
9691
|
+
const body = readFileSync5(file);
|
|
9609
9692
|
const form = new FormData();
|
|
9610
9693
|
form.set("file", new Blob([body], { type: mime }), name);
|
|
9611
9694
|
if (folder) form.set("folderId", folder.id);
|
|
@@ -9659,7 +9742,7 @@ async function cmdAsset(argv) {
|
|
|
9659
9742
|
}
|
|
9660
9743
|
|
|
9661
9744
|
// src/plugin/recipe.ts
|
|
9662
|
-
import { readFileSync as
|
|
9745
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
9663
9746
|
import { basename as basename6 } from "path";
|
|
9664
9747
|
var BOOLEAN_FLAGS4 = /* @__PURE__ */ new Set(["json", "help"]);
|
|
9665
9748
|
var USAGE = "vos recipe push <file.md> (--folder <folderId|slug> | --asset <assetId>)";
|
|
@@ -9679,7 +9762,7 @@ async function cmdPush2(argv) {
|
|
|
9679
9762
|
throw new UsageError(USAGE);
|
|
9680
9763
|
}
|
|
9681
9764
|
const filename = recipeFilename(file);
|
|
9682
|
-
const body =
|
|
9765
|
+
const body = readFileSync6(file);
|
|
9683
9766
|
const r = createReporter(flags.json === true);
|
|
9684
9767
|
const origin = platformOrigin({
|
|
9685
9768
|
origin: strFlag(flags, "origin"),
|
|
@@ -10588,9 +10671,10 @@ function convertAgentBrowser(records, opts = {}) {
|
|
|
10588
10671
|
async function cmdActions(argv) {
|
|
10589
10672
|
const { positionals, flags } = parseArgs(argv, /* @__PURE__ */ new Set(["json"]));
|
|
10590
10673
|
const [sub, input] = positionals;
|
|
10674
|
+
if (sub === "script" && input) return cmdActionsScript(input, flags);
|
|
10591
10675
|
if (sub !== "from-agent-browser" || !input) {
|
|
10592
10676
|
throw new UsageError(
|
|
10593
|
-
"vos actions from-agent-browser <steps.jsonl> [--out actions.json] [--url <url>] [--viewport WxH] [--json]"
|
|
10677
|
+
"vos actions from-agent-browser <steps.jsonl> [--out actions.json] [--url <url>] [--viewport WxH] [--json]\n vos actions script <actions.json> [--json]"
|
|
10594
10678
|
);
|
|
10595
10679
|
}
|
|
10596
10680
|
const r = createReporter(flags.json === true);
|
|
@@ -10638,6 +10722,18 @@ function summary(result) {
|
|
|
10638
10722
|
notes: result.notes
|
|
10639
10723
|
};
|
|
10640
10724
|
}
|
|
10725
|
+
async function cmdActionsScript(input, flags) {
|
|
10726
|
+
const r = createReporter(flags.json === true);
|
|
10727
|
+
const { readFile: readFile16 } = await import("fs/promises");
|
|
10728
|
+
const { shotList, formatShotList } = await import("./shotList-FYDHO5AI.js");
|
|
10729
|
+
const raw = JSON.parse(await readFile16(resolve8(input), "utf8"));
|
|
10730
|
+
const errors = validateActions(raw);
|
|
10731
|
+
if (errors.length) throw new UsageError(`${input}:
|
|
10732
|
+
${errors.join("\n ")}`);
|
|
10733
|
+
const list = shotList(raw);
|
|
10734
|
+
r.done({ ...list, text: formatShotList(list) }, formatShotList(list));
|
|
10735
|
+
return EXIT_OK;
|
|
10736
|
+
}
|
|
10641
10737
|
|
|
10642
10738
|
// src/plugin/run.ts
|
|
10643
10739
|
var BOOLEAN_FLAGS5 = /* @__PURE__ */ new Set([
|
|
@@ -10667,8 +10763,8 @@ var MULTI_FLAGS2 = /* @__PURE__ */ new Set(["set", "override", "browser-arg", "h
|
|
|
10667
10763
|
var HELP = `vos \u2014 record a browser flow, plan effects, render a product video; sync with vos.so
|
|
10668
10764
|
|
|
10669
10765
|
Take pipeline
|
|
10670
|
-
vos create --actions actions.json [--url <url>] [--out take] [out.webm] [--strict] [--allow-wall] [--keep-frames] [--max-duration <s>] [--storage-state <file>] [--header name=value]... [--browser-arg=<switch>]... [--background <slug|url|none>] [render flags] [--json]
|
|
10671
|
-
vos record --actions actions.json [--url <url>] [--out take] [--strict] [--dry-run] [--allow-wall] [--keep-frames] [--max-duration <s>] [--storage-state <file>] [--header name=value]... [--browser-arg=<switch>]... [--background <slug|url|none>] [--json]
|
|
10766
|
+
vos create --actions actions.json [--url <url>] [--out take] [out.webm] [--strict] [--allow-wall] [--keep-frames] [--max-duration <s>] [--storage-state <file>|--session <name>] [--header name=value]... [--browser-arg=<switch>]... [--background <slug|url|none>] [render flags] [--json]
|
|
10767
|
+
vos record --actions actions.json [--url <url>] [--out take] [--strict] [--dry-run] [--allow-wall] [--keep-frames] [--max-duration <s>] [--storage-state <file>|--session <name>] [--header name=value]... [--browser-arg=<switch>]... [--background <slug|url|none>] [--json]
|
|
10672
10768
|
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]
|
|
10673
10769
|
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]
|
|
10674
10770
|
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]
|
|
@@ -10680,6 +10776,11 @@ Take pipeline
|
|
|
10680
10776
|
vos validate <actions.json|take|kit.json> [--picture] [--json]
|
|
10681
10777
|
vos judge <kit.json> --against <MANIFEST.json> [--out dir] [--json]
|
|
10682
10778
|
vos actions from-agent-browser <steps.jsonl> [--out actions.json] [--url <url>] [--viewport WxH] [--json]
|
|
10779
|
+
vos session open <url> --name <app> a plain Chrome window on a vos-owned profile; sign in, close it
|
|
10780
|
+
vos session check <name> [--url <url>] headless: does the session still open the page? exit 0/4
|
|
10781
|
+
vos session list | rm <name> what exists and how old; delete one
|
|
10782
|
+
vos actions script <actions.json> [--json]
|
|
10783
|
+
the flow as numbered beats in words, for a person recording it by hand
|
|
10683
10784
|
|
|
10684
10785
|
Platform (vos.so) \u2014 fetch, edit, push, pull, repeat
|
|
10685
10786
|
vos login [--key <vos_sk_\u2026>] [--label <name>] [--no-browser]
|
|
@@ -10976,10 +11077,35 @@ function takeStorageState(flags) {
|
|
|
10976
11077
|
const raw = strFlag(flags, "storage-state");
|
|
10977
11078
|
if (!raw) return void 0;
|
|
10978
11079
|
const path = resolve9(raw);
|
|
10979
|
-
if (!
|
|
11080
|
+
if (!existsSync16(path))
|
|
10980
11081
|
throw new UsageError(`--storage-state: no such file: ${path}`);
|
|
10981
11082
|
return path;
|
|
10982
11083
|
}
|
|
11084
|
+
function takeSessionName(flags, storageState) {
|
|
11085
|
+
const name = strFlag(flags, "session");
|
|
11086
|
+
if (!name) return void 0;
|
|
11087
|
+
const bad = sessionNameVerdict(name);
|
|
11088
|
+
if (bad) throw new UsageError(bad);
|
|
11089
|
+
if (storageState)
|
|
11090
|
+
throw new UsageError(
|
|
11091
|
+
"--session and --storage-state are two doors to one take: a persistent profile cannot take a storage state. Pass one"
|
|
11092
|
+
);
|
|
11093
|
+
if (!existsSync16(sessionDir(name)))
|
|
11094
|
+
throw new UsageError(
|
|
11095
|
+
`no session "${name}". Make one: vos session open <url> --name ${name} (a Chrome window opens; sign in and close it)`
|
|
11096
|
+
);
|
|
11097
|
+
return name;
|
|
11098
|
+
}
|
|
11099
|
+
async function inSession(name, actions, fn) {
|
|
11100
|
+
if (!name) return fn(void 0);
|
|
11101
|
+
return withSession(name, (ctx) => fn(ctx), {
|
|
11102
|
+
headless: true,
|
|
11103
|
+
viewport: {
|
|
11104
|
+
width: actions.viewport?.width ?? 1280,
|
|
11105
|
+
height: actions.viewport?.height ?? 720
|
|
11106
|
+
}
|
|
11107
|
+
});
|
|
11108
|
+
}
|
|
10983
11109
|
async function takeBackdrop(flags, r) {
|
|
10984
11110
|
const { backdrop, note } = await openingBackdrop(
|
|
10985
11111
|
strFlag(flags, "background"),
|
|
@@ -11037,6 +11163,7 @@ async function cmdRecord(argv) {
|
|
|
11037
11163
|
const backdrop = await takeBackdrop(flags, r);
|
|
11038
11164
|
const maxDurationSeconds = await maxDuration(flags, r);
|
|
11039
11165
|
const storageState = takeStorageState(flags);
|
|
11166
|
+
const sessionName = takeSessionName(flags, storageState);
|
|
11040
11167
|
const browserArgs = takeBrowserArgs(multi);
|
|
11041
11168
|
const headers = takeHeaders(multi);
|
|
11042
11169
|
if (flags["dry-run"] === true) {
|
|
@@ -11047,19 +11174,17 @@ async function cmdRecord(argv) {
|
|
|
11047
11174
|
r.log("rehearsing (nothing is captured or written)\u2026");
|
|
11048
11175
|
r.event({ event: "phase", phase: "rehearse" });
|
|
11049
11176
|
const started = Date.now();
|
|
11050
|
-
const rec = await
|
|
11051
|
-
|
|
11052
|
-
url,
|
|
11177
|
+
const rec = await inSession(
|
|
11178
|
+
sessionName,
|
|
11053
11179
|
actions,
|
|
11054
|
-
takePaths(outDir),
|
|
11055
|
-
r.log,
|
|
11056
|
-
{
|
|
11180
|
+
(context) => recordTake(browser2, url, actions, takePaths(outDir), r.log, {
|
|
11057
11181
|
storageState,
|
|
11058
11182
|
headers,
|
|
11183
|
+
context,
|
|
11059
11184
|
dryRun: true,
|
|
11060
11185
|
// A rehearsal is always strict, and it touches no directory.
|
|
11061
11186
|
onArrival: wallGate(flags, r, { strict: true })
|
|
11062
|
-
}
|
|
11187
|
+
})
|
|
11063
11188
|
);
|
|
11064
11189
|
const steps = rec.meta.steps ?? [];
|
|
11065
11190
|
const lines = steps.map((s) => {
|
|
@@ -11072,6 +11197,7 @@ async function cmdRecord(argv) {
|
|
|
11072
11197
|
...strFlag(flags, "storage-state") ? [`--storage-state ${strFlag(flags, "storage-state")}`] : [],
|
|
11073
11198
|
...browserArgs.map((a) => `--browser-arg=${a}`),
|
|
11074
11199
|
...Object.entries(headers).map(([k, v]) => `--header ${k}=${v}`),
|
|
11200
|
+
...sessionName ? [`--session ${sessionName}`] : [],
|
|
11075
11201
|
...flags["allow-wall"] === true ? ["--allow-wall"] : []
|
|
11076
11202
|
].join(" ");
|
|
11077
11203
|
const nextOut = strFlag(flags, "out") ?? "take";
|
|
@@ -11097,7 +11223,7 @@ ${lines.join("\n")}
|
|
|
11097
11223
|
}
|
|
11098
11224
|
const paths = takePaths(outDir);
|
|
11099
11225
|
const prepare = async () => {
|
|
11100
|
-
if (
|
|
11226
|
+
if (existsSync16(join19(outDir, "meta.json"))) {
|
|
11101
11227
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
11102
11228
|
r.log(
|
|
11103
11229
|
`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" : "")
|
|
@@ -11110,15 +11236,20 @@ ${lines.join("\n")}
|
|
|
11110
11236
|
try {
|
|
11111
11237
|
r.log("recording\u2026");
|
|
11112
11238
|
r.event({ event: "phase", phase: "record" });
|
|
11113
|
-
const rec = await
|
|
11114
|
-
|
|
11115
|
-
|
|
11116
|
-
|
|
11117
|
-
|
|
11118
|
-
|
|
11119
|
-
|
|
11239
|
+
const rec = await inSession(
|
|
11240
|
+
sessionName,
|
|
11241
|
+
actions,
|
|
11242
|
+
(context) => recordTake(browser, url, actions, paths, r.log, {
|
|
11243
|
+
maxDurationSeconds,
|
|
11244
|
+
storageState,
|
|
11245
|
+
headers,
|
|
11246
|
+
context,
|
|
11247
|
+
onArrival: wallGate(flags, r, {
|
|
11248
|
+
strict: flags.strict === true,
|
|
11249
|
+
prepare
|
|
11250
|
+
})
|
|
11120
11251
|
})
|
|
11121
|
-
|
|
11252
|
+
);
|
|
11122
11253
|
r.event({ event: "phase", phase: "encode" });
|
|
11123
11254
|
r.log("encoding\u2026");
|
|
11124
11255
|
const enc = await encodeRecording(
|
|
@@ -11156,7 +11287,7 @@ ${lines.join("\n")}
|
|
|
11156
11287
|
},
|
|
11157
11288
|
strictFail ? `STRICT: take recorded but incomplete \u2014 ${strictReason(rec)}; fix the flow and re-record.${skippedNote}${exposureNote(rec)}` : `Take ready: ${outDir}
|
|
11158
11289
|
${(rec.meta.durationMs / 1e3).toFixed(1)}s \xB7 ${rec.frames.length} frames \xB7 ${rec.events.length} cursor events \xB7 ${clicks} clicks \xB7 ${plan.doc.zoom.length} zoom spans planned \xB7 ${rec.freezePct}% frozen${rec.freezePct >= 25 ? " \u26A0 keep motion in frame or trim" : ""}${skippedNote}
|
|
11159
|
-
Next: edit ${
|
|
11290
|
+
Next: edit ${join19(outDir, "doc.json")} (optional), then: vos render ${outDir}${exposureNote(rec)}`
|
|
11160
11291
|
);
|
|
11161
11292
|
return strictFail ? EXIT_USAGE : EXIT_OK;
|
|
11162
11293
|
} finally {
|
|
@@ -11185,7 +11316,7 @@ async function cmdCreate2(argv) {
|
|
|
11185
11316
|
if (fmtRaw !== "webm" && fmtRaw !== "mp4")
|
|
11186
11317
|
throw new UsageError("--format must be webm or mp4");
|
|
11187
11318
|
const format = fmtRaw;
|
|
11188
|
-
const out = positionals[0] ??
|
|
11319
|
+
const out = positionals[0] ?? join19(outDir, `out.${format}`);
|
|
11189
11320
|
const parallel = numFlag(flags, "parallel", 1);
|
|
11190
11321
|
if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
|
|
11191
11322
|
throw new UsageError("--parallel expects an integer between 1 and 16");
|
|
@@ -11193,11 +11324,12 @@ async function cmdCreate2(argv) {
|
|
|
11193
11324
|
const backdrop = await takeBackdrop(flags, r);
|
|
11194
11325
|
const maxDurationSeconds = await maxDuration(flags, r);
|
|
11195
11326
|
const storageState = takeStorageState(flags);
|
|
11327
|
+
const sessionName = takeSessionName(flags, storageState);
|
|
11196
11328
|
const browserArgs = takeBrowserArgs(multi);
|
|
11197
11329
|
const headers = takeHeaders(multi);
|
|
11198
11330
|
const paths = takePaths(outDir);
|
|
11199
11331
|
const prepare = async () => {
|
|
11200
|
-
if (
|
|
11332
|
+
if (existsSync16(join19(outDir, "meta.json"))) {
|
|
11201
11333
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
11202
11334
|
r.log(
|
|
11203
11335
|
`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" : "")
|
|
@@ -11210,15 +11342,20 @@ async function cmdCreate2(argv) {
|
|
|
11210
11342
|
try {
|
|
11211
11343
|
r.log("recording\u2026");
|
|
11212
11344
|
r.event({ event: "phase", phase: "record" });
|
|
11213
|
-
const rec = await
|
|
11214
|
-
|
|
11215
|
-
|
|
11216
|
-
|
|
11217
|
-
|
|
11218
|
-
|
|
11219
|
-
|
|
11345
|
+
const rec = await inSession(
|
|
11346
|
+
sessionName,
|
|
11347
|
+
actions,
|
|
11348
|
+
(context) => recordTake(browser, url, actions, paths, r.log, {
|
|
11349
|
+
maxDurationSeconds,
|
|
11350
|
+
storageState,
|
|
11351
|
+
headers,
|
|
11352
|
+
context,
|
|
11353
|
+
onArrival: wallGate(flags, r, {
|
|
11354
|
+
strict: flags.strict === true,
|
|
11355
|
+
prepare
|
|
11356
|
+
})
|
|
11220
11357
|
})
|
|
11221
|
-
|
|
11358
|
+
);
|
|
11222
11359
|
r.event({ event: "phase", phase: "encode" });
|
|
11223
11360
|
r.log("encoding\u2026");
|
|
11224
11361
|
await encodeRecording(
|
|
@@ -11298,13 +11435,13 @@ async function cmdPlan(argv) {
|
|
|
11298
11435
|
);
|
|
11299
11436
|
const r = createReporter(flags.json === true);
|
|
11300
11437
|
if (flags.fresh === true) {
|
|
11301
|
-
await rm5(
|
|
11438
|
+
await rm5(join19(dir, "doc.json"), { force: true });
|
|
11302
11439
|
r.log("note: --fresh discarded the existing doc.json");
|
|
11303
11440
|
}
|
|
11304
11441
|
let reuse;
|
|
11305
11442
|
if (flags.reuse === true) {
|
|
11306
|
-
const from = resolve9(strFlag(flags, "from") ??
|
|
11307
|
-
if (!
|
|
11443
|
+
const from = resolve9(strFlag(flags, "from") ?? join19(dir, PREV_DOC_NAME));
|
|
11444
|
+
if (!existsSync16(from)) {
|
|
11308
11445
|
throw new UsageError(
|
|
11309
11446
|
`--reuse: ${from} does not exist \u2014 a re-record into this take writes doc.prev.json, or pass --from <doc.json>`
|
|
11310
11447
|
);
|
|
@@ -11315,7 +11452,7 @@ async function cmdPlan(argv) {
|
|
|
11315
11452
|
};
|
|
11316
11453
|
}
|
|
11317
11454
|
const style = await resolveStyleRef(flags);
|
|
11318
|
-
const hasDoc =
|
|
11455
|
+
const hasDoc = existsSync16(join19(dir, "doc.json"));
|
|
11319
11456
|
const backdrop = hasDoc ? null : await takeBackdrop(flags, r);
|
|
11320
11457
|
const release = await releaseInputs(dir, flags, r, multi.with ?? []);
|
|
11321
11458
|
const motionWanted = !hasDoc || flags.motion === true || flags.fresh === true;
|
|
@@ -11373,7 +11510,7 @@ async function cmdPlan(argv) {
|
|
|
11373
11510
|
...s.motion ? { motion: s.motion } : {},
|
|
11374
11511
|
...s.reuse ? { reuse: s.reuse } : {}
|
|
11375
11512
|
},
|
|
11376
|
-
`${s.reuse ? "Reused" : s.fresh ? "Planned" : "Refreshed"} ${
|
|
11513
|
+
`${s.reuse ? "Reused" : s.fresh ? "Planned" : "Refreshed"} ${join19(dir, "doc.json")}: ${s.zoomAuto} auto + ${s.zoomManual} manual zoom spans${s.cursorKept ? "" : " (cursor track dropped)"}${layoutLines}${motionLines}${reuseLines}`
|
|
11377
11514
|
);
|
|
11378
11515
|
return EXIT_OK;
|
|
11379
11516
|
}
|
|
@@ -11499,7 +11636,7 @@ async function cmdRender(argv) {
|
|
|
11499
11636
|
if (fmtRaw !== "webm" && fmtRaw !== "mp4")
|
|
11500
11637
|
throw new UsageError("--format must be webm or mp4");
|
|
11501
11638
|
const format = fmtRaw;
|
|
11502
|
-
const out = positionals[1] ??
|
|
11639
|
+
const out = positionals[1] ?? join19(dir, `out.${format}`);
|
|
11503
11640
|
const parallel = numFlag(flags, "parallel", 1);
|
|
11504
11641
|
if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
|
|
11505
11642
|
throw new UsageError("--parallel expects an integer between 1 and 16");
|
|
@@ -11746,11 +11883,11 @@ async function resolveStyleRef(flags) {
|
|
|
11746
11883
|
}
|
|
11747
11884
|
async function resolveDocRef(ref, flags, what) {
|
|
11748
11885
|
const styleRef = ref;
|
|
11749
|
-
const file =
|
|
11886
|
+
const file = existsSync16(styleRef) ? resolve9(
|
|
11750
11887
|
styleRef,
|
|
11751
|
-
|
|
11888
|
+
existsSync16(join19(styleRef, "doc.json")) ? "doc.json" : ""
|
|
11752
11889
|
) : null;
|
|
11753
|
-
if (file &&
|
|
11890
|
+
if (file && existsSync16(file)) {
|
|
11754
11891
|
return {
|
|
11755
11892
|
from: file,
|
|
11756
11893
|
doc: migrateHostedDoc5(
|
|
@@ -11837,7 +11974,7 @@ async function cmdDigest(argv) {
|
|
|
11837
11974
|
kinds,
|
|
11838
11975
|
frames: d.images.full,
|
|
11839
11976
|
crops: d.images.crop,
|
|
11840
|
-
sheet: d.images.sheet ?
|
|
11977
|
+
sheet: d.images.sheet ? join19(result.outDir, d.images.sheet) : null,
|
|
11841
11978
|
scenes: kinds.scene ?? 0,
|
|
11842
11979
|
sourceDuration: d.take.sourceDuration,
|
|
11843
11980
|
outputDuration: d.take.outputDuration,
|
|
@@ -11865,7 +12002,7 @@ async function cmdOpen(argv) {
|
|
|
11865
12002
|
if (!dir) throw new UsageError("vos open <take> [--studio <url>]");
|
|
11866
12003
|
const r = createReporter(flags.json === true);
|
|
11867
12004
|
const take = await loadTake(dir);
|
|
11868
|
-
if (!
|
|
12005
|
+
if (!existsSync16(take.paths.recording)) {
|
|
11869
12006
|
throw new UsageError(`${dir} has no recording \u2014 re-run record`);
|
|
11870
12007
|
}
|
|
11871
12008
|
const studio = (strFlag(flags, "studio") ?? "http://localhost:6060").replace(
|
|
@@ -11906,9 +12043,9 @@ async function cmdJudge(argv) {
|
|
|
11906
12043
|
"vos judge <kit.json> --against <MANIFEST.json> [--out dir] [--json]\n(the manifest names the reference set: id, file, role, layout, facts, rule per asset)"
|
|
11907
12044
|
);
|
|
11908
12045
|
const r = createReporter(flags.json === true);
|
|
11909
|
-
const kitFile = target.endsWith("kit.json") ? target :
|
|
11910
|
-
if (!
|
|
11911
|
-
if (!
|
|
12046
|
+
const kitFile = target.endsWith("kit.json") ? target : join19(target, "kit.json");
|
|
12047
|
+
if (!existsSync16(kitFile)) throw new UsageError(`${kitFile}: no kit manifest`);
|
|
12048
|
+
if (!existsSync16(against))
|
|
11912
12049
|
throw new UsageError(`${against}: no reference manifest`);
|
|
11913
12050
|
const result = await judgeKit(kitFile, against, strFlag(flags, "out"));
|
|
11914
12051
|
const verdicts = JSON.parse(await readFile15(result.verdictFile, "utf8")).verdicts;
|
|
@@ -12030,7 +12167,7 @@ async function cmdValidate(argv) {
|
|
|
12030
12167
|
const target = positionals[0];
|
|
12031
12168
|
if (!target) throw new UsageError("vos validate <actions.json|take>");
|
|
12032
12169
|
const r = createReporter(flags.json === true);
|
|
12033
|
-
const kitTarget = target.endsWith("kit.json") ? target :
|
|
12170
|
+
const kitTarget = target.endsWith("kit.json") ? target : existsSync16(join19(target, "kit.json")) && !isTakeDir(target) ? join19(target, "kit.json") : null;
|
|
12034
12171
|
if (kitTarget) {
|
|
12035
12172
|
const picture = flags.picture === true;
|
|
12036
12173
|
const verdict = await validateKit(kitTarget, { picture });
|
|
@@ -12062,9 +12199,9 @@ async function cmdValidate(argv) {
|
|
|
12062
12199
|
r.done({ valid: true, target }, `${target}: valid actions file`);
|
|
12063
12200
|
return EXIT_OK;
|
|
12064
12201
|
}
|
|
12065
|
-
if (!isTakeDir(target) &&
|
|
12202
|
+
if (!isTakeDir(target) && existsSync16(join19(target, "config.json"))) {
|
|
12066
12203
|
const parsed = JSON.parse(
|
|
12067
|
-
await readFile15(
|
|
12204
|
+
await readFile15(join19(target, "config.json"), "utf8")
|
|
12068
12205
|
);
|
|
12069
12206
|
const pre = preflightConfig(parsed);
|
|
12070
12207
|
const problems2 = pre.ok ? [] : pre.issues.map((i) => `config.json: ${i}`);
|
|
@@ -12105,7 +12242,7 @@ async function cmdValidate(argv) {
|
|
|
12105
12242
|
const take = await loadTake(target);
|
|
12106
12243
|
const problems = [];
|
|
12107
12244
|
const warnings = [];
|
|
12108
|
-
if (!
|
|
12245
|
+
if (!existsSync16(take.paths.recording)) {
|
|
12109
12246
|
problems.push("missing recording (re-run record)");
|
|
12110
12247
|
} else {
|
|
12111
12248
|
const mismatch = containerMismatch(
|
|
@@ -12346,6 +12483,8 @@ async function run(argv) {
|
|
|
12346
12483
|
return await cmdRecipe(rest);
|
|
12347
12484
|
case "brand":
|
|
12348
12485
|
return await cmdBrand(rest);
|
|
12486
|
+
case "session":
|
|
12487
|
+
return await cmdSession(rest);
|
|
12349
12488
|
case "actions":
|
|
12350
12489
|
return await cmdActions(rest);
|
|
12351
12490
|
default:
|
|
@@ -12379,6 +12518,121 @@ async function run(argv) {
|
|
|
12379
12518
|
return EXIT_ERROR;
|
|
12380
12519
|
}
|
|
12381
12520
|
}
|
|
12521
|
+
async function cmdSession(argv) {
|
|
12522
|
+
const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS5);
|
|
12523
|
+
const [sub, arg] = positionals;
|
|
12524
|
+
const r = createReporter(flags.json === true);
|
|
12525
|
+
const usage = "vos session open <url> --name <app> | check <name> [--url <url>] | list | rm <name>";
|
|
12526
|
+
switch (sub) {
|
|
12527
|
+
case "open": {
|
|
12528
|
+
if (!arg) throw new UsageError(usage);
|
|
12529
|
+
const name = strFlag(flags, "name");
|
|
12530
|
+
const bad = sessionNameVerdict(name);
|
|
12531
|
+
if (bad) throw new UsageError(bad);
|
|
12532
|
+
let url;
|
|
12533
|
+
try {
|
|
12534
|
+
url = new URL(arg);
|
|
12535
|
+
} catch {
|
|
12536
|
+
throw new UsageError(`vos session open wants a URL, got "${arg}"`);
|
|
12537
|
+
}
|
|
12538
|
+
if (!/^https?:$/.test(url.protocol))
|
|
12539
|
+
throw new UsageError("vos session open wants an http(s) URL");
|
|
12540
|
+
await openSession(name, url.href, { log: r.log });
|
|
12541
|
+
const summary2 = await summarizeSession(name);
|
|
12542
|
+
r.done(
|
|
12543
|
+
{
|
|
12544
|
+
name: summary2.name,
|
|
12545
|
+
origins: summary2.origins,
|
|
12546
|
+
newestExpires: summary2.newestExpires,
|
|
12547
|
+
sessionOnly: summary2.sessionOnly
|
|
12548
|
+
},
|
|
12549
|
+
`${summaryLine(summary2)}
|
|
12550
|
+
Record on it: vos record --actions actions.json --out take --session ${name} --strict`
|
|
12551
|
+
);
|
|
12552
|
+
return summary2.origins.length ? EXIT_OK : EXIT_ERROR;
|
|
12553
|
+
}
|
|
12554
|
+
case "check": {
|
|
12555
|
+
if (!arg) throw new UsageError(usage);
|
|
12556
|
+
const bad = sessionNameVerdict(arg);
|
|
12557
|
+
if (bad) throw new UsageError(bad);
|
|
12558
|
+
if (!existsSync16(sessionDir(arg)))
|
|
12559
|
+
throw new UsageError(`no session "${arg}" (vos session list)`);
|
|
12560
|
+
const summary2 = await summarizeSession(arg);
|
|
12561
|
+
const url = strFlag(flags, "url");
|
|
12562
|
+
if (!url) {
|
|
12563
|
+
r.done(
|
|
12564
|
+
{
|
|
12565
|
+
name: arg,
|
|
12566
|
+
origins: summary2.origins,
|
|
12567
|
+
newestExpires: summary2.newestExpires,
|
|
12568
|
+
sessionOnly: summary2.sessionOnly
|
|
12569
|
+
},
|
|
12570
|
+
summaryLine(summary2)
|
|
12571
|
+
);
|
|
12572
|
+
return summary2.origins.length ? EXIT_OK : EXIT_WALL;
|
|
12573
|
+
}
|
|
12574
|
+
const verdict = await withSession(arg, async (ctx) => {
|
|
12575
|
+
const page = await ctx.newPage();
|
|
12576
|
+
const response = await page.goto(url, { waitUntil: "networkidle", timeout: 3e4 }).catch(() => null);
|
|
12577
|
+
const seen = await page.evaluate(WALL_PROBE).catch(() => null);
|
|
12578
|
+
return wallVerdict({
|
|
12579
|
+
askedUrl: url,
|
|
12580
|
+
landedUrl: page.url(),
|
|
12581
|
+
...response ? { status: response.status() } : {},
|
|
12582
|
+
passwordFields: seen?.passwordFields ?? 0,
|
|
12583
|
+
newPasswordField: seen?.newPasswordField ?? false,
|
|
12584
|
+
oneTimeCodeField: seen?.oneTimeCodeField ?? false
|
|
12585
|
+
});
|
|
12586
|
+
});
|
|
12587
|
+
if (verdict) {
|
|
12588
|
+
r.done(
|
|
12589
|
+
{
|
|
12590
|
+
name: arg,
|
|
12591
|
+
ok: false,
|
|
12592
|
+
wall: verdict,
|
|
12593
|
+
origins: summary2.origins,
|
|
12594
|
+
newestExpires: summary2.newestExpires
|
|
12595
|
+
},
|
|
12596
|
+
`session "${arg}" no longer opens ${url}: ${verdict.message.split(". ")[0]}. It expired, or the sign-in never landed. Sign in again: vos session open ${url} --name ${arg}`
|
|
12597
|
+
);
|
|
12598
|
+
return EXIT_WALL;
|
|
12599
|
+
}
|
|
12600
|
+
r.done(
|
|
12601
|
+
{
|
|
12602
|
+
name: arg,
|
|
12603
|
+
ok: true,
|
|
12604
|
+
origins: summary2.origins,
|
|
12605
|
+
newestExpires: summary2.newestExpires
|
|
12606
|
+
},
|
|
12607
|
+
`session "${arg}" opens ${url} signed in. ${summaryLine(summary2)}`
|
|
12608
|
+
);
|
|
12609
|
+
return EXIT_OK;
|
|
12610
|
+
}
|
|
12611
|
+
case "list": {
|
|
12612
|
+
const rows = listSessions();
|
|
12613
|
+
r.done(
|
|
12614
|
+
{ sessions: rows },
|
|
12615
|
+
rows.length ? rows.map(
|
|
12616
|
+
(x) => ` ${x.name} ${x.ageDays} day${x.ageDays === 1 ? "" : "s"} old`
|
|
12617
|
+
).join("\n") : "no sessions. Make one: vos session open <url> --name <app>"
|
|
12618
|
+
);
|
|
12619
|
+
return EXIT_OK;
|
|
12620
|
+
}
|
|
12621
|
+
case "rm": {
|
|
12622
|
+
if (!arg) throw new UsageError(usage);
|
|
12623
|
+
const bad = sessionNameVerdict(arg);
|
|
12624
|
+
if (bad) throw new UsageError(bad);
|
|
12625
|
+
const gone = removeSession(arg);
|
|
12626
|
+
r.done(
|
|
12627
|
+
{ name: arg, removed: gone },
|
|
12628
|
+
gone ? `removed session "${arg}"` : `no session "${arg}"`
|
|
12629
|
+
);
|
|
12630
|
+
return gone ? EXIT_OK : EXIT_ERROR;
|
|
12631
|
+
}
|
|
12632
|
+
default:
|
|
12633
|
+
throw new UsageError(usage);
|
|
12634
|
+
}
|
|
12635
|
+
}
|
|
12382
12636
|
|
|
12383
12637
|
export {
|
|
12384
12638
|
validateActions,
|
|
@@ -12411,4 +12665,4 @@ export {
|
|
|
12411
12665
|
verbHelp,
|
|
12412
12666
|
run
|
|
12413
12667
|
};
|
|
12414
|
-
//# sourceMappingURL=chunk-
|
|
12668
|
+
//# sourceMappingURL=chunk-JRRM7P5G.js.map
|