@vosjs/cli 0.44.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 +16 -1
- package/dist/{chunk-WCSAECM4.js → chunk-JRRM7P5G.js} +408 -91
- package/dist/chunk-JRRM7P5G.js.map +1 -0
- package/dist/cli.js +3 -3
- package/dist/index.d.ts +7 -1
- package/dist/index.js +1 -1
- package/dist/{run-CRRKT34P.js → run-3TY74GWE.js} +2 -2
- package/package.json +5 -5
- package/dist/chunk-WCSAECM4.js.map +0 -1
- /package/dist/{run-CRRKT34P.js.map → run-3TY74GWE.js.map} +0 -0
|
@@ -22,8 +22,8 @@ import {
|
|
|
22
22
|
|
|
23
23
|
// src/plugin/run.ts
|
|
24
24
|
import { mkdir as mkdir10, readFile as readFile15, rm as rm5 } from "fs/promises";
|
|
25
|
-
import { existsSync as
|
|
26
|
-
import { join as
|
|
25
|
+
import { existsSync as existsSync16 } from "fs";
|
|
26
|
+
import { join as join19, resolve as resolve9 } from "path";
|
|
27
27
|
import {
|
|
28
28
|
END_CARD_FROM as END_CARD_FROM2,
|
|
29
29
|
docOutputDuration as docOutputDuration3,
|
|
@@ -7043,12 +7043,14 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
7043
7043
|
const maxSeconds = dry ? Infinity : opts.maxDurationSeconds ?? Infinity;
|
|
7044
7044
|
const vw = actions.viewport?.width ?? 1280;
|
|
7045
7045
|
const vh = actions.viewport?.height ?? 720;
|
|
7046
|
-
const context = await browser.newContext({
|
|
7046
|
+
const context = opts.context ?? await browser.newContext({
|
|
7047
7047
|
viewport: { width: vw, height: vh },
|
|
7048
7048
|
deviceScaleFactor: 1,
|
|
7049
7049
|
...opts.storageState ? { storageState: opts.storageState } : {},
|
|
7050
7050
|
...opts.headers && Object.keys(opts.headers).length ? { extraHTTPHeaders: opts.headers } : {}
|
|
7051
7051
|
});
|
|
7052
|
+
if (opts.context && opts.headers && Object.keys(opts.headers).length)
|
|
7053
|
+
await context.setExtraHTTPHeaders(opts.headers);
|
|
7052
7054
|
const masks = actions.mask ?? [];
|
|
7053
7055
|
if (masks.length) await context.addInitScript(maskInitScript(masks));
|
|
7054
7056
|
const exposureLog = new ExposureLog(masks);
|
|
@@ -7437,6 +7439,148 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
7437
7439
|
};
|
|
7438
7440
|
}
|
|
7439
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
|
+
|
|
7440
7584
|
// src/plugin/encode.ts
|
|
7441
7585
|
var MEDIABUNNY_URL2 = "https://esm.sh/mediabunny@1.55.7?target=es2022";
|
|
7442
7586
|
var ENCODE_HTML = `<!doctype html><html><head><meta charset="utf-8"></head><body>
|
|
@@ -7492,9 +7636,9 @@ async function encodeRecording(browser, takeDir, onProgress) {
|
|
|
7492
7636
|
}
|
|
7493
7637
|
|
|
7494
7638
|
// src/plugin/plan.ts
|
|
7495
|
-
import { existsSync as
|
|
7639
|
+
import { existsSync as existsSync13 } from "fs";
|
|
7496
7640
|
import { readFile as readFile12 } from "fs/promises";
|
|
7497
|
-
import { join as
|
|
7641
|
+
import { join as join15 } from "path";
|
|
7498
7642
|
import {
|
|
7499
7643
|
DEFAULT_FRAME_STYLE,
|
|
7500
7644
|
STYLE_FIELDS,
|
|
@@ -7716,8 +7860,8 @@ function retimeCut(prev, newSteps, newDurationMs) {
|
|
|
7716
7860
|
// src/plugin/plan.ts
|
|
7717
7861
|
var overlaps = (a, b) => a.in < b.out && b.in < a.out;
|
|
7718
7862
|
async function readDigestActivity(dir) {
|
|
7719
|
-
const file =
|
|
7720
|
-
if (!
|
|
7863
|
+
const file = join15(dir, "digest", "digest.json");
|
|
7864
|
+
if (!existsSync13(file)) return null;
|
|
7721
7865
|
try {
|
|
7722
7866
|
const d = JSON.parse(await readFile12(file, "utf8"));
|
|
7723
7867
|
return Array.isArray(d.activity) && d.activity.every((v) => typeof v === "number") ? d.activity : null;
|
|
@@ -7929,15 +8073,15 @@ async function planTake(dir, opts = {}) {
|
|
|
7929
8073
|
|
|
7930
8074
|
// src/plugin/sync.ts
|
|
7931
8075
|
import { createHash } from "crypto";
|
|
7932
|
-
import { existsSync as
|
|
8076
|
+
import { existsSync as existsSync14, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "fs";
|
|
7933
8077
|
import { readFile as readFile13 } from "fs/promises";
|
|
7934
8078
|
import { createInterface } from "readline/promises";
|
|
7935
|
-
import { basename as basename3, join as
|
|
8079
|
+
import { basename as basename3, join as join17 } from "path";
|
|
7936
8080
|
import { lowerToComposition as lowerToComposition3, migrateHostedDoc as migrateHostedDoc3 } from "@vosjs/studio-core";
|
|
7937
8081
|
|
|
7938
8082
|
// src/plugin/folder.ts
|
|
7939
8083
|
import { mkdir as mkdir8, writeFile as writeFile10 } from "fs/promises";
|
|
7940
|
-
import { join as
|
|
8084
|
+
import { join as join16 } from "path";
|
|
7941
8085
|
import { recipeHints } from "@vosjs/shared/frontmatter";
|
|
7942
8086
|
import { migrateHostedDoc as migrateHostedDoc2 } from "@vosjs/studio-core";
|
|
7943
8087
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "help", "media"]);
|
|
@@ -8113,18 +8257,18 @@ async function cmdPull(argv) {
|
|
|
8113
8257
|
throw new Error(apiError(`pull folder ${folder.slug}`, res));
|
|
8114
8258
|
const payload = res.body;
|
|
8115
8259
|
const out = strFlag(flags, "out") ?? folder.slug;
|
|
8116
|
-
await mkdir8(
|
|
8260
|
+
await mkdir8(join16(out, "recipes"), { recursive: true });
|
|
8117
8261
|
const lines = [];
|
|
8118
8262
|
const recipes = [];
|
|
8119
8263
|
for (const rec of payload.recipes) {
|
|
8120
|
-
const path =
|
|
8264
|
+
const path = join16(out, "recipes", safeName(rec.filename));
|
|
8121
8265
|
await writeFile10(path, rec.body ?? "");
|
|
8122
8266
|
recipes.push({ path, line: recipeLine(rec.filename, rec.body ?? "") });
|
|
8123
8267
|
}
|
|
8124
8268
|
for (const rec of payload.inheritedRecipes) {
|
|
8125
8269
|
const from = safeName(rec.folderSlug ?? "inherited");
|
|
8126
|
-
await mkdir8(
|
|
8127
|
-
const path =
|
|
8270
|
+
await mkdir8(join16(out, "recipes", "_inherited", from), { recursive: true });
|
|
8271
|
+
const path = join16(
|
|
8128
8272
|
out,
|
|
8129
8273
|
"recipes",
|
|
8130
8274
|
"_inherited",
|
|
@@ -8139,7 +8283,7 @@ async function cmdPull(argv) {
|
|
|
8139
8283
|
}
|
|
8140
8284
|
const members = [];
|
|
8141
8285
|
for (const v of payload.voses) {
|
|
8142
|
-
const dir =
|
|
8286
|
+
const dir = join16(out, "members", safeName(v.slug || v.title || v.id));
|
|
8143
8287
|
await mkdir8(dir, { recursive: true });
|
|
8144
8288
|
const cfg = await apiJson(origin, `/api/vos/${v.id}/config`, { key });
|
|
8145
8289
|
if (cfg.status !== 200) {
|
|
@@ -8147,7 +8291,7 @@ async function cmdPull(argv) {
|
|
|
8147
8291
|
continue;
|
|
8148
8292
|
}
|
|
8149
8293
|
await writeFile10(
|
|
8150
|
-
|
|
8294
|
+
join16(dir, "config.json"),
|
|
8151
8295
|
JSON.stringify(cfg.body.config, null, 2)
|
|
8152
8296
|
);
|
|
8153
8297
|
let take = false;
|
|
@@ -8159,7 +8303,7 @@ async function cmdPull(argv) {
|
|
|
8159
8303
|
);
|
|
8160
8304
|
if (doc.status === 200) {
|
|
8161
8305
|
await writeFile10(
|
|
8162
|
-
|
|
8306
|
+
join16(dir, "doc.json"),
|
|
8163
8307
|
JSON.stringify(doc.body, null, 2)
|
|
8164
8308
|
);
|
|
8165
8309
|
take = true;
|
|
@@ -8428,7 +8572,24 @@ function programPushTarget(ask) {
|
|
|
8428
8572
|
}
|
|
8429
8573
|
return { kind: "version", vosId: tracked, tracked: true };
|
|
8430
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
|
+
}
|
|
8431
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
|
+
);
|
|
8432
8593
|
const take = await loadTake(dir);
|
|
8433
8594
|
if (!take.doc) {
|
|
8434
8595
|
throw new Error("no doc.json in this take \u2014 run `vos plan` first");
|
|
@@ -8438,7 +8599,7 @@ async function pushTake(dir, flags, r) {
|
|
|
8438
8599
|
throw new Error(`doc.json fails lint:
|
|
8439
8600
|
${lint.problems.join("\n ")}`);
|
|
8440
8601
|
}
|
|
8441
|
-
if (!
|
|
8602
|
+
if (!existsSync14(take.paths.recording)) {
|
|
8442
8603
|
throw new Error(
|
|
8443
8604
|
"no recording in this take \u2014 record it, or `vos pull --media` to bring it home"
|
|
8444
8605
|
);
|
|
@@ -8530,8 +8691,8 @@ async function pushTake(dir, flags, r) {
|
|
|
8530
8691
|
delete docForPush.source.micKey;
|
|
8531
8692
|
}
|
|
8532
8693
|
for (const ref of docMediaRefs(docForPush)) {
|
|
8533
|
-
const file =
|
|
8534
|
-
if (!
|
|
8694
|
+
const file = join17(dir, takeRelativeFile(ref.key));
|
|
8695
|
+
if (!existsSync14(file)) {
|
|
8535
8696
|
r.log(` ${ref.where}: ${ref.key} is not in the take \u2014 left as is`);
|
|
8536
8697
|
continue;
|
|
8537
8698
|
}
|
|
@@ -8675,9 +8836,9 @@ async function pullTake(dir, flags, r) {
|
|
|
8675
8836
|
};
|
|
8676
8837
|
}
|
|
8677
8838
|
let media2;
|
|
8678
|
-
if (flags.media &&
|
|
8839
|
+
if (flags.media && existsSync14(join17(dir, "doc.json"))) {
|
|
8679
8840
|
const local = JSON.parse(
|
|
8680
|
-
await readFile13(
|
|
8841
|
+
await readFile13(join17(dir, "doc.json"), "utf8")
|
|
8681
8842
|
);
|
|
8682
8843
|
media2 = await pullMedia(ctx, dir, local, r.log);
|
|
8683
8844
|
}
|
|
@@ -8738,10 +8899,10 @@ async function pullTake(dir, flags, r) {
|
|
|
8738
8899
|
const hostedDoc = migrateHostedDoc3(docRes.json);
|
|
8739
8900
|
const doc = hostedDoc;
|
|
8740
8901
|
const localRecording = takeMediaFile(dir, "recording", RECORDING_NAME);
|
|
8741
|
-
if (
|
|
8902
|
+
if (existsSync14(localRecording)) {
|
|
8742
8903
|
doc.source.videoKey = basename3(localRecording);
|
|
8743
8904
|
}
|
|
8744
|
-
await writeJson(
|
|
8905
|
+
await writeJson(join17(dir, "doc.json"), doc, true);
|
|
8745
8906
|
const media = flags.media ? await pullMedia(ctx, dir, doc, r.log) : void 0;
|
|
8746
8907
|
const versions = await api(ctx, `/vos/${vosId}/versions`);
|
|
8747
8908
|
const headRow = (versions.json.versions ?? []).find((v) => v.id === head);
|
|
@@ -8760,10 +8921,10 @@ async function pullTake(dir, flags, r) {
|
|
|
8760
8921
|
}
|
|
8761
8922
|
|
|
8762
8923
|
// src/plugin/program.ts
|
|
8763
|
-
import { existsSync as
|
|
8924
|
+
import { existsSync as existsSync15 } from "fs";
|
|
8764
8925
|
import { mkdir as mkdir9, readFile as readFile14, writeFile as writeFile11 } from "fs/promises";
|
|
8765
8926
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
8766
|
-
import { basename as basename4, dirname as dirname4, join as
|
|
8927
|
+
import { basename as basename4, dirname as dirname4, join as join18 } from "path";
|
|
8767
8928
|
import {
|
|
8768
8929
|
CURRENT_CONFIG_VERSION,
|
|
8769
8930
|
migrateConfig,
|
|
@@ -8933,8 +9094,8 @@ function preflightConfig(parsed) {
|
|
|
8933
9094
|
}
|
|
8934
9095
|
function resolveConfigPath(target) {
|
|
8935
9096
|
if (target.endsWith(".json")) return target;
|
|
8936
|
-
const inDir =
|
|
8937
|
-
if (
|
|
9097
|
+
const inDir = join18(target, "config.json");
|
|
9098
|
+
if (existsSync15(inDir)) return inDir;
|
|
8938
9099
|
throw new UsageError(
|
|
8939
9100
|
`${target} has no config.json (and is not a take \u2014 no doc.json)`
|
|
8940
9101
|
);
|
|
@@ -8962,7 +9123,7 @@ async function cmdFetch(argv) {
|
|
|
8962
9123
|
const out = strFlag(flags, "out") ?? slug;
|
|
8963
9124
|
await mkdir9(out, { recursive: true });
|
|
8964
9125
|
await writeFile11(
|
|
8965
|
-
|
|
9126
|
+
join18(out, "config.json"),
|
|
8966
9127
|
JSON.stringify(cfg.body.config, null, 2)
|
|
8967
9128
|
);
|
|
8968
9129
|
const title = typeof vosMeta.title === "string" ? vosMeta.title : "";
|
|
@@ -8987,12 +9148,12 @@ async function cmdFetch(argv) {
|
|
|
8987
9148
|
const hosted = migrateHostedDoc4(doc.body);
|
|
8988
9149
|
const { config: own } = await writeProgramDoc(out, hosted);
|
|
8989
9150
|
if (own) {
|
|
8990
|
-
await writeFile11(
|
|
9151
|
+
await writeFile11(join18(out, "config.json"), JSON.stringify(own, null, 2));
|
|
8991
9152
|
}
|
|
8992
9153
|
} else if (doc.status === 200) {
|
|
8993
9154
|
take = true;
|
|
8994
9155
|
const hosted = migrateHostedDoc4(doc.body);
|
|
8995
|
-
await writeFile11(
|
|
9156
|
+
await writeFile11(join18(out, "doc.json"), JSON.stringify(hosted, null, 2));
|
|
8996
9157
|
if (flags.media === true) {
|
|
8997
9158
|
const media = await pullMedia({ origin, key }, out, hosted, r.log);
|
|
8998
9159
|
mediaLine = media.downloaded.length ? ` + ${media.downloaded.map((m) => m.file).join(", ")}` : "";
|
|
@@ -9355,10 +9516,10 @@ async function cmdPullProgram(argv) {
|
|
|
9355
9516
|
const cfg = await apiJson(origin, `/api/vos/${vosId}/config`, { key });
|
|
9356
9517
|
if (cfg.status !== 200)
|
|
9357
9518
|
throw new Error(apiError(`fetch head config for ${vosId}`, cfg));
|
|
9358
|
-
const configPath =
|
|
9519
|
+
const configPath = join18(dir, "config.json");
|
|
9359
9520
|
let backedUp = false;
|
|
9360
|
-
if (
|
|
9361
|
-
await writeFile11(
|
|
9521
|
+
if (existsSync15(configPath)) {
|
|
9522
|
+
await writeFile11(join18(dir, "config.backup.json"), await readFile14(configPath));
|
|
9362
9523
|
backedUp = true;
|
|
9363
9524
|
}
|
|
9364
9525
|
let headConfig = cfg.body.config;
|
|
@@ -9391,7 +9552,7 @@ async function cmdPullProgram(argv) {
|
|
|
9391
9552
|
protected: protectedIds,
|
|
9392
9553
|
changes,
|
|
9393
9554
|
out: configPath,
|
|
9394
|
-
backup: backedUp ?
|
|
9555
|
+
backup: backedUp ? join18(dir, "config.backup.json") : null
|
|
9395
9556
|
},
|
|
9396
9557
|
`Pulled ${changes.length} version${changes.length === 1 ? "" : "s"} \u2192 ${configPath}` + (backedUp ? ` (previous copy: config.backup.json)` : "") + `
|
|
9397
9558
|
Re-apply your edit on the new head, then: vos push ${configPath} --vos ${vosId}`
|
|
@@ -9453,8 +9614,8 @@ function storedProgramConfig(config, programDoc) {
|
|
|
9453
9614
|
return lowerProgramDoc(doc).config;
|
|
9454
9615
|
}
|
|
9455
9616
|
async function readProgramDoc(dir, config) {
|
|
9456
|
-
const docPath =
|
|
9457
|
-
if (!
|
|
9617
|
+
const docPath = join18(dir, "doc.json");
|
|
9618
|
+
if (!existsSync15(docPath)) return null;
|
|
9458
9619
|
const parsed = JSON.parse(await readFile14(docPath, "utf8"));
|
|
9459
9620
|
if (typeof parsed !== "object" || parsed === null || "source" in parsed)
|
|
9460
9621
|
return null;
|
|
@@ -9466,14 +9627,14 @@ async function writeProgramDoc(dir, hosted) {
|
|
|
9466
9627
|
const program = hosted.program && typeof hosted.program === "object" ? hosted.program : {};
|
|
9467
9628
|
const { config, ...rest } = program;
|
|
9468
9629
|
const onDisk = { ...hosted, program: rest };
|
|
9469
|
-
await writeFile11(
|
|
9630
|
+
await writeFile11(join18(dir, "doc.json"), JSON.stringify(onDisk, null, 2));
|
|
9470
9631
|
return {
|
|
9471
9632
|
config: config && typeof config === "object" ? config : null
|
|
9472
9633
|
};
|
|
9473
9634
|
}
|
|
9474
9635
|
|
|
9475
9636
|
// src/plugin/asset.ts
|
|
9476
|
-
import { readFileSync as
|
|
9637
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
9477
9638
|
import { basename as basename5, extname as extname2 } from "path";
|
|
9478
9639
|
var BOOLEAN_FLAGS3 = /* @__PURE__ */ new Set(["json", "help"]);
|
|
9479
9640
|
var MIME_BY_EXT = {
|
|
@@ -9527,7 +9688,7 @@ async function cmdPush(argv) {
|
|
|
9527
9688
|
for (const file of positionals) {
|
|
9528
9689
|
const name = basename5(file);
|
|
9529
9690
|
const mime = MIME_BY_EXT[extname2(name).toLowerCase()] ?? "application/octet-stream";
|
|
9530
|
-
const body =
|
|
9691
|
+
const body = readFileSync5(file);
|
|
9531
9692
|
const form = new FormData();
|
|
9532
9693
|
form.set("file", new Blob([body], { type: mime }), name);
|
|
9533
9694
|
if (folder) form.set("folderId", folder.id);
|
|
@@ -9581,7 +9742,7 @@ async function cmdAsset(argv) {
|
|
|
9581
9742
|
}
|
|
9582
9743
|
|
|
9583
9744
|
// src/plugin/recipe.ts
|
|
9584
|
-
import { readFileSync as
|
|
9745
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
9585
9746
|
import { basename as basename6 } from "path";
|
|
9586
9747
|
var BOOLEAN_FLAGS4 = /* @__PURE__ */ new Set(["json", "help"]);
|
|
9587
9748
|
var USAGE = "vos recipe push <file.md> (--folder <folderId|slug> | --asset <assetId>)";
|
|
@@ -9601,7 +9762,7 @@ async function cmdPush2(argv) {
|
|
|
9601
9762
|
throw new UsageError(USAGE);
|
|
9602
9763
|
}
|
|
9603
9764
|
const filename = recipeFilename(file);
|
|
9604
|
-
const body =
|
|
9765
|
+
const body = readFileSync6(file);
|
|
9605
9766
|
const r = createReporter(flags.json === true);
|
|
9606
9767
|
const origin = platformOrigin({
|
|
9607
9768
|
origin: strFlag(flags, "origin"),
|
|
@@ -10602,8 +10763,8 @@ var MULTI_FLAGS2 = /* @__PURE__ */ new Set(["set", "override", "browser-arg", "h
|
|
|
10602
10763
|
var HELP = `vos \u2014 record a browser flow, plan effects, render a product video; sync with vos.so
|
|
10603
10764
|
|
|
10604
10765
|
Take pipeline
|
|
10605
|
-
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]
|
|
10606
|
-
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]
|
|
10607
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]
|
|
10608
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]
|
|
10609
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]
|
|
@@ -10615,6 +10776,9 @@ Take pipeline
|
|
|
10615
10776
|
vos validate <actions.json|take|kit.json> [--picture] [--json]
|
|
10616
10777
|
vos judge <kit.json> --against <MANIFEST.json> [--out dir] [--json]
|
|
10617
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
|
|
10618
10782
|
vos actions script <actions.json> [--json]
|
|
10619
10783
|
the flow as numbered beats in words, for a person recording it by hand
|
|
10620
10784
|
|
|
@@ -10913,10 +11077,35 @@ function takeStorageState(flags) {
|
|
|
10913
11077
|
const raw = strFlag(flags, "storage-state");
|
|
10914
11078
|
if (!raw) return void 0;
|
|
10915
11079
|
const path = resolve9(raw);
|
|
10916
|
-
if (!
|
|
11080
|
+
if (!existsSync16(path))
|
|
10917
11081
|
throw new UsageError(`--storage-state: no such file: ${path}`);
|
|
10918
11082
|
return path;
|
|
10919
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
|
+
}
|
|
10920
11109
|
async function takeBackdrop(flags, r) {
|
|
10921
11110
|
const { backdrop, note } = await openingBackdrop(
|
|
10922
11111
|
strFlag(flags, "background"),
|
|
@@ -10974,6 +11163,7 @@ async function cmdRecord(argv) {
|
|
|
10974
11163
|
const backdrop = await takeBackdrop(flags, r);
|
|
10975
11164
|
const maxDurationSeconds = await maxDuration(flags, r);
|
|
10976
11165
|
const storageState = takeStorageState(flags);
|
|
11166
|
+
const sessionName = takeSessionName(flags, storageState);
|
|
10977
11167
|
const browserArgs = takeBrowserArgs(multi);
|
|
10978
11168
|
const headers = takeHeaders(multi);
|
|
10979
11169
|
if (flags["dry-run"] === true) {
|
|
@@ -10984,19 +11174,17 @@ async function cmdRecord(argv) {
|
|
|
10984
11174
|
r.log("rehearsing (nothing is captured or written)\u2026");
|
|
10985
11175
|
r.event({ event: "phase", phase: "rehearse" });
|
|
10986
11176
|
const started = Date.now();
|
|
10987
|
-
const rec = await
|
|
10988
|
-
|
|
10989
|
-
url,
|
|
11177
|
+
const rec = await inSession(
|
|
11178
|
+
sessionName,
|
|
10990
11179
|
actions,
|
|
10991
|
-
takePaths(outDir),
|
|
10992
|
-
r.log,
|
|
10993
|
-
{
|
|
11180
|
+
(context) => recordTake(browser2, url, actions, takePaths(outDir), r.log, {
|
|
10994
11181
|
storageState,
|
|
10995
11182
|
headers,
|
|
11183
|
+
context,
|
|
10996
11184
|
dryRun: true,
|
|
10997
11185
|
// A rehearsal is always strict, and it touches no directory.
|
|
10998
11186
|
onArrival: wallGate(flags, r, { strict: true })
|
|
10999
|
-
}
|
|
11187
|
+
})
|
|
11000
11188
|
);
|
|
11001
11189
|
const steps = rec.meta.steps ?? [];
|
|
11002
11190
|
const lines = steps.map((s) => {
|
|
@@ -11009,6 +11197,7 @@ async function cmdRecord(argv) {
|
|
|
11009
11197
|
...strFlag(flags, "storage-state") ? [`--storage-state ${strFlag(flags, "storage-state")}`] : [],
|
|
11010
11198
|
...browserArgs.map((a) => `--browser-arg=${a}`),
|
|
11011
11199
|
...Object.entries(headers).map(([k, v]) => `--header ${k}=${v}`),
|
|
11200
|
+
...sessionName ? [`--session ${sessionName}`] : [],
|
|
11012
11201
|
...flags["allow-wall"] === true ? ["--allow-wall"] : []
|
|
11013
11202
|
].join(" ");
|
|
11014
11203
|
const nextOut = strFlag(flags, "out") ?? "take";
|
|
@@ -11034,7 +11223,7 @@ ${lines.join("\n")}
|
|
|
11034
11223
|
}
|
|
11035
11224
|
const paths = takePaths(outDir);
|
|
11036
11225
|
const prepare = async () => {
|
|
11037
|
-
if (
|
|
11226
|
+
if (existsSync16(join19(outDir, "meta.json"))) {
|
|
11038
11227
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
11039
11228
|
r.log(
|
|
11040
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" : "")
|
|
@@ -11047,15 +11236,20 @@ ${lines.join("\n")}
|
|
|
11047
11236
|
try {
|
|
11048
11237
|
r.log("recording\u2026");
|
|
11049
11238
|
r.event({ event: "phase", phase: "record" });
|
|
11050
|
-
const rec = await
|
|
11051
|
-
|
|
11052
|
-
|
|
11053
|
-
|
|
11054
|
-
|
|
11055
|
-
|
|
11056
|
-
|
|
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
|
+
})
|
|
11057
11251
|
})
|
|
11058
|
-
|
|
11252
|
+
);
|
|
11059
11253
|
r.event({ event: "phase", phase: "encode" });
|
|
11060
11254
|
r.log("encoding\u2026");
|
|
11061
11255
|
const enc = await encodeRecording(
|
|
@@ -11093,7 +11287,7 @@ ${lines.join("\n")}
|
|
|
11093
11287
|
},
|
|
11094
11288
|
strictFail ? `STRICT: take recorded but incomplete \u2014 ${strictReason(rec)}; fix the flow and re-record.${skippedNote}${exposureNote(rec)}` : `Take ready: ${outDir}
|
|
11095
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}
|
|
11096
|
-
Next: edit ${
|
|
11290
|
+
Next: edit ${join19(outDir, "doc.json")} (optional), then: vos render ${outDir}${exposureNote(rec)}`
|
|
11097
11291
|
);
|
|
11098
11292
|
return strictFail ? EXIT_USAGE : EXIT_OK;
|
|
11099
11293
|
} finally {
|
|
@@ -11122,7 +11316,7 @@ async function cmdCreate2(argv) {
|
|
|
11122
11316
|
if (fmtRaw !== "webm" && fmtRaw !== "mp4")
|
|
11123
11317
|
throw new UsageError("--format must be webm or mp4");
|
|
11124
11318
|
const format = fmtRaw;
|
|
11125
|
-
const out = positionals[0] ??
|
|
11319
|
+
const out = positionals[0] ?? join19(outDir, `out.${format}`);
|
|
11126
11320
|
const parallel = numFlag(flags, "parallel", 1);
|
|
11127
11321
|
if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
|
|
11128
11322
|
throw new UsageError("--parallel expects an integer between 1 and 16");
|
|
@@ -11130,11 +11324,12 @@ async function cmdCreate2(argv) {
|
|
|
11130
11324
|
const backdrop = await takeBackdrop(flags, r);
|
|
11131
11325
|
const maxDurationSeconds = await maxDuration(flags, r);
|
|
11132
11326
|
const storageState = takeStorageState(flags);
|
|
11327
|
+
const sessionName = takeSessionName(flags, storageState);
|
|
11133
11328
|
const browserArgs = takeBrowserArgs(multi);
|
|
11134
11329
|
const headers = takeHeaders(multi);
|
|
11135
11330
|
const paths = takePaths(outDir);
|
|
11136
11331
|
const prepare = async () => {
|
|
11137
|
-
if (
|
|
11332
|
+
if (existsSync16(join19(outDir, "meta.json"))) {
|
|
11138
11333
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
11139
11334
|
r.log(
|
|
11140
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" : "")
|
|
@@ -11147,15 +11342,20 @@ async function cmdCreate2(argv) {
|
|
|
11147
11342
|
try {
|
|
11148
11343
|
r.log("recording\u2026");
|
|
11149
11344
|
r.event({ event: "phase", phase: "record" });
|
|
11150
|
-
const rec = await
|
|
11151
|
-
|
|
11152
|
-
|
|
11153
|
-
|
|
11154
|
-
|
|
11155
|
-
|
|
11156
|
-
|
|
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
|
+
})
|
|
11157
11357
|
})
|
|
11158
|
-
|
|
11358
|
+
);
|
|
11159
11359
|
r.event({ event: "phase", phase: "encode" });
|
|
11160
11360
|
r.log("encoding\u2026");
|
|
11161
11361
|
await encodeRecording(
|
|
@@ -11235,13 +11435,13 @@ async function cmdPlan(argv) {
|
|
|
11235
11435
|
);
|
|
11236
11436
|
const r = createReporter(flags.json === true);
|
|
11237
11437
|
if (flags.fresh === true) {
|
|
11238
|
-
await rm5(
|
|
11438
|
+
await rm5(join19(dir, "doc.json"), { force: true });
|
|
11239
11439
|
r.log("note: --fresh discarded the existing doc.json");
|
|
11240
11440
|
}
|
|
11241
11441
|
let reuse;
|
|
11242
11442
|
if (flags.reuse === true) {
|
|
11243
|
-
const from = resolve9(strFlag(flags, "from") ??
|
|
11244
|
-
if (!
|
|
11443
|
+
const from = resolve9(strFlag(flags, "from") ?? join19(dir, PREV_DOC_NAME));
|
|
11444
|
+
if (!existsSync16(from)) {
|
|
11245
11445
|
throw new UsageError(
|
|
11246
11446
|
`--reuse: ${from} does not exist \u2014 a re-record into this take writes doc.prev.json, or pass --from <doc.json>`
|
|
11247
11447
|
);
|
|
@@ -11252,7 +11452,7 @@ async function cmdPlan(argv) {
|
|
|
11252
11452
|
};
|
|
11253
11453
|
}
|
|
11254
11454
|
const style = await resolveStyleRef(flags);
|
|
11255
|
-
const hasDoc =
|
|
11455
|
+
const hasDoc = existsSync16(join19(dir, "doc.json"));
|
|
11256
11456
|
const backdrop = hasDoc ? null : await takeBackdrop(flags, r);
|
|
11257
11457
|
const release = await releaseInputs(dir, flags, r, multi.with ?? []);
|
|
11258
11458
|
const motionWanted = !hasDoc || flags.motion === true || flags.fresh === true;
|
|
@@ -11310,7 +11510,7 @@ async function cmdPlan(argv) {
|
|
|
11310
11510
|
...s.motion ? { motion: s.motion } : {},
|
|
11311
11511
|
...s.reuse ? { reuse: s.reuse } : {}
|
|
11312
11512
|
},
|
|
11313
|
-
`${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}`
|
|
11314
11514
|
);
|
|
11315
11515
|
return EXIT_OK;
|
|
11316
11516
|
}
|
|
@@ -11436,7 +11636,7 @@ async function cmdRender(argv) {
|
|
|
11436
11636
|
if (fmtRaw !== "webm" && fmtRaw !== "mp4")
|
|
11437
11637
|
throw new UsageError("--format must be webm or mp4");
|
|
11438
11638
|
const format = fmtRaw;
|
|
11439
|
-
const out = positionals[1] ??
|
|
11639
|
+
const out = positionals[1] ?? join19(dir, `out.${format}`);
|
|
11440
11640
|
const parallel = numFlag(flags, "parallel", 1);
|
|
11441
11641
|
if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
|
|
11442
11642
|
throw new UsageError("--parallel expects an integer between 1 and 16");
|
|
@@ -11683,11 +11883,11 @@ async function resolveStyleRef(flags) {
|
|
|
11683
11883
|
}
|
|
11684
11884
|
async function resolveDocRef(ref, flags, what) {
|
|
11685
11885
|
const styleRef = ref;
|
|
11686
|
-
const file =
|
|
11886
|
+
const file = existsSync16(styleRef) ? resolve9(
|
|
11687
11887
|
styleRef,
|
|
11688
|
-
|
|
11888
|
+
existsSync16(join19(styleRef, "doc.json")) ? "doc.json" : ""
|
|
11689
11889
|
) : null;
|
|
11690
|
-
if (file &&
|
|
11890
|
+
if (file && existsSync16(file)) {
|
|
11691
11891
|
return {
|
|
11692
11892
|
from: file,
|
|
11693
11893
|
doc: migrateHostedDoc5(
|
|
@@ -11774,7 +11974,7 @@ async function cmdDigest(argv) {
|
|
|
11774
11974
|
kinds,
|
|
11775
11975
|
frames: d.images.full,
|
|
11776
11976
|
crops: d.images.crop,
|
|
11777
|
-
sheet: d.images.sheet ?
|
|
11977
|
+
sheet: d.images.sheet ? join19(result.outDir, d.images.sheet) : null,
|
|
11778
11978
|
scenes: kinds.scene ?? 0,
|
|
11779
11979
|
sourceDuration: d.take.sourceDuration,
|
|
11780
11980
|
outputDuration: d.take.outputDuration,
|
|
@@ -11802,7 +12002,7 @@ async function cmdOpen(argv) {
|
|
|
11802
12002
|
if (!dir) throw new UsageError("vos open <take> [--studio <url>]");
|
|
11803
12003
|
const r = createReporter(flags.json === true);
|
|
11804
12004
|
const take = await loadTake(dir);
|
|
11805
|
-
if (!
|
|
12005
|
+
if (!existsSync16(take.paths.recording)) {
|
|
11806
12006
|
throw new UsageError(`${dir} has no recording \u2014 re-run record`);
|
|
11807
12007
|
}
|
|
11808
12008
|
const studio = (strFlag(flags, "studio") ?? "http://localhost:6060").replace(
|
|
@@ -11843,9 +12043,9 @@ async function cmdJudge(argv) {
|
|
|
11843
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)"
|
|
11844
12044
|
);
|
|
11845
12045
|
const r = createReporter(flags.json === true);
|
|
11846
|
-
const kitFile = target.endsWith("kit.json") ? target :
|
|
11847
|
-
if (!
|
|
11848
|
-
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))
|
|
11849
12049
|
throw new UsageError(`${against}: no reference manifest`);
|
|
11850
12050
|
const result = await judgeKit(kitFile, against, strFlag(flags, "out"));
|
|
11851
12051
|
const verdicts = JSON.parse(await readFile15(result.verdictFile, "utf8")).verdicts;
|
|
@@ -11967,7 +12167,7 @@ async function cmdValidate(argv) {
|
|
|
11967
12167
|
const target = positionals[0];
|
|
11968
12168
|
if (!target) throw new UsageError("vos validate <actions.json|take>");
|
|
11969
12169
|
const r = createReporter(flags.json === true);
|
|
11970
|
-
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;
|
|
11971
12171
|
if (kitTarget) {
|
|
11972
12172
|
const picture = flags.picture === true;
|
|
11973
12173
|
const verdict = await validateKit(kitTarget, { picture });
|
|
@@ -11999,9 +12199,9 @@ async function cmdValidate(argv) {
|
|
|
11999
12199
|
r.done({ valid: true, target }, `${target}: valid actions file`);
|
|
12000
12200
|
return EXIT_OK;
|
|
12001
12201
|
}
|
|
12002
|
-
if (!isTakeDir(target) &&
|
|
12202
|
+
if (!isTakeDir(target) && existsSync16(join19(target, "config.json"))) {
|
|
12003
12203
|
const parsed = JSON.parse(
|
|
12004
|
-
await readFile15(
|
|
12204
|
+
await readFile15(join19(target, "config.json"), "utf8")
|
|
12005
12205
|
);
|
|
12006
12206
|
const pre = preflightConfig(parsed);
|
|
12007
12207
|
const problems2 = pre.ok ? [] : pre.issues.map((i) => `config.json: ${i}`);
|
|
@@ -12042,7 +12242,7 @@ async function cmdValidate(argv) {
|
|
|
12042
12242
|
const take = await loadTake(target);
|
|
12043
12243
|
const problems = [];
|
|
12044
12244
|
const warnings = [];
|
|
12045
|
-
if (!
|
|
12245
|
+
if (!existsSync16(take.paths.recording)) {
|
|
12046
12246
|
problems.push("missing recording (re-run record)");
|
|
12047
12247
|
} else {
|
|
12048
12248
|
const mismatch = containerMismatch(
|
|
@@ -12283,6 +12483,8 @@ async function run(argv) {
|
|
|
12283
12483
|
return await cmdRecipe(rest);
|
|
12284
12484
|
case "brand":
|
|
12285
12485
|
return await cmdBrand(rest);
|
|
12486
|
+
case "session":
|
|
12487
|
+
return await cmdSession(rest);
|
|
12286
12488
|
case "actions":
|
|
12287
12489
|
return await cmdActions(rest);
|
|
12288
12490
|
default:
|
|
@@ -12316,6 +12518,121 @@ async function run(argv) {
|
|
|
12316
12518
|
return EXIT_ERROR;
|
|
12317
12519
|
}
|
|
12318
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
|
+
}
|
|
12319
12636
|
|
|
12320
12637
|
export {
|
|
12321
12638
|
validateActions,
|
|
@@ -12348,4 +12665,4 @@ export {
|
|
|
12348
12665
|
verbHelp,
|
|
12349
12666
|
run
|
|
12350
12667
|
};
|
|
12351
|
-
//# sourceMappingURL=chunk-
|
|
12668
|
+
//# sourceMappingURL=chunk-JRRM7P5G.js.map
|