@vosjs/cli 0.16.0 → 0.17.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/dist/{chunk-PKHORUJ2.js → chunk-Q5NF5EN2.js} +390 -198
- package/dist/chunk-Q5NF5EN2.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{run-5TIKH2IB.js → run-V4JIEWVF.js} +2 -2
- package/package.json +6 -6
- package/schema/doc.schema.json +10 -0
- package/dist/chunk-PKHORUJ2.js.map +0 -1
- /package/dist/{run-5TIKH2IB.js.map → run-V4JIEWVF.js.map} +0 -0
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
} from "./chunk-NHKHTIDR.js";
|
|
7
7
|
|
|
8
8
|
// src/plugin/run.ts
|
|
9
|
-
import { mkdir as
|
|
10
|
-
import { existsSync as
|
|
11
|
-
import { join as
|
|
9
|
+
import { mkdir as mkdir9, readFile as readFile13, rm as rm4 } from "fs/promises";
|
|
10
|
+
import { existsSync as existsSync13 } from "fs";
|
|
11
|
+
import { join as join16, resolve as resolve8 } from "path";
|
|
12
12
|
import { totalDuration as totalDuration2 } from "@vosjs/timeline";
|
|
13
13
|
import { migrateHostedDoc as migrateHostedDoc4, ratedSegments as ratedSegments6 } from "@vosjs/studio-core";
|
|
14
14
|
|
|
@@ -1279,6 +1279,11 @@ var MIME = {
|
|
|
1279
1279
|
".json": "application/json",
|
|
1280
1280
|
".jpg": "image/jpeg",
|
|
1281
1281
|
".png": "image/png",
|
|
1282
|
+
// A brand mark or an image overlay: an <img> refuses an SVG served as an
|
|
1283
|
+
// octet stream, so the kinds a doc can key are named.
|
|
1284
|
+
".svg": "image/svg+xml",
|
|
1285
|
+
".webp": "image/webp",
|
|
1286
|
+
".gif": "image/gif",
|
|
1282
1287
|
".webm": "video/webm",
|
|
1283
1288
|
".mp4": "video/mp4",
|
|
1284
1289
|
// Take-dir audio (doc.audio keys like "/music.mp3") — decode goes through
|
|
@@ -1347,10 +1352,10 @@ function startTakeServer(rootDir, pages) {
|
|
|
1347
1352
|
res.writeHead(404).end();
|
|
1348
1353
|
});
|
|
1349
1354
|
return new Promise(
|
|
1350
|
-
(
|
|
1355
|
+
(resolve9) => server.listen(0, () => {
|
|
1351
1356
|
const addr = server.address();
|
|
1352
1357
|
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
1353
|
-
|
|
1358
|
+
resolve9({ base: `http://localhost:${port}`, close: () => server.close() });
|
|
1354
1359
|
})
|
|
1355
1360
|
);
|
|
1356
1361
|
}
|
|
@@ -2613,12 +2618,12 @@ async function writeIndexJson(result) {
|
|
|
2613
2618
|
}
|
|
2614
2619
|
|
|
2615
2620
|
// src/plugin/deliver.ts
|
|
2616
|
-
import { mkdir as
|
|
2621
|
+
import { mkdir as mkdir5, mkdtemp, rename as rename4, rm as rm3, stat, writeFile as writeFile7 } from "fs/promises";
|
|
2617
2622
|
import { tmpdir } from "os";
|
|
2618
|
-
import { join as
|
|
2623
|
+
import { join as join6, relative, resolve as resolve4 } from "path";
|
|
2619
2624
|
import { totalDuration } from "@vosjs/timeline";
|
|
2620
|
-
import { existsSync as
|
|
2621
|
-
import { readFile as
|
|
2625
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2626
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
2622
2627
|
import { parseFrontmatter } from "@vosjs/shared/frontmatter";
|
|
2623
2628
|
import {
|
|
2624
2629
|
CHANNEL_SPECS_VERIFIED,
|
|
@@ -4096,7 +4101,7 @@ function stageSplitCover(input) {
|
|
|
4096
4101
|
const column = portrait || square ? 0.84 : 0.36;
|
|
4097
4102
|
const left = portrait || square ? 0.08 : 0.07;
|
|
4098
4103
|
const size = portrait ? 60 : square ? 68 : 84;
|
|
4099
|
-
const word = (id, text, preset, family, px, y, color, extra, role) => {
|
|
4104
|
+
const word = (id, text, preset, family, px, y, color, extra, role, x0 = left) => {
|
|
4100
4105
|
if (!text) return;
|
|
4101
4106
|
const lines = text.split("\n");
|
|
4102
4107
|
const longest = Math.max(...lines.map((l) => l.length));
|
|
@@ -4117,7 +4122,7 @@ function stageSplitCover(input) {
|
|
|
4117
4122
|
align: "left",
|
|
4118
4123
|
maxWidth: column,
|
|
4119
4124
|
lineHeight: lh,
|
|
4120
|
-
transform: { x:
|
|
4125
|
+
transform: { x: x0 + widest / 2, y, scale: 1, rotation: 0 },
|
|
4121
4126
|
// Words on the ground carry no footage shadow: a drop shadow under a
|
|
4122
4127
|
// headline on a plate is the old-web tell.
|
|
4123
4128
|
shadow: 0,
|
|
@@ -4128,7 +4133,7 @@ function stageSplitCover(input) {
|
|
|
4128
4133
|
...extra
|
|
4129
4134
|
});
|
|
4130
4135
|
boxes.push({
|
|
4131
|
-
x:
|
|
4136
|
+
x: x0,
|
|
4132
4137
|
y: y - h / 2,
|
|
4133
4138
|
w: widest,
|
|
4134
4139
|
h,
|
|
@@ -4137,6 +4142,28 @@ function stageSplitCover(input) {
|
|
|
4137
4142
|
label: text.length > 24 ? `${text.slice(0, 24)}\u2026` : text
|
|
4138
4143
|
});
|
|
4139
4144
|
};
|
|
4145
|
+
const markKey = str(v.logoKey, "");
|
|
4146
|
+
const markAspect = typeof v.logoAspect === "number" && v.logoAspect > 0 ? v.logoAspect : 1;
|
|
4147
|
+
const wideMark = !!markKey && markAspect > 2.2;
|
|
4148
|
+
const lockup = (px, y) => {
|
|
4149
|
+
if (!markKey) return left;
|
|
4150
|
+
const hPx = wideMark ? px * 1.6 : px * 1.3;
|
|
4151
|
+
const w = Math.min(column, hPx * markAspect / designW);
|
|
4152
|
+
clips.push({
|
|
4153
|
+
id: "stage-mark",
|
|
4154
|
+
kind: "image",
|
|
4155
|
+
key: markKey,
|
|
4156
|
+
width: +w.toFixed(4),
|
|
4157
|
+
radius: 0,
|
|
4158
|
+
shadow: "none",
|
|
4159
|
+
transform: { x: left + w / 2, y, scale: 1, rotation: 0 },
|
|
4160
|
+
start: 0,
|
|
4161
|
+
duration: +input.outputSeconds.toFixed(3),
|
|
4162
|
+
enter: "none",
|
|
4163
|
+
exit: "none"
|
|
4164
|
+
});
|
|
4165
|
+
return left + w + px * 0.5 / designW;
|
|
4166
|
+
};
|
|
4140
4167
|
const kickerY = portrait ? 0.1 : square ? 0.12 : 0.24;
|
|
4141
4168
|
const titleY = portrait ? 0.24 : square ? 0.28 : 0.46;
|
|
4142
4169
|
const brandY = portrait ? 0.94 : square ? 0.94 : 0.86;
|
|
@@ -4162,17 +4189,20 @@ function stageSplitCover(input) {
|
|
|
4162
4189
|
{ weight: 600, letterSpacing: -Math.round(size * 0.02) },
|
|
4163
4190
|
"headline"
|
|
4164
4191
|
);
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4192
|
+
const brandX = lockup(25, brandY);
|
|
4193
|
+
if (!wideMark)
|
|
4194
|
+
word(
|
|
4195
|
+
"stage-brand",
|
|
4196
|
+
brand,
|
|
4197
|
+
"label",
|
|
4198
|
+
body,
|
|
4199
|
+
25,
|
|
4200
|
+
brandY,
|
|
4201
|
+
ink,
|
|
4202
|
+
{ weight: 700, letterSpacing: 1 },
|
|
4203
|
+
"body",
|
|
4204
|
+
brandX
|
|
4205
|
+
);
|
|
4176
4206
|
set.push(`overlays=${JSON.stringify(clips)}`);
|
|
4177
4207
|
return { set, text: boxes, shot };
|
|
4178
4208
|
}
|
|
@@ -4252,7 +4282,32 @@ function stageTile(input) {
|
|
|
4252
4282
|
exit: "none"
|
|
4253
4283
|
};
|
|
4254
4284
|
if (wordless) {
|
|
4255
|
-
|
|
4285
|
+
const markKey = str(v.logoKey, "");
|
|
4286
|
+
const markAspect = typeof v.logoAspect === "number" && v.logoAspect > 0 ? v.logoAspect : 1;
|
|
4287
|
+
if (markKey) {
|
|
4288
|
+
const hFrac = top * 0.5;
|
|
4289
|
+
const w = Math.min(
|
|
4290
|
+
0.8,
|
|
4291
|
+
hFrac * input.size.h * markAspect / input.size.w
|
|
4292
|
+
);
|
|
4293
|
+
set.push(
|
|
4294
|
+
`overlays=${JSON.stringify([
|
|
4295
|
+
{
|
|
4296
|
+
id: "stage-mark",
|
|
4297
|
+
kind: "image",
|
|
4298
|
+
key: markKey,
|
|
4299
|
+
width: +w.toFixed(4),
|
|
4300
|
+
radius: 0,
|
|
4301
|
+
shadow: "none",
|
|
4302
|
+
transform: { x: left + w / 2, y: top / 2, scale: 1, rotation: 0 },
|
|
4303
|
+
start: 0,
|
|
4304
|
+
duration: +input.outputSeconds.toFixed(3),
|
|
4305
|
+
enter: "none",
|
|
4306
|
+
exit: "none"
|
|
4307
|
+
}
|
|
4308
|
+
])}`
|
|
4309
|
+
);
|
|
4310
|
+
} else set.push("overlays=[]");
|
|
4256
4311
|
return { set, text: [], shot };
|
|
4257
4312
|
}
|
|
4258
4313
|
set.push(`overlays=${JSON.stringify([clip])}`);
|
|
@@ -4270,6 +4325,116 @@ function stageTile(input) {
|
|
|
4270
4325
|
return { set, text, shot };
|
|
4271
4326
|
}
|
|
4272
4327
|
|
|
4328
|
+
// src/plugin/markAsset.ts
|
|
4329
|
+
import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile6 } from "fs/promises";
|
|
4330
|
+
import { existsSync as existsSync5 } from "fs";
|
|
4331
|
+
import { isAbsolute, join as join5, resolve as resolve3 } from "path";
|
|
4332
|
+
function markExtension(url) {
|
|
4333
|
+
const m = /\.(svg|png|webp|jpe?g)(?:[?#]|$)/i.exec(url);
|
|
4334
|
+
if (!m) return null;
|
|
4335
|
+
const e = m[1].toLowerCase();
|
|
4336
|
+
return e === "jpeg" ? "jpg" : e;
|
|
4337
|
+
}
|
|
4338
|
+
function imageAspect(bytes, ext) {
|
|
4339
|
+
if (ext === "svg") {
|
|
4340
|
+
const head = new TextDecoder().decode(bytes.subarray(0, 4096));
|
|
4341
|
+
const vb = /viewBox\s*=\s*["']\s*[-\d.]+[\s,]+[-\d.]+[\s,]+([\d.]+)[\s,]+([\d.]+)/i.exec(
|
|
4342
|
+
head
|
|
4343
|
+
);
|
|
4344
|
+
if (vb) return ratio(+vb[1], +vb[2]);
|
|
4345
|
+
const w = /<svg[^>]*\swidth\s*=\s*["']([\d.]+)/i.exec(head);
|
|
4346
|
+
const h = /<svg[^>]*\sheight\s*=\s*["']([\d.]+)/i.exec(head);
|
|
4347
|
+
if (w && h) return ratio(+w[1], +h[1]);
|
|
4348
|
+
return null;
|
|
4349
|
+
}
|
|
4350
|
+
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
4351
|
+
if (ext === "png") {
|
|
4352
|
+
if (bytes.length < 24 || bytes[0] !== 137 || bytes[1] !== 80) return null;
|
|
4353
|
+
return ratio(dv.getUint32(16), dv.getUint32(20));
|
|
4354
|
+
}
|
|
4355
|
+
if (ext === "webp") {
|
|
4356
|
+
if (bytes.length < 30) return null;
|
|
4357
|
+
const tag = String.fromCharCode(...bytes.subarray(12, 16));
|
|
4358
|
+
if (tag === "VP8X")
|
|
4359
|
+
return ratio(
|
|
4360
|
+
1 + (dv.getUint32(24, true) & 16777215),
|
|
4361
|
+
1 + (dv.getUint32(27, true) & 16777215)
|
|
4362
|
+
);
|
|
4363
|
+
if (tag === "VP8L") {
|
|
4364
|
+
const b = dv.getUint32(21, true);
|
|
4365
|
+
return ratio(1 + (b & 16383), 1 + (b >> 14 & 16383));
|
|
4366
|
+
}
|
|
4367
|
+
if (tag === "VP8 ")
|
|
4368
|
+
return ratio(
|
|
4369
|
+
dv.getUint16(26, true) & 16383,
|
|
4370
|
+
dv.getUint16(28, true) & 16383
|
|
4371
|
+
);
|
|
4372
|
+
return null;
|
|
4373
|
+
}
|
|
4374
|
+
if (ext === "jpg") {
|
|
4375
|
+
let i = 2;
|
|
4376
|
+
while (i + 9 < bytes.length) {
|
|
4377
|
+
if (bytes[i] !== 255) return null;
|
|
4378
|
+
const marker = bytes[i + 1];
|
|
4379
|
+
const len = dv.getUint16(i + 2);
|
|
4380
|
+
if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
|
|
4381
|
+
return ratio(dv.getUint16(i + 7), dv.getUint16(i + 5));
|
|
4382
|
+
}
|
|
4383
|
+
i += 2 + len;
|
|
4384
|
+
}
|
|
4385
|
+
return null;
|
|
4386
|
+
}
|
|
4387
|
+
return null;
|
|
4388
|
+
}
|
|
4389
|
+
function ratio(w, h) {
|
|
4390
|
+
return w > 0 && h > 0 ? w / h : null;
|
|
4391
|
+
}
|
|
4392
|
+
async function fetchBrandMarks(takeDir, roles) {
|
|
4393
|
+
const out = { light: null, dark: null, notes: [] };
|
|
4394
|
+
if (!roles) return out;
|
|
4395
|
+
const one = async (role, name) => {
|
|
4396
|
+
const src = (roles[role] ?? "").trim();
|
|
4397
|
+
if (!src) return null;
|
|
4398
|
+
const ext = markExtension(src);
|
|
4399
|
+
if (!ext) {
|
|
4400
|
+
out.notes.push(
|
|
4401
|
+
`${role}: ${src} is not an svg, png, webp or jpg; the wordmark stands in`
|
|
4402
|
+
);
|
|
4403
|
+
return null;
|
|
4404
|
+
}
|
|
4405
|
+
let bytes;
|
|
4406
|
+
try {
|
|
4407
|
+
if (/^https?:/i.test(src)) {
|
|
4408
|
+
const res = await fetch(src);
|
|
4409
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
4410
|
+
bytes = new Uint8Array(await res.arrayBuffer());
|
|
4411
|
+
} else {
|
|
4412
|
+
const file = isAbsolute(src) ? src : resolve3(takeDir, src);
|
|
4413
|
+
if (!existsSync5(file)) throw new Error("no such file");
|
|
4414
|
+
bytes = new Uint8Array(await readFile5(file));
|
|
4415
|
+
}
|
|
4416
|
+
} catch (e) {
|
|
4417
|
+
out.notes.push(
|
|
4418
|
+
`${role}: ${src} could not be read (${e instanceof Error ? e.message : String(e)}); the wordmark stands in`
|
|
4419
|
+
);
|
|
4420
|
+
return null;
|
|
4421
|
+
}
|
|
4422
|
+
const aspect = imageAspect(bytes, ext);
|
|
4423
|
+
if (!aspect) {
|
|
4424
|
+
out.notes.push(
|
|
4425
|
+
`${role}: ${src} has no readable size; the wordmark stands in`
|
|
4426
|
+
);
|
|
4427
|
+
return null;
|
|
4428
|
+
}
|
|
4429
|
+
await mkdir4(join5(takeDir, "brand"), { recursive: true });
|
|
4430
|
+
await writeFile6(join5(takeDir, "brand", `${name}.${ext}`), bytes);
|
|
4431
|
+
return { key: `/brand/${name}.${ext}`, aspect };
|
|
4432
|
+
};
|
|
4433
|
+
out.light = await one("logoUrl", "mark");
|
|
4434
|
+
out.dark = await one("logoOnDarkUrl", "mark-on-dark");
|
|
4435
|
+
return out;
|
|
4436
|
+
}
|
|
4437
|
+
|
|
4273
4438
|
// src/plugin/motionPlan.ts
|
|
4274
4439
|
import { ratedSegments as ratedSegments4, spanOutputExtent as spanOutputExtent4 } from "@vosjs/studio-core";
|
|
4275
4440
|
var SOUND_DESTINATIONS = /* @__PURE__ */ new Set([
|
|
@@ -4325,10 +4490,13 @@ function planMotion(input) {
|
|
|
4325
4490
|
if (headline) card.headline = headline;
|
|
4326
4491
|
if (sub && sub !== headline) card.sub = sub;
|
|
4327
4492
|
if (brand) card.wordmark = brand;
|
|
4493
|
+
if (input.mark) card.mark = input.mark;
|
|
4328
4494
|
set.push(`endCard=${JSON.stringify(card)}`);
|
|
4329
4495
|
notes.push("end card");
|
|
4330
4496
|
} else {
|
|
4331
|
-
skipped.push(
|
|
4497
|
+
skipped.push(
|
|
4498
|
+
`${d.id}: no end card (no headline or wordmark in LAUNCH.md, BRAND.md or the flags)`
|
|
4499
|
+
);
|
|
4332
4500
|
}
|
|
4333
4501
|
}
|
|
4334
4502
|
if (!loop && d.text !== "none" && input.captions.length && !off(launch.captions)) {
|
|
@@ -4336,7 +4504,9 @@ function planMotion(input) {
|
|
|
4336
4504
|
const steps = doc.source.meta.steps ?? [];
|
|
4337
4505
|
const clips = [];
|
|
4338
4506
|
for (const c of input.captions) {
|
|
4339
|
-
const step = steps.find(
|
|
4507
|
+
const step = steps.find(
|
|
4508
|
+
(s) => c.id !== void 0 && s.id === c.id || s.step === c.step
|
|
4509
|
+
);
|
|
4340
4510
|
if (!step || step.skipped) continue;
|
|
4341
4511
|
const t = stepOutputTime(rated, step, 0.2);
|
|
4342
4512
|
if (t === null || t < range[0] || t > range[1] - 1) continue;
|
|
@@ -4384,7 +4554,9 @@ function planMotion(input) {
|
|
|
4384
4554
|
});
|
|
4385
4555
|
notes.push(`bed ${track.slug}`);
|
|
4386
4556
|
} else if (launch.music && !off(launch.music)) {
|
|
4387
|
-
skipped.push(
|
|
4557
|
+
skipped.push(
|
|
4558
|
+
`${d.id}: music "${launch.music}" is not a catalog track or mood${catalog ? "" : " (the catalog could not be read)"}`
|
|
4559
|
+
);
|
|
4388
4560
|
}
|
|
4389
4561
|
const click = catalog?.sfx.find((s) => s.slug === "sfx-click");
|
|
4390
4562
|
if (click && !doc.source.micKey && !off(launch.clicks)) {
|
|
@@ -4444,19 +4616,19 @@ function resolveChannels(raw) {
|
|
|
4444
4616
|
return out;
|
|
4445
4617
|
}
|
|
4446
4618
|
async function readBrandBesideTake(dir, explicit) {
|
|
4447
|
-
const candidates = explicit ? [explicit] : [
|
|
4619
|
+
const candidates = explicit ? [explicit] : [join6(dir, "BRAND.md"), join6(dir, "..", "BRAND.md")];
|
|
4448
4620
|
for (const file of candidates) {
|
|
4449
|
-
if (!
|
|
4450
|
-
const roles = parseFrontmatter(await
|
|
4621
|
+
if (!existsSync6(file)) continue;
|
|
4622
|
+
const roles = parseFrontmatter(await readFile6(file, "utf8"));
|
|
4451
4623
|
return { file, roles };
|
|
4452
4624
|
}
|
|
4453
4625
|
return null;
|
|
4454
4626
|
}
|
|
4455
4627
|
async function readLaunchBesideTake(dir, explicit) {
|
|
4456
|
-
const candidates = explicit ? [explicit] : [
|
|
4628
|
+
const candidates = explicit ? [explicit] : [join6(dir, "LAUNCH.md"), join6(dir, "..", "LAUNCH.md")];
|
|
4457
4629
|
for (const file of candidates) {
|
|
4458
|
-
if (!
|
|
4459
|
-
return { file, roles: parseFrontmatter(await
|
|
4630
|
+
if (!existsSync6(file)) continue;
|
|
4631
|
+
return { file, roles: parseFrontmatter(await readFile6(file, "utf8")) };
|
|
4460
4632
|
}
|
|
4461
4633
|
return null;
|
|
4462
4634
|
}
|
|
@@ -4559,7 +4731,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
|
|
|
4559
4731
|
const meta = doc.source.meta;
|
|
4560
4732
|
const vw = meta.captureWidth ?? meta.width;
|
|
4561
4733
|
const vh = meta.captureHeight ?? meta.height;
|
|
4562
|
-
const probeDir = await mkdtemp(
|
|
4734
|
+
const probeDir = await mkdtemp(join6(tmpdir(), "vos-moments-"));
|
|
4563
4735
|
try {
|
|
4564
4736
|
const probe = await framesTake(browser, dir, {
|
|
4565
4737
|
times: candidates.map((c) => c.time),
|
|
@@ -4573,7 +4745,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
|
|
|
4573
4745
|
});
|
|
4574
4746
|
const byTime = /* @__PURE__ */ new Map();
|
|
4575
4747
|
for (const frame of probe.frames) {
|
|
4576
|
-
const img = decodePng(new Uint8Array(await
|
|
4748
|
+
const img = decodePng(new Uint8Array(await readFile6(frame.file)));
|
|
4577
4749
|
if (!img) continue;
|
|
4578
4750
|
const whole = { x: 0, y: 0, w: img.w, h: img.h };
|
|
4579
4751
|
byTime.set(+frame.time.toFixed(3), {
|
|
@@ -4644,10 +4816,16 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4644
4816
|
const take = await loadTake(dir);
|
|
4645
4817
|
if (!take.doc) throw new Error(`${dir} has no doc.json \u2014 run plan first`);
|
|
4646
4818
|
const doc = take.doc;
|
|
4819
|
+
const marks = await fetchBrandMarks(dir, opts.brandRoles);
|
|
4820
|
+
for (const n of marks.notes) opts.onPhase?.(`note: ${n}`);
|
|
4821
|
+
const darkGround = opts.look?.kind === "dark";
|
|
4822
|
+
const mark = darkGround ? marks.dark : marks.light;
|
|
4823
|
+
if (mark)
|
|
4824
|
+
opts.onPhase?.(`brand mark: ${mark.key} (${mark.aspect.toFixed(2)}:1)`);
|
|
4647
4825
|
const duration = totalDuration(ratedSegments5(doc));
|
|
4648
4826
|
const videoSeconds = opts.range ? Math.min(opts.range[1], duration) - Math.min(opts.range[0], duration) : duration;
|
|
4649
|
-
const outDir =
|
|
4650
|
-
await
|
|
4827
|
+
const outDir = resolve4(opts.outDir ?? join6(dir, "kit"));
|
|
4828
|
+
await mkdir5(outDir, { recursive: true });
|
|
4651
4829
|
const destinations = opts.channels.flatMap((c) => destinationsForChannel(c));
|
|
4652
4830
|
const assets = [];
|
|
4653
4831
|
const skipped = [];
|
|
@@ -4689,7 +4867,8 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4689
4867
|
launch: opts.launchRoles ?? {},
|
|
4690
4868
|
captions: opts.captions ?? [],
|
|
4691
4869
|
catalog: opts.catalog ?? null,
|
|
4692
|
-
ink: endCardInk(opts.look, opts.brandRoles)
|
|
4870
|
+
ink: endCardInk(opts.look, opts.brandRoles),
|
|
4871
|
+
mark
|
|
4693
4872
|
});
|
|
4694
4873
|
set.push(...plan.set);
|
|
4695
4874
|
if (plan.notes.length) opts.onPhase?.(`${d.id}: ${plan.notes.join(", ")}`);
|
|
@@ -4710,10 +4889,14 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4710
4889
|
const meta = doc.source.meta;
|
|
4711
4890
|
const heroTime = opts.shotTime ?? (stillTimes.length ? stillTimes[0] : duration / 2);
|
|
4712
4891
|
const fill = posterValues(opts.brandRoles, opts.words ?? {});
|
|
4892
|
+
if (mark) {
|
|
4893
|
+
fill.values.logoKey = mark.key;
|
|
4894
|
+
fill.values.logoAspect = mark.aspect;
|
|
4895
|
+
}
|
|
4713
4896
|
opts.onPhase?.(
|
|
4714
4897
|
`poster shot (full bleed at ${heroTime.toFixed(2)}s), baked as an object`
|
|
4715
4898
|
);
|
|
4716
|
-
const serveDir = await mkdtemp(
|
|
4899
|
+
const serveDir = await mkdtemp(join6(tmpdir(), "vos-poster-"));
|
|
4717
4900
|
try {
|
|
4718
4901
|
const shot = await framesTake(browser, dir, {
|
|
4719
4902
|
times: [heroTime],
|
|
@@ -4735,7 +4918,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4735
4918
|
]
|
|
4736
4919
|
}
|
|
4737
4920
|
});
|
|
4738
|
-
const raw = decodePng(new Uint8Array(await
|
|
4921
|
+
const raw = decodePng(new Uint8Array(await readFile6(shot.frames[0].file)));
|
|
4739
4922
|
if (!raw) throw new Error("the poster shot could not be decoded");
|
|
4740
4923
|
const PAD = 0.06;
|
|
4741
4924
|
const baked = bakeShot(raw, {
|
|
@@ -4743,7 +4926,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4743
4926
|
hairline: fill.lightGround ? 0.14 : 0,
|
|
4744
4927
|
shadow: fill.lightGround ? 0.28 : 0.4
|
|
4745
4928
|
});
|
|
4746
|
-
await
|
|
4929
|
+
await writeFile7(join6(serveDir, "shot.png"), encodePng(baked));
|
|
4747
4930
|
const shotAspect = raw.w / raw.h;
|
|
4748
4931
|
for (const { d, plan } of cardPlans) {
|
|
4749
4932
|
if (plan.stage) {
|
|
@@ -4759,7 +4942,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4759
4942
|
opts.onPhase?.(
|
|
4760
4943
|
`${d.channel} ${d.asset} (${specWords(d)}) from ${plan.from}`
|
|
4761
4944
|
);
|
|
4762
|
-
const shotDir = await mkdtemp(
|
|
4945
|
+
const shotDir = await mkdtemp(join6(tmpdir(), "vos-stage-"));
|
|
4763
4946
|
try {
|
|
4764
4947
|
const captured = await framesTake(browser, dir, {
|
|
4765
4948
|
times: [heroTime],
|
|
@@ -4771,7 +4954,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4771
4954
|
set: [...staged.set, ...opts.overrides?.set ?? []]
|
|
4772
4955
|
}
|
|
4773
4956
|
});
|
|
4774
|
-
const to2 =
|
|
4957
|
+
const to2 = join6(outDir, `${d.id}.png`);
|
|
4775
4958
|
await rename4(captured.frames[0].file, to2);
|
|
4776
4959
|
const bytes2 = (await stat(to2)).size;
|
|
4777
4960
|
if (d.maxBytes !== void 0 && bytes2 > d.maxBytes) {
|
|
@@ -4843,8 +5026,8 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4843
5026
|
[{ name: `${d.id}.png`, width: d.px.w, height: d.px.h }],
|
|
4844
5027
|
time
|
|
4845
5028
|
);
|
|
4846
|
-
const from =
|
|
4847
|
-
const to =
|
|
5029
|
+
const from = join6(serveDir, `${d.id}.png`);
|
|
5030
|
+
const to = join6(outDir, `${d.id}.png`);
|
|
4848
5031
|
await rename4(from, to);
|
|
4849
5032
|
const bytes = (await stat(to)).size;
|
|
4850
5033
|
if (d.maxBytes !== void 0 && bytes > d.maxBytes) {
|
|
@@ -4905,7 +5088,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4905
5088
|
}
|
|
4906
5089
|
}
|
|
4907
5090
|
opts.onPhase?.(`${label} (${specWords(d)})`);
|
|
4908
|
-
const outFile =
|
|
5091
|
+
const outFile = join6(outDir, `${d.id}.${d.format}`);
|
|
4909
5092
|
const bitrate = d.maxBytes !== void 0 ? Math.min(
|
|
4910
5093
|
1e7,
|
|
4911
5094
|
Math.floor(d.maxBytes * 8 / seconds * 0.85)
|
|
@@ -4952,7 +5135,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4952
5135
|
for (let i = 0; i < captured.frames.length; i++) {
|
|
4953
5136
|
const frame = captured.frames[i];
|
|
4954
5137
|
const name = captured.frames.length > 1 ? `${d.id}-${i + 1}.png` : `${d.id}.png`;
|
|
4955
|
-
const to =
|
|
5138
|
+
const to = join6(outDir, name);
|
|
4956
5139
|
await rename4(frame.file, to);
|
|
4957
5140
|
const bytes = (await stat(to)).size;
|
|
4958
5141
|
if (d.maxBytes !== void 0 && bytes > d.maxBytes) {
|
|
@@ -4990,8 +5173,8 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4990
5173
|
skipped,
|
|
4991
5174
|
assets
|
|
4992
5175
|
};
|
|
4993
|
-
const kitFile =
|
|
4994
|
-
await
|
|
5176
|
+
const kitFile = join6(outDir, "kit.json");
|
|
5177
|
+
await writeFile7(kitFile, JSON.stringify(kit, null, 2));
|
|
4995
5178
|
return { kit, kitFile, outDir };
|
|
4996
5179
|
}
|
|
4997
5180
|
|
|
@@ -5008,9 +5191,9 @@ async function fetchMusicCatalog(origin) {
|
|
|
5008
5191
|
}
|
|
5009
5192
|
|
|
5010
5193
|
// src/plugin/judge.ts
|
|
5011
|
-
import { existsSync as
|
|
5012
|
-
import { mkdir as
|
|
5013
|
-
import { dirname, isAbsolute, join as
|
|
5194
|
+
import { existsSync as existsSync7 } from "fs";
|
|
5195
|
+
import { mkdir as mkdir6, readFile as readFile7, writeFile as writeFile8 } from "fs/promises";
|
|
5196
|
+
import { dirname, isAbsolute as isAbsolute2, join as join7, resolve as resolve5 } from "path";
|
|
5014
5197
|
import { DESTINATIONS as DESTINATIONS2 } from "@vosjs/studio-core";
|
|
5015
5198
|
var RUBRIC = [
|
|
5016
5199
|
"A plate, and room on it: the subject sits at 74 to 89% of the width with headroom; nobody bleeds a window on all four sides.",
|
|
@@ -5114,26 +5297,26 @@ function composeSheet(left, right, height = 540, gutter = 32) {
|
|
|
5114
5297
|
return { w, h, data: out };
|
|
5115
5298
|
}
|
|
5116
5299
|
async function judgeKit(kitPath, manifestPath, outDir) {
|
|
5117
|
-
const kit = JSON.parse(await
|
|
5118
|
-
const manifest = JSON.parse(await
|
|
5119
|
-
const manifestDir = dirname(
|
|
5120
|
-
const kitDir = dirname(
|
|
5121
|
-
const out =
|
|
5122
|
-
await
|
|
5300
|
+
const kit = JSON.parse(await readFile7(kitPath, "utf8"));
|
|
5301
|
+
const manifest = JSON.parse(await readFile7(manifestPath, "utf8"));
|
|
5302
|
+
const manifestDir = dirname(resolve5(manifestPath));
|
|
5303
|
+
const kitDir = dirname(resolve5(kitPath));
|
|
5304
|
+
const out = resolve5(outDir ?? join7(kitDir, "judge"));
|
|
5305
|
+
await mkdir6(out, { recursive: true });
|
|
5123
5306
|
const sheets = [];
|
|
5124
5307
|
const skipped = [];
|
|
5125
5308
|
const refCache = /* @__PURE__ */ new Map();
|
|
5126
5309
|
const loadRef = async (file) => {
|
|
5127
5310
|
if (refCache.has(file)) return refCache.get(file) ?? null;
|
|
5128
|
-
const p =
|
|
5129
|
-
const img =
|
|
5311
|
+
const p = join7(manifestDir, file);
|
|
5312
|
+
const img = existsSync7(p) ? decodePng(new Uint8Array(await readFile7(p))) : null;
|
|
5130
5313
|
refCache.set(file, img);
|
|
5131
5314
|
return img;
|
|
5132
5315
|
};
|
|
5133
5316
|
for (const a of kit.assets) {
|
|
5134
5317
|
if (!/\.png$/i.test(a.path)) continue;
|
|
5135
|
-
const file =
|
|
5136
|
-
const img =
|
|
5318
|
+
const file = isAbsolute2(a.path) && existsSync7(a.path) ? a.path : join7(kitDir, a.path.split("/").pop() ?? a.path);
|
|
5319
|
+
const img = existsSync7(file) ? decodePng(new Uint8Array(await readFile7(file))) : null;
|
|
5137
5320
|
if (!img) {
|
|
5138
5321
|
skipped.push(`${a.destination ?? a.path}: unreadable`);
|
|
5139
5322
|
continue;
|
|
@@ -5153,8 +5336,8 @@ async function judgeKit(kitPath, manifestPath, outDir) {
|
|
|
5153
5336
|
const id = stem.replace(/[^A-Za-z0-9_-]+/g, "-") || a.destination || "asset";
|
|
5154
5337
|
const nameA = `${id}--A.png`;
|
|
5155
5338
|
const nameB = `${id}--B.png`;
|
|
5156
|
-
await
|
|
5157
|
-
await
|
|
5339
|
+
await writeFile8(join7(out, nameA), encodePng(composeSheet(img, refImg)));
|
|
5340
|
+
await writeFile8(join7(out, nameB), encodePng(composeSheet(refImg, img)));
|
|
5158
5341
|
const rubric = [
|
|
5159
5342
|
`# ${id} against ${ref.id} (${ref.file})`,
|
|
5160
5343
|
"",
|
|
@@ -5174,12 +5357,12 @@ async function judgeKit(kitPath, manifestPath, outDir) {
|
|
|
5174
5357
|
"In judge.json: `win` true when the kit asset is at least as good as the reference on the rules that apply to its genre, false when it is worse, null for a tie; `reasons` names the rules by number.",
|
|
5175
5358
|
""
|
|
5176
5359
|
].join("\n");
|
|
5177
|
-
await
|
|
5360
|
+
await writeFile8(join7(out, `${id}.md`), rubric);
|
|
5178
5361
|
sheets.push({ asset: id, reference: ref.id, sheetA: nameA, sheetB: nameB, rubric: `${id}.md` });
|
|
5179
5362
|
}
|
|
5180
|
-
const verdictFile =
|
|
5181
|
-
if (!
|
|
5182
|
-
await
|
|
5363
|
+
const verdictFile = join7(out, "judge.json");
|
|
5364
|
+
if (!existsSync7(verdictFile)) {
|
|
5365
|
+
await writeFile8(
|
|
5183
5366
|
verdictFile,
|
|
5184
5367
|
JSON.stringify(
|
|
5185
5368
|
{
|
|
@@ -5205,7 +5388,7 @@ function winRate(verdicts) {
|
|
|
5205
5388
|
// src/plugin/kitPicture.ts
|
|
5206
5389
|
import { execFile } from "child_process";
|
|
5207
5390
|
import { promisify } from "util";
|
|
5208
|
-
import { readFile as
|
|
5391
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
5209
5392
|
var execFileP = promisify(execFile);
|
|
5210
5393
|
var SUBJECT_BAND = { min: 0.6, max: 0.92 };
|
|
5211
5394
|
var BLANK_INK = 0.12;
|
|
@@ -5481,7 +5664,7 @@ async function pictureChecks(assets) {
|
|
|
5481
5664
|
let ffmpeg = null;
|
|
5482
5665
|
for (const a of assets) {
|
|
5483
5666
|
if (/\.png$/i.test(a.file)) {
|
|
5484
|
-
const img = decodePng(new Uint8Array(await
|
|
5667
|
+
const img = decodePng(new Uint8Array(await readFile8(a.file)));
|
|
5485
5668
|
if (!img) {
|
|
5486
5669
|
findings.push({
|
|
5487
5670
|
code: "unreadable",
|
|
@@ -5574,9 +5757,9 @@ function formatFinding(f) {
|
|
|
5574
5757
|
}
|
|
5575
5758
|
|
|
5576
5759
|
// src/plugin/validateKit.ts
|
|
5577
|
-
import { existsSync as
|
|
5578
|
-
import { readFile as
|
|
5579
|
-
import { dirname as dirname2, isAbsolute as
|
|
5760
|
+
import { existsSync as existsSync8 } from "fs";
|
|
5761
|
+
import { readFile as readFile9, stat as stat2 } from "fs/promises";
|
|
5762
|
+
import { dirname as dirname2, isAbsolute as isAbsolute3, join as join8 } from "path";
|
|
5580
5763
|
import { DESTINATIONS as DESTINATIONS3 } from "@vosjs/studio-core";
|
|
5581
5764
|
var PNG_SIG2 = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
5582
5765
|
function pngDimensions(bytes) {
|
|
@@ -5595,7 +5778,7 @@ async function probeVideo(path) {
|
|
|
5595
5778
|
const MB = await import("mediabunny");
|
|
5596
5779
|
const input = new MB.Input({
|
|
5597
5780
|
formats: MB.ALL_FORMATS,
|
|
5598
|
-
source: new MB.BufferSource(new Uint8Array(await
|
|
5781
|
+
source: new MB.BufferSource(new Uint8Array(await readFile9(path)))
|
|
5599
5782
|
});
|
|
5600
5783
|
try {
|
|
5601
5784
|
const track = await input.getPrimaryVideoTrack();
|
|
@@ -5616,7 +5799,7 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
5616
5799
|
const measured = [];
|
|
5617
5800
|
let kit;
|
|
5618
5801
|
try {
|
|
5619
|
-
kit = JSON.parse(await
|
|
5802
|
+
kit = JSON.parse(await readFile9(kitPath, "utf8"));
|
|
5620
5803
|
} catch (e) {
|
|
5621
5804
|
return {
|
|
5622
5805
|
valid: false,
|
|
@@ -5635,8 +5818,8 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
5635
5818
|
}
|
|
5636
5819
|
const base = dirname2(kitPath);
|
|
5637
5820
|
const resolvePath = (p) => {
|
|
5638
|
-
if (
|
|
5639
|
-
const beside =
|
|
5821
|
+
if (isAbsolute3(p) && existsSync8(p)) return p;
|
|
5822
|
+
const beside = join8(base, p.split("/").pop() ?? p);
|
|
5640
5823
|
return beside;
|
|
5641
5824
|
};
|
|
5642
5825
|
const perDestination = /* @__PURE__ */ new Map();
|
|
@@ -5662,7 +5845,7 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
5662
5845
|
let h = null;
|
|
5663
5846
|
let seconds = null;
|
|
5664
5847
|
if (/\.(png|jpe?g|webp)$/i.test(file)) {
|
|
5665
|
-
const head = Buffer.from(await
|
|
5848
|
+
const head = Buffer.from(await readFile9(file));
|
|
5666
5849
|
const kind = sniffImage(head);
|
|
5667
5850
|
if (file.toLowerCase().endsWith(".png") && kind !== "png")
|
|
5668
5851
|
problems.push(
|
|
@@ -5773,7 +5956,7 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
5773
5956
|
// src/plugin/platform.ts
|
|
5774
5957
|
import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
5775
5958
|
import { homedir } from "os";
|
|
5776
|
-
import { join as
|
|
5959
|
+
import { join as join9 } from "path";
|
|
5777
5960
|
function platformOrigin(flags = {}) {
|
|
5778
5961
|
const legacyEnv = process.env.VOS_API_BASE?.trim();
|
|
5779
5962
|
const raw = flags.origin ?? flags.api ?? process.env.VOS_ORIGIN?.trim() ?? legacyEnv ?? "https://vos.so";
|
|
@@ -5806,7 +5989,7 @@ function parseVosId(input) {
|
|
|
5806
5989
|
function deriveSlug(title) {
|
|
5807
5990
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50).replace(/-+$/, "") || "remix";
|
|
5808
5991
|
}
|
|
5809
|
-
var CREDENTIALS_PATH =
|
|
5992
|
+
var CREDENTIALS_PATH = join9(homedir(), ".config", "vos", "credentials");
|
|
5810
5993
|
function resolveCredential(explicit) {
|
|
5811
5994
|
const flag = explicit?.trim();
|
|
5812
5995
|
if (flag) return flag;
|
|
@@ -5829,7 +6012,7 @@ function requireCredential(explicit) {
|
|
|
5829
6012
|
return key;
|
|
5830
6013
|
}
|
|
5831
6014
|
function writeCredential(key) {
|
|
5832
|
-
mkdirSync(
|
|
6015
|
+
mkdirSync(join9(homedir(), ".config", "vos"), { recursive: true, mode: 448 });
|
|
5833
6016
|
writeFileSync(CREDENTIALS_PATH, `${key.trim()}
|
|
5834
6017
|
`, { mode: 384 });
|
|
5835
6018
|
return CREDENTIALS_PATH;
|
|
@@ -5873,7 +6056,7 @@ function readJsonFile(file) {
|
|
|
5873
6056
|
}
|
|
5874
6057
|
}
|
|
5875
6058
|
function readSyncState(dir) {
|
|
5876
|
-
const own = readJsonFile(
|
|
6059
|
+
const own = readJsonFile(join9(dir, SYNC_STATE_NAME));
|
|
5877
6060
|
if (own && typeof own.vosId === "string") {
|
|
5878
6061
|
return {
|
|
5879
6062
|
vosId: own.vosId,
|
|
@@ -5884,7 +6067,7 @@ function readSyncState(dir) {
|
|
|
5884
6067
|
...typeof own.remixOfId === "string" ? { remixOfId: own.remixOfId } : {}
|
|
5885
6068
|
};
|
|
5886
6069
|
}
|
|
5887
|
-
const push = readJsonFile(
|
|
6070
|
+
const push = readJsonFile(join9(dir, "push.json"));
|
|
5888
6071
|
if (push && typeof push.vosId === "string") {
|
|
5889
6072
|
return {
|
|
5890
6073
|
vosId: push.vosId,
|
|
@@ -5892,7 +6075,7 @@ function readSyncState(dir) {
|
|
|
5892
6075
|
...typeof push.pushedAt === "string" ? { pushedAt: push.pushedAt } : {}
|
|
5893
6076
|
};
|
|
5894
6077
|
}
|
|
5895
|
-
const meta = readJsonFile(
|
|
6078
|
+
const meta = readJsonFile(join9(dir, "meta.json"));
|
|
5896
6079
|
if (meta && typeof meta.id === "string") {
|
|
5897
6080
|
return {
|
|
5898
6081
|
vosId: meta.id,
|
|
@@ -5910,7 +6093,7 @@ function writeSyncState(dir, patch) {
|
|
|
5910
6093
|
...patch
|
|
5911
6094
|
};
|
|
5912
6095
|
writeFileSync(
|
|
5913
|
-
|
|
6096
|
+
join9(dir, SYNC_STATE_NAME),
|
|
5914
6097
|
`${JSON.stringify(merged, null, 2)}
|
|
5915
6098
|
`
|
|
5916
6099
|
);
|
|
@@ -5931,7 +6114,7 @@ function formatChanges(changes) {
|
|
|
5931
6114
|
|
|
5932
6115
|
// src/plugin/recorder.ts
|
|
5933
6116
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
5934
|
-
import { join as
|
|
6117
|
+
import { join as join10 } from "path";
|
|
5935
6118
|
|
|
5936
6119
|
// src/plugin/recordingCap.ts
|
|
5937
6120
|
var HOSTED_RECORDING_CAP_SECONDS = 30 * 60;
|
|
@@ -6019,7 +6202,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6019
6202
|
cdp.on("Page.screencastFrame", (ev) => {
|
|
6020
6203
|
const tsMs = ev.metadata.timestamp ? ev.metadata.timestamp * 1e3 : Date.now();
|
|
6021
6204
|
const file = `frame-${String(frameIdx++).padStart(5, "0")}.jpg`;
|
|
6022
|
-
writeFileSync2(
|
|
6205
|
+
writeFileSync2(join10(paths.framesDir, file), Buffer.from(ev.data, "base64"));
|
|
6023
6206
|
frames.push({ file, tMs: Math.max(0, Math.round(tsMs - t0)) });
|
|
6024
6207
|
cdp.send("Page.screencastFrameAck", { sessionId: ev.sessionId }).catch(() => {
|
|
6025
6208
|
});
|
|
@@ -6242,7 +6425,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6242
6425
|
await sleep(200);
|
|
6243
6426
|
const pageTitle = await page.title().catch(() => "");
|
|
6244
6427
|
await context.close();
|
|
6245
|
-
const firstFrame = frames[0] ? jpegDims(readFileSync3(
|
|
6428
|
+
const firstFrame = frames[0] ? jpegDims(readFileSync3(join10(paths.framesDir, frames[0].file))) : null;
|
|
6246
6429
|
const meta = {
|
|
6247
6430
|
dpr: 1,
|
|
6248
6431
|
zoom: 1,
|
|
@@ -6350,9 +6533,9 @@ async function encodeRecording(browser, takeDir, onProgress) {
|
|
|
6350
6533
|
}
|
|
6351
6534
|
|
|
6352
6535
|
// src/plugin/plan.ts
|
|
6353
|
-
import { existsSync as
|
|
6354
|
-
import { readFile as
|
|
6355
|
-
import { join as
|
|
6536
|
+
import { existsSync as existsSync9 } from "fs";
|
|
6537
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
6538
|
+
import { join as join11 } from "path";
|
|
6356
6539
|
import {
|
|
6357
6540
|
DEFAULT_FRAME_STYLE,
|
|
6358
6541
|
STYLE_FIELDS,
|
|
@@ -6545,10 +6728,10 @@ function retimeCut(prev, newSteps, newDurationMs) {
|
|
|
6545
6728
|
// src/plugin/plan.ts
|
|
6546
6729
|
var overlaps = (a, b) => a.in < b.out && b.in < a.out;
|
|
6547
6730
|
async function readDigestActivity(dir) {
|
|
6548
|
-
const file =
|
|
6549
|
-
if (!
|
|
6731
|
+
const file = join11(dir, "digest", "digest.json");
|
|
6732
|
+
if (!existsSync9(file)) return null;
|
|
6550
6733
|
try {
|
|
6551
|
-
const d = JSON.parse(await
|
|
6734
|
+
const d = JSON.parse(await readFile10(file, "utf8"));
|
|
6552
6735
|
return Array.isArray(d.activity) && d.activity.every((v) => typeof v === "number") ? d.activity : null;
|
|
6553
6736
|
} catch {
|
|
6554
6737
|
return null;
|
|
@@ -6661,16 +6844,16 @@ async function planTake(dir, opts = {}) {
|
|
|
6661
6844
|
|
|
6662
6845
|
// src/plugin/sync.ts
|
|
6663
6846
|
import { createHash } from "crypto";
|
|
6664
|
-
import { existsSync as
|
|
6665
|
-
import { readFile as
|
|
6847
|
+
import { existsSync as existsSync11 } from "fs";
|
|
6848
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
6666
6849
|
import { createInterface } from "readline/promises";
|
|
6667
|
-
import { basename, join as
|
|
6850
|
+
import { basename, join as join14 } from "path";
|
|
6668
6851
|
import { lowerToComposition as lowerToComposition3, migrateHostedDoc as migrateHostedDoc2 } from "@vosjs/studio-core";
|
|
6669
6852
|
|
|
6670
6853
|
// src/plugin/media.ts
|
|
6671
|
-
import { createWriteStream, existsSync as
|
|
6672
|
-
import { writeFile as
|
|
6673
|
-
import { join as
|
|
6854
|
+
import { createWriteStream, existsSync as existsSync10 } from "fs";
|
|
6855
|
+
import { writeFile as writeFile9 } from "fs/promises";
|
|
6856
|
+
import { join as join12 } from "path";
|
|
6674
6857
|
import { Readable } from "stream";
|
|
6675
6858
|
import { pipeline } from "stream/promises";
|
|
6676
6859
|
var MIC_NAME = "mic.webm";
|
|
@@ -6691,8 +6874,8 @@ async function pullMedia(ctx, dir, doc, log) {
|
|
|
6691
6874
|
const url = doc.source[key];
|
|
6692
6875
|
const assetId = assetIdOf(url);
|
|
6693
6876
|
if (!assetId) continue;
|
|
6694
|
-
const target =
|
|
6695
|
-
if (
|
|
6877
|
+
const target = join12(dir, file);
|
|
6878
|
+
if (existsSync10(target)) {
|
|
6696
6879
|
result.kept.push(file);
|
|
6697
6880
|
} else {
|
|
6698
6881
|
const abs = /^https?:/.test(url) ? url : `${ctx.origin}${url}`;
|
|
@@ -6714,17 +6897,17 @@ async function pullMedia(ctx, dir, doc, log) {
|
|
|
6714
6897
|
}
|
|
6715
6898
|
doc.source[key] = file;
|
|
6716
6899
|
}
|
|
6717
|
-
const metaPath =
|
|
6718
|
-
if (!
|
|
6719
|
-
const cursorPath =
|
|
6720
|
-
if (!
|
|
6721
|
-
await
|
|
6900
|
+
const metaPath = join12(dir, "meta.json");
|
|
6901
|
+
if (!existsSync10(metaPath)) await writeJson(metaPath, doc.source.meta, true);
|
|
6902
|
+
const cursorPath = join12(dir, "cursor.json");
|
|
6903
|
+
if (!existsSync10(cursorPath)) await writeJson(cursorPath, doc.source.cursor);
|
|
6904
|
+
await writeFile9(join12(dir, "doc.json"), JSON.stringify(doc, null, 2));
|
|
6722
6905
|
return result;
|
|
6723
6906
|
}
|
|
6724
6907
|
|
|
6725
6908
|
// src/plugin/folder.ts
|
|
6726
|
-
import { mkdir as
|
|
6727
|
-
import { join as
|
|
6909
|
+
import { mkdir as mkdir7, writeFile as writeFile10 } from "fs/promises";
|
|
6910
|
+
import { join as join13 } from "path";
|
|
6728
6911
|
import { recipeHints } from "@vosjs/shared/frontmatter";
|
|
6729
6912
|
import { migrateHostedDoc } from "@vosjs/studio-core";
|
|
6730
6913
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "help", "media"]);
|
|
@@ -6900,25 +7083,25 @@ async function cmdPull(argv) {
|
|
|
6900
7083
|
throw new Error(apiError(`pull folder ${folder.slug}`, res));
|
|
6901
7084
|
const payload = res.body;
|
|
6902
7085
|
const out = strFlag(flags, "out") ?? folder.slug;
|
|
6903
|
-
await
|
|
7086
|
+
await mkdir7(join13(out, "recipes"), { recursive: true });
|
|
6904
7087
|
const lines = [];
|
|
6905
7088
|
const recipes = [];
|
|
6906
7089
|
for (const rec of payload.recipes) {
|
|
6907
|
-
const path =
|
|
6908
|
-
await
|
|
7090
|
+
const path = join13(out, "recipes", safeName(rec.filename));
|
|
7091
|
+
await writeFile10(path, rec.body ?? "");
|
|
6909
7092
|
recipes.push({ path, line: recipeLine(rec.filename, rec.body ?? "") });
|
|
6910
7093
|
}
|
|
6911
7094
|
for (const rec of payload.inheritedRecipes) {
|
|
6912
7095
|
const from = safeName(rec.folderSlug ?? "inherited");
|
|
6913
|
-
await
|
|
6914
|
-
const path =
|
|
7096
|
+
await mkdir7(join13(out, "recipes", "_inherited", from), { recursive: true });
|
|
7097
|
+
const path = join13(
|
|
6915
7098
|
out,
|
|
6916
7099
|
"recipes",
|
|
6917
7100
|
"_inherited",
|
|
6918
7101
|
from,
|
|
6919
7102
|
safeName(rec.filename)
|
|
6920
7103
|
);
|
|
6921
|
-
await
|
|
7104
|
+
await writeFile10(path, rec.body ?? "");
|
|
6922
7105
|
recipes.push({
|
|
6923
7106
|
path,
|
|
6924
7107
|
line: `${recipeLine(rec.filename, rec.body ?? "")} [inherited from ${rec.folderName ?? from}]`
|
|
@@ -6926,15 +7109,15 @@ async function cmdPull(argv) {
|
|
|
6926
7109
|
}
|
|
6927
7110
|
const members = [];
|
|
6928
7111
|
for (const v of payload.voses) {
|
|
6929
|
-
const dir =
|
|
6930
|
-
await
|
|
7112
|
+
const dir = join13(out, "members", safeName(v.slug || v.title || v.id));
|
|
7113
|
+
await mkdir7(dir, { recursive: true });
|
|
6931
7114
|
const cfg = await apiJson(origin, `/api/vos/${v.id}/config`, { key });
|
|
6932
7115
|
if (cfg.status !== 200) {
|
|
6933
7116
|
lines.push(`skipped ${v.title}: ${apiError("fetch config", cfg)}`);
|
|
6934
7117
|
continue;
|
|
6935
7118
|
}
|
|
6936
|
-
await
|
|
6937
|
-
|
|
7119
|
+
await writeFile10(
|
|
7120
|
+
join13(dir, "config.json"),
|
|
6938
7121
|
JSON.stringify(cfg.body.config, null, 2)
|
|
6939
7122
|
);
|
|
6940
7123
|
let take = false;
|
|
@@ -6945,8 +7128,8 @@ async function cmdPull(argv) {
|
|
|
6945
7128
|
{ key }
|
|
6946
7129
|
);
|
|
6947
7130
|
if (doc.status === 200) {
|
|
6948
|
-
await
|
|
6949
|
-
|
|
7131
|
+
await writeFile10(
|
|
7132
|
+
join13(dir, "doc.json"),
|
|
6950
7133
|
JSON.stringify(doc.body, null, 2)
|
|
6951
7134
|
);
|
|
6952
7135
|
take = true;
|
|
@@ -7063,7 +7246,7 @@ async function pushTake(dir, flags, r) {
|
|
|
7063
7246
|
throw new Error(`doc.json fails lint:
|
|
7064
7247
|
${lint.problems.join("\n ")}`);
|
|
7065
7248
|
}
|
|
7066
|
-
if (!
|
|
7249
|
+
if (!existsSync11(take.paths.recording)) {
|
|
7067
7250
|
throw new Error("no recording.webm in this take");
|
|
7068
7251
|
}
|
|
7069
7252
|
const ctx = apiContext(flags);
|
|
@@ -7095,7 +7278,7 @@ async function pushTake(dir, flags, r) {
|
|
|
7095
7278
|
throw new Error("push cancelled");
|
|
7096
7279
|
}
|
|
7097
7280
|
}
|
|
7098
|
-
const bytes = await
|
|
7281
|
+
const bytes = await readFile11(take.paths.recording);
|
|
7099
7282
|
const hash = createHash("sha256").update(bytes).digest("hex");
|
|
7100
7283
|
r.log(`uploading recording (${Math.round(bytes.length / 1024)} kB)\u2026`);
|
|
7101
7284
|
const upload = await api(ctx, "/assets/recording", {
|
|
@@ -7246,9 +7429,9 @@ async function pullTake(dir, flags, r) {
|
|
|
7246
7429
|
};
|
|
7247
7430
|
}
|
|
7248
7431
|
let media2;
|
|
7249
|
-
if (flags.media &&
|
|
7432
|
+
if (flags.media && existsSync11(join14(dir, "doc.json"))) {
|
|
7250
7433
|
const local = JSON.parse(
|
|
7251
|
-
await
|
|
7434
|
+
await readFile11(join14(dir, "doc.json"), "utf8")
|
|
7252
7435
|
);
|
|
7253
7436
|
media2 = await pullMedia(ctx, dir, local, r.log);
|
|
7254
7437
|
}
|
|
@@ -7308,10 +7491,10 @@ async function pullTake(dir, flags, r) {
|
|
|
7308
7491
|
}
|
|
7309
7492
|
const hostedDoc = migrateHostedDoc2(docRes.json);
|
|
7310
7493
|
const doc = hostedDoc;
|
|
7311
|
-
if (
|
|
7494
|
+
if (existsSync11(join14(dir, RECORDING_NAME))) {
|
|
7312
7495
|
doc.source.videoKey = RECORDING_NAME;
|
|
7313
7496
|
}
|
|
7314
|
-
await writeJson(
|
|
7497
|
+
await writeJson(join14(dir, "doc.json"), doc, true);
|
|
7315
7498
|
const media = flags.media ? await pullMedia(ctx, dir, doc, r.log) : void 0;
|
|
7316
7499
|
const versions = await api(ctx, `/vos/${vosId}/versions`);
|
|
7317
7500
|
const headRow = (versions.json.versions ?? []).find((v) => v.id === head);
|
|
@@ -7330,10 +7513,10 @@ async function pullTake(dir, flags, r) {
|
|
|
7330
7513
|
}
|
|
7331
7514
|
|
|
7332
7515
|
// src/plugin/program.ts
|
|
7333
|
-
import { existsSync as
|
|
7334
|
-
import { mkdir as
|
|
7516
|
+
import { existsSync as existsSync12 } from "fs";
|
|
7517
|
+
import { mkdir as mkdir8, readFile as readFile12, writeFile as writeFile11 } from "fs/promises";
|
|
7335
7518
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
7336
|
-
import { basename as basename2, dirname as dirname3, join as
|
|
7519
|
+
import { basename as basename2, dirname as dirname3, join as join15 } from "path";
|
|
7337
7520
|
import {
|
|
7338
7521
|
CURRENT_CONFIG_VERSION,
|
|
7339
7522
|
migrateConfig,
|
|
@@ -7503,8 +7686,8 @@ function preflightConfig(parsed) {
|
|
|
7503
7686
|
}
|
|
7504
7687
|
function resolveConfigPath(target) {
|
|
7505
7688
|
if (target.endsWith(".json")) return target;
|
|
7506
|
-
const inDir =
|
|
7507
|
-
if (
|
|
7689
|
+
const inDir = join15(target, "config.json");
|
|
7690
|
+
if (existsSync12(inDir)) return inDir;
|
|
7508
7691
|
throw new UsageError(
|
|
7509
7692
|
`${target} has no config.json (and is not a take \u2014 no doc.json)`
|
|
7510
7693
|
);
|
|
@@ -7530,9 +7713,9 @@ async function cmdFetch(argv) {
|
|
|
7530
7713
|
throw new Error(apiError(`fetch config for ${vosId}`, cfg));
|
|
7531
7714
|
const slug = typeof vosMeta.slug === "string" && vosMeta.slug ? vosMeta.slug : vosId;
|
|
7532
7715
|
const out = strFlag(flags, "out") ?? slug;
|
|
7533
|
-
await
|
|
7534
|
-
await
|
|
7535
|
-
|
|
7716
|
+
await mkdir8(out, { recursive: true });
|
|
7717
|
+
await writeFile11(
|
|
7718
|
+
join15(out, "config.json"),
|
|
7536
7719
|
JSON.stringify(cfg.body.config, null, 2)
|
|
7537
7720
|
);
|
|
7538
7721
|
const title = typeof vosMeta.title === "string" ? vosMeta.title : "";
|
|
@@ -7557,12 +7740,12 @@ async function cmdFetch(argv) {
|
|
|
7557
7740
|
const hosted = migrateHostedDoc3(doc.body);
|
|
7558
7741
|
const { config: own } = await writeProgramDoc(out, hosted);
|
|
7559
7742
|
if (own) {
|
|
7560
|
-
await
|
|
7743
|
+
await writeFile11(join15(out, "config.json"), JSON.stringify(own, null, 2));
|
|
7561
7744
|
}
|
|
7562
7745
|
} else if (doc.status === 200) {
|
|
7563
7746
|
take = true;
|
|
7564
7747
|
const hosted = migrateHostedDoc3(doc.body);
|
|
7565
|
-
await
|
|
7748
|
+
await writeFile11(join15(out, "doc.json"), JSON.stringify(hosted, null, 2));
|
|
7566
7749
|
if (flags.media === true) {
|
|
7567
7750
|
const media = await pullMedia({ origin, key }, out, hosted, r.log);
|
|
7568
7751
|
mediaLine = media.downloaded.length ? ` + ${media.downloaded.map((m) => m.file).join(", ")}` : "";
|
|
@@ -7674,7 +7857,7 @@ async function cmdPushProgram(argv) {
|
|
|
7674
7857
|
api: strFlag(flags, "api")
|
|
7675
7858
|
});
|
|
7676
7859
|
const dir = dirname3(source);
|
|
7677
|
-
const parsed = JSON.parse(await
|
|
7860
|
+
const parsed = JSON.parse(await readFile12(source, "utf8"));
|
|
7678
7861
|
const pre = preflightConfig(parsed);
|
|
7679
7862
|
if (!pre.ok || !pre.config) {
|
|
7680
7863
|
for (const issue of pre.issues) r.log(`error ${issue}`);
|
|
@@ -7907,10 +8090,10 @@ async function cmdPullProgram(argv) {
|
|
|
7907
8090
|
const cfg = await apiJson(origin, `/api/vos/${vosId}/config`, { key });
|
|
7908
8091
|
if (cfg.status !== 200)
|
|
7909
8092
|
throw new Error(apiError(`fetch head config for ${vosId}`, cfg));
|
|
7910
|
-
const configPath =
|
|
8093
|
+
const configPath = join15(dir, "config.json");
|
|
7911
8094
|
let backedUp = false;
|
|
7912
|
-
if (
|
|
7913
|
-
await
|
|
8095
|
+
if (existsSync12(configPath)) {
|
|
8096
|
+
await writeFile11(join15(dir, "config.backup.json"), await readFile12(configPath));
|
|
7914
8097
|
backedUp = true;
|
|
7915
8098
|
}
|
|
7916
8099
|
let headConfig = cfg.body.config;
|
|
@@ -7929,7 +8112,7 @@ async function cmdPullProgram(argv) {
|
|
|
7929
8112
|
if (own) headConfig = own;
|
|
7930
8113
|
}
|
|
7931
8114
|
}
|
|
7932
|
-
await
|
|
8115
|
+
await writeFile11(configPath, JSON.stringify(headConfig, null, 2));
|
|
7933
8116
|
writeSyncState(dir, {
|
|
7934
8117
|
vosId,
|
|
7935
8118
|
versionId: typeof head.id === "string" ? head.id : since
|
|
@@ -7943,7 +8126,7 @@ async function cmdPullProgram(argv) {
|
|
|
7943
8126
|
protected: protectedIds,
|
|
7944
8127
|
changes,
|
|
7945
8128
|
out: configPath,
|
|
7946
|
-
backup: backedUp ?
|
|
8129
|
+
backup: backedUp ? join15(dir, "config.backup.json") : null
|
|
7947
8130
|
},
|
|
7948
8131
|
`Pulled ${changes.length} version${changes.length === 1 ? "" : "s"} \u2192 ${configPath}` + (backedUp ? ` (previous copy: config.backup.json)` : "") + `
|
|
7949
8132
|
Re-apply your edit on the new head, then: vos push ${configPath} --vos ${vosId}`
|
|
@@ -8000,9 +8183,9 @@ function isTakeDir(target) {
|
|
|
8000
8183
|
return directoryKind(target) === "take";
|
|
8001
8184
|
}
|
|
8002
8185
|
async function readProgramDoc(dir, config) {
|
|
8003
|
-
const docPath =
|
|
8004
|
-
if (!
|
|
8005
|
-
const parsed = JSON.parse(await
|
|
8186
|
+
const docPath = join15(dir, "doc.json");
|
|
8187
|
+
if (!existsSync12(docPath)) return null;
|
|
8188
|
+
const parsed = JSON.parse(await readFile12(docPath, "utf8"));
|
|
8006
8189
|
if (typeof parsed !== "object" || parsed === null || "source" in parsed)
|
|
8007
8190
|
return null;
|
|
8008
8191
|
const raw = parsed;
|
|
@@ -8013,7 +8196,7 @@ async function writeProgramDoc(dir, hosted) {
|
|
|
8013
8196
|
const program = hosted.program && typeof hosted.program === "object" ? hosted.program : {};
|
|
8014
8197
|
const { config, ...rest } = program;
|
|
8015
8198
|
const onDisk = { ...hosted, program: rest };
|
|
8016
|
-
await
|
|
8199
|
+
await writeFile11(join15(dir, "doc.json"), JSON.stringify(onDisk, null, 2));
|
|
8017
8200
|
return {
|
|
8018
8201
|
config: config && typeof config === "object" ? config : null
|
|
8019
8202
|
};
|
|
@@ -8220,8 +8403,8 @@ async function cmdRecipe(argv) {
|
|
|
8220
8403
|
}
|
|
8221
8404
|
|
|
8222
8405
|
// src/plugin/brand.ts
|
|
8223
|
-
import { writeFile as
|
|
8224
|
-
import { resolve as
|
|
8406
|
+
import { writeFile as writeFile12 } from "fs/promises";
|
|
8407
|
+
import { resolve as resolve6 } from "path";
|
|
8225
8408
|
import { parseFrontmatter as parseFrontmatter2 } from "@vosjs/shared/frontmatter";
|
|
8226
8409
|
import { lookKindForGround } from "@vosjs/studio-core";
|
|
8227
8410
|
var HEX_RE = /#(?:[0-9a-f]{6}|[0-9a-f]{3})\b/gi;
|
|
@@ -8425,7 +8608,14 @@ function composeBrand(input) {
|
|
|
8425
8608
|
const designBody = design?.fonts.slice(1).find((f) => !/\bmono\b/i.test(f));
|
|
8426
8609
|
const fontBody = designBody ?? firstFamily2(w.body.fontFamily);
|
|
8427
8610
|
p.fontBody = designBody ? `named in design.md` : `the body's computed face`;
|
|
8428
|
-
const
|
|
8611
|
+
const isIcon = (u) => /favicon|apple-touch|icon/i.test(u);
|
|
8612
|
+
const onDark = (u) => /on-dark|white/i.test(u);
|
|
8613
|
+
const logoUrl = design?.logos.find(
|
|
8614
|
+
(u) => /wordmark|logo|mark/i.test(u) && !isIcon(u) && !onDark(u)
|
|
8615
|
+
) ?? null;
|
|
8616
|
+
const logoOnDarkUrl = design?.logos.find(
|
|
8617
|
+
(u) => /wordmark|logo|mark/i.test(u) && !isIcon(u) && onDark(u)
|
|
8618
|
+
) ?? null;
|
|
8429
8619
|
const iconUrl = w.icons.find((u) => /apple-touch/i.test(u)) ?? (w.icons.length ? w.icons[0] : null);
|
|
8430
8620
|
p.logoUrl = logoUrl ? `linked from design.md` : `none linked; the icon stands in`;
|
|
8431
8621
|
p.iconUrl = iconUrl ? `the page's icon link` : `no icon link`;
|
|
@@ -8444,6 +8634,7 @@ function composeBrand(input) {
|
|
|
8444
8634
|
fontDisplay,
|
|
8445
8635
|
fontBody,
|
|
8446
8636
|
logoUrl: logoUrl ?? iconUrl,
|
|
8637
|
+
logoOnDarkUrl,
|
|
8447
8638
|
iconUrl,
|
|
8448
8639
|
ogImage: w.ogImage,
|
|
8449
8640
|
wordmark,
|
|
@@ -8473,6 +8664,7 @@ function renderBrandMd(c, claim) {
|
|
|
8473
8664
|
`fontDisplay: ${q(k.fontDisplay)}`,
|
|
8474
8665
|
`fontBody: ${q(k.fontBody)}`,
|
|
8475
8666
|
`logoUrl: ${q(k.logoUrl)}`,
|
|
8667
|
+
`logoOnDarkUrl: ${q(k.logoOnDarkUrl)}`,
|
|
8476
8668
|
`iconUrl: ${q(k.iconUrl)}`,
|
|
8477
8669
|
`ogImage: ${q(k.ogImage)}`,
|
|
8478
8670
|
`wordmark: ${q(k.wordmark)}`,
|
|
@@ -8498,7 +8690,7 @@ function renderBrandMd(c, claim) {
|
|
|
8498
8690
|
"",
|
|
8499
8691
|
"## Use",
|
|
8500
8692
|
"",
|
|
8501
|
-
`The poster family binds these roles: \`bgA/bgB/bgC\` for the ground, \`ink\` for type, \`fontDisplay\` for the headline (if it is not in the hosted catalog, \`vos check\` warns and the nearest hosted serif or sans stands in), \`logoUrl\` for the mark. A take's frame ground is a gradient of \`accent\` toward \`bgC\` unless the brand is paper, in which case \`bgB\` toward \`bgC\`.`
|
|
8693
|
+
`The poster family binds these roles: \`bgA/bgB/bgC\` for the ground, \`ink\` for type, \`fontDisplay\` for the headline (if it is not in the hosted catalog, \`vos check\` warns and the nearest hosted serif or sans stands in), \`logoUrl\` for the mark (placed beside the wordmark on the cards and the end card; \`logoOnDarkUrl\` stands in on a dark ground). A site that publishes only a favicon or an app icon gets the icon here, tile and all: publish a bare mark and name it in design.md. A take's frame ground is a gradient of \`accent\` toward \`bgC\` unless the brand is paper, in which case \`bgB\` toward \`bgC\`.`
|
|
8502
8694
|
];
|
|
8503
8695
|
if (c.avoid.length) {
|
|
8504
8696
|
lines.push("", "## Avoid (the site says)", "");
|
|
@@ -8529,7 +8721,7 @@ async function cmdBrand(argv) {
|
|
|
8529
8721
|
throw new UsageError(`not a URL: ${raw}`);
|
|
8530
8722
|
}
|
|
8531
8723
|
const r = createReporter(flags.json === true);
|
|
8532
|
-
const out =
|
|
8724
|
+
const out = resolve6(strFlag(flags, "out") ?? "BRAND.md");
|
|
8533
8725
|
r.log(`reading ${origin}/design.md and /llms.txt\u2026`);
|
|
8534
8726
|
const [designText, llmsText] = await Promise.all([
|
|
8535
8727
|
fetchText(`${origin}/design.md`),
|
|
@@ -8563,7 +8755,7 @@ async function cmdBrand(argv) {
|
|
|
8563
8755
|
composition,
|
|
8564
8756
|
llms?.claim ?? design?.description ?? null
|
|
8565
8757
|
);
|
|
8566
|
-
await
|
|
8758
|
+
await writeFile12(out, md);
|
|
8567
8759
|
const k = composition.kit;
|
|
8568
8760
|
r.done(
|
|
8569
8761
|
{
|
|
@@ -8581,8 +8773,8 @@ async function cmdBrand(argv) {
|
|
|
8581
8773
|
}
|
|
8582
8774
|
|
|
8583
8775
|
// src/plugin/agentBrowser.ts
|
|
8584
|
-
import { writeFile as
|
|
8585
|
-
import { resolve as
|
|
8776
|
+
import { writeFile as writeFile13 } from "fs/promises";
|
|
8777
|
+
import { resolve as resolve7 } from "path";
|
|
8586
8778
|
function parseAgentBrowserLog(text) {
|
|
8587
8779
|
const records = [];
|
|
8588
8780
|
const problems = [];
|
|
@@ -9052,7 +9244,7 @@ async function cmdActions(argv) {
|
|
|
9052
9244
|
);
|
|
9053
9245
|
}
|
|
9054
9246
|
const r = createReporter(flags.json === true);
|
|
9055
|
-
const out =
|
|
9247
|
+
const out = resolve7(strFlag(flags, "out") ?? "actions.json");
|
|
9056
9248
|
const viewportFlag = strFlag(flags, "viewport");
|
|
9057
9249
|
let viewport;
|
|
9058
9250
|
if (viewportFlag) {
|
|
@@ -9060,8 +9252,8 @@ async function cmdActions(argv) {
|
|
|
9060
9252
|
if (!m) throw new UsageError("--viewport expects WxH, e.g. 1280x720");
|
|
9061
9253
|
viewport = { width: Number(m[1]), height: Number(m[2]) };
|
|
9062
9254
|
}
|
|
9063
|
-
const { readFile:
|
|
9064
|
-
const text = await
|
|
9255
|
+
const { readFile: readFile14 } = await import("fs/promises");
|
|
9256
|
+
const text = await readFile14(resolve7(input), "utf8");
|
|
9065
9257
|
const { records, problems } = parseAgentBrowserLog(text);
|
|
9066
9258
|
for (const p of problems) r.log(` ${p}`);
|
|
9067
9259
|
const result = convertAgentBrowser(records, {
|
|
@@ -9078,7 +9270,7 @@ async function cmdActions(argv) {
|
|
|
9078
9270
|
);
|
|
9079
9271
|
return EXIT_ERROR;
|
|
9080
9272
|
}
|
|
9081
|
-
await
|
|
9273
|
+
await writeFile13(out, `${JSON.stringify(result.actions, null, 2)}
|
|
9082
9274
|
`);
|
|
9083
9275
|
r.done(
|
|
9084
9276
|
{ ok: true, out, url: result.actions.url ?? null, ...summary(result) },
|
|
@@ -9349,7 +9541,7 @@ lint-gated, so a bad override fails like a bad doc.json):
|
|
|
9349
9541
|
first ready loop (the house backdrop the studio opens on), or a flat ground offline
|
|
9350
9542
|
`;
|
|
9351
9543
|
async function loadActions(file) {
|
|
9352
|
-
const raw = JSON.parse(await
|
|
9544
|
+
const raw = JSON.parse(await readFile13(file, "utf8"));
|
|
9353
9545
|
const errors = validateActions(raw);
|
|
9354
9546
|
if (errors.length)
|
|
9355
9547
|
throw new UsageError(`invalid actions file:
|
|
@@ -9405,16 +9597,16 @@ async function cmdRecord(argv) {
|
|
|
9405
9597
|
const url = strFlag(flags, "url") ?? actions.url;
|
|
9406
9598
|
if (!url)
|
|
9407
9599
|
throw new UsageError('no URL \u2014 set "url" in the actions file or pass --url');
|
|
9408
|
-
const outDir =
|
|
9600
|
+
const outDir = resolve8(strFlag(flags, "out") ?? "take");
|
|
9409
9601
|
const backdrop = await takeBackdrop(flags, r);
|
|
9410
9602
|
const maxDurationSeconds = await maxDuration(flags, r);
|
|
9411
|
-
if (
|
|
9603
|
+
if (existsSync13(join16(outDir, "meta.json"))) {
|
|
9412
9604
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
9413
9605
|
r.log(
|
|
9414
9606
|
`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" : "")
|
|
9415
9607
|
);
|
|
9416
9608
|
}
|
|
9417
|
-
await
|
|
9609
|
+
await mkdir9(outDir, { recursive: true });
|
|
9418
9610
|
const paths = await ensureTakeDir(outDir);
|
|
9419
9611
|
const browser = await launchBrowser();
|
|
9420
9612
|
try {
|
|
@@ -9455,7 +9647,7 @@ async function cmdRecord(argv) {
|
|
|
9455
9647
|
},
|
|
9456
9648
|
strictFail ? `STRICT: take recorded but incomplete \u2014 ${strictReason(rec)}; fix the flow and re-record.${skippedNote}` : `Take ready: ${outDir}
|
|
9457
9649
|
${(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}
|
|
9458
|
-
Next: edit ${
|
|
9650
|
+
Next: edit ${join16(outDir, "doc.json")} (optional), then: vos render ${outDir}`
|
|
9459
9651
|
);
|
|
9460
9652
|
return strictFail ? EXIT_USAGE : EXIT_OK;
|
|
9461
9653
|
} finally {
|
|
@@ -9479,25 +9671,25 @@ async function cmdCreate2(argv) {
|
|
|
9479
9671
|
const url = strFlag(flags, "url") ?? actions.url;
|
|
9480
9672
|
if (!url)
|
|
9481
9673
|
throw new UsageError('no URL \u2014 set "url" in the actions file or pass --url');
|
|
9482
|
-
const outDir =
|
|
9674
|
+
const outDir = resolve8(strFlag(flags, "out") ?? "take");
|
|
9483
9675
|
const fmtRaw = strFlag(flags, "format") ?? "webm";
|
|
9484
9676
|
if (fmtRaw !== "webm" && fmtRaw !== "mp4")
|
|
9485
9677
|
throw new UsageError("--format must be webm or mp4");
|
|
9486
9678
|
const format = fmtRaw;
|
|
9487
|
-
const out = positionals[0] ??
|
|
9679
|
+
const out = positionals[0] ?? join16(outDir, `out.${format}`);
|
|
9488
9680
|
const parallel = numFlag(flags, "parallel", 1);
|
|
9489
9681
|
if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
|
|
9490
9682
|
throw new UsageError("--parallel expects an integer between 1 and 16");
|
|
9491
9683
|
}
|
|
9492
9684
|
const backdrop = await takeBackdrop(flags, r);
|
|
9493
9685
|
const maxDurationSeconds = await maxDuration(flags, r);
|
|
9494
|
-
if (
|
|
9686
|
+
if (existsSync13(join16(outDir, "meta.json"))) {
|
|
9495
9687
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
9496
9688
|
r.log(
|
|
9497
9689
|
`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" : "")
|
|
9498
9690
|
);
|
|
9499
9691
|
}
|
|
9500
|
-
await
|
|
9692
|
+
await mkdir9(outDir, { recursive: true });
|
|
9501
9693
|
const paths = await ensureTakeDir(outDir);
|
|
9502
9694
|
const browser = await launchBrowser();
|
|
9503
9695
|
try {
|
|
@@ -9578,24 +9770,24 @@ async function cmdPlan(argv) {
|
|
|
9578
9770
|
);
|
|
9579
9771
|
const r = createReporter(flags.json === true);
|
|
9580
9772
|
if (flags.fresh === true) {
|
|
9581
|
-
await rm4(
|
|
9773
|
+
await rm4(join16(dir, "doc.json"), { force: true });
|
|
9582
9774
|
r.log("note: --fresh discarded the existing doc.json");
|
|
9583
9775
|
}
|
|
9584
9776
|
let reuse;
|
|
9585
9777
|
if (flags.reuse === true) {
|
|
9586
|
-
const from =
|
|
9587
|
-
if (!
|
|
9778
|
+
const from = resolve8(strFlag(flags, "from") ?? join16(dir, PREV_DOC_NAME));
|
|
9779
|
+
if (!existsSync13(from)) {
|
|
9588
9780
|
throw new UsageError(
|
|
9589
9781
|
`--reuse: ${from} does not exist \u2014 a re-record into this take writes doc.prev.json, or pass --from <doc.json>`
|
|
9590
9782
|
);
|
|
9591
9783
|
}
|
|
9592
9784
|
reuse = {
|
|
9593
9785
|
from,
|
|
9594
|
-
doc: JSON.parse(await
|
|
9786
|
+
doc: JSON.parse(await readFile13(from, "utf8"))
|
|
9595
9787
|
};
|
|
9596
9788
|
}
|
|
9597
9789
|
const style = await resolveStyleRef(flags);
|
|
9598
|
-
const backdrop =
|
|
9790
|
+
const backdrop = existsSync13(join16(dir, "doc.json")) ? null : await takeBackdrop(flags, r);
|
|
9599
9791
|
const s = await planTake(dir, {
|
|
9600
9792
|
...style ? { style } : {},
|
|
9601
9793
|
...reuse ? { reuse } : {},
|
|
@@ -9607,7 +9799,7 @@ async function cmdPlan(argv) {
|
|
|
9607
9799
|
${s.reuse.flagged.join("\n ")}` : "") : "";
|
|
9608
9800
|
r.done(
|
|
9609
9801
|
{
|
|
9610
|
-
take:
|
|
9802
|
+
take: resolve8(dir),
|
|
9611
9803
|
fresh: s.fresh,
|
|
9612
9804
|
cursorKept: s.cursorKept,
|
|
9613
9805
|
zoomAuto: s.zoomAuto,
|
|
@@ -9617,7 +9809,7 @@ async function cmdPlan(argv) {
|
|
|
9617
9809
|
...s.styleFrom ? { styleFrom: s.styleFrom, styleFields: s.styleFields } : {},
|
|
9618
9810
|
...s.reuse ? { reuse: s.reuse } : {}
|
|
9619
9811
|
},
|
|
9620
|
-
`${s.reuse ? "Reused" : s.fresh ? "Planned" : "Refreshed"} ${
|
|
9812
|
+
`${s.reuse ? "Reused" : s.fresh ? "Planned" : "Refreshed"} ${join16(dir, "doc.json")}: ${s.zoomAuto} auto + ${s.zoomManual} manual zoom spans${s.cursorKept ? "" : " (cursor track dropped)"}${reuseLines}`
|
|
9621
9813
|
);
|
|
9622
9814
|
return EXIT_OK;
|
|
9623
9815
|
}
|
|
@@ -9637,7 +9829,7 @@ async function cmdRender(argv) {
|
|
|
9637
9829
|
if (fmtRaw !== "webm" && fmtRaw !== "mp4")
|
|
9638
9830
|
throw new UsageError("--format must be webm or mp4");
|
|
9639
9831
|
const format = fmtRaw;
|
|
9640
|
-
const out = positionals[1] ??
|
|
9832
|
+
const out = positionals[1] ?? join16(dir, `out.${format}`);
|
|
9641
9833
|
const parallel = numFlag(flags, "parallel", 1);
|
|
9642
9834
|
if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
|
|
9643
9835
|
throw new UsageError("--parallel expects an integer between 1 and 16");
|
|
@@ -9822,10 +10014,10 @@ async function cmdDeliver(argv) {
|
|
|
9822
10014
|
} else if (posterRef !== void 0 && templateByName(posterRef)) {
|
|
9823
10015
|
poster = { from: `template ${posterRef}`, config: templateByName(posterRef) };
|
|
9824
10016
|
} else if (posterRef !== void 0) {
|
|
9825
|
-
if (
|
|
10017
|
+
if (existsSync13(posterRef)) {
|
|
9826
10018
|
poster = {
|
|
9827
|
-
from:
|
|
9828
|
-
config: JSON.parse(await
|
|
10019
|
+
from: resolve8(posterRef),
|
|
10020
|
+
config: JSON.parse(await readFile13(posterRef, "utf8"))
|
|
9829
10021
|
};
|
|
9830
10022
|
} else {
|
|
9831
10023
|
const origin = platformOrigin({
|
|
@@ -9951,14 +10143,14 @@ Store uploads stay manual: hand the human this directory and the manifest.`
|
|
|
9951
10143
|
async function resolveStyleRef(flags) {
|
|
9952
10144
|
const styleRef = strFlag(flags, "style");
|
|
9953
10145
|
if (!styleRef) return null;
|
|
9954
|
-
const file =
|
|
10146
|
+
const file = existsSync13(styleRef) ? resolve8(
|
|
9955
10147
|
styleRef,
|
|
9956
|
-
|
|
10148
|
+
existsSync13(join16(styleRef, "doc.json")) ? "doc.json" : ""
|
|
9957
10149
|
) : null;
|
|
9958
|
-
if (file &&
|
|
10150
|
+
if (file && existsSync13(file)) {
|
|
9959
10151
|
return {
|
|
9960
10152
|
from: file,
|
|
9961
|
-
doc: JSON.parse(await
|
|
10153
|
+
doc: JSON.parse(await readFile13(file, "utf8"))
|
|
9962
10154
|
};
|
|
9963
10155
|
}
|
|
9964
10156
|
const origin = platformOrigin({
|
|
@@ -9995,7 +10187,7 @@ async function cmdDigest(argv) {
|
|
|
9995
10187
|
const take = await loadTake(dir);
|
|
9996
10188
|
if (!take.doc) throw new UsageError(`${dir} has no doc.json \u2014 run plan first`);
|
|
9997
10189
|
const transcriptPath = strFlag(flags, "transcript");
|
|
9998
|
-
const transcript = transcriptPath ? parseTranscript(JSON.parse(await
|
|
10190
|
+
const transcript = transcriptPath ? parseTranscript(JSON.parse(await readFile13(transcriptPath, "utf8"))) : null;
|
|
9999
10191
|
const style = await resolveStyleRef(flags);
|
|
10000
10192
|
const noFrames = flags["no-frames"] === true;
|
|
10001
10193
|
let browser = null;
|
|
@@ -10026,7 +10218,7 @@ async function cmdDigest(argv) {
|
|
|
10026
10218
|
kinds,
|
|
10027
10219
|
frames: d.images.full,
|
|
10028
10220
|
crops: d.images.crop,
|
|
10029
|
-
sheet: d.images.sheet ?
|
|
10221
|
+
sheet: d.images.sheet ? join16(result.outDir, d.images.sheet) : null,
|
|
10030
10222
|
scenes: kinds.scene ?? 0,
|
|
10031
10223
|
sourceDuration: d.take.sourceDuration,
|
|
10032
10224
|
outputDuration: d.take.outputDuration,
|
|
@@ -10050,14 +10242,14 @@ async function cmdOpen(argv) {
|
|
|
10050
10242
|
if (!dir) throw new UsageError("vos open <take> [--studio <url>]");
|
|
10051
10243
|
const r = createReporter(flags.json === true);
|
|
10052
10244
|
const take = await loadTake(dir);
|
|
10053
|
-
if (!
|
|
10245
|
+
if (!existsSync13(take.paths.recording)) {
|
|
10054
10246
|
throw new UsageError(`${dir} has no recording.webm \u2014 re-run record`);
|
|
10055
10247
|
}
|
|
10056
10248
|
const studio = (strFlag(flags, "studio") ?? "http://localhost:6060").replace(
|
|
10057
10249
|
/\/+$/,
|
|
10058
10250
|
""
|
|
10059
10251
|
);
|
|
10060
|
-
const server = await startTakeServer(
|
|
10252
|
+
const server = await startTakeServer(resolve8(dir), {});
|
|
10061
10253
|
const url = `${studio}/studio?take=${encodeURIComponent(server.base)}`;
|
|
10062
10254
|
r.event({ event: "open", server: server.base, url });
|
|
10063
10255
|
r.log(`take served at ${server.base}`);
|
|
@@ -10091,11 +10283,11 @@ async function cmdJudge(argv) {
|
|
|
10091
10283
|
"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)"
|
|
10092
10284
|
);
|
|
10093
10285
|
const r = createReporter(flags.json === true);
|
|
10094
|
-
const kitFile = target.endsWith("kit.json") ? target :
|
|
10095
|
-
if (!
|
|
10096
|
-
if (!
|
|
10286
|
+
const kitFile = target.endsWith("kit.json") ? target : join16(target, "kit.json");
|
|
10287
|
+
if (!existsSync13(kitFile)) throw new UsageError(`${kitFile}: no kit manifest`);
|
|
10288
|
+
if (!existsSync13(against)) throw new UsageError(`${against}: no reference manifest`);
|
|
10097
10289
|
const result = await judgeKit(kitFile, against, strFlag(flags, "out"));
|
|
10098
|
-
const verdicts = JSON.parse(await
|
|
10290
|
+
const verdicts = JSON.parse(await readFile13(result.verdictFile, "utf8")).verdicts;
|
|
10099
10291
|
const rate = winRate(verdicts);
|
|
10100
10292
|
const lines = result.sheets.map(
|
|
10101
10293
|
(s) => `${s.asset} vs ${s.reference}: ${s.sheetA}, ${s.sheetB}, ${s.rubric}`
|
|
@@ -10115,7 +10307,7 @@ async function cmdValidate(argv) {
|
|
|
10115
10307
|
const target = positionals[0];
|
|
10116
10308
|
if (!target) throw new UsageError("vos validate <actions.json|take>");
|
|
10117
10309
|
const r = createReporter(flags.json === true);
|
|
10118
|
-
const kitTarget = target.endsWith("kit.json") ? target :
|
|
10310
|
+
const kitTarget = target.endsWith("kit.json") ? target : existsSync13(join16(target, "kit.json")) && !isTakeDir(target) ? join16(target, "kit.json") : null;
|
|
10119
10311
|
if (kitTarget) {
|
|
10120
10312
|
const picture = flags.picture === true;
|
|
10121
10313
|
const verdict = await validateKit(kitTarget, { picture });
|
|
@@ -10147,9 +10339,9 @@ async function cmdValidate(argv) {
|
|
|
10147
10339
|
r.done({ valid: true, target }, `${target}: valid actions file`);
|
|
10148
10340
|
return EXIT_OK;
|
|
10149
10341
|
}
|
|
10150
|
-
if (!isTakeDir(target) &&
|
|
10342
|
+
if (!isTakeDir(target) && existsSync13(join16(target, "config.json"))) {
|
|
10151
10343
|
const parsed = JSON.parse(
|
|
10152
|
-
await
|
|
10344
|
+
await readFile13(join16(target, "config.json"), "utf8")
|
|
10153
10345
|
);
|
|
10154
10346
|
const pre = preflightConfig(parsed);
|
|
10155
10347
|
const problems2 = pre.ok ? [] : pre.issues.map((i) => `config.json: ${i}`);
|
|
@@ -10190,7 +10382,7 @@ async function cmdValidate(argv) {
|
|
|
10190
10382
|
const take = await loadTake(target);
|
|
10191
10383
|
const problems = [];
|
|
10192
10384
|
const warnings = [];
|
|
10193
|
-
if (!
|
|
10385
|
+
if (!existsSync13(take.paths.recording))
|
|
10194
10386
|
problems.push("missing recording.webm (re-run record)");
|
|
10195
10387
|
if (!take.doc) problems.push("missing doc.json (run plan)");
|
|
10196
10388
|
if (take.doc) {
|
|
@@ -10235,7 +10427,7 @@ async function cmdPush3(argv) {
|
|
|
10235
10427
|
const r = createReporter(flags.json === true);
|
|
10236
10428
|
const overrides = multi.override;
|
|
10237
10429
|
const result = await pushTake(
|
|
10238
|
-
|
|
10430
|
+
resolve8(dir),
|
|
10239
10431
|
{
|
|
10240
10432
|
key: strFlag(flags, "key"),
|
|
10241
10433
|
api: strFlag(flags, "api"),
|
|
@@ -10262,7 +10454,7 @@ async function cmdPull2(argv) {
|
|
|
10262
10454
|
const dir = positionals[0] ?? ".";
|
|
10263
10455
|
const r = createReporter(flags.json === true);
|
|
10264
10456
|
const result = await pullTake(
|
|
10265
|
-
|
|
10457
|
+
resolve8(dir),
|
|
10266
10458
|
{
|
|
10267
10459
|
key: strFlag(flags, "key"),
|
|
10268
10460
|
api: strFlag(flags, "api"),
|
|
@@ -10397,4 +10589,4 @@ export {
|
|
|
10397
10589
|
convertAgentBrowser,
|
|
10398
10590
|
run
|
|
10399
10591
|
};
|
|
10400
|
-
//# sourceMappingURL=chunk-
|
|
10592
|
+
//# sourceMappingURL=chunk-Q5NF5EN2.js.map
|