@vosjs/cli 0.50.4 → 0.52.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.
@@ -0,0 +1,776 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/flagUse.ts
4
+ var given = /* @__PURE__ */ new Set();
5
+ var used = /* @__PURE__ */ new Set();
6
+ function trackFlags(bag) {
7
+ return new Proxy(bag, {
8
+ get(target, key, receiver) {
9
+ if (typeof key === "string") used.add(key);
10
+ return Reflect.get(target, key, receiver);
11
+ },
12
+ has(target, key) {
13
+ if (typeof key === "string") used.add(key);
14
+ return Reflect.has(target, key);
15
+ },
16
+ // hasOwnProperty.call(flags, name) lands here.
17
+ getOwnPropertyDescriptor(target, key) {
18
+ if (typeof key === "string") used.add(key);
19
+ return Reflect.getOwnPropertyDescriptor(target, key);
20
+ },
21
+ // A spread, Object.keys or JSON.stringify hands every flag onward; what
22
+ // happens to them after that cannot be seen, so they all count as read.
23
+ ownKeys(target) {
24
+ for (const key of Reflect.ownKeys(target))
25
+ if (typeof key === "string") used.add(key);
26
+ return Reflect.ownKeys(target);
27
+ }
28
+ });
29
+ }
30
+ function noteGivenFlag(name) {
31
+ given.add(name);
32
+ }
33
+ function unusedFlags() {
34
+ return [...given].filter((name) => !used.has(name) && name !== "help");
35
+ }
36
+ function helpFlagsFor(help, verb) {
37
+ const out = /* @__PURE__ */ new Set();
38
+ let inVerb = false;
39
+ for (const line of help.split("\n")) {
40
+ const head = /^\s{2}vos\s+(\S+)/.exec(line);
41
+ if (head) inVerb = head[1] === verb;
42
+ else if (!/^\s{3,}\S/.test(line)) inVerb = false;
43
+ if (!inVerb) continue;
44
+ for (const m of line.matchAll(/--([a-z][a-z0-9-]*)/g)) out.add(m[1]);
45
+ }
46
+ return [...out];
47
+ }
48
+ function editDistance(a, b) {
49
+ const row = Array.from({ length: b.length + 1 }, (_, i) => i);
50
+ for (let i = 1; i <= a.length; i++) {
51
+ let prev = row[0];
52
+ row[0] = i;
53
+ for (let j = 1; j <= b.length; j++) {
54
+ const keep = row[j];
55
+ row[j] = Math.min(
56
+ row[j] + 1,
57
+ row[j - 1] + 1,
58
+ prev + (a[i - 1] === b[j - 1] ? 0 : 1)
59
+ );
60
+ prev = keep;
61
+ }
62
+ }
63
+ return row[b.length];
64
+ }
65
+ function nearestFlag(name, candidates) {
66
+ let best = null;
67
+ let bestScore = Infinity;
68
+ for (const c of candidates) {
69
+ if (c === name) continue;
70
+ const d = c.includes(name) || name.includes(c) ? 1 : editDistance(name, c);
71
+ if (d < bestScore) {
72
+ bestScore = d;
73
+ best = c;
74
+ }
75
+ }
76
+ return best !== null && bestScore <= Math.max(2, Math.floor(name.length / 3)) ? best : null;
77
+ }
78
+ function unusedFlagsMessage(verb, documented) {
79
+ const unused = unusedFlags();
80
+ if (!unused.length) return null;
81
+ const parts = unused.map((name) => {
82
+ const near = nearestFlag(name, documented);
83
+ return `--${name}${near ? ` (did you mean --${near}?)` : ""}`;
84
+ });
85
+ const list = documented.length ? ` It reads: ${documented.map((f) => `--${f}`).join(" ")}.` : "";
86
+ return `vos ${verb} RAN, and ignored ${parts.join(", ")}: ${unused.length > 1 ? "they are" : "it is"} not something this verb reads, so what it printed above was made without ${unused.length > 1 ? "them" : "it"}.${list}`;
87
+ }
88
+
89
+ // src/args.ts
90
+ var UsageError = class extends Error {
91
+ };
92
+ function parseArgs(argv, booleanFlags) {
93
+ const positionals = [];
94
+ const flags = {};
95
+ for (let i = 0; i < argv.length; i++) {
96
+ const arg = argv[i];
97
+ if (arg === "--") {
98
+ positionals.push(...argv.slice(i + 1));
99
+ break;
100
+ }
101
+ if (arg.startsWith("--")) {
102
+ const eq = arg.indexOf("=");
103
+ if (eq !== -1) {
104
+ flags[arg.slice(2, eq)] = arg.slice(eq + 1);
105
+ continue;
106
+ }
107
+ const name = arg.slice(2);
108
+ if (booleanFlags.has(name)) {
109
+ flags[name] = true;
110
+ continue;
111
+ }
112
+ const next = argv[i + 1];
113
+ if (next === void 0 || next.startsWith("--")) {
114
+ throw new UsageError(`--${name} expects a value`);
115
+ }
116
+ flags[name] = next;
117
+ i++;
118
+ continue;
119
+ }
120
+ positionals.push(arg);
121
+ }
122
+ for (const name of Object.keys(flags)) noteGivenFlag(name);
123
+ return { positionals, flags: trackFlags(flags) };
124
+ }
125
+ function numFlag(flags, name, fallback) {
126
+ const v = flags[name];
127
+ if (v === void 0) return fallback;
128
+ const n = Number(v);
129
+ if (!Number.isFinite(n))
130
+ throw new UsageError(`--${name} expects a number, got "${String(v)}"`);
131
+ return n;
132
+ }
133
+
134
+ // src/loadConfig.ts
135
+ import { readFile } from "fs/promises";
136
+ import { existsSync, readFileSync, statSync } from "fs";
137
+ import { join } from "path";
138
+ import {
139
+ CURRENT_CONFIG_VERSION,
140
+ migrateConfig,
141
+ vosConfigJsonSchema
142
+ } from "@vosjs/core";
143
+ function directoryKind(target) {
144
+ try {
145
+ if (!statSync(target).isDirectory()) return "none";
146
+ } catch {
147
+ return "none";
148
+ }
149
+ const docPath = join(target, "doc.json");
150
+ if (existsSync(docPath)) {
151
+ try {
152
+ const doc = JSON.parse(readFileSync(docPath, "utf8"));
153
+ if (typeof doc === "object" && doc !== null && "source" in doc)
154
+ return "take";
155
+ } catch {
156
+ return "take";
157
+ }
158
+ }
159
+ return existsSync(join(target, "config.json")) ? "program" : "none";
160
+ }
161
+ async function loadProgramDirectory(dir, warnings) {
162
+ const config = JSON.parse(
163
+ await readFile(join(dir, "config.json"), "utf8")
164
+ );
165
+ const docPath = join(dir, "doc.json");
166
+ if (!existsSync(docPath)) return config;
167
+ const doc = JSON.parse(await readFile(docPath, "utf8"));
168
+ if (typeof doc !== "object" || doc === null || "source" in doc) return config;
169
+ if (typeof config !== "object" || config === null) {
170
+ throw new UsageError(`${dir}/config.json does not contain a JSON object`);
171
+ }
172
+ const raw = doc;
173
+ const program = raw.program && typeof raw.program === "object" ? raw.program : {};
174
+ const { lowerProgramDoc } = await import("@vosjs/studio-core");
175
+ const composed = lowerProgramDoc(
176
+ { ...raw, program: { ...program, config } },
177
+ { bake: true }
178
+ ).config;
179
+ warnings.push(
180
+ "composed config.json with doc.json (a program document: its layers and tween edits ride the render)"
181
+ );
182
+ return composed;
183
+ }
184
+ async function readSourceText(source) {
185
+ if (/^https?:\/\//.test(source)) {
186
+ const res = await fetch(source);
187
+ if (!res.ok) throw new Error(`fetch ${source} \u2192 ${res.status}`);
188
+ return await res.text();
189
+ }
190
+ let isDir = false;
191
+ try {
192
+ isDir = statSync(source).isDirectory();
193
+ } catch {
194
+ }
195
+ if (isDir) {
196
+ throw new UsageError(
197
+ `${source} is a directory with no config.json and no doc.json \u2014 pass a config file, a URL, a program directory or a take`
198
+ );
199
+ }
200
+ return await readFile(source, "utf8");
201
+ }
202
+ async function loadVosConfig(source) {
203
+ const warnings = [];
204
+ let parsed;
205
+ const kind = /^https?:\/\//.test(source) ? "none" : directoryKind(source);
206
+ if (kind === "take") {
207
+ throw new UsageError(
208
+ `${source} is a take (its doc.json is a recording document). Render it through the take pipeline: vos render ${source} [out]; vos frames ${source} for stills.`
209
+ );
210
+ }
211
+ if (kind === "program") {
212
+ parsed = await loadProgramDirectory(source, warnings);
213
+ return finish(source, parsed, warnings);
214
+ }
215
+ const raw = await readSourceText(source);
216
+ try {
217
+ parsed = JSON.parse(raw);
218
+ } catch (e) {
219
+ throw new UsageError(`${source} is not valid JSON: ${e.message}`);
220
+ }
221
+ return finish(source, parsed, warnings);
222
+ }
223
+ function finish(source, parsed, warnings) {
224
+ if (typeof parsed !== "object" || parsed === null) {
225
+ throw new UsageError(`${source} does not contain a JSON object`);
226
+ }
227
+ let obj = parsed;
228
+ if (typeof obj.config === "object" && obj.config !== null && !("createTimeline" in obj)) {
229
+ obj = obj.config;
230
+ warnings.push("unwrapped { config } envelope");
231
+ }
232
+ const version = obj.version;
233
+ const migrated = migrateConfig(obj);
234
+ if (version === void 0) {
235
+ warnings.push(
236
+ `config declares no "version". Add "version": ${CURRENT_CONFIG_VERSION} before pushing it.`
237
+ );
238
+ } else if (version !== CURRENT_CONFIG_VERSION) {
239
+ warnings.push(
240
+ `migrated config v${String(version)} to v${CURRENT_CONFIG_VERSION}`
241
+ );
242
+ }
243
+ const check = vosConfigJsonSchema.safeParse(migrated);
244
+ if (!check.success) {
245
+ const issues = check.error.issues.slice(0, 5).map((i) => ` ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
246
+ throw new UsageError(`invalid vos config:
247
+ ${issues}`);
248
+ }
249
+ return { config: migrated, warnings };
250
+ }
251
+ function configDuration(config) {
252
+ const d = config.duration;
253
+ return typeof d === "number" && Number.isFinite(d) && d > 0 ? d : void 0;
254
+ }
255
+
256
+ // src/browser.ts
257
+ import { chromium } from "playwright";
258
+ var BrowserUnavailableError = class extends Error {
259
+ constructor(cause) {
260
+ super(
261
+ `Could not launch a Chromium-family browser (${cause}).
262
+ Fixes, in order of preference:
263
+ 1. Install Google Chrome (used automatically), or
264
+ 2. npx playwright install chromium, or
265
+ 3. Set VOS_BROWSER_PATH to a Chrome/Chromium executable.`
266
+ );
267
+ }
268
+ };
269
+ async function launchBrowser(args = []) {
270
+ const opts = args.length ? { args } : {};
271
+ const explicit = process.env.VOS_BROWSER_PATH;
272
+ if (explicit) {
273
+ return chromium.launch({ ...opts, executablePath: explicit }).catch((e) => {
274
+ throw new BrowserUnavailableError(
275
+ `VOS_BROWSER_PATH failed: ${e.message}`
276
+ );
277
+ });
278
+ }
279
+ try {
280
+ return await chromium.launch({ ...opts, channel: "chrome" });
281
+ } catch {
282
+ try {
283
+ return await chromium.launch(opts);
284
+ } catch (e) {
285
+ throw new BrowserUnavailableError(
286
+ e.message.split("\n")[0] ?? "unknown"
287
+ );
288
+ }
289
+ }
290
+ }
291
+
292
+ // src/plugin/container.ts
293
+ var OCTET_STREAM = "application/octet-stream";
294
+ var TYPES_BY_EXTENSION = {
295
+ svg: "image/svg+xml",
296
+ png: "image/png",
297
+ jpg: "image/jpeg",
298
+ jpeg: "image/jpeg",
299
+ webp: "image/webp",
300
+ gif: "image/gif",
301
+ avif: "image/avif",
302
+ webm: "video/webm",
303
+ mp4: "video/mp4",
304
+ mov: "video/quicktime",
305
+ mp3: "audio/mpeg",
306
+ wav: "audio/wav",
307
+ ogg: "audio/ogg",
308
+ m4a: "audio/mp4"
309
+ };
310
+ var EXTENSION_BY_TYPE = {
311
+ "image/jpeg": ".jpg",
312
+ "video/mp4": ".mp4",
313
+ "audio/mp4": ".m4a"
314
+ };
315
+ var TAKE_MEDIA_EXTENSIONS = [
316
+ ".webm",
317
+ ".mp4",
318
+ ".mov",
319
+ ".m4a",
320
+ ".mp3",
321
+ ".ogg",
322
+ ".wav"
323
+ ];
324
+ var MEDIA_HEAD_BYTES = 16;
325
+ var ISO_BRANDS = {
326
+ "qt ": "video/quicktime",
327
+ "M4A ": "audio/mp4",
328
+ "M4B ": "audio/mp4",
329
+ avif: "image/avif",
330
+ avis: "image/avif"
331
+ };
332
+ var RIFF_FORMS = {
333
+ WEBP: "image/webp",
334
+ WAVE: "audio/wav",
335
+ "AVI ": "video/x-msvideo"
336
+ };
337
+ function sniffMediaType(head) {
338
+ const ascii = (at, len) => head.length >= at + len ? String.fromCharCode(...head.subarray(at, at + len)) : "";
339
+ const startsWith = (...bytes) => bytes.every((b, i) => head[i] === b);
340
+ if (startsWith(26, 69, 223, 163)) return "video/webm";
341
+ if (ascii(4, 4) === "ftyp") return ISO_BRANDS[ascii(8, 4)] ?? "video/mp4";
342
+ if (ascii(0, 4) === "RIFF") return RIFF_FORMS[ascii(8, 4)] ?? null;
343
+ if (startsWith(137, 80, 78, 71, 13, 10, 26, 10))
344
+ return "image/png";
345
+ if (startsWith(255, 216, 255)) return "image/jpeg";
346
+ if (ascii(0, 4) === "GIF8") return "image/gif";
347
+ if (ascii(0, 4) === "OggS") return "audio/ogg";
348
+ if (ascii(0, 3) === "ID3") return "audio/mpeg";
349
+ return null;
350
+ }
351
+ function bareType(value) {
352
+ const type = (value ?? "").split(";")[0].trim().toLowerCase();
353
+ return !type || type === OCTET_STREAM || type === "binary/octet-stream" ? "" : type;
354
+ }
355
+ function mediaContentType(file) {
356
+ const ext = (/\.([a-z0-9]+)$/i.exec(file)?.[1] ?? "").toLowerCase();
357
+ return TYPES_BY_EXTENSION[ext] ?? OCTET_STREAM;
358
+ }
359
+ function extensionFor(contentType) {
360
+ const type = (contentType ?? "").split(";")[0].trim().toLowerCase();
361
+ if (EXTENSION_BY_TYPE[type]) return EXTENSION_BY_TYPE[type];
362
+ const ext = Object.keys(TYPES_BY_EXTENSION).find(
363
+ (e) => TYPES_BY_EXTENSION[e] === type
364
+ );
365
+ return ext ? `.${ext}` : "";
366
+ }
367
+ function resolveMediaType(claims) {
368
+ const sniffed = claims.head?.length ? sniffMediaType(claims.head) : null;
369
+ if (sniffed) return { type: sniffed, from: "bytes" };
370
+ const declared = bareType(claims.declared);
371
+ if (declared) return { type: declared, from: "declared" };
372
+ const named = mediaContentType(claims.filename ?? "");
373
+ if (named !== OCTET_STREAM) return { type: named, from: "filename" };
374
+ return { type: OCTET_STREAM, from: "unknown" };
375
+ }
376
+ function nameForType(filename, type) {
377
+ const ext = extensionFor(type);
378
+ if (!ext) return filename;
379
+ const current = /\.[a-z0-9]+$/i.exec(filename)?.[0] ?? "";
380
+ if (current && mediaContentType(`x${current}`) === type) return filename;
381
+ return `${filename.slice(0, filename.length - current.length)}${ext}`;
382
+ }
383
+ function containerMismatch(filename, head) {
384
+ const actual = sniffMediaType(head);
385
+ if (!actual) return null;
386
+ const claimed = mediaContentType(filename);
387
+ if (claimed === actual) return null;
388
+ return `${filename} is ${actual}, not the ${claimed} its name claims \u2014 rename it to ${nameForType(filename, actual)}`;
389
+ }
390
+
391
+ // src/outputs.ts
392
+ function extOf(path) {
393
+ return /\.([a-z0-9]+)$/i.exec(path ?? "")?.[1]?.toLowerCase() ?? "";
394
+ }
395
+ function videoFormat(flag, out) {
396
+ const named = extOf(out);
397
+ const fromName = named === "mp4" ? "mp4" : named === "webm" ? "webm" : null;
398
+ if (flag !== void 0) {
399
+ if (flag !== "webm" && flag !== "mp4")
400
+ throw new UsageError("--format must be webm or mp4");
401
+ if (fromName && fromName !== flag)
402
+ throw new UsageError(
403
+ `--format ${flag} contradicts the output name ${out}: name it .${flag}, or drop --format and the name decides`
404
+ );
405
+ return flag;
406
+ }
407
+ return fromName ?? "webm";
408
+ }
409
+ function stillFormat(out) {
410
+ const ext = extOf(out);
411
+ if (ext === "png") return "png";
412
+ if (ext === "jpg" || ext === "jpeg") return "jpeg";
413
+ if (ext === "webp" || ext === "") return "webp";
414
+ throw new UsageError(
415
+ `vos still writes .webp, .png or .jpg, not .${ext}: rename ${out}`
416
+ );
417
+ }
418
+ function parseTimes(raw, duration) {
419
+ const times = raw.split(",").map((s) => s.trim()).filter(Boolean).map((s) => {
420
+ const pct = /^(\d+(?:\.\d+)?)%$/.exec(s);
421
+ const t = pct ? Number(pct[1]) / 100 * duration : Number(s);
422
+ if (!Number.isFinite(t) || t < 0)
423
+ throw new UsageError(
424
+ `--times expects seconds or percentages (0,1.5,50%), got "${s}"`
425
+ );
426
+ return Math.min(t, Math.max(0, duration - 1 / 60));
427
+ });
428
+ if (!times.length) throw new UsageError("--times needs at least one time");
429
+ return times;
430
+ }
431
+ function stillOutFor(out, time, many) {
432
+ if (!many) return out;
433
+ const m = /^(.*?)(\.[a-z0-9]+)?$/i.exec(out);
434
+ return `${m?.[1] ?? out}-${time.toFixed(2)}s${m?.[2] ?? ""}`;
435
+ }
436
+ function setFlags(argv) {
437
+ const out = [];
438
+ for (let i = 0; i < argv.length; i++) {
439
+ const a = argv[i];
440
+ if (a === "--set" && argv[i + 1] !== void 0) out.push(argv[++i]);
441
+ else if (a.startsWith("--set=")) out.push(a.slice("--set=".length));
442
+ }
443
+ return out;
444
+ }
445
+ function applyDataSets(config, sets) {
446
+ if (!sets.length) return config;
447
+ const data = {
448
+ ...config.data ?? {}
449
+ };
450
+ for (const set of sets) {
451
+ const eq = set.indexOf("=");
452
+ const path = eq === -1 ? set : set.slice(0, eq);
453
+ const m = /^data\.([A-Za-z0-9_$-]+)$/.exec(path);
454
+ if (eq === -1 || !m)
455
+ throw new UsageError(
456
+ `--set on a program takes data.<key>=<value> (a knob's value), got "${set}"`
457
+ );
458
+ const raw = set.slice(eq + 1);
459
+ let value = raw;
460
+ try {
461
+ value = JSON.parse(raw);
462
+ } catch {
463
+ }
464
+ data[m[1]] = value;
465
+ }
466
+ return { ...config, data };
467
+ }
468
+
469
+ // src/plugin/args.ts
470
+ function parseArgs2(argv, booleanFlags, multiFlags = /* @__PURE__ */ new Set()) {
471
+ const positionals = [];
472
+ const flags = {};
473
+ const multi = {};
474
+ const put = (name, value) => {
475
+ if (multiFlags.has(name)) (multi[name] ??= []).push(value);
476
+ else flags[name] = value;
477
+ };
478
+ for (let i = 0; i < argv.length; i++) {
479
+ const arg = argv[i];
480
+ if (arg === "--") {
481
+ positionals.push(...argv.slice(i + 1));
482
+ break;
483
+ }
484
+ if (arg.startsWith("--")) {
485
+ const eq = arg.indexOf("=");
486
+ if (eq !== -1) {
487
+ put(arg.slice(2, eq), arg.slice(eq + 1));
488
+ continue;
489
+ }
490
+ const name = arg.slice(2);
491
+ if (booleanFlags.has(name)) {
492
+ flags[name] = true;
493
+ continue;
494
+ }
495
+ if (i + 1 >= argv.length || argv[i + 1].startsWith("--")) {
496
+ throw new UsageError(`--${name} expects a value`);
497
+ }
498
+ put(name, argv[i + 1]);
499
+ i++;
500
+ continue;
501
+ }
502
+ positionals.push(arg);
503
+ }
504
+ for (const name of Object.keys(flags)) noteGivenFlag(name);
505
+ for (const name of Object.keys(multi)) noteGivenFlag(name);
506
+ return { positionals, flags: trackFlags(flags), multi: trackFlags(multi) };
507
+ }
508
+ function numFlag2(flags, name, fallback) {
509
+ const v = flags[name];
510
+ if (typeof v !== "string") return fallback;
511
+ const n = Number(v);
512
+ if (!Number.isFinite(n))
513
+ throw new UsageError(`--${name} expects a number, got "${v}"`);
514
+ return n;
515
+ }
516
+ function strFlag(flags, name) {
517
+ const v = flags[name];
518
+ return typeof v === "string" ? v : void 0;
519
+ }
520
+ function hasFlag(flags, name) {
521
+ return Object.prototype.hasOwnProperty.call(flags, name);
522
+ }
523
+
524
+ // src/plugin/platform.ts
525
+ import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
526
+ import { homedir } from "os";
527
+ import { join as join2 } from "path";
528
+
529
+ // src/plugin/hostAgent.ts
530
+ import { existsSync as existsSync2 } from "fs";
531
+ var SLUG = /^[a-z0-9][a-z0-9.-]{0,39}$/;
532
+ var MARKERS = [
533
+ ["cursor", (e) => !!e.CURSOR_TRACE_ID],
534
+ [
535
+ "cursor-cli",
536
+ (e) => !!e.CURSOR_AGENT || e.CURSOR_EXTENSION_HOST_ROLE === "agent-exec"
537
+ ],
538
+ ["gemini-cli", (e) => !!e.GEMINI_CLI],
539
+ ["codex", (e) => !!(e.CODEX_SANDBOX || e.CODEX_CI || e.CODEX_THREAD_ID)],
540
+ ["antigravity", (e) => !!e.ANTIGRAVITY_AGENT],
541
+ ["augment-cli", (e) => !!e.AUGMENT_AGENT],
542
+ ["opencode", (e) => !!e.OPENCODE_CLIENT],
543
+ [
544
+ "claude-code",
545
+ (e) => !!(e.CLAUDECODE || e.CLAUDE_CODE) && !e.CLAUDE_CODE_IS_COWORK
546
+ ],
547
+ ["claude-cowork", (e) => !!(e.CLAUDECODE || e.CLAUDE_CODE)],
548
+ ["replit", (e) => !!e.REPL_ID],
549
+ [
550
+ "github-copilot",
551
+ (e) => !!(e.COPILOT_MODEL || e.COPILOT_ALLOW_ALL || e.COPILOT_GITHUB_TOKEN)
552
+ ]
553
+ ];
554
+ var DEVIN_LOCAL_PATH = "/opt/.devin";
555
+ function slugFromAiAgent(value) {
556
+ const head = value.trim().toLowerCase().split("_")[0] ?? "";
557
+ if (head === "github-copilot-cli") return "github-copilot";
558
+ return SLUG.test(head) ? head : null;
559
+ }
560
+ function detectHostAgent(env = process.env, exists = existsSync2) {
561
+ if (env.AI_AGENT) {
562
+ const slug = slugFromAiAgent(env.AI_AGENT);
563
+ if (slug) return slug;
564
+ }
565
+ for (const [slug, test] of MARKERS) if (test(env)) return slug;
566
+ try {
567
+ if (exists(DEVIN_LOCAL_PATH)) return "devin";
568
+ } catch {
569
+ }
570
+ return null;
571
+ }
572
+
573
+ // src/plugin/platform.ts
574
+ function platformOrigin(flags = {}) {
575
+ const legacyEnv = process.env.VOS_API_BASE?.trim();
576
+ const raw = flags.origin ?? flags.api ?? process.env.VOS_ORIGIN?.trim() ?? legacyEnv ?? "https://vos.so";
577
+ if (!flags.origin && !flags.api && !process.env.VOS_ORIGIN && legacyEnv) {
578
+ process.stderr.write(
579
+ "note: VOS_API_BASE is deprecated \u2014 set VOS_ORIGIN (an origin, no /api suffix)\n"
580
+ );
581
+ }
582
+ return raw.replace(/\/+$/, "").replace(/\/api$/, "");
583
+ }
584
+ function parseVosId(input) {
585
+ if (!/^https?:\/\//.test(input)) {
586
+ if (/^[A-Za-z0-9_-]+$/.test(input)) return input;
587
+ throw new UsageError(`"${input}" is not a vos id or URL`);
588
+ }
589
+ let url;
590
+ try {
591
+ url = new URL(input);
592
+ } catch {
593
+ throw new UsageError(`"${input}" is not a valid URL`);
594
+ }
595
+ const query = url.searchParams.get("vos");
596
+ if (query) return query;
597
+ const path = url.pathname.match(/\/vos\/([A-Za-z0-9_-]+)/);
598
+ if (path) return path[1];
599
+ throw new UsageError(
600
+ `could not find a vos id in ${input} \u2014 expected /vos/{id} or ?vos={id}`
601
+ );
602
+ }
603
+ function deriveSlug(title) {
604
+ return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50).replace(/-+$/, "") || "remix";
605
+ }
606
+ var CREDENTIALS_PATH = join2(homedir(), ".config", "vos", "credentials");
607
+ function resolveCredential(explicit) {
608
+ const flag = explicit?.trim();
609
+ if (flag) return flag;
610
+ const env = process.env.VOS_API_KEY?.trim();
611
+ if (env) return env;
612
+ try {
613
+ const first = readFileSync2(CREDENTIALS_PATH, "utf8").split("\n")[0].trim();
614
+ return first || null;
615
+ } catch {
616
+ return null;
617
+ }
618
+ }
619
+ function requireCredential(explicit) {
620
+ const key = resolveCredential(explicit);
621
+ if (!key) {
622
+ throw new Error(
623
+ "no credential found \u2014 pass --key, set VOS_API_KEY, or run `vos login` (mint a content key at https://vos.so/app/api; a vos_rg_ remix grant works too)"
624
+ );
625
+ }
626
+ return key;
627
+ }
628
+ function writeCredential(key) {
629
+ mkdirSync(join2(homedir(), ".config", "vos"), { recursive: true, mode: 448 });
630
+ writeFileSync(CREDENTIALS_PATH, `${key.trim()}
631
+ `, { mode: 384 });
632
+ return CREDENTIALS_PATH;
633
+ }
634
+ function clientId(env = process.env, exists) {
635
+ const explicit = env.VOS_CLIENT?.trim();
636
+ if (explicit && explicit.length <= 60 && !/[\r\n]/.test(explicit))
637
+ return explicit;
638
+ const host = detectHostAgent(env, exists);
639
+ return host ? `${host} vos-cli` : "vos-cli";
640
+ }
641
+ async function apiJson(origin, path, init = {}) {
642
+ const headers = {
643
+ accept: "application/json",
644
+ ...init.headers
645
+ };
646
+ if (init.key) headers.authorization = `Bearer ${init.key}`;
647
+ if (init.body !== void 0) headers["content-type"] = "application/json";
648
+ const reqBody = init.raw !== void 0 ? init.raw : init.body === void 0 ? void 0 : JSON.stringify(init.body);
649
+ const res = await fetch(`${origin}${path}`, {
650
+ method: init.method ?? "GET",
651
+ headers,
652
+ body: reqBody
653
+ });
654
+ let body = {};
655
+ try {
656
+ body = await res.json();
657
+ } catch {
658
+ }
659
+ return { status: res.status, body };
660
+ }
661
+ function apiError(what, r) {
662
+ const detail = typeof r.body.error === "string" ? r.body.error : r.body.details !== void 0 ? JSON.stringify(r.body.details) : "";
663
+ const hint = r.status === 401 ? " (run `vos login`, or set VOS_API_KEY \u2014 mint a key at https://vos.so/app/api)" : "";
664
+ return `${what} \u2192 ${r.status}${detail ? `: ${detail}` : ""}${hint}`;
665
+ }
666
+ var SYNC_STATE_NAME = "vos.json";
667
+ function readJsonFile(file) {
668
+ try {
669
+ return JSON.parse(readFileSync2(file, "utf8"));
670
+ } catch {
671
+ return null;
672
+ }
673
+ }
674
+ function readSyncState(dir) {
675
+ const own = readJsonFile(join2(dir, SYNC_STATE_NAME));
676
+ if (own && typeof own.vosId === "string") {
677
+ return {
678
+ vosId: own.vosId,
679
+ versionId: typeof own.versionId === "string" ? own.versionId : null,
680
+ ...typeof own.pushedAt === "string" ? { pushedAt: own.pushedAt } : {},
681
+ ...typeof own.title === "string" ? { title: own.title } : {},
682
+ ...typeof own.slug === "string" ? { slug: own.slug } : {},
683
+ ...typeof own.remixOfId === "string" ? { remixOfId: own.remixOfId } : {}
684
+ };
685
+ }
686
+ const push = readJsonFile(join2(dir, "push.json"));
687
+ if (push && typeof push.vosId === "string") {
688
+ return {
689
+ vosId: push.vosId,
690
+ versionId: typeof push.versionId === "string" ? push.versionId : null,
691
+ ...typeof push.pushedAt === "string" ? { pushedAt: push.pushedAt } : {}
692
+ };
693
+ }
694
+ const meta = readJsonFile(join2(dir, "meta.json"));
695
+ if (meta && typeof meta.id === "string") {
696
+ return {
697
+ vosId: meta.id,
698
+ versionId: typeof meta.currentVersionId === "string" ? meta.currentVersionId : null,
699
+ ...typeof meta.title === "string" ? { title: meta.title } : {},
700
+ ...typeof meta.slug === "string" ? { slug: meta.slug } : {},
701
+ ...typeof meta.remixOfId === "string" ? { remixOfId: meta.remixOfId } : {}
702
+ };
703
+ }
704
+ return null;
705
+ }
706
+ function writeSyncState(dir, patch) {
707
+ const merged = {
708
+ ...readSyncState(dir) ?? { versionId: null },
709
+ ...patch
710
+ };
711
+ writeFileSync(
712
+ join2(dir, SYNC_STATE_NAME),
713
+ `${JSON.stringify(merged, null, 2)}
714
+ `
715
+ );
716
+ return merged;
717
+ }
718
+ function formatChanges(changes) {
719
+ const lines = [];
720
+ for (const ch of changes) {
721
+ const label = ch.label ? ` \xB7 ${ch.label}` : "";
722
+ const who = ch.origin === "studio" ? "human" : ch.origin ?? "unknown";
723
+ lines.push(
724
+ `v${String(ch.versionNumber ?? "?")} (${who}${label}): ${ch.summary ?? ""}`
725
+ );
726
+ if (ch.note) lines.push(` note: ${ch.note}`);
727
+ }
728
+ return lines;
729
+ }
730
+
731
+ export {
732
+ helpFlagsFor,
733
+ unusedFlagsMessage,
734
+ UsageError,
735
+ parseArgs,
736
+ numFlag,
737
+ directoryKind,
738
+ loadProgramDirectory,
739
+ readSourceText,
740
+ loadVosConfig,
741
+ configDuration,
742
+ BrowserUnavailableError,
743
+ launchBrowser,
744
+ TAKE_MEDIA_EXTENSIONS,
745
+ MEDIA_HEAD_BYTES,
746
+ sniffMediaType,
747
+ extensionFor,
748
+ resolveMediaType,
749
+ nameForType,
750
+ containerMismatch,
751
+ videoFormat,
752
+ stillFormat,
753
+ parseTimes,
754
+ stillOutFor,
755
+ setFlags,
756
+ applyDataSets,
757
+ parseArgs2,
758
+ numFlag2,
759
+ strFlag,
760
+ hasFlag,
761
+ platformOrigin,
762
+ parseVosId,
763
+ deriveSlug,
764
+ CREDENTIALS_PATH,
765
+ resolveCredential,
766
+ requireCredential,
767
+ writeCredential,
768
+ clientId,
769
+ apiJson,
770
+ apiError,
771
+ SYNC_STATE_NAME,
772
+ readSyncState,
773
+ writeSyncState,
774
+ formatChanges
775
+ };
776
+ //# sourceMappingURL=chunk-KSEU3YK5.js.map