@vosjs/cli 0.60.0 → 0.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -328,13 +328,13 @@ vos pull bright-loop [--since <versionId>] [--check] # what changed on vos.so
328
328
  # --check reports without writing; --since walks from a base you name
329
329
  vos duplicate <vosId> # a private sibling of your OWN vos (someone else's is remixed: fetch, then push --remix-of)
330
330
  vos folder list | create <name> [--parent] [--desc] | move <ids…> --to <folder|none> | pull <ref> [--media]
331
- vos asset push <file…> [--folder <slug>] | rename <id> <name.ext> # sound and video too: they land in Sound > Uploads
331
+ vos asset push <file…> [--folder <slug>] | rename <id> <name.ext> # any kind the platform takes (GET /api/limits): models, pictures, SVG, fonts, HDR, sound, video
332
332
  vos recipe push <FILE.md> --folder <slug> | --asset <id> # the one recipe write: create, or replace in place
333
333
  ```
334
334
 
335
335
  `push` is polymorphic by a deterministic sniff, never a flag: a take directory (a `doc.json` carrying `source`) pushes recording and document through the take pipeline; a `config.json` (or a directory holding one) pushes the program, and a `doc.json` beside it that carries `program` (a program document: overlays, objects, audio, speed, tween edits, its own length; `program.config` omitted on disk) rides along, lint-gated. `fetch` and `pull` write a program document back the same way. `pull` takes `--since <versionId>` (walk the changelog from that base instead of the tracked one) and `--check` (print what changed and stop: nothing on disk moves) on both paths. A program push LANDS where `vos.json` says: a directory that tracks a vos iterates it, so the fetch, edit, check, push, pull loop needs no flags, and making something separate from the same config is the explicit `--remix-of` door. Program pushes take `--vos`, `--title`, `--slug`, `--desc`, `--tags`, `--folder`, `--remix-of`, `--base`, `--label`, `--note`, `--override <id>` (repeatable) and `--claimable`; take pushes take `--title`, `--label`, `--note`, `--folder`, `--override` and `--yes`. Both take `--wait`. A new vos pushed without `--folder` lands unfiled, at the root of the shelf, and the done line says so.
336
336
 
337
- **A program's files are declared, never typed into a function.** `config.json` names each file it uses in `assets` (`"assets": { "logo": { "ref": "./logo.png", "kind": "image" }, "shots": { "ref": ["./shots/1.png", "./shots/2.png"] } }`) and reads `ctx.assets.logo`, or `"$assets.logo"` in an element, object or font. On disk a `ref` is a path beside the config, so `vos render`, `vos still` and `vos preview` serve the file to the page from disk. `vos push` uploads each local file (content-addressed, so a re-push sends nothing) and what it stores names the hosted file as `asset:<id>`, the one spelling the platform resolves for every render; the config on disk keeps its paths. `vos fetch --media` brings a program's hosted files home under `assets/` and re-points the manifest at them; without `--media` the manifest keeps `asset:<id>` and a local render reads each file with your key. `vos check` says when a `"$assets.<name>"` names nothing, when a declared path is not a file, and when a function names a hosted file in code instead of the manifest: a file named only in code does not load on a render of a private program and does not follow a remix. A `--claimable` push carries no files, so it is refused when the manifest names local ones.
337
+ **A program's files are declared, never typed into a function.** `config.json` names each file it uses in `assets` (`"assets": { "logo": { "ref": "./logo.png", "kind": "image" }, "shots": { "ref": ["./shots/1.png", "./shots/2.png"] } }`) and reads `ctx.assets.logo`, or `"$assets.logo"` in an element, object or font. On disk a `ref` is a path in the config's own directory or below, so `vos render`, `vos still` and `vos preview` serve the file to the page from disk. A path that climbs out of that directory (`../`, an absolute path, a link that points away) is not served and not uploaded, and a config loaded from a URL reads no local file: a program's code runs in a page, and what the page is served the program can send anywhere. `vos push` uploads each local file (content-addressed, so a re-push sends nothing; pictures, video, sound and `.glb` models go, while a font or an HDR is named by URL) and what it stores names the hosted file as `asset:<id>`, the one spelling the platform resolves for every render; the config on disk keeps its paths. `vos fetch --media` brings a program's hosted files home under `assets/` and re-points the manifest at them (`assets/.hosted.json` remembers which hosted file each one is, so a later fetch replaces one that changed); without `--media` the manifest keeps `asset:<id>` and a local render reads each file with your key. `vos check` says when a `"$assets.<name>"` names nothing, when a declared path is not a file, and when a function names a hosted file in code instead of the manifest: a file named only in code does not load on a render of a private program and does not follow a remix. A `--claimable` push carries no files, so it is refused when the manifest names local ones.
338
338
 
339
339
  **Sound is a track, never an element.** A document's added sound lives in `doc.json` (`audio: [{ id, key, name, start, in, out, duration, gain, fadeIn, fadeOut }]`) for a program as for a take: `vos push` uploads each local file its keys name and keys the hosted asset; `vos render` of a program mixes it locally; the studio shows it as a track with gain and fades. An `audio` element embedded in the config is not a track (renders do not mix it, and a `data:` source can push a config past what a claimable push carries); `vos check` says so. `vos asset push score.ogg` uploads a sound on its own and prints the url a key takes; an unfiled sound that no document uses is removed after 7 days, so pass `--folder` or reference it.
340
340
 
@@ -4,12 +4,12 @@ import {
4
4
  localManifestRefs,
5
5
  pullManifest,
6
6
  uploadManifest
7
- } from "./chunk-PUXY32AP.js";
8
- import "./chunk-36P5RREC.js";
7
+ } from "./chunk-EZW6ZCEH.js";
8
+ import "./chunk-WCF6KIOK.js";
9
9
  export {
10
10
  hostedLiteralWarnings,
11
11
  localManifestRefs,
12
12
  pullManifest,
13
13
  uploadManifest
14
14
  };
15
- //# sourceMappingURL=assetManifest-MM5WGOWG.js.map
15
+ //# sourceMappingURL=assetManifest-NS64PLY5.js.map
@@ -9,7 +9,7 @@ import {
9
9
  reencodeStill,
10
10
  renderStill,
11
11
  videoFormat
12
- } from "./chunk-OMDRILG7.js";
12
+ } from "./chunk-LWHVZ5WI.js";
13
13
  import {
14
14
  PREV_DOC_NAME,
15
15
  RECORDING_NAME,
@@ -34,7 +34,7 @@ import {
34
34
  uploadDocRefs,
35
35
  uploadManifest,
36
36
  writeJson
37
- } from "./chunk-PUXY32AP.js";
37
+ } from "./chunk-EZW6ZCEH.js";
38
38
  import {
39
39
  CREDENTIALS_PATH,
40
40
  MEDIA_HEAD_BYTES,
@@ -64,7 +64,7 @@ import {
64
64
  strFlag,
65
65
  writeCredential,
66
66
  writeSyncState
67
- } from "./chunk-36P5RREC.js";
67
+ } from "./chunk-WCF6KIOK.js";
68
68
  import {
69
69
  deadLine,
70
70
  deadWarns
@@ -7255,6 +7255,7 @@ async function pushTake(dir, flags, r) {
7255
7255
  `recording upload failed: ${e instanceof Error ? e.message : String(e)}`
7256
7256
  );
7257
7257
  }
7258
+ for (const note of upload.notes) r.log(` note: ${note}`);
7258
7259
  const assetId = upload.id;
7259
7260
  const assetUrl = upload.url;
7260
7261
  r.event({ event: "recording", assetId, reused: upload.reused });
@@ -7752,7 +7753,7 @@ async function cmdFetch(argv) {
7752
7753
  const configPath = join15(out, "config.json");
7753
7754
  const written = JSON.parse(await readFile13(configPath, "utf8"));
7754
7755
  const hostedCount = manifestRefs(written).filter(
7755
- ({ ref }) => hostedAssetId(ref)
7756
+ ({ ref }) => hostedAssetId(ref, [origin, "https://vos.so", "https://www.vos.so"])
7756
7757
  ).length;
7757
7758
  if (hostedCount && flags.media === true) {
7758
7759
  const pulled = await pullManifest({ origin, key }, out, written, r.log);
@@ -8358,18 +8359,8 @@ async function writeProgramDoc(dir, hosted) {
8358
8359
  // src/plugin/asset.ts
8359
8360
  import { createHash as createHash2 } from "crypto";
8360
8361
  import { readFileSync as readFileSync3 } from "fs";
8361
- import { basename as basename3, extname as extname2 } from "path";
8362
+ import { basename as basename3 } from "path";
8362
8363
  var BOOLEAN_FLAGS3 = /* @__PURE__ */ new Set(["json", "help"]);
8363
- var MIME_BY_EXT = {
8364
- ".png": "image/png",
8365
- ".jpg": "image/jpeg",
8366
- ".jpeg": "image/jpeg",
8367
- ".webp": "image/webp",
8368
- ".gif": "image/gif",
8369
- ".glb": "model/gltf-binary",
8370
- ".gltf": "model/gltf+json",
8371
- ".md": "text/markdown"
8372
- };
8373
8364
  async function cmdRename(argv) {
8374
8365
  const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS3);
8375
8366
  const [assetId, filename] = positionals;
@@ -8408,7 +8399,6 @@ async function cmdPush(argv) {
8408
8399
  const folderRef = strFlag(flags, "folder");
8409
8400
  const folder = folderRef ? resolveFolder(await listFolders(origin, key), folderRef) : null;
8410
8401
  const uploaded = [];
8411
- let unfiledMedia = 0;
8412
8402
  for (const file of positionals) {
8413
8403
  const name = basename3(file);
8414
8404
  const body = readFileSync3(file);
@@ -8416,56 +8406,36 @@ async function cmdPush(argv) {
8416
8406
  head: body.subarray(0, MEDIA_HEAD_BYTES),
8417
8407
  filename: name
8418
8408
  });
8419
- if (/^(audio|video)\//.test(media.type)) {
8420
- const sent = nameForType(name, media.type);
8421
- let put;
8422
- try {
8423
- put = await uploadAsset({ origin, key }, new Uint8Array(body), {
8424
- filename: sent,
8425
- contentType: media.type,
8426
- contentHash: createHash2("sha256").update(body).digest("hex"),
8427
- ...folder ? { folderId: folder.id } : {}
8428
- });
8429
- } catch (e) {
8430
- const landed = uploaded.length ? ` (${uploaded.length} of ${positionals.length} landed before it)` : "";
8431
- throw new Error(
8432
- `push ${name}${landed}: ${e instanceof Error ? e.message : String(e)}`
8433
- );
8434
- }
8435
- if (!folder && !put.reused) unfiledMedia += 1;
8436
- uploaded.push({ id: put.id, filename: sent });
8437
- r.log(
8438
- `uploaded ${sent} (${put.id})${put.reused ? " \u2014 already on your shelf" : ""} ${put.url}`
8439
- );
8440
- continue;
8441
- }
8442
- const mime = MIME_BY_EXT[extname2(name).toLowerCase()] ?? "application/octet-stream";
8443
- const form = new FormData();
8444
- form.set("file", new Blob([body], { type: mime }), name);
8445
- if (folder) form.set("folderId", folder.id);
8446
- const res = await fetch(`${origin}/api/assets/upload`, {
8447
- method: "POST",
8448
- headers: { accept: "application/json", authorization: `Bearer ${key}` },
8449
- body: form
8450
- });
8451
- let payload = {};
8409
+ const isMedia = /^(audio|video)\//.test(media.type);
8410
+ const sent = isMedia ? nameForType(name, media.type) : name;
8411
+ let put;
8452
8412
  try {
8453
- payload = await res.json();
8454
- } catch {
8455
- }
8456
- if (res.status !== 200 && res.status !== 201) {
8413
+ put = await uploadAsset({ origin, key }, new Uint8Array(body), {
8414
+ filename: sent,
8415
+ ...isMedia ? { contentType: media.type } : {},
8416
+ contentHash: createHash2("sha256").update(body).digest("hex"),
8417
+ intent: "library",
8418
+ ...folder ? { folderId: folder.id } : {}
8419
+ });
8420
+ } catch (e) {
8457
8421
  const landed = uploaded.length ? ` (${uploaded.length} of ${positionals.length} landed before it)` : "";
8458
8422
  throw new Error(
8459
- apiError(`push ${name}${landed}`, {
8460
- status: res.status,
8461
- body: payload
8462
- })
8423
+ `push ${name}${landed}: ${e instanceof Error ? e.message : String(e)}`
8463
8424
  );
8464
8425
  }
8465
- uploaded.push({ id: payload.id, filename: payload.filename });
8426
+ const stored = put.filename || sent;
8427
+ uploaded.push({
8428
+ id: put.id,
8429
+ filename: stored,
8430
+ kind: put.kind,
8431
+ url: put.url,
8432
+ ...put.reused ? { reused: true } : {},
8433
+ ...put.notes.length ? { notes: put.notes } : {}
8434
+ });
8466
8435
  r.log(
8467
- `uploaded ${String(payload.filename ?? name)} (${String(payload.id)})`
8436
+ `uploaded ${stored} (${put.id})${put.reused ? " \u2014 already on your shelf" : ""} ${put.url}`
8468
8437
  );
8438
+ for (const note of put.notes) r.log(` note: ${note}`);
8469
8439
  }
8470
8440
  r.done(
8471
8441
  {
@@ -8473,8 +8443,7 @@ async function cmdPush(argv) {
8473
8443
  count: uploaded.length,
8474
8444
  folder: folder?.slug ?? null
8475
8445
  },
8476
- `pushed ${uploaded.length} asset${uploaded.length === 1 ? "" : "s"}${folder ? ` into ${folder.slug}` : ""}` + (unfiledMedia ? `
8477
- note: an unfiled sound or video that no document uses is removed after 7 days. Reference it from a doc (doc.audio key = its url) or push it with --folder <slug>` : "")
8446
+ `pushed ${uploaded.length} asset${uploaded.length === 1 ? "" : "s"}${folder ? ` into ${folder.slug}` : ""}`
8478
8447
  );
8479
8448
  return EXIT_OK;
8480
8449
  }
@@ -9045,7 +9014,7 @@ async function cmdCompare(argv) {
9045
9014
  const config = loaded.config;
9046
9015
  const duration = configDuration(config) ?? 5;
9047
9016
  const assets = await programAssets(config, {
9048
- baseDir: existsSync15(source) && statSync4(source).isDirectory() ? source : dirname5(source),
9017
+ baseDir: /^https?:\/\//.test(source) ? null : existsSync15(source) && statSync4(source).isDirectory() ? source : dirname5(source),
9049
9018
  origin: platformOrigin({}),
9050
9019
  key: resolveCredential(),
9051
9020
  log: (line) => r.log(line)
@@ -9185,7 +9154,7 @@ import { findFontFamily as findFontFamily3 } from "@vosjs/shared";
9185
9154
  import { existsSync as existsSync16, readFileSync as readFileSync5, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
9186
9155
  import { createServer as createServer2 } from "http";
9187
9156
  import { createRequire } from "module";
9188
- import { basename as basename5, extname as extname3, join as join19 } from "path";
9157
+ import { basename as basename5, extname as extname2, join as join19 } from "path";
9189
9158
  function themeColors(sources) {
9190
9159
  const out = {};
9191
9160
  const re = /\bconst\s+([A-Za-z_$][\w$]*)\s*(?::\s*string\s*)?=\s*['"](#[0-9a-fA-F]{3,8})['"]/g;
@@ -9366,7 +9335,7 @@ async function readRemotion(dir, log) {
9366
9335
  if (!f.startsWith(out) || !existsSync16(f) || statSync5(f).isDirectory())
9367
9336
  f = join19(out, "index.html");
9368
9337
  rs.writeHead(200, {
9369
- "content-type": TYPES[extname3(f)] ?? "application/octet-stream"
9338
+ "content-type": TYPES[extname2(f)] ?? "application/octet-stream"
9370
9339
  });
9371
9340
  rs.end(readFileSync5(f));
9372
9341
  }).listen(0, "127.0.0.1");
@@ -10684,6 +10653,7 @@ function scaffoldSourceDir(inv) {
10684
10653
  }
10685
10654
 
10686
10655
  // src/plugin/recipe.ts
10656
+ import { createHash as createHash3 } from "crypto";
10687
10657
  import { readFileSync as readFileSync8 } from "fs";
10688
10658
  import { basename as basename7 } from "path";
10689
10659
  var BOOLEAN_FLAGS9 = /* @__PURE__ */ new Set(["json", "help"]);
@@ -10712,55 +10682,49 @@ async function cmdPush2(argv) {
10712
10682
  });
10713
10683
  const key = requireCredential(strFlag(flags, "key"));
10714
10684
  if (assetId) {
10715
- const res2 = await apiJson(origin, `/api/assets/${assetId}/file`, {
10685
+ const res = await apiJson(origin, `/api/assets/${assetId}/file`, {
10716
10686
  method: "PUT",
10717
10687
  key,
10718
10688
  raw: new Uint8Array(body),
10719
10689
  headers: { "content-type": "text/markdown" }
10720
10690
  });
10721
- if (res2.status !== 200) {
10722
- throw new Error(apiError(`replace recipe ${assetId}`, res2));
10691
+ if (res.status !== 200) {
10692
+ throw new Error(apiError(`replace recipe ${assetId}`, res));
10723
10693
  }
10724
10694
  r.done(
10725
- { id: assetId, filename: res2.body.filename, replaced: true },
10726
- `replaced recipe ${assetId} (${String(res2.body.filename)}) in place \u2014 the prior body is kept, restore it from the recipe page`
10695
+ { id: assetId, filename: res.body.filename, replaced: true },
10696
+ `replaced recipe ${assetId} (${String(res.body.filename)}) in place \u2014 the prior body is kept, restore it from the recipe page`
10727
10697
  );
10728
10698
  return EXIT_OK;
10729
10699
  }
10730
10700
  const folder = resolveFolder(await listFolders(origin, key), folderRef);
10731
- const form = new FormData();
10732
- form.set("file", new Blob([body], { type: "text/markdown" }), filename);
10733
- form.set("folderId", folder.id);
10734
- const res = await fetch(`${origin}/api/assets/upload`, {
10735
- method: "POST",
10736
- headers: { accept: "application/json", authorization: `Bearer ${key}` },
10737
- body: form
10738
- });
10739
- let payload = {};
10701
+ let put;
10740
10702
  try {
10741
- payload = await res.json();
10742
- } catch {
10743
- }
10744
- if (res.status !== 200 && res.status !== 201) {
10703
+ put = await uploadAsset({ origin, key }, new Uint8Array(body), {
10704
+ filename,
10705
+ contentType: "text/markdown",
10706
+ contentHash: createHash3("sha256").update(body).digest("hex"),
10707
+ intent: "library",
10708
+ folderId: folder.id
10709
+ });
10710
+ } catch (e) {
10745
10711
  throw new Error(
10746
- apiError(`push recipe ${filename}`, {
10747
- status: res.status,
10748
- body: payload
10749
- })
10712
+ `push recipe ${filename}: ${e instanceof Error ? e.message : String(e)}`
10750
10713
  );
10751
10714
  }
10752
- const meta = payload.metadata ?? {};
10753
- const stored = typeof payload.filename === "string" ? payload.filename : filename;
10715
+ const meta = put.metadata ?? {};
10716
+ const stored = put.filename || filename;
10754
10717
  r.done(
10755
10718
  {
10756
- id: payload.id,
10757
- filename: payload.filename,
10719
+ id: put.id,
10720
+ filename: stored,
10758
10721
  folder: folder.slug,
10759
10722
  name: meta.name,
10760
- description: meta.description
10723
+ description: meta.description,
10724
+ ...put.notes.length ? { notes: put.notes } : {}
10761
10725
  },
10762
- `pushed ${stored} into ${folder.slug} (${String(payload.id)})${typeof meta.name === "string" ? ` \u2014 ${meta.name}` : ""}
10763
- Replace it later with: vos recipe push ${file} --asset ${String(payload.id)}`
10726
+ `pushed ${stored} into ${folder.slug} (${put.id})${typeof meta.name === "string" ? ` \u2014 ${meta.name}` : ""}
10727
+ Replace it later with: vos recipe push ${file} --asset ${put.id}`
10764
10728
  );
10765
10729
  return EXIT_OK;
10766
10730
  }
@@ -12683,12 +12647,12 @@ Platform (vos.so) \u2014 fetch, edit, push, pull, repeat
12683
12647
  and fileUrl untouched; the extension must keep matching the
12684
12648
  asset's kind (a recipe stays .md)
12685
12649
  vos asset push <file...> [--folder <folderId|slug>]
12686
- upload files onto your shelf (a key takes models .glb/.gltf,
12687
- recipes .md and images .png/.jpg/.webp/.gif \u2014 posters and
12688
- store stills file into the release's project; 40 images/24h \u2014
12689
- and sound and video, which land in Sound > Uploads and print
12690
- the url a doc.json audio key takes; an unfiled one that no
12691
- document uses is removed after 7 days, so pass --folder)
12650
+ add files to your library: models, pictures and SVGs, fonts,
12651
+ HDR maps, sound, video, captions and recipes. The platform
12652
+ reads each file and answers what it is and the name it is
12653
+ stored under; the same bytes twice are the same file. Prints
12654
+ the url a doc.json key takes. GET /api/limits lists the
12655
+ kinds, their size caps and their daily rates
12692
12656
  vos recipe push <file.md> --folder <folderId|slug>
12693
12657
  vos recipe push <file.md> --asset <assetId>
12694
12658
  put a recipe (a plain .md) on the shelf: --folder creates it
@@ -14643,4 +14607,4 @@ export {
14643
14607
  verbHelp,
14644
14608
  run
14645
14609
  };
14646
- //# sourceMappingURL=chunk-NE5MHC3D.js.map
14610
+ //# sourceMappingURL=chunk-C4P6MZJG.js.map