@vosjs/cli 0.44.0 → 0.45.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -1
- package/dist/{chunk-WCSAECM4.js → chunk-4HW2XR5K.js} +409 -91
- package/dist/chunk-4HW2XR5K.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-S4EZ5OIT.js} +2 -2
- package/package.json +3 -3
- package/dist/chunk-WCSAECM4.js.map +0 -1
- /package/dist/{run-CRRKT34P.js.map → run-S4EZ5OIT.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,149 @@ 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
|
+
const leave = process.platform === "darwin" ? "then quit Chrome with \u2318Q (closing the window alone leaves it running, and this waiting)" : "then close the window";
|
|
7502
|
+
opts.log(
|
|
7503
|
+
`a Chrome window opened on session "${name}": sign in to ${new URL(url).host}, ${leave}`
|
|
7504
|
+
);
|
|
7505
|
+
return new Promise((resolve10, reject) => {
|
|
7506
|
+
const child = spawn(exe, args, { stdio: "ignore" });
|
|
7507
|
+
child.on("error", reject);
|
|
7508
|
+
child.on("exit", (code) => resolve10({ code }));
|
|
7509
|
+
});
|
|
7510
|
+
}
|
|
7511
|
+
async function withSession(name, fn, opts = {}) {
|
|
7512
|
+
const dir = sessionDir(name);
|
|
7513
|
+
if (!existsSync12(dir))
|
|
7514
|
+
throw new Error(`no session "${name}" (vos session list)`);
|
|
7515
|
+
const explicit = process.env.VOS_BROWSER_PATH;
|
|
7516
|
+
const ctx = await chromium.launchPersistentContext(dir, {
|
|
7517
|
+
headless: opts.headless ?? true,
|
|
7518
|
+
...explicit ? { executablePath: explicit } : { channel: "chrome" },
|
|
7519
|
+
// The real keychain: the whole reason a profile Chrome wrote can be read.
|
|
7520
|
+
ignoreDefaultArgs: ["--use-mock-keychain"],
|
|
7521
|
+
...opts.viewport ? { viewport: opts.viewport, deviceScaleFactor: 1 } : {}
|
|
7522
|
+
});
|
|
7523
|
+
try {
|
|
7524
|
+
return await fn(ctx);
|
|
7525
|
+
} finally {
|
|
7526
|
+
await ctx.close().catch(() => {
|
|
7527
|
+
});
|
|
7528
|
+
}
|
|
7529
|
+
}
|
|
7530
|
+
async function summarizeSession(name) {
|
|
7531
|
+
const dir = sessionDir(name);
|
|
7532
|
+
const created = statSync3(dir).birthtimeMs || statSync3(dir).mtimeMs;
|
|
7533
|
+
return withSession(name, async (ctx) => {
|
|
7534
|
+
const cookies = await ctx.cookies();
|
|
7535
|
+
const byHost = /* @__PURE__ */ new Map();
|
|
7536
|
+
let newest = 0;
|
|
7537
|
+
let sessionOnly = 0;
|
|
7538
|
+
for (const c of cookies) {
|
|
7539
|
+
const host = c.domain.replace(/^\./, "");
|
|
7540
|
+
byHost.set(host, (byHost.get(host) ?? 0) + 1);
|
|
7541
|
+
if (c.expires && c.expires > 0) newest = Math.max(newest, c.expires);
|
|
7542
|
+
else sessionOnly++;
|
|
7543
|
+
}
|
|
7544
|
+
return {
|
|
7545
|
+
name,
|
|
7546
|
+
origins: [...byHost.entries()].map(([host, n]) => ({ host, cookies: n })).sort((a, b) => b.cookies - a.cookies),
|
|
7547
|
+
newestExpires: newest ? new Date(newest * 1e3).toISOString() : null,
|
|
7548
|
+
sessionOnly,
|
|
7549
|
+
ageDays: Math.floor((Date.now() - created) / 864e5)
|
|
7550
|
+
};
|
|
7551
|
+
});
|
|
7552
|
+
}
|
|
7553
|
+
function summaryLine(s) {
|
|
7554
|
+
if (!s.origins.length)
|
|
7555
|
+
return `session "${s.name}" holds no cookies: the window closed before a sign-in landed, or the sign-in never happened`;
|
|
7556
|
+
const hosts = s.origins.slice(0, 4).map((o) => `${o.host} (${o.cookies})`).join(", ");
|
|
7557
|
+
const more = s.origins.length > 4 ? ` and ${s.origins.length - 4} more` : "";
|
|
7558
|
+
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";
|
|
7559
|
+
return `session "${s.name}" holds cookies for ${hosts}${more}; ${exp}`;
|
|
7560
|
+
}
|
|
7561
|
+
function listSessions() {
|
|
7562
|
+
if (!existsSync12(SESSIONS_DIR)) return [];
|
|
7563
|
+
return readdirSync2(SESSIONS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && NAME.test(d.name)).map((d) => {
|
|
7564
|
+
const st = statSync3(join14(SESSIONS_DIR, d.name));
|
|
7565
|
+
return {
|
|
7566
|
+
name: d.name,
|
|
7567
|
+
ageDays: Math.floor(
|
|
7568
|
+
(Date.now() - (st.birthtimeMs || st.mtimeMs)) / 864e5
|
|
7569
|
+
)
|
|
7570
|
+
};
|
|
7571
|
+
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
7572
|
+
}
|
|
7573
|
+
function removeSession(name) {
|
|
7574
|
+
const dir = sessionDir(name);
|
|
7575
|
+
if (!existsSync12(dir)) return false;
|
|
7576
|
+
rmSync(dir, { recursive: true, force: true });
|
|
7577
|
+
return true;
|
|
7578
|
+
}
|
|
7579
|
+
function looksLikeStorageState(value) {
|
|
7580
|
+
if (typeof value !== "object" || value === null) return false;
|
|
7581
|
+
const v = value;
|
|
7582
|
+
return Array.isArray(v.cookies) && Array.isArray(v.origins);
|
|
7583
|
+
}
|
|
7584
|
+
|
|
7440
7585
|
// src/plugin/encode.ts
|
|
7441
7586
|
var MEDIABUNNY_URL2 = "https://esm.sh/mediabunny@1.55.7?target=es2022";
|
|
7442
7587
|
var ENCODE_HTML = `<!doctype html><html><head><meta charset="utf-8"></head><body>
|
|
@@ -7492,9 +7637,9 @@ async function encodeRecording(browser, takeDir, onProgress) {
|
|
|
7492
7637
|
}
|
|
7493
7638
|
|
|
7494
7639
|
// src/plugin/plan.ts
|
|
7495
|
-
import { existsSync as
|
|
7640
|
+
import { existsSync as existsSync13 } from "fs";
|
|
7496
7641
|
import { readFile as readFile12 } from "fs/promises";
|
|
7497
|
-
import { join as
|
|
7642
|
+
import { join as join15 } from "path";
|
|
7498
7643
|
import {
|
|
7499
7644
|
DEFAULT_FRAME_STYLE,
|
|
7500
7645
|
STYLE_FIELDS,
|
|
@@ -7716,8 +7861,8 @@ function retimeCut(prev, newSteps, newDurationMs) {
|
|
|
7716
7861
|
// src/plugin/plan.ts
|
|
7717
7862
|
var overlaps = (a, b) => a.in < b.out && b.in < a.out;
|
|
7718
7863
|
async function readDigestActivity(dir) {
|
|
7719
|
-
const file =
|
|
7720
|
-
if (!
|
|
7864
|
+
const file = join15(dir, "digest", "digest.json");
|
|
7865
|
+
if (!existsSync13(file)) return null;
|
|
7721
7866
|
try {
|
|
7722
7867
|
const d = JSON.parse(await readFile12(file, "utf8"));
|
|
7723
7868
|
return Array.isArray(d.activity) && d.activity.every((v) => typeof v === "number") ? d.activity : null;
|
|
@@ -7929,15 +8074,15 @@ async function planTake(dir, opts = {}) {
|
|
|
7929
8074
|
|
|
7930
8075
|
// src/plugin/sync.ts
|
|
7931
8076
|
import { createHash } from "crypto";
|
|
7932
|
-
import { existsSync as
|
|
8077
|
+
import { existsSync as existsSync14, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "fs";
|
|
7933
8078
|
import { readFile as readFile13 } from "fs/promises";
|
|
7934
8079
|
import { createInterface } from "readline/promises";
|
|
7935
|
-
import { basename as basename3, join as
|
|
8080
|
+
import { basename as basename3, join as join17 } from "path";
|
|
7936
8081
|
import { lowerToComposition as lowerToComposition3, migrateHostedDoc as migrateHostedDoc3 } from "@vosjs/studio-core";
|
|
7937
8082
|
|
|
7938
8083
|
// src/plugin/folder.ts
|
|
7939
8084
|
import { mkdir as mkdir8, writeFile as writeFile10 } from "fs/promises";
|
|
7940
|
-
import { join as
|
|
8085
|
+
import { join as join16 } from "path";
|
|
7941
8086
|
import { recipeHints } from "@vosjs/shared/frontmatter";
|
|
7942
8087
|
import { migrateHostedDoc as migrateHostedDoc2 } from "@vosjs/studio-core";
|
|
7943
8088
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "help", "media"]);
|
|
@@ -8113,18 +8258,18 @@ async function cmdPull(argv) {
|
|
|
8113
8258
|
throw new Error(apiError(`pull folder ${folder.slug}`, res));
|
|
8114
8259
|
const payload = res.body;
|
|
8115
8260
|
const out = strFlag(flags, "out") ?? folder.slug;
|
|
8116
|
-
await mkdir8(
|
|
8261
|
+
await mkdir8(join16(out, "recipes"), { recursive: true });
|
|
8117
8262
|
const lines = [];
|
|
8118
8263
|
const recipes = [];
|
|
8119
8264
|
for (const rec of payload.recipes) {
|
|
8120
|
-
const path =
|
|
8265
|
+
const path = join16(out, "recipes", safeName(rec.filename));
|
|
8121
8266
|
await writeFile10(path, rec.body ?? "");
|
|
8122
8267
|
recipes.push({ path, line: recipeLine(rec.filename, rec.body ?? "") });
|
|
8123
8268
|
}
|
|
8124
8269
|
for (const rec of payload.inheritedRecipes) {
|
|
8125
8270
|
const from = safeName(rec.folderSlug ?? "inherited");
|
|
8126
|
-
await mkdir8(
|
|
8127
|
-
const path =
|
|
8271
|
+
await mkdir8(join16(out, "recipes", "_inherited", from), { recursive: true });
|
|
8272
|
+
const path = join16(
|
|
8128
8273
|
out,
|
|
8129
8274
|
"recipes",
|
|
8130
8275
|
"_inherited",
|
|
@@ -8139,7 +8284,7 @@ async function cmdPull(argv) {
|
|
|
8139
8284
|
}
|
|
8140
8285
|
const members = [];
|
|
8141
8286
|
for (const v of payload.voses) {
|
|
8142
|
-
const dir =
|
|
8287
|
+
const dir = join16(out, "members", safeName(v.slug || v.title || v.id));
|
|
8143
8288
|
await mkdir8(dir, { recursive: true });
|
|
8144
8289
|
const cfg = await apiJson(origin, `/api/vos/${v.id}/config`, { key });
|
|
8145
8290
|
if (cfg.status !== 200) {
|
|
@@ -8147,7 +8292,7 @@ async function cmdPull(argv) {
|
|
|
8147
8292
|
continue;
|
|
8148
8293
|
}
|
|
8149
8294
|
await writeFile10(
|
|
8150
|
-
|
|
8295
|
+
join16(dir, "config.json"),
|
|
8151
8296
|
JSON.stringify(cfg.body.config, null, 2)
|
|
8152
8297
|
);
|
|
8153
8298
|
let take = false;
|
|
@@ -8159,7 +8304,7 @@ async function cmdPull(argv) {
|
|
|
8159
8304
|
);
|
|
8160
8305
|
if (doc.status === 200) {
|
|
8161
8306
|
await writeFile10(
|
|
8162
|
-
|
|
8307
|
+
join16(dir, "doc.json"),
|
|
8163
8308
|
JSON.stringify(doc.body, null, 2)
|
|
8164
8309
|
);
|
|
8165
8310
|
take = true;
|
|
@@ -8428,7 +8573,24 @@ function programPushTarget(ask) {
|
|
|
8428
8573
|
}
|
|
8429
8574
|
return { kind: "version", vosId: tracked, tracked: true };
|
|
8430
8575
|
}
|
|
8576
|
+
function storageStateInDir(dir) {
|
|
8577
|
+
for (const f of readdirSync3(dir)) {
|
|
8578
|
+
if (!f.endsWith(".json") || f === "doc.json" || f === "meta.json" || f === "actions.json" || f === "cursor.json" || f === "vos.json" || f === "doc.prev.json")
|
|
8579
|
+
continue;
|
|
8580
|
+
try {
|
|
8581
|
+
const v = JSON.parse(readFileSync4(join17(dir, f), "utf8"));
|
|
8582
|
+
if (looksLikeStorageState(v)) return join17(dir, f);
|
|
8583
|
+
} catch {
|
|
8584
|
+
}
|
|
8585
|
+
}
|
|
8586
|
+
return null;
|
|
8587
|
+
}
|
|
8431
8588
|
async function pushTake(dir, flags, r) {
|
|
8589
|
+
const stateFile = storageStateInDir(dir);
|
|
8590
|
+
if (stateFile)
|
|
8591
|
+
throw new Error(
|
|
8592
|
+
`${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`
|
|
8593
|
+
);
|
|
8432
8594
|
const take = await loadTake(dir);
|
|
8433
8595
|
if (!take.doc) {
|
|
8434
8596
|
throw new Error("no doc.json in this take \u2014 run `vos plan` first");
|
|
@@ -8438,7 +8600,7 @@ async function pushTake(dir, flags, r) {
|
|
|
8438
8600
|
throw new Error(`doc.json fails lint:
|
|
8439
8601
|
${lint.problems.join("\n ")}`);
|
|
8440
8602
|
}
|
|
8441
|
-
if (!
|
|
8603
|
+
if (!existsSync14(take.paths.recording)) {
|
|
8442
8604
|
throw new Error(
|
|
8443
8605
|
"no recording in this take \u2014 record it, or `vos pull --media` to bring it home"
|
|
8444
8606
|
);
|
|
@@ -8530,8 +8692,8 @@ async function pushTake(dir, flags, r) {
|
|
|
8530
8692
|
delete docForPush.source.micKey;
|
|
8531
8693
|
}
|
|
8532
8694
|
for (const ref of docMediaRefs(docForPush)) {
|
|
8533
|
-
const file =
|
|
8534
|
-
if (!
|
|
8695
|
+
const file = join17(dir, takeRelativeFile(ref.key));
|
|
8696
|
+
if (!existsSync14(file)) {
|
|
8535
8697
|
r.log(` ${ref.where}: ${ref.key} is not in the take \u2014 left as is`);
|
|
8536
8698
|
continue;
|
|
8537
8699
|
}
|
|
@@ -8675,9 +8837,9 @@ async function pullTake(dir, flags, r) {
|
|
|
8675
8837
|
};
|
|
8676
8838
|
}
|
|
8677
8839
|
let media2;
|
|
8678
|
-
if (flags.media &&
|
|
8840
|
+
if (flags.media && existsSync14(join17(dir, "doc.json"))) {
|
|
8679
8841
|
const local = JSON.parse(
|
|
8680
|
-
await readFile13(
|
|
8842
|
+
await readFile13(join17(dir, "doc.json"), "utf8")
|
|
8681
8843
|
);
|
|
8682
8844
|
media2 = await pullMedia(ctx, dir, local, r.log);
|
|
8683
8845
|
}
|
|
@@ -8738,10 +8900,10 @@ async function pullTake(dir, flags, r) {
|
|
|
8738
8900
|
const hostedDoc = migrateHostedDoc3(docRes.json);
|
|
8739
8901
|
const doc = hostedDoc;
|
|
8740
8902
|
const localRecording = takeMediaFile(dir, "recording", RECORDING_NAME);
|
|
8741
|
-
if (
|
|
8903
|
+
if (existsSync14(localRecording)) {
|
|
8742
8904
|
doc.source.videoKey = basename3(localRecording);
|
|
8743
8905
|
}
|
|
8744
|
-
await writeJson(
|
|
8906
|
+
await writeJson(join17(dir, "doc.json"), doc, true);
|
|
8745
8907
|
const media = flags.media ? await pullMedia(ctx, dir, doc, r.log) : void 0;
|
|
8746
8908
|
const versions = await api(ctx, `/vos/${vosId}/versions`);
|
|
8747
8909
|
const headRow = (versions.json.versions ?? []).find((v) => v.id === head);
|
|
@@ -8760,10 +8922,10 @@ async function pullTake(dir, flags, r) {
|
|
|
8760
8922
|
}
|
|
8761
8923
|
|
|
8762
8924
|
// src/plugin/program.ts
|
|
8763
|
-
import { existsSync as
|
|
8925
|
+
import { existsSync as existsSync15 } from "fs";
|
|
8764
8926
|
import { mkdir as mkdir9, readFile as readFile14, writeFile as writeFile11 } from "fs/promises";
|
|
8765
8927
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
8766
|
-
import { basename as basename4, dirname as dirname4, join as
|
|
8928
|
+
import { basename as basename4, dirname as dirname4, join as join18 } from "path";
|
|
8767
8929
|
import {
|
|
8768
8930
|
CURRENT_CONFIG_VERSION,
|
|
8769
8931
|
migrateConfig,
|
|
@@ -8933,8 +9095,8 @@ function preflightConfig(parsed) {
|
|
|
8933
9095
|
}
|
|
8934
9096
|
function resolveConfigPath(target) {
|
|
8935
9097
|
if (target.endsWith(".json")) return target;
|
|
8936
|
-
const inDir =
|
|
8937
|
-
if (
|
|
9098
|
+
const inDir = join18(target, "config.json");
|
|
9099
|
+
if (existsSync15(inDir)) return inDir;
|
|
8938
9100
|
throw new UsageError(
|
|
8939
9101
|
`${target} has no config.json (and is not a take \u2014 no doc.json)`
|
|
8940
9102
|
);
|
|
@@ -8962,7 +9124,7 @@ async function cmdFetch(argv) {
|
|
|
8962
9124
|
const out = strFlag(flags, "out") ?? slug;
|
|
8963
9125
|
await mkdir9(out, { recursive: true });
|
|
8964
9126
|
await writeFile11(
|
|
8965
|
-
|
|
9127
|
+
join18(out, "config.json"),
|
|
8966
9128
|
JSON.stringify(cfg.body.config, null, 2)
|
|
8967
9129
|
);
|
|
8968
9130
|
const title = typeof vosMeta.title === "string" ? vosMeta.title : "";
|
|
@@ -8987,12 +9149,12 @@ async function cmdFetch(argv) {
|
|
|
8987
9149
|
const hosted = migrateHostedDoc4(doc.body);
|
|
8988
9150
|
const { config: own } = await writeProgramDoc(out, hosted);
|
|
8989
9151
|
if (own) {
|
|
8990
|
-
await writeFile11(
|
|
9152
|
+
await writeFile11(join18(out, "config.json"), JSON.stringify(own, null, 2));
|
|
8991
9153
|
}
|
|
8992
9154
|
} else if (doc.status === 200) {
|
|
8993
9155
|
take = true;
|
|
8994
9156
|
const hosted = migrateHostedDoc4(doc.body);
|
|
8995
|
-
await writeFile11(
|
|
9157
|
+
await writeFile11(join18(out, "doc.json"), JSON.stringify(hosted, null, 2));
|
|
8996
9158
|
if (flags.media === true) {
|
|
8997
9159
|
const media = await pullMedia({ origin, key }, out, hosted, r.log);
|
|
8998
9160
|
mediaLine = media.downloaded.length ? ` + ${media.downloaded.map((m) => m.file).join(", ")}` : "";
|
|
@@ -9355,10 +9517,10 @@ async function cmdPullProgram(argv) {
|
|
|
9355
9517
|
const cfg = await apiJson(origin, `/api/vos/${vosId}/config`, { key });
|
|
9356
9518
|
if (cfg.status !== 200)
|
|
9357
9519
|
throw new Error(apiError(`fetch head config for ${vosId}`, cfg));
|
|
9358
|
-
const configPath =
|
|
9520
|
+
const configPath = join18(dir, "config.json");
|
|
9359
9521
|
let backedUp = false;
|
|
9360
|
-
if (
|
|
9361
|
-
await writeFile11(
|
|
9522
|
+
if (existsSync15(configPath)) {
|
|
9523
|
+
await writeFile11(join18(dir, "config.backup.json"), await readFile14(configPath));
|
|
9362
9524
|
backedUp = true;
|
|
9363
9525
|
}
|
|
9364
9526
|
let headConfig = cfg.body.config;
|
|
@@ -9391,7 +9553,7 @@ async function cmdPullProgram(argv) {
|
|
|
9391
9553
|
protected: protectedIds,
|
|
9392
9554
|
changes,
|
|
9393
9555
|
out: configPath,
|
|
9394
|
-
backup: backedUp ?
|
|
9556
|
+
backup: backedUp ? join18(dir, "config.backup.json") : null
|
|
9395
9557
|
},
|
|
9396
9558
|
`Pulled ${changes.length} version${changes.length === 1 ? "" : "s"} \u2192 ${configPath}` + (backedUp ? ` (previous copy: config.backup.json)` : "") + `
|
|
9397
9559
|
Re-apply your edit on the new head, then: vos push ${configPath} --vos ${vosId}`
|
|
@@ -9453,8 +9615,8 @@ function storedProgramConfig(config, programDoc) {
|
|
|
9453
9615
|
return lowerProgramDoc(doc).config;
|
|
9454
9616
|
}
|
|
9455
9617
|
async function readProgramDoc(dir, config) {
|
|
9456
|
-
const docPath =
|
|
9457
|
-
if (!
|
|
9618
|
+
const docPath = join18(dir, "doc.json");
|
|
9619
|
+
if (!existsSync15(docPath)) return null;
|
|
9458
9620
|
const parsed = JSON.parse(await readFile14(docPath, "utf8"));
|
|
9459
9621
|
if (typeof parsed !== "object" || parsed === null || "source" in parsed)
|
|
9460
9622
|
return null;
|
|
@@ -9466,14 +9628,14 @@ async function writeProgramDoc(dir, hosted) {
|
|
|
9466
9628
|
const program = hosted.program && typeof hosted.program === "object" ? hosted.program : {};
|
|
9467
9629
|
const { config, ...rest } = program;
|
|
9468
9630
|
const onDisk = { ...hosted, program: rest };
|
|
9469
|
-
await writeFile11(
|
|
9631
|
+
await writeFile11(join18(dir, "doc.json"), JSON.stringify(onDisk, null, 2));
|
|
9470
9632
|
return {
|
|
9471
9633
|
config: config && typeof config === "object" ? config : null
|
|
9472
9634
|
};
|
|
9473
9635
|
}
|
|
9474
9636
|
|
|
9475
9637
|
// src/plugin/asset.ts
|
|
9476
|
-
import { readFileSync as
|
|
9638
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
9477
9639
|
import { basename as basename5, extname as extname2 } from "path";
|
|
9478
9640
|
var BOOLEAN_FLAGS3 = /* @__PURE__ */ new Set(["json", "help"]);
|
|
9479
9641
|
var MIME_BY_EXT = {
|
|
@@ -9527,7 +9689,7 @@ async function cmdPush(argv) {
|
|
|
9527
9689
|
for (const file of positionals) {
|
|
9528
9690
|
const name = basename5(file);
|
|
9529
9691
|
const mime = MIME_BY_EXT[extname2(name).toLowerCase()] ?? "application/octet-stream";
|
|
9530
|
-
const body =
|
|
9692
|
+
const body = readFileSync5(file);
|
|
9531
9693
|
const form = new FormData();
|
|
9532
9694
|
form.set("file", new Blob([body], { type: mime }), name);
|
|
9533
9695
|
if (folder) form.set("folderId", folder.id);
|
|
@@ -9581,7 +9743,7 @@ async function cmdAsset(argv) {
|
|
|
9581
9743
|
}
|
|
9582
9744
|
|
|
9583
9745
|
// src/plugin/recipe.ts
|
|
9584
|
-
import { readFileSync as
|
|
9746
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
9585
9747
|
import { basename as basename6 } from "path";
|
|
9586
9748
|
var BOOLEAN_FLAGS4 = /* @__PURE__ */ new Set(["json", "help"]);
|
|
9587
9749
|
var USAGE = "vos recipe push <file.md> (--folder <folderId|slug> | --asset <assetId>)";
|
|
@@ -9601,7 +9763,7 @@ async function cmdPush2(argv) {
|
|
|
9601
9763
|
throw new UsageError(USAGE);
|
|
9602
9764
|
}
|
|
9603
9765
|
const filename = recipeFilename(file);
|
|
9604
|
-
const body =
|
|
9766
|
+
const body = readFileSync6(file);
|
|
9605
9767
|
const r = createReporter(flags.json === true);
|
|
9606
9768
|
const origin = platformOrigin({
|
|
9607
9769
|
origin: strFlag(flags, "origin"),
|
|
@@ -10602,8 +10764,8 @@ var MULTI_FLAGS2 = /* @__PURE__ */ new Set(["set", "override", "browser-arg", "h
|
|
|
10602
10764
|
var HELP = `vos \u2014 record a browser flow, plan effects, render a product video; sync with vos.so
|
|
10603
10765
|
|
|
10604
10766
|
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]
|
|
10767
|
+
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]
|
|
10768
|
+
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
10769
|
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
10770
|
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
10771
|
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 +10777,9 @@ Take pipeline
|
|
|
10615
10777
|
vos validate <actions.json|take|kit.json> [--picture] [--json]
|
|
10616
10778
|
vos judge <kit.json> --against <MANIFEST.json> [--out dir] [--json]
|
|
10617
10779
|
vos actions from-agent-browser <steps.jsonl> [--out actions.json] [--url <url>] [--viewport WxH] [--json]
|
|
10780
|
+
vos session open <url> --name <app> a plain Chrome window on a vos-owned profile; sign in, close it
|
|
10781
|
+
vos session check <name> [--url <url>] headless: does the session still open the page? exit 0/4
|
|
10782
|
+
vos session list | rm <name> what exists and how old; delete one
|
|
10618
10783
|
vos actions script <actions.json> [--json]
|
|
10619
10784
|
the flow as numbered beats in words, for a person recording it by hand
|
|
10620
10785
|
|
|
@@ -10913,10 +11078,35 @@ function takeStorageState(flags) {
|
|
|
10913
11078
|
const raw = strFlag(flags, "storage-state");
|
|
10914
11079
|
if (!raw) return void 0;
|
|
10915
11080
|
const path = resolve9(raw);
|
|
10916
|
-
if (!
|
|
11081
|
+
if (!existsSync16(path))
|
|
10917
11082
|
throw new UsageError(`--storage-state: no such file: ${path}`);
|
|
10918
11083
|
return path;
|
|
10919
11084
|
}
|
|
11085
|
+
function takeSessionName(flags, storageState) {
|
|
11086
|
+
const name = strFlag(flags, "session");
|
|
11087
|
+
if (!name) return void 0;
|
|
11088
|
+
const bad = sessionNameVerdict(name);
|
|
11089
|
+
if (bad) throw new UsageError(bad);
|
|
11090
|
+
if (storageState)
|
|
11091
|
+
throw new UsageError(
|
|
11092
|
+
"--session and --storage-state are two doors to one take: a persistent profile cannot take a storage state. Pass one"
|
|
11093
|
+
);
|
|
11094
|
+
if (!existsSync16(sessionDir(name)))
|
|
11095
|
+
throw new UsageError(
|
|
11096
|
+
`no session "${name}". Make one: vos session open <url> --name ${name} (a Chrome window opens; sign in and close it)`
|
|
11097
|
+
);
|
|
11098
|
+
return name;
|
|
11099
|
+
}
|
|
11100
|
+
async function inSession(name, actions, fn) {
|
|
11101
|
+
if (!name) return fn(void 0);
|
|
11102
|
+
return withSession(name, (ctx) => fn(ctx), {
|
|
11103
|
+
headless: true,
|
|
11104
|
+
viewport: {
|
|
11105
|
+
width: actions.viewport?.width ?? 1280,
|
|
11106
|
+
height: actions.viewport?.height ?? 720
|
|
11107
|
+
}
|
|
11108
|
+
});
|
|
11109
|
+
}
|
|
10920
11110
|
async function takeBackdrop(flags, r) {
|
|
10921
11111
|
const { backdrop, note } = await openingBackdrop(
|
|
10922
11112
|
strFlag(flags, "background"),
|
|
@@ -10974,6 +11164,7 @@ async function cmdRecord(argv) {
|
|
|
10974
11164
|
const backdrop = await takeBackdrop(flags, r);
|
|
10975
11165
|
const maxDurationSeconds = await maxDuration(flags, r);
|
|
10976
11166
|
const storageState = takeStorageState(flags);
|
|
11167
|
+
const sessionName = takeSessionName(flags, storageState);
|
|
10977
11168
|
const browserArgs = takeBrowserArgs(multi);
|
|
10978
11169
|
const headers = takeHeaders(multi);
|
|
10979
11170
|
if (flags["dry-run"] === true) {
|
|
@@ -10984,19 +11175,17 @@ async function cmdRecord(argv) {
|
|
|
10984
11175
|
r.log("rehearsing (nothing is captured or written)\u2026");
|
|
10985
11176
|
r.event({ event: "phase", phase: "rehearse" });
|
|
10986
11177
|
const started = Date.now();
|
|
10987
|
-
const rec = await
|
|
10988
|
-
|
|
10989
|
-
url,
|
|
11178
|
+
const rec = await inSession(
|
|
11179
|
+
sessionName,
|
|
10990
11180
|
actions,
|
|
10991
|
-
takePaths(outDir),
|
|
10992
|
-
r.log,
|
|
10993
|
-
{
|
|
11181
|
+
(context) => recordTake(browser2, url, actions, takePaths(outDir), r.log, {
|
|
10994
11182
|
storageState,
|
|
10995
11183
|
headers,
|
|
11184
|
+
context,
|
|
10996
11185
|
dryRun: true,
|
|
10997
11186
|
// A rehearsal is always strict, and it touches no directory.
|
|
10998
11187
|
onArrival: wallGate(flags, r, { strict: true })
|
|
10999
|
-
}
|
|
11188
|
+
})
|
|
11000
11189
|
);
|
|
11001
11190
|
const steps = rec.meta.steps ?? [];
|
|
11002
11191
|
const lines = steps.map((s) => {
|
|
@@ -11009,6 +11198,7 @@ async function cmdRecord(argv) {
|
|
|
11009
11198
|
...strFlag(flags, "storage-state") ? [`--storage-state ${strFlag(flags, "storage-state")}`] : [],
|
|
11010
11199
|
...browserArgs.map((a) => `--browser-arg=${a}`),
|
|
11011
11200
|
...Object.entries(headers).map(([k, v]) => `--header ${k}=${v}`),
|
|
11201
|
+
...sessionName ? [`--session ${sessionName}`] : [],
|
|
11012
11202
|
...flags["allow-wall"] === true ? ["--allow-wall"] : []
|
|
11013
11203
|
].join(" ");
|
|
11014
11204
|
const nextOut = strFlag(flags, "out") ?? "take";
|
|
@@ -11034,7 +11224,7 @@ ${lines.join("\n")}
|
|
|
11034
11224
|
}
|
|
11035
11225
|
const paths = takePaths(outDir);
|
|
11036
11226
|
const prepare = async () => {
|
|
11037
|
-
if (
|
|
11227
|
+
if (existsSync16(join19(outDir, "meta.json"))) {
|
|
11038
11228
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
11039
11229
|
r.log(
|
|
11040
11230
|
`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 +11237,20 @@ ${lines.join("\n")}
|
|
|
11047
11237
|
try {
|
|
11048
11238
|
r.log("recording\u2026");
|
|
11049
11239
|
r.event({ event: "phase", phase: "record" });
|
|
11050
|
-
const rec = await
|
|
11051
|
-
|
|
11052
|
-
|
|
11053
|
-
|
|
11054
|
-
|
|
11055
|
-
|
|
11056
|
-
|
|
11240
|
+
const rec = await inSession(
|
|
11241
|
+
sessionName,
|
|
11242
|
+
actions,
|
|
11243
|
+
(context) => recordTake(browser, url, actions, paths, r.log, {
|
|
11244
|
+
maxDurationSeconds,
|
|
11245
|
+
storageState,
|
|
11246
|
+
headers,
|
|
11247
|
+
context,
|
|
11248
|
+
onArrival: wallGate(flags, r, {
|
|
11249
|
+
strict: flags.strict === true,
|
|
11250
|
+
prepare
|
|
11251
|
+
})
|
|
11057
11252
|
})
|
|
11058
|
-
|
|
11253
|
+
);
|
|
11059
11254
|
r.event({ event: "phase", phase: "encode" });
|
|
11060
11255
|
r.log("encoding\u2026");
|
|
11061
11256
|
const enc = await encodeRecording(
|
|
@@ -11093,7 +11288,7 @@ ${lines.join("\n")}
|
|
|
11093
11288
|
},
|
|
11094
11289
|
strictFail ? `STRICT: take recorded but incomplete \u2014 ${strictReason(rec)}; fix the flow and re-record.${skippedNote}${exposureNote(rec)}` : `Take ready: ${outDir}
|
|
11095
11290
|
${(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 ${
|
|
11291
|
+
Next: edit ${join19(outDir, "doc.json")} (optional), then: vos render ${outDir}${exposureNote(rec)}`
|
|
11097
11292
|
);
|
|
11098
11293
|
return strictFail ? EXIT_USAGE : EXIT_OK;
|
|
11099
11294
|
} finally {
|
|
@@ -11122,7 +11317,7 @@ async function cmdCreate2(argv) {
|
|
|
11122
11317
|
if (fmtRaw !== "webm" && fmtRaw !== "mp4")
|
|
11123
11318
|
throw new UsageError("--format must be webm or mp4");
|
|
11124
11319
|
const format = fmtRaw;
|
|
11125
|
-
const out = positionals[0] ??
|
|
11320
|
+
const out = positionals[0] ?? join19(outDir, `out.${format}`);
|
|
11126
11321
|
const parallel = numFlag(flags, "parallel", 1);
|
|
11127
11322
|
if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
|
|
11128
11323
|
throw new UsageError("--parallel expects an integer between 1 and 16");
|
|
@@ -11130,11 +11325,12 @@ async function cmdCreate2(argv) {
|
|
|
11130
11325
|
const backdrop = await takeBackdrop(flags, r);
|
|
11131
11326
|
const maxDurationSeconds = await maxDuration(flags, r);
|
|
11132
11327
|
const storageState = takeStorageState(flags);
|
|
11328
|
+
const sessionName = takeSessionName(flags, storageState);
|
|
11133
11329
|
const browserArgs = takeBrowserArgs(multi);
|
|
11134
11330
|
const headers = takeHeaders(multi);
|
|
11135
11331
|
const paths = takePaths(outDir);
|
|
11136
11332
|
const prepare = async () => {
|
|
11137
|
-
if (
|
|
11333
|
+
if (existsSync16(join19(outDir, "meta.json"))) {
|
|
11138
11334
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
11139
11335
|
r.log(
|
|
11140
11336
|
`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 +11343,20 @@ async function cmdCreate2(argv) {
|
|
|
11147
11343
|
try {
|
|
11148
11344
|
r.log("recording\u2026");
|
|
11149
11345
|
r.event({ event: "phase", phase: "record" });
|
|
11150
|
-
const rec = await
|
|
11151
|
-
|
|
11152
|
-
|
|
11153
|
-
|
|
11154
|
-
|
|
11155
|
-
|
|
11156
|
-
|
|
11346
|
+
const rec = await inSession(
|
|
11347
|
+
sessionName,
|
|
11348
|
+
actions,
|
|
11349
|
+
(context) => recordTake(browser, url, actions, paths, r.log, {
|
|
11350
|
+
maxDurationSeconds,
|
|
11351
|
+
storageState,
|
|
11352
|
+
headers,
|
|
11353
|
+
context,
|
|
11354
|
+
onArrival: wallGate(flags, r, {
|
|
11355
|
+
strict: flags.strict === true,
|
|
11356
|
+
prepare
|
|
11357
|
+
})
|
|
11157
11358
|
})
|
|
11158
|
-
|
|
11359
|
+
);
|
|
11159
11360
|
r.event({ event: "phase", phase: "encode" });
|
|
11160
11361
|
r.log("encoding\u2026");
|
|
11161
11362
|
await encodeRecording(
|
|
@@ -11235,13 +11436,13 @@ async function cmdPlan(argv) {
|
|
|
11235
11436
|
);
|
|
11236
11437
|
const r = createReporter(flags.json === true);
|
|
11237
11438
|
if (flags.fresh === true) {
|
|
11238
|
-
await rm5(
|
|
11439
|
+
await rm5(join19(dir, "doc.json"), { force: true });
|
|
11239
11440
|
r.log("note: --fresh discarded the existing doc.json");
|
|
11240
11441
|
}
|
|
11241
11442
|
let reuse;
|
|
11242
11443
|
if (flags.reuse === true) {
|
|
11243
|
-
const from = resolve9(strFlag(flags, "from") ??
|
|
11244
|
-
if (!
|
|
11444
|
+
const from = resolve9(strFlag(flags, "from") ?? join19(dir, PREV_DOC_NAME));
|
|
11445
|
+
if (!existsSync16(from)) {
|
|
11245
11446
|
throw new UsageError(
|
|
11246
11447
|
`--reuse: ${from} does not exist \u2014 a re-record into this take writes doc.prev.json, or pass --from <doc.json>`
|
|
11247
11448
|
);
|
|
@@ -11252,7 +11453,7 @@ async function cmdPlan(argv) {
|
|
|
11252
11453
|
};
|
|
11253
11454
|
}
|
|
11254
11455
|
const style = await resolveStyleRef(flags);
|
|
11255
|
-
const hasDoc =
|
|
11456
|
+
const hasDoc = existsSync16(join19(dir, "doc.json"));
|
|
11256
11457
|
const backdrop = hasDoc ? null : await takeBackdrop(flags, r);
|
|
11257
11458
|
const release = await releaseInputs(dir, flags, r, multi.with ?? []);
|
|
11258
11459
|
const motionWanted = !hasDoc || flags.motion === true || flags.fresh === true;
|
|
@@ -11310,7 +11511,7 @@ async function cmdPlan(argv) {
|
|
|
11310
11511
|
...s.motion ? { motion: s.motion } : {},
|
|
11311
11512
|
...s.reuse ? { reuse: s.reuse } : {}
|
|
11312
11513
|
},
|
|
11313
|
-
`${s.reuse ? "Reused" : s.fresh ? "Planned" : "Refreshed"} ${
|
|
11514
|
+
`${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
11515
|
);
|
|
11315
11516
|
return EXIT_OK;
|
|
11316
11517
|
}
|
|
@@ -11436,7 +11637,7 @@ async function cmdRender(argv) {
|
|
|
11436
11637
|
if (fmtRaw !== "webm" && fmtRaw !== "mp4")
|
|
11437
11638
|
throw new UsageError("--format must be webm or mp4");
|
|
11438
11639
|
const format = fmtRaw;
|
|
11439
|
-
const out = positionals[1] ??
|
|
11640
|
+
const out = positionals[1] ?? join19(dir, `out.${format}`);
|
|
11440
11641
|
const parallel = numFlag(flags, "parallel", 1);
|
|
11441
11642
|
if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
|
|
11442
11643
|
throw new UsageError("--parallel expects an integer between 1 and 16");
|
|
@@ -11683,11 +11884,11 @@ async function resolveStyleRef(flags) {
|
|
|
11683
11884
|
}
|
|
11684
11885
|
async function resolveDocRef(ref, flags, what) {
|
|
11685
11886
|
const styleRef = ref;
|
|
11686
|
-
const file =
|
|
11887
|
+
const file = existsSync16(styleRef) ? resolve9(
|
|
11687
11888
|
styleRef,
|
|
11688
|
-
|
|
11889
|
+
existsSync16(join19(styleRef, "doc.json")) ? "doc.json" : ""
|
|
11689
11890
|
) : null;
|
|
11690
|
-
if (file &&
|
|
11891
|
+
if (file && existsSync16(file)) {
|
|
11691
11892
|
return {
|
|
11692
11893
|
from: file,
|
|
11693
11894
|
doc: migrateHostedDoc5(
|
|
@@ -11774,7 +11975,7 @@ async function cmdDigest(argv) {
|
|
|
11774
11975
|
kinds,
|
|
11775
11976
|
frames: d.images.full,
|
|
11776
11977
|
crops: d.images.crop,
|
|
11777
|
-
sheet: d.images.sheet ?
|
|
11978
|
+
sheet: d.images.sheet ? join19(result.outDir, d.images.sheet) : null,
|
|
11778
11979
|
scenes: kinds.scene ?? 0,
|
|
11779
11980
|
sourceDuration: d.take.sourceDuration,
|
|
11780
11981
|
outputDuration: d.take.outputDuration,
|
|
@@ -11802,7 +12003,7 @@ async function cmdOpen(argv) {
|
|
|
11802
12003
|
if (!dir) throw new UsageError("vos open <take> [--studio <url>]");
|
|
11803
12004
|
const r = createReporter(flags.json === true);
|
|
11804
12005
|
const take = await loadTake(dir);
|
|
11805
|
-
if (!
|
|
12006
|
+
if (!existsSync16(take.paths.recording)) {
|
|
11806
12007
|
throw new UsageError(`${dir} has no recording \u2014 re-run record`);
|
|
11807
12008
|
}
|
|
11808
12009
|
const studio = (strFlag(flags, "studio") ?? "http://localhost:6060").replace(
|
|
@@ -11843,9 +12044,9 @@ async function cmdJudge(argv) {
|
|
|
11843
12044
|
"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
12045
|
);
|
|
11845
12046
|
const r = createReporter(flags.json === true);
|
|
11846
|
-
const kitFile = target.endsWith("kit.json") ? target :
|
|
11847
|
-
if (!
|
|
11848
|
-
if (!
|
|
12047
|
+
const kitFile = target.endsWith("kit.json") ? target : join19(target, "kit.json");
|
|
12048
|
+
if (!existsSync16(kitFile)) throw new UsageError(`${kitFile}: no kit manifest`);
|
|
12049
|
+
if (!existsSync16(against))
|
|
11849
12050
|
throw new UsageError(`${against}: no reference manifest`);
|
|
11850
12051
|
const result = await judgeKit(kitFile, against, strFlag(flags, "out"));
|
|
11851
12052
|
const verdicts = JSON.parse(await readFile15(result.verdictFile, "utf8")).verdicts;
|
|
@@ -11967,7 +12168,7 @@ async function cmdValidate(argv) {
|
|
|
11967
12168
|
const target = positionals[0];
|
|
11968
12169
|
if (!target) throw new UsageError("vos validate <actions.json|take>");
|
|
11969
12170
|
const r = createReporter(flags.json === true);
|
|
11970
|
-
const kitTarget = target.endsWith("kit.json") ? target :
|
|
12171
|
+
const kitTarget = target.endsWith("kit.json") ? target : existsSync16(join19(target, "kit.json")) && !isTakeDir(target) ? join19(target, "kit.json") : null;
|
|
11971
12172
|
if (kitTarget) {
|
|
11972
12173
|
const picture = flags.picture === true;
|
|
11973
12174
|
const verdict = await validateKit(kitTarget, { picture });
|
|
@@ -11999,9 +12200,9 @@ async function cmdValidate(argv) {
|
|
|
11999
12200
|
r.done({ valid: true, target }, `${target}: valid actions file`);
|
|
12000
12201
|
return EXIT_OK;
|
|
12001
12202
|
}
|
|
12002
|
-
if (!isTakeDir(target) &&
|
|
12203
|
+
if (!isTakeDir(target) && existsSync16(join19(target, "config.json"))) {
|
|
12003
12204
|
const parsed = JSON.parse(
|
|
12004
|
-
await readFile15(
|
|
12205
|
+
await readFile15(join19(target, "config.json"), "utf8")
|
|
12005
12206
|
);
|
|
12006
12207
|
const pre = preflightConfig(parsed);
|
|
12007
12208
|
const problems2 = pre.ok ? [] : pre.issues.map((i) => `config.json: ${i}`);
|
|
@@ -12042,7 +12243,7 @@ async function cmdValidate(argv) {
|
|
|
12042
12243
|
const take = await loadTake(target);
|
|
12043
12244
|
const problems = [];
|
|
12044
12245
|
const warnings = [];
|
|
12045
|
-
if (!
|
|
12246
|
+
if (!existsSync16(take.paths.recording)) {
|
|
12046
12247
|
problems.push("missing recording (re-run record)");
|
|
12047
12248
|
} else {
|
|
12048
12249
|
const mismatch = containerMismatch(
|
|
@@ -12283,6 +12484,8 @@ async function run(argv) {
|
|
|
12283
12484
|
return await cmdRecipe(rest);
|
|
12284
12485
|
case "brand":
|
|
12285
12486
|
return await cmdBrand(rest);
|
|
12487
|
+
case "session":
|
|
12488
|
+
return await cmdSession(rest);
|
|
12286
12489
|
case "actions":
|
|
12287
12490
|
return await cmdActions(rest);
|
|
12288
12491
|
default:
|
|
@@ -12316,6 +12519,121 @@ async function run(argv) {
|
|
|
12316
12519
|
return EXIT_ERROR;
|
|
12317
12520
|
}
|
|
12318
12521
|
}
|
|
12522
|
+
async function cmdSession(argv) {
|
|
12523
|
+
const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS5);
|
|
12524
|
+
const [sub, arg] = positionals;
|
|
12525
|
+
const r = createReporter(flags.json === true);
|
|
12526
|
+
const usage = "vos session open <url> --name <app> | check <name> [--url <url>] | list | rm <name>";
|
|
12527
|
+
switch (sub) {
|
|
12528
|
+
case "open": {
|
|
12529
|
+
if (!arg) throw new UsageError(usage);
|
|
12530
|
+
const name = strFlag(flags, "name");
|
|
12531
|
+
const bad = sessionNameVerdict(name);
|
|
12532
|
+
if (bad) throw new UsageError(bad);
|
|
12533
|
+
let url;
|
|
12534
|
+
try {
|
|
12535
|
+
url = new URL(arg);
|
|
12536
|
+
} catch {
|
|
12537
|
+
throw new UsageError(`vos session open wants a URL, got "${arg}"`);
|
|
12538
|
+
}
|
|
12539
|
+
if (!/^https?:$/.test(url.protocol))
|
|
12540
|
+
throw new UsageError("vos session open wants an http(s) URL");
|
|
12541
|
+
await openSession(name, url.href, { log: r.log });
|
|
12542
|
+
const summary2 = await summarizeSession(name);
|
|
12543
|
+
r.done(
|
|
12544
|
+
{
|
|
12545
|
+
name: summary2.name,
|
|
12546
|
+
origins: summary2.origins,
|
|
12547
|
+
newestExpires: summary2.newestExpires,
|
|
12548
|
+
sessionOnly: summary2.sessionOnly
|
|
12549
|
+
},
|
|
12550
|
+
`${summaryLine(summary2)}
|
|
12551
|
+
Record on it: vos record --actions actions.json --out take --session ${name} --strict`
|
|
12552
|
+
);
|
|
12553
|
+
return summary2.origins.length ? EXIT_OK : EXIT_ERROR;
|
|
12554
|
+
}
|
|
12555
|
+
case "check": {
|
|
12556
|
+
if (!arg) throw new UsageError(usage);
|
|
12557
|
+
const bad = sessionNameVerdict(arg);
|
|
12558
|
+
if (bad) throw new UsageError(bad);
|
|
12559
|
+
if (!existsSync16(sessionDir(arg)))
|
|
12560
|
+
throw new UsageError(`no session "${arg}" (vos session list)`);
|
|
12561
|
+
const summary2 = await summarizeSession(arg);
|
|
12562
|
+
const url = strFlag(flags, "url");
|
|
12563
|
+
if (!url) {
|
|
12564
|
+
r.done(
|
|
12565
|
+
{
|
|
12566
|
+
name: arg,
|
|
12567
|
+
origins: summary2.origins,
|
|
12568
|
+
newestExpires: summary2.newestExpires,
|
|
12569
|
+
sessionOnly: summary2.sessionOnly
|
|
12570
|
+
},
|
|
12571
|
+
summaryLine(summary2)
|
|
12572
|
+
);
|
|
12573
|
+
return summary2.origins.length ? EXIT_OK : EXIT_WALL;
|
|
12574
|
+
}
|
|
12575
|
+
const verdict = await withSession(arg, async (ctx) => {
|
|
12576
|
+
const page = await ctx.newPage();
|
|
12577
|
+
const response = await page.goto(url, { waitUntil: "networkidle", timeout: 3e4 }).catch(() => null);
|
|
12578
|
+
const seen = await page.evaluate(WALL_PROBE).catch(() => null);
|
|
12579
|
+
return wallVerdict({
|
|
12580
|
+
askedUrl: url,
|
|
12581
|
+
landedUrl: page.url(),
|
|
12582
|
+
...response ? { status: response.status() } : {},
|
|
12583
|
+
passwordFields: seen?.passwordFields ?? 0,
|
|
12584
|
+
newPasswordField: seen?.newPasswordField ?? false,
|
|
12585
|
+
oneTimeCodeField: seen?.oneTimeCodeField ?? false
|
|
12586
|
+
});
|
|
12587
|
+
});
|
|
12588
|
+
if (verdict) {
|
|
12589
|
+
r.done(
|
|
12590
|
+
{
|
|
12591
|
+
name: arg,
|
|
12592
|
+
ok: false,
|
|
12593
|
+
wall: verdict,
|
|
12594
|
+
origins: summary2.origins,
|
|
12595
|
+
newestExpires: summary2.newestExpires
|
|
12596
|
+
},
|
|
12597
|
+
`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}`
|
|
12598
|
+
);
|
|
12599
|
+
return EXIT_WALL;
|
|
12600
|
+
}
|
|
12601
|
+
r.done(
|
|
12602
|
+
{
|
|
12603
|
+
name: arg,
|
|
12604
|
+
ok: true,
|
|
12605
|
+
origins: summary2.origins,
|
|
12606
|
+
newestExpires: summary2.newestExpires
|
|
12607
|
+
},
|
|
12608
|
+
`session "${arg}" opens ${url} signed in. ${summaryLine(summary2)}`
|
|
12609
|
+
);
|
|
12610
|
+
return EXIT_OK;
|
|
12611
|
+
}
|
|
12612
|
+
case "list": {
|
|
12613
|
+
const rows = listSessions();
|
|
12614
|
+
r.done(
|
|
12615
|
+
{ sessions: rows },
|
|
12616
|
+
rows.length ? rows.map(
|
|
12617
|
+
(x) => ` ${x.name} ${x.ageDays} day${x.ageDays === 1 ? "" : "s"} old`
|
|
12618
|
+
).join("\n") : "no sessions. Make one: vos session open <url> --name <app>"
|
|
12619
|
+
);
|
|
12620
|
+
return EXIT_OK;
|
|
12621
|
+
}
|
|
12622
|
+
case "rm": {
|
|
12623
|
+
if (!arg) throw new UsageError(usage);
|
|
12624
|
+
const bad = sessionNameVerdict(arg);
|
|
12625
|
+
if (bad) throw new UsageError(bad);
|
|
12626
|
+
const gone = removeSession(arg);
|
|
12627
|
+
r.done(
|
|
12628
|
+
{ name: arg, removed: gone },
|
|
12629
|
+
gone ? `removed session "${arg}"` : `no session "${arg}"`
|
|
12630
|
+
);
|
|
12631
|
+
return gone ? EXIT_OK : EXIT_ERROR;
|
|
12632
|
+
}
|
|
12633
|
+
default:
|
|
12634
|
+
throw new UsageError(usage);
|
|
12635
|
+
}
|
|
12636
|
+
}
|
|
12319
12637
|
|
|
12320
12638
|
export {
|
|
12321
12639
|
validateActions,
|
|
@@ -12348,4 +12666,4 @@ export {
|
|
|
12348
12666
|
verbHelp,
|
|
12349
12667
|
run
|
|
12350
12668
|
};
|
|
12351
|
-
//# sourceMappingURL=chunk-
|
|
12669
|
+
//# sourceMappingURL=chunk-4HW2XR5K.js.map
|