@vosjs/cli 0.80.0 → 0.81.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/cli.js CHANGED
@@ -839,7 +839,7 @@ async function cmdCheck(argv) {
839
839
  const result = runCheck(parsed);
840
840
  if (result.config) {
841
841
  const baseDir = baseDirOf(source);
842
- const { hostedLiteralWarnings } = await import("./assetManifest-ETT3C7ZA.js");
842
+ const { hostedLiteralWarnings } = await import("./assetManifest-27NKSS7N.js");
843
843
  const extra = [
844
844
  ...baseDir ? missingManifestFiles(result.config, baseDir) : [],
845
845
  ...hostedLiteralWarnings(result.config)
@@ -873,7 +873,7 @@ async function cmdCheck(argv) {
873
873
  return result.ok ? EXIT_OK : EXIT_ERROR;
874
874
  }
875
875
  async function delegate(argv) {
876
- const { run } = await import("./run-2ELHMPTB.js");
876
+ const { run } = await import("./run-MEDD2Q74.js");
877
877
  return await run(argv);
878
878
  }
879
879
  async function printHelp() {
@@ -907,7 +907,7 @@ async function main() {
907
907
  const engine = HELP_ENGINE.split("\n").filter(
908
908
  (l) => l.startsWith(` vos ${cmd} `)
909
909
  );
910
- const { verbHelp } = await import("./run-2ELHMPTB.js");
910
+ const { verbHelp } = await import("./run-MEDD2Q74.js");
911
911
  const take = verbHelp(cmd);
912
912
  process.stdout.write(
913
913
  `${engine.join("\n")}
@@ -935,7 +935,7 @@ ${take.includes("no such verb") ? "" : take}`
935
935
  main().then(async (code) => {
936
936
  if (code === EXIT_OK) {
937
937
  const verb = process.argv[2] ?? "";
938
- const { HELP } = await import("./run-2ELHMPTB.js");
938
+ const { HELP } = await import("./run-MEDD2Q74.js");
939
939
  const documented = [
940
940
  ...helpFlagsFor(HELP_ENGINE, verb),
941
941
  ...helpFlagsFor(HELP, verb)
package/dist/index.d.ts CHANGED
@@ -137,33 +137,6 @@ type RecordOpts = Omit<RecordOpts$1, 'context' | 'env' | 'platform' | 'outputs'
137
137
  };
138
138
  declare function recordTake(browser: Browser, url: string, actions: ActionsFile, paths: TakePaths, log: (msg: string) => void, opts?: RecordOpts): Promise<_vosjs_render_core_record.RecordResult>;
139
139
 
140
- /** Entry point — the delegation contract for @vosjs/cli: the host forwards
141
- every non-engine verb here (`vos <verb>`). The exported `manifest` (index.ts)
142
- tells the host which verbs exist and how to summarize them in help. */
143
- declare function run(argv: string[]): Promise<number>;
144
-
145
- /**
146
- * The verb manifest of the take pipeline and the vos.so verbs — what
147
- * `vos help` lists under the engine verbs. It kept the shape a separately
148
- * installed plugin once handed the host (name + host range), so a script
149
- * reading it keeps working.
150
- */
151
- interface PluginVerb {
152
- name: string;
153
- summary: string;
154
- }
155
- interface PluginManifest {
156
- name: string;
157
- /** Host versions this plugin speaks the run(argv) contract with. */
158
- hostRange: string;
159
- verbs: PluginVerb[];
160
- }
161
- declare const manifest: PluginManifest;
162
-
163
- declare function encodeRecording(browser: Browser, takeDir: string, onProgress: (fraction: number) => void): Promise<{
164
- bytes: number;
165
- }>;
166
-
167
140
  /** The release's words a plan writes into the cut's titles and end card. */
168
141
  interface ReleaseWords {
169
142
  headline?: string | null;
@@ -316,6 +289,33 @@ interface PlanOptions {
316
289
  }
317
290
  declare function planTake(dir: string, opts?: PlanOptions): Promise<PlanSummary>;
318
291
 
292
+ /** Entry point — the delegation contract for @vosjs/cli: the host forwards
293
+ every non-engine verb here (`vos <verb>`). The exported `manifest` (index.ts)
294
+ tells the host which verbs exist and how to summarize them in help. */
295
+ declare function run(argv: string[]): Promise<number>;
296
+
297
+ /**
298
+ * The verb manifest of the take pipeline and the vos.so verbs — what
299
+ * `vos help` lists under the engine verbs. It kept the shape a separately
300
+ * installed plugin once handed the host (name + host range), so a script
301
+ * reading it keeps working.
302
+ */
303
+ interface PluginVerb {
304
+ name: string;
305
+ summary: string;
306
+ }
307
+ interface PluginManifest {
308
+ name: string;
309
+ /** Host versions this plugin speaks the run(argv) contract with. */
310
+ hostRange: string;
311
+ verbs: PluginVerb[];
312
+ }
313
+ declare const manifest: PluginManifest;
314
+
315
+ declare function encodeRecording(browser: Browser, takeDir: string, onProgress: (fraction: number) => void): Promise<{
316
+ bytes: number;
317
+ }>;
318
+
319
319
  interface DigestOptions {
320
320
  outDir?: string;
321
321
  full?: number;
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  startTakeServer,
15
15
  validateActions,
16
16
  waitForPageDone
17
- } from "./chunk-SP5OHTVB.js";
17
+ } from "./chunk-S3RI2SHH.js";
18
18
  import {
19
19
  BrowserUnavailableError,
20
20
  configDuration,
@@ -29,7 +29,7 @@ import {
29
29
  loadTake,
30
30
  pullMedia,
31
31
  takePaths
32
- } from "./chunk-PJ6MEJZV.js";
32
+ } from "./chunk-352AC3V6.js";
33
33
  import {
34
34
  apiError,
35
35
  apiJson,
@@ -2,23 +2,27 @@
2
2
  import {
3
3
  HELP,
4
4
  MULTI_FLAGS,
5
+ previousCutReport,
6
+ reRecord,
5
7
  run,
6
8
  strictFails,
7
9
  takeBackdrop,
8
10
  takeBrowserArgs,
9
11
  verbHelp
10
- } from "./chunk-SP5OHTVB.js";
12
+ } from "./chunk-S3RI2SHH.js";
11
13
  import "./chunk-B373NVJE.js";
12
- import "./chunk-PJ6MEJZV.js";
14
+ import "./chunk-352AC3V6.js";
13
15
  import "./chunk-J3KHIZCH.js";
14
16
  import "./chunk-6XPFWQKP.js";
15
17
  export {
16
18
  HELP,
17
19
  MULTI_FLAGS,
20
+ previousCutReport,
21
+ reRecord,
18
22
  run,
19
23
  strictFails,
20
24
  takeBackdrop,
21
25
  takeBrowserArgs,
22
26
  verbHelp
23
27
  };
24
- //# sourceMappingURL=run-2ELHMPTB.js.map
28
+ //# sourceMappingURL=run-MEDD2Q74.js.map
@@ -143,6 +143,7 @@ function shotList(actions) {
143
143
  // gestures the recorder adds around each ask: about a second a beat
144
144
  aboutS: Math.round((total + beats.length * 1e3) / 1e3),
145
145
  setup: actions.setup?.length ?? 0,
146
+ ...actions.teardown?.length ? { teardown: actions.teardown.length } : {},
146
147
  ...peers.length ? { peers } : {}
147
148
  };
148
149
  }
@@ -173,6 +174,10 @@ function formatShotList(list) {
173
174
  out.push(
174
175
  "Move at a talking pace, let each screen settle before the next beat, and end on the finished state. Press the icon again to stop; the take opens in the studio and saves to your shelf."
175
176
  );
177
+ if (list.teardown)
178
+ out.push(
179
+ `After you stop: the script's ${list.teardown} teardown step${list.teardown === 1 ? "" : "s"} (remove what the take created) are yours to do; they are not part of the take.`
180
+ );
176
181
  return out.join("\n");
177
182
  }
178
183
  export {
@@ -182,4 +187,4 @@ export {
182
187
  sayTarget,
183
188
  shotList
184
189
  };
185
- //# sourceMappingURL=shotList-MSNWLVDN.js.map
190
+ //# sourceMappingURL=shotList-PL7XP6AC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugin/shotList.ts"],"sourcesContent":["/**\n * The shot list: an `actions.json` said in words, for a person who will\n * record it by hand. The last rung of the session ladder is \"the human\n * records, the agent cuts\", and an agent that falls to it should hand over\n * more than an apology: the flow it worked out, as numbered beats with the\n * pace it wanted, so its knowledge survives the hop to a human. The take\n * comes back through the ordinary handoff and the agent cuts it.\n *\n * Pure: an ActionsFile in, lines out. Selectors are said as what they name\n * where the script says (an id, a caption, a text= locator), and as the\n * selector itself where it does not: a person reading `#filter-late` can\n * find it; a person reading nothing cannot.\n */\nimport type { ActionStep, ActionsFile } from './actions'\nimport { askedMs } from './pace'\nimport { pressSequence, splitChord } from '@vosjs/render-core/record'\n\n/** A key or a chord as a person reads it: `Mod` is ⌘ on a Mac, Ctrl elsewhere. */\nexport function sayChord(chord: string): string {\n const { mods, key } = splitChord(chord)\n const word = (k: string) =>\n k === 'Mod' || k === 'ControlOrMeta'\n ? '⌘/Ctrl'\n : k === 'Control'\n ? 'Ctrl'\n : k === 'Meta'\n ? '⌘'\n : k === 'Alt'\n ? 'Alt/Option'\n : k === ' '\n ? 'Space'\n : k\n return [...mods, key].map(word).join('+')\n}\n\n/** A viewport point as a person finds it: where it is, and the script's numbers. */\nfunction sayPoint(p: { x?: number; y?: number }): string {\n const x = p.x ?? 0\n const y = p.y ?? 0\n return `the spot at ${Math.round(x)}, ${Math.round(y)} (${y < 360 ? 'upper' : 'lower'} ${x < 640 ? 'left' : 'right'})`\n}\n\n/** A list in words: `a`, `a then b`, `a, b then c`. */\nconst thenList = (xs: string[]): string =>\n xs.length <= 1\n ? (xs[0] ?? '')\n : `${xs.slice(0, -1).join(', ')} then ${xs[xs.length - 1]}`\n\nexport interface ShotBeat {\n n: number\n /** what to do, in words */\n line: string\n /** how long to hold after, in seconds, when the script asks for a hold */\n holdS?: number\n}\n\nexport interface ShotList {\n url?: string\n viewport?: { width: number; height: number }\n beats: ShotBeat[]\n /** the take's asked length, from the script's own pacing */\n aboutS: number\n setup: number\n /** The script's teardown steps: what the person undoes once they stop. */\n teardown?: number\n /**\n * What each other person does (the script's `peers`), as beats tied to\n * the take's own: a second pair of hands, in their own browser, on the\n * same page.\n */\n peers?: { id: string; lines: string[] }[]\n}\n\n/** A selector as a person reads it: the text it names, or the selector itself. */\nexport function sayTarget(selector: string): string {\n const text = /^text=(['\"]?)(.+?)\\1$/.exec(selector)\n if (text) return `\"${text[2]}\"`\n const has = /:has-text\\((['\"])(.+?)\\1\\)/.exec(selector)\n if (has) return `\"${has[2]}\"`\n const role = /getByRole\\(['\"](\\w+)['\"],\\s*\\{\\s*name:\\s*(['\"])(.+?)\\2/.exec(\n selector,\n )\n if (role) return `the ${role[1]} \"${role[3]}\"`\n const label = /\\[aria-label=(['\"])(.+?)\\1\\]/.exec(selector)\n if (label) return `\"${label[2]}\"`\n const ph = /\\[placeholder=(['\"])(.+?)\\1\\]/.exec(selector)\n if (ph) return `the \"${ph[2]}\" field`\n return `\\`${selector}\\``\n}\n\nconst s1 = (ms: number) => `${Math.round(ms / 100) / 10}`.replace(/\\.0$/, '')\n\n/** A step's id, said as a name when it reads as one (`late`, `new-order`, `openBilling`). */\nexport function sayId(id: string | undefined): string | null {\n if (!id || /^\\d+$/.test(id) || id.length < 3) return null\n const words = id\n .replace(/([a-z])([A-Z])/g, '$1 $2')\n .replace(/[-_]+/g, ' ')\n .toLowerCase()\n .trim()\n return words.length >= 3 ? words : null\n}\n\n/** What a step points at, for a person: the words the selector names, else the id's, else the selector. */\nfunction sayStepTarget(step: { selector: string; id?: string }): string {\n const bySelector = sayTarget(step.selector)\n if (!bySelector.startsWith('`')) return bySelector\n const byId = sayId(step.id)\n return byId ? `${byId} (${bySelector})` : bySelector\n}\n\n/** A page condition, for a person: what they should see before going on. */\nfunction sayCondition(c: {\n selector?: string\n text?: string\n gone?: string\n enabled?: boolean\n js?: string\n equals?: unknown\n id?: string\n}): string {\n // A state the app keeps in script: a person reads it in the browser's\n // console, so the beat names the expression to run there.\n if (c.js !== undefined)\n return c.equals !== undefined\n ? `the console's \\`${c.js}\\` gives ${JSON.stringify(c.equals)}`\n : `the console's \\`${c.js}\\` is true`\n if (c.text !== undefined) return `\"${c.text}\" shows on the page`\n if (c.gone !== undefined) return `${sayTarget(c.gone)} is gone`\n const target = sayStepTarget({ selector: c.selector ?? '', id: c.id })\n if (c.enabled === true) return `${target} is enabled`\n if (c.enabled === false) return `${target} is disabled`\n return `${target} is showing`\n}\n\nfunction beatOf(step: ActionStep): string | null {\n const line = gestureBeat(step)\n if (!line) return line\n // What the cut's camera does here, said where a person reads it: a step\n // marked as the beat's payoff is zoomed in on, one marked against it is not.\n const camera =\n step.zoom === true\n ? ' (zoomed in on in the cut)'\n : step.zoom === false\n ? ' (the cut stays wide here)'\n : ''\n // A step that spends something real is marked where a person reads it.\n return step.guard\n ? `${line}${camera} (this spends something real: do it only if you mean to)`\n : `${line}${camera}`\n}\n\nfunction gestureBeat(step: ActionStep): string | null {\n const cap = step.caption\n const named = cap ? `${cap}: ` : ''\n switch (step.do) {\n case 'expect':\n return `${named}Check that ${sayCondition(step)}`\n case 'await':\n return `${named}Wait until ${sayCondition({ ...step.until, id: step.id })} (off camera: pause the recording meanwhile)`\n case 'download':\n return step.selector !== undefined\n ? `${named}Download the file ${sayStepTarget({ selector: step.selector, id: step.id })} gives${step.as ? `, as ${step.as}` : ''}`\n : `${named}Download what ${sayTarget(step.src ?? '')} plays${step.as ? `, as ${step.as}` : ''}`\n case 'wait':\n return null\n case 'hover':\n return `${named}Rest the pointer on ${step.selector !== undefined ? sayStepTarget({ selector: step.selector, id: step.id }) : sayPoint(step)}`\n case 'click': {\n const verb =\n step.count === 3\n ? 'Triple-click'\n : step.count === 2\n ? 'Double-click'\n : 'Click'\n const button =\n step.button === 'right'\n ? ' with the right button'\n : step.button === 'middle'\n ? ' with the middle button'\n : ''\n const target =\n step.selector !== undefined\n ? sayStepTarget({ selector: step.selector, id: step.id })\n : sayPoint(step)\n const holding = step.modifiers?.length\n ? `Hold ${step.modifiers.map(sayChord).join('+')} and ${verb.toLowerCase()}`\n : verb\n return `${named}${holding} ${target}${button}`\n }\n case 'type':\n return `${named}${step.replace ? 'Replace what is in' : 'Type'}${step.replace ? ` ${sayStepTarget(step)} with \"${step.text}\"` : ` \"${step.text}\" into ${sayStepTarget(step)}`}${step.at === 'end' ? ', at the end' : step.at === 'start' ? ', at the start' : ''}`\n case 'press':\n return `${named}Press ${thenList(pressSequence(step).map(sayChord))}${step.selector ? ` in ${sayStepTarget(step as { selector: string; id?: string })}` : ''}`\n case 'paste': {\n const what =\n step.text !== undefined\n ? `\"${step.text.length > 60 ? `${step.text.slice(0, 57)}...` : step.text}\"`\n : `the file ${step.file}`\n return `${named}Copy ${what}, then paste it${step.selector ? ` into ${sayStepTarget({ selector: step.selector, id: step.id })}` : ''} (⌘/Ctrl+V)`\n }\n case 'scroll':\n return `${named}Scroll ${step.dy < 0 ? 'up' : 'down'} ${step.dy < 0 ? 'a little' : 'a little'}`\n case 'move':\n return `${named}Move the pointer to the ${step.y < 360 ? 'upper' : 'lower'} ${step.x < 640 ? 'left' : 'right'}`\n case 'drag': {\n const holding = step.modifiers?.length\n ? `Hold ${step.modifiers.map(sayChord).join('+')} and `\n : ''\n const verb = holding ? 'drag' : 'Drag'\n if (step.path !== undefined)\n return `${named}${holding}${holding ? 'draw' : 'Draw'} the stroke ${typeof step.path === 'string' ? `in ${step.path}` : `of ${step.path.length} points`}, in one press`\n return `${named}${holding}${verb} ${step.selector ? sayStepTarget(step as { selector: string; id?: string }) : 'from the start point'} across`\n }\n }\n}\n\nexport function shotList(actions: ActionsFile): ShotList {\n const beats: ShotBeat[] = []\n let pendingHold = 0\n let total = 0\n // Which beat a take step is (or, for a wait, the beat it holds), so a\n // peer's step can say when it happens.\n const beatOfId = new Map<string, number>()\n for (const step of actions.steps) {\n if (step.id)\n beatOfId.set(\n step.id,\n Math.max(1, beats.length + (step.do === 'wait' ? 0 : 1)),\n )\n total += askedMs(step)\n if (step.do === 'wait') {\n // A wait after a beat is that beat's hold; a leading wait is the\n // settle before the first, which a person does without being told.\n if (beats.length) {\n const last = beats[beats.length - 1]\n last.holdS = (last.holdS ?? 0) + step.ms / 1000\n } else pendingHold += step.ms\n continue\n }\n const line = beatOf(step)\n if (!line) continue\n const beat: ShotBeat = { n: beats.length + 1, line }\n if (step.do === 'hover') beat.holdS = askedMs(step) / 1000\n beats.push(beat)\n }\n void pendingHold\n const peers = (actions.peers ?? []).map((peer) => ({\n id: peer.id,\n lines: peer.steps\n .map((step) => {\n const line = step.do === 'wait' ? `Wait ${s1(step.ms)} s` : beatOf(step)\n if (!line) return null\n const when = step.with\n ? `As beat ${beatOfId.get(step.with) ?? '?'} starts: `\n : step.after\n ? `Once beat ${beatOfId.get(step.after) ?? '?'} is done: `\n : ''\n return `${when}${line}`\n })\n .filter((l): l is string => l !== null),\n }))\n return {\n url: actions.url,\n viewport: actions.viewport,\n beats,\n // gestures the recorder adds around each ask: about a second a beat\n aboutS: Math.round((total + beats.length * 1000) / 1000),\n setup: actions.setup?.length ?? 0,\n ...(actions.teardown?.length ? { teardown: actions.teardown.length } : {}),\n ...(peers.length ? { peers } : {}),\n }\n}\n\n/** The list as the person reads it. */\nexport function formatShotList(list: ShotList): string {\n const out: string[] = []\n out.push(\n `Record this with the vosso extension in your own signed-in browser, about ${list.aboutS} seconds${list.viewport ? `, window about ${list.viewport.width}×${list.viewport.height}` : ''}.`,\n )\n if (list.url)\n out.push(`Start on ${list.url}, signed in, with the page settled.`)\n if (list.setup)\n out.push(\n `Before you press record: the script's ${list.setup} setup step${list.setup === 1 ? '' : 's'} (sign in, dismiss banners) are yours to do first; they are not part of the take.`,\n )\n out.push('')\n for (const b of list.beats) {\n const hold =\n b.holdS && b.holdS >= 0.4 ? ` Hold ${s1(b.holdS * 1000)} s.` : ''\n out.push(`${b.n}. ${b.line}.${hold}`)\n }\n for (const peer of list.peers ?? []) {\n out.push('')\n out.push(\n `A second person (${peer.id}) does this at the same time, in their own browser on the same page; their screen is not recorded, the app shows what they do:`,\n )\n peer.lines.forEach((l, i) => out.push(` ${peer.id}${i + 1}. ${l}.`))\n }\n out.push('')\n out.push(\n 'Move at a talking pace, let each screen settle before the next beat, and end on the finished state. Press the icon again to stop; the take opens in the studio and saves to your shelf.',\n )\n if (list.teardown)\n out.push(\n `After you stop: the script's ${list.teardown} teardown step${list.teardown === 1 ? '' : 's'} (remove what the take created) are yours to do; they are not part of the take.`,\n )\n return out.join('\\n')\n}\n"],"mappings":";;;;;;AAeA,SAAS,eAAe,kBAAkB;AAGnC,SAAS,SAAS,OAAuB;AAC9C,QAAM,EAAE,MAAM,IAAI,IAAI,WAAW,KAAK;AACtC,QAAM,OAAO,CAAC,MACZ,MAAM,SAAS,MAAM,kBACjB,gBACA,MAAM,YACJ,SACA,MAAM,SACJ,WACA,MAAM,QACJ,eACA,MAAM,MACJ,UACA;AACd,SAAO,CAAC,GAAG,MAAM,GAAG,EAAE,IAAI,IAAI,EAAE,KAAK,GAAG;AAC1C;AAGA,SAAS,SAAS,GAAuC;AACvD,QAAM,IAAI,EAAE,KAAK;AACjB,QAAM,IAAI,EAAE,KAAK;AACjB,SAAO,eAAe,KAAK,MAAM,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,KAAK,IAAI,MAAM,UAAU,OAAO,IAAI,IAAI,MAAM,SAAS,OAAO;AACrH;AAGA,IAAM,WAAW,CAAC,OAChB,GAAG,UAAU,IACR,GAAG,CAAC,KAAK,KACV,GAAG,GAAG,MAAM,GAAG,EAAE,EAAE,KAAK,IAAI,CAAC,SAAS,GAAG,GAAG,SAAS,CAAC,CAAC;AA4BtD,SAAS,UAAU,UAA0B;AAClD,QAAM,OAAO,wBAAwB,KAAK,QAAQ;AAClD,MAAI,KAAM,QAAO,IAAI,KAAK,CAAC,CAAC;AAC5B,QAAM,MAAM,6BAA6B,KAAK,QAAQ;AACtD,MAAI,IAAK,QAAO,IAAI,IAAI,CAAC,CAAC;AAC1B,QAAM,OAAO,yDAAyD;AAAA,IACpE;AAAA,EACF;AACA,MAAI,KAAM,QAAO,OAAO,KAAK,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC;AAC3C,QAAM,QAAQ,+BAA+B,KAAK,QAAQ;AAC1D,MAAI,MAAO,QAAO,IAAI,MAAM,CAAC,CAAC;AAC9B,QAAM,KAAK,gCAAgC,KAAK,QAAQ;AACxD,MAAI,GAAI,QAAO,QAAQ,GAAG,CAAC,CAAC;AAC5B,SAAO,KAAK,QAAQ;AACtB;AAEA,IAAM,KAAK,CAAC,OAAe,GAAG,KAAK,MAAM,KAAK,GAAG,IAAI,EAAE,GAAG,QAAQ,QAAQ,EAAE;AAGrE,SAAS,MAAM,IAAuC;AAC3D,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAE,KAAK,GAAG,SAAS,EAAG,QAAO;AACrD,QAAM,QAAQ,GACX,QAAQ,mBAAmB,OAAO,EAClC,QAAQ,UAAU,GAAG,EACrB,YAAY,EACZ,KAAK;AACR,SAAO,MAAM,UAAU,IAAI,QAAQ;AACrC;AAGA,SAAS,cAAc,MAAiD;AACtE,QAAM,aAAa,UAAU,KAAK,QAAQ;AAC1C,MAAI,CAAC,WAAW,WAAW,GAAG,EAAG,QAAO;AACxC,QAAM,OAAO,MAAM,KAAK,EAAE;AAC1B,SAAO,OAAO,GAAG,IAAI,KAAK,UAAU,MAAM;AAC5C;AAGA,SAAS,aAAa,GAQX;AAGT,MAAI,EAAE,OAAO;AACX,WAAO,EAAE,WAAW,SAChB,mBAAmB,EAAE,EAAE,YAAY,KAAK,UAAU,EAAE,MAAM,CAAC,KAC3D,mBAAmB,EAAE,EAAE;AAC7B,MAAI,EAAE,SAAS,OAAW,QAAO,IAAI,EAAE,IAAI;AAC3C,MAAI,EAAE,SAAS,OAAW,QAAO,GAAG,UAAU,EAAE,IAAI,CAAC;AACrD,QAAM,SAAS,cAAc,EAAE,UAAU,EAAE,YAAY,IAAI,IAAI,EAAE,GAAG,CAAC;AACrE,MAAI,EAAE,YAAY,KAAM,QAAO,GAAG,MAAM;AACxC,MAAI,EAAE,YAAY,MAAO,QAAO,GAAG,MAAM;AACzC,SAAO,GAAG,MAAM;AAClB;AAEA,SAAS,OAAO,MAAiC;AAC/C,QAAM,OAAO,YAAY,IAAI;AAC7B,MAAI,CAAC,KAAM,QAAO;AAGlB,QAAM,SACJ,KAAK,SAAS,OACV,+BACA,KAAK,SAAS,QACZ,+BACA;AAER,SAAO,KAAK,QACR,GAAG,IAAI,GAAG,MAAM,6DAChB,GAAG,IAAI,GAAG,MAAM;AACtB;AAEA,SAAS,YAAY,MAAiC;AACpD,QAAM,MAAM,KAAK;AACjB,QAAM,QAAQ,MAAM,GAAG,GAAG,OAAO;AACjC,UAAQ,KAAK,IAAI;AAAA,IACf,KAAK;AACH,aAAO,GAAG,KAAK,cAAc,aAAa,IAAI,CAAC;AAAA,IACjD,KAAK;AACH,aAAO,GAAG,KAAK,cAAc,aAAa,EAAE,GAAG,KAAK,OAAO,IAAI,KAAK,GAAG,CAAC,CAAC;AAAA,IAC3E,KAAK;AACH,aAAO,KAAK,aAAa,SACrB,GAAG,KAAK,qBAAqB,cAAc,EAAE,UAAU,KAAK,UAAU,IAAI,KAAK,GAAG,CAAC,CAAC,SAAS,KAAK,KAAK,QAAQ,KAAK,EAAE,KAAK,EAAE,KAC7H,GAAG,KAAK,iBAAiB,UAAU,KAAK,OAAO,EAAE,CAAC,SAAS,KAAK,KAAK,QAAQ,KAAK,EAAE,KAAK,EAAE;AAAA,IACjG,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,GAAG,KAAK,uBAAuB,KAAK,aAAa,SAAY,cAAc,EAAE,UAAU,KAAK,UAAU,IAAI,KAAK,GAAG,CAAC,IAAI,SAAS,IAAI,CAAC;AAAA,IAC9I,KAAK,SAAS;AACZ,YAAM,OACJ,KAAK,UAAU,IACX,iBACA,KAAK,UAAU,IACb,iBACA;AACR,YAAM,SACJ,KAAK,WAAW,UACZ,2BACA,KAAK,WAAW,WACd,4BACA;AACR,YAAM,SACJ,KAAK,aAAa,SACd,cAAc,EAAE,UAAU,KAAK,UAAU,IAAI,KAAK,GAAG,CAAC,IACtD,SAAS,IAAI;AACnB,YAAM,UAAU,KAAK,WAAW,SAC5B,QAAQ,KAAK,UAAU,IAAI,QAAQ,EAAE,KAAK,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC,KACxE;AACJ,aAAO,GAAG,KAAK,GAAG,OAAO,IAAI,MAAM,GAAG,MAAM;AAAA,IAC9C;AAAA,IACA,KAAK;AACH,aAAO,GAAG,KAAK,GAAG,KAAK,UAAU,uBAAuB,MAAM,GAAG,KAAK,UAAU,IAAI,cAAc,IAAI,CAAC,UAAU,KAAK,IAAI,MAAM,KAAK,KAAK,IAAI,UAAU,cAAc,IAAI,CAAC,EAAE,GAAG,KAAK,OAAO,QAAQ,iBAAiB,KAAK,OAAO,UAAU,mBAAmB,EAAE;AAAA,IAClQ,KAAK;AACH,aAAO,GAAG,KAAK,SAAS,SAAS,cAAc,IAAI,EAAE,IAAI,QAAQ,CAAC,CAAC,GAAG,KAAK,WAAW,OAAO,cAAc,IAAyC,CAAC,KAAK,EAAE;AAAA,IAC9J,KAAK,SAAS;AACZ,YAAM,OACJ,KAAK,SAAS,SACV,IAAI,KAAK,KAAK,SAAS,KAAK,GAAG,KAAK,KAAK,MAAM,GAAG,EAAE,CAAC,QAAQ,KAAK,IAAI,MACtE,YAAY,KAAK,IAAI;AAC3B,aAAO,GAAG,KAAK,QAAQ,IAAI,kBAAkB,KAAK,WAAW,SAAS,cAAc,EAAE,UAAU,KAAK,UAAU,IAAI,KAAK,GAAG,CAAC,CAAC,KAAK,EAAE;AAAA,IACtI;AAAA,IACA,KAAK;AACH,aAAO,GAAG,KAAK,UAAU,KAAK,KAAK,IAAI,OAAO,MAAM,IAAI,KAAK,KAAK,IAAI,aAAa,UAAU;AAAA,IAC/F,KAAK;AACH,aAAO,GAAG,KAAK,2BAA2B,KAAK,IAAI,MAAM,UAAU,OAAO,IAAI,KAAK,IAAI,MAAM,SAAS,OAAO;AAAA,IAC/G,KAAK,QAAQ;AACX,YAAM,UAAU,KAAK,WAAW,SAC5B,QAAQ,KAAK,UAAU,IAAI,QAAQ,EAAE,KAAK,GAAG,CAAC,UAC9C;AACJ,YAAM,OAAO,UAAU,SAAS;AAChC,UAAI,KAAK,SAAS;AAChB,eAAO,GAAG,KAAK,GAAG,OAAO,GAAG,UAAU,SAAS,MAAM,eAAe,OAAO,KAAK,SAAS,WAAW,MAAM,KAAK,IAAI,KAAK,MAAM,KAAK,KAAK,MAAM,SAAS;AACzJ,aAAO,GAAG,KAAK,GAAG,OAAO,GAAG,IAAI,IAAI,KAAK,WAAW,cAAc,IAAyC,IAAI,sBAAsB;AAAA,IACvI;AAAA,EACF;AACF;AAEO,SAAS,SAAS,SAAgC;AACvD,QAAM,QAAoB,CAAC;AAC3B,MAAI,cAAc;AAClB,MAAI,QAAQ;AAGZ,QAAM,WAAW,oBAAI,IAAoB;AACzC,aAAW,QAAQ,QAAQ,OAAO;AAChC,QAAI,KAAK;AACP,eAAS;AAAA,QACP,KAAK;AAAA,QACL,KAAK,IAAI,GAAG,MAAM,UAAU,KAAK,OAAO,SAAS,IAAI,EAAE;AAAA,MACzD;AACF,aAAS,QAAQ,IAAI;AACrB,QAAI,KAAK,OAAO,QAAQ;AAGtB,UAAI,MAAM,QAAQ;AAChB,cAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,aAAK,SAAS,KAAK,SAAS,KAAK,KAAK,KAAK;AAAA,MAC7C,MAAO,gBAAe,KAAK;AAC3B;AAAA,IACF;AACA,UAAM,OAAO,OAAO,IAAI;AACxB,QAAI,CAAC,KAAM;AACX,UAAM,OAAiB,EAAE,GAAG,MAAM,SAAS,GAAG,KAAK;AACnD,QAAI,KAAK,OAAO,QAAS,MAAK,QAAQ,QAAQ,IAAI,IAAI;AACtD,UAAM,KAAK,IAAI;AAAA,EACjB;AACA,OAAK;AACL,QAAM,SAAS,QAAQ,SAAS,CAAC,GAAG,IAAI,CAAC,UAAU;AAAA,IACjD,IAAI,KAAK;AAAA,IACT,OAAO,KAAK,MACT,IAAI,CAAC,SAAS;AACb,YAAM,OAAO,KAAK,OAAO,SAAS,QAAQ,GAAG,KAAK,EAAE,CAAC,OAAO,OAAO,IAAI;AACvE,UAAI,CAAC,KAAM,QAAO;AAClB,YAAM,OAAO,KAAK,OACd,WAAW,SAAS,IAAI,KAAK,IAAI,KAAK,GAAG,cACzC,KAAK,QACH,aAAa,SAAS,IAAI,KAAK,KAAK,KAAK,GAAG,eAC5C;AACN,aAAO,GAAG,IAAI,GAAG,IAAI;AAAA,IACvB,CAAC,EACA,OAAO,CAAC,MAAmB,MAAM,IAAI;AAAA,EAC1C,EAAE;AACF,SAAO;AAAA,IACL,KAAK,QAAQ;AAAA,IACb,UAAU,QAAQ;AAAA,IAClB;AAAA;AAAA,IAEA,QAAQ,KAAK,OAAO,QAAQ,MAAM,SAAS,OAAQ,GAAI;AAAA,IACvD,OAAO,QAAQ,OAAO,UAAU;AAAA,IAChC,GAAI,QAAQ,UAAU,SAAS,EAAE,UAAU,QAAQ,SAAS,OAAO,IAAI,CAAC;AAAA,IACxE,GAAI,MAAM,SAAS,EAAE,MAAM,IAAI,CAAC;AAAA,EAClC;AACF;AAGO,SAAS,eAAe,MAAwB;AACrD,QAAM,MAAgB,CAAC;AACvB,MAAI;AAAA,IACF,6EAA6E,KAAK,MAAM,WAAW,KAAK,WAAW,kBAAkB,KAAK,SAAS,KAAK,OAAI,KAAK,SAAS,MAAM,KAAK,EAAE;AAAA,EACzL;AACA,MAAI,KAAK;AACP,QAAI,KAAK,YAAY,KAAK,GAAG,qCAAqC;AACpE,MAAI,KAAK;AACP,QAAI;AAAA,MACF,yCAAyC,KAAK,KAAK,cAAc,KAAK,UAAU,IAAI,KAAK,GAAG;AAAA,IAC9F;AACF,MAAI,KAAK,EAAE;AACX,aAAW,KAAK,KAAK,OAAO;AAC1B,UAAM,OACJ,EAAE,SAAS,EAAE,SAAS,MAAM,SAAS,GAAG,EAAE,QAAQ,GAAI,CAAC,QAAQ;AACjE,QAAI,KAAK,GAAG,EAAE,CAAC,KAAK,EAAE,IAAI,IAAI,IAAI,EAAE;AAAA,EACtC;AACA,aAAW,QAAQ,KAAK,SAAS,CAAC,GAAG;AACnC,QAAI,KAAK,EAAE;AACX,QAAI;AAAA,MACF,oBAAoB,KAAK,EAAE;AAAA,IAC7B;AACA,SAAK,MAAM,QAAQ,CAAC,GAAG,MAAM,IAAI,KAAK,KAAK,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC;AAAA,EACtE;AACA,MAAI,KAAK,EAAE;AACX,MAAI;AAAA,IACF;AAAA,EACF;AACA,MAAI,KAAK;AACP,QAAI;AAAA,MACF,gCAAgC,KAAK,QAAQ,iBAAiB,KAAK,aAAa,IAAI,KAAK,GAAG;AAAA,IAC9F;AACF,SAAO,IAAI,KAAK,IAAI;AACtB;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vosjs/cli",
3
- "version": "0.80.0",
3
+ "version": "0.81.0",
4
4
  "description": "The vos CLI: record the real product from a scripted browser flow, auto-zoom from the cursor track, cut as data in doc.json, render deterministic video, deliver a release's video cuts per destination spec, and sync with vos.so. One binary, every verb, MIT.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -54,11 +54,11 @@
54
54
  "@vosjs/core": "^0.31.0",
55
55
  "@vosjs/editor": "^1.6.0",
56
56
  "@vosjs/elements": "^0.12.1",
57
- "@vosjs/render-core": "^0.9.0",
58
57
  "@vosjs/shared": "^0.7.0",
59
- "@vosjs/studio-core": "^0.45.0",
60
- "@vosjs/timeline": "^0.5.0",
61
- "@vosjs/tween": "^0.8.3"
58
+ "@vosjs/render-core": "^0.10.0",
59
+ "@vosjs/studio-core": "^0.46.0",
60
+ "@vosjs/tween": "^0.8.3",
61
+ "@vosjs/timeline": "^0.5.0"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@types/node": "^22",
@@ -33,51 +33,14 @@
33
33
  "type": "array",
34
34
  "description": "Run BEFORE the camera rolls, after the first navigation: a sign-in form, a cookie banner, an onboarding tour. Plain actions with no cursor, no frames, no pace and nothing in meta.steps; then the recorder opens `url` again and the take begins where the setup left it. A selector that never appears fails the take before anything is recorded (exit 2). A `type` step's text may be { \"env\": \"NAME\" }, read at run time and never logged or stored; a literal typed into a password field is refused, because actions.json is committed and pushed.",
35
35
  "items": {
36
- "type": "object",
37
- "required": ["do"],
38
- "additionalProperties": false,
39
- "properties": {
40
- "do": {
41
- "type": "string",
42
- "enum": ["wait", "click", "type", "press", "goto"]
43
- },
44
- "id": {
45
- "type": "string"
46
- },
47
- "selector": {
48
- "type": "string"
49
- },
50
- "text": {
51
- "oneOf": [
52
- {
53
- "type": "string"
54
- },
55
- {
56
- "type": "object",
57
- "required": ["env"],
58
- "additionalProperties": false,
59
- "properties": {
60
- "env": {
61
- "type": "string",
62
- "description": "the environment variable holding the value"
63
- }
64
- }
65
- }
66
- ]
67
- },
68
- "key": {
69
- "type": "string",
70
- "description": "press: a Playwright key name (Enter, Tab, Escape)"
71
- },
72
- "url": {
73
- "type": "string",
74
- "description": "goto: a page to open, for a sign-in that lives elsewhere"
75
- },
76
- "ms": {
77
- "type": "number",
78
- "description": "wait: the pause; click: the read after the page settled, held from its last visual change (default 150); type and scroll: the settle after (defaults 150/200)"
79
- }
80
- }
36
+ "$ref": "#/$defs/offCameraStep"
37
+ }
38
+ },
39
+ "teardown": {
40
+ "type": "array",
41
+ "description": "Run AFTER the take, off camera, with the same verbs and rules as setup: what the take created in the product (an API key, a project, an invite) removed again, so a rehearsal or a re-record leaves the account as it found it. The steps run once the screencast has stopped, on the take's own page and session, after a --dry-run rehearsal too, and also when the take stops early or throws after its first step ran. Nothing of them is captured or written to meta.steps. Bounded at 60 s in all. A step that fails stops the teardown and is reported (the --json done event's `teardown`), and never fails the take or changes its exit code. A { \"env\": \"NAME\" } the shell does not hold refuses the take before it begins.",
42
+ "items": {
43
+ "$ref": "#/$defs/offCameraStep"
81
44
  }
82
45
  },
83
46
  "ready": {
@@ -1107,6 +1070,54 @@
1107
1070
  }
1108
1071
  }
1109
1072
  ]
1073
+ },
1074
+ "offCameraStep": {
1075
+ "type": "object",
1076
+ "required": ["do"],
1077
+ "additionalProperties": false,
1078
+ "properties": {
1079
+ "do": {
1080
+ "type": "string",
1081
+ "enum": ["wait", "click", "type", "press", "goto"]
1082
+ },
1083
+ "id": {
1084
+ "type": "string"
1085
+ },
1086
+ "selector": {
1087
+ "type": "string"
1088
+ },
1089
+ "text": {
1090
+ "oneOf": [
1091
+ {
1092
+ "type": "string"
1093
+ },
1094
+ {
1095
+ "type": "object",
1096
+ "required": ["env"],
1097
+ "additionalProperties": false,
1098
+ "properties": {
1099
+ "env": {
1100
+ "type": "string",
1101
+ "description": "the environment variable holding the value"
1102
+ }
1103
+ }
1104
+ }
1105
+ ]
1106
+ },
1107
+ "key": {
1108
+ "type": "string",
1109
+ "description": "press: a Playwright key name (Enter, Tab, Escape)"
1110
+ },
1111
+ "url": {
1112
+ "type": "string",
1113
+ "description": "goto: a page to open, for a sign-in that lives elsewhere"
1114
+ },
1115
+ "ms": {
1116
+ "type": "number",
1117
+ "description": "wait: the pause; click: the read after the page settled, held from its last visual change (default 150); type and scroll: the settle after (defaults 150/200)"
1118
+ }
1119
+ },
1120
+ "description": "One setup or teardown step: a plain action, with no cursor and no frames."
1110
1121
  }
1111
1122
  }
1112
1123
  }
@@ -334,7 +334,33 @@
334
334
  },
335
335
  "cursor": {
336
336
  "type": "object",
337
- "description": "Cursor rendering (visible, hideWhenIdle, smoothing, size, style, click effects). See CursorStyle in @vosjs/studio-core. `style: \"arrow\"` (the default for new takes) draws an OS-style pointer with its tip on the recorded point; `\"default\"` (every earlier take) draws the white dot. `visible: false` hides the drawn pointer only \u2014 the track still drives cursor-follow zoom, and click effects keep drawing (silence those with clickFx.style: \"none\"). `hideWhenIdle` (default true) fades the dot out after ~1s parked and back in when it moves; scrolling is not movement, and it is always back at full opacity on a click.",
337
+ "description": "Cursor rendering (visible, hideWhenIdle, smoothing, size, style, click effects). See CursorStyle in @vosjs/studio-core. `style: \"arrow\"` (the default for new takes) draws an OS-style pointer with its tip on the recorded point; `\"default\"` (every earlier take) draws the white dot. `visible: false` hides the drawn pointer only \u2014 the track still drives cursor-follow zoom, and click effects keep drawing (silence those with clickFx.style: \"none\"). `hideWhenIdle` (default true) fades the dot out after ~1s parked and back in when it moves; scrolling is not movement, and it is always back at full opacity on a click. `off` hides the drawn pointer over time ranges.",
338
+ "properties": {
339
+ "off": {
340
+ "type": "array",
341
+ "description": "Time ranges in which the drawn pointer is hidden, in OUTPUT seconds (the timeline the viewer sees, like overlays, so a range stays put through trims and speed changes). `{ in, out?, from? }`: an absent `out` hides it to the end of the film. The pointer fades out over 0.15 s before `in` and back in over 0.15 s after `out`, so the range itself is fully hidden; overlapping ranges read as their union. The track still drives cursor-follow zoom and click effects, as with `visible: false`. `from` marks a range something else placed (`endcard`: the end card's, written by `vos plan` and replaced or removed with the end card); a range without one is yours and a re-plan never touches it.",
342
+ "items": {
343
+ "type": "object",
344
+ "required": ["in"],
345
+ "properties": {
346
+ "in": {
347
+ "type": "number",
348
+ "minimum": 0,
349
+ "description": "OUTPUT seconds the pointer is gone from."
350
+ },
351
+ "out": {
352
+ "type": "number",
353
+ "description": "OUTPUT seconds it returns at, past `in`. Absent = to the end of the film."
354
+ },
355
+ "from": {
356
+ "type": "string",
357
+ "description": "What placed it (e.g. `endcard`). Absent = a person's own."
358
+ }
359
+ },
360
+ "additionalProperties": false
361
+ }
362
+ }
363
+ },
338
364
  "additionalProperties": true
339
365
  },
340
366
  "cam": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/plugin/assetManifest.ts","../src/plugin/pathName.ts","../src/plugin/media.ts","../src/plugin/take.ts","../src/plugin/uploadAsset.ts","../src/plugin/pasteFiles.ts"],"sourcesContent":["/**\n * A program's declared files (`config.assets`) crossing to and from vos.so.\n *\n * On disk a manifest names files beside the config (`./logo.png`), so a\n * program renders offline and reads like the repository it lives in. On the\n * platform a manifest names hosted assets (`asset:<id>`), the one spelling\n * the platform resolves for every surface: a private file reaches a render\n * page, a remix brings its files along, and a file a program still plays is\n * never collected. A push is the crossing one way, `vos fetch --media` the\n * other; neither rewrites the other side's spelling in place.\n */\nimport { existsSync, readFileSync, realpathSync, rmSync } from 'node:fs'\nimport { join, relative } from 'node:path'\nimport { hostedAssetId, localFile, manifestRefs } from '../programAssets'\nimport { safePathName } from './pathName'\nimport { writeFileSafeSync } from './safeWrite'\nimport { downloadMedia, uploadDocRefs } from './media'\nimport type { DocMediaRef } from './media'\nimport type { UploadTarget } from './uploadAsset'\n\ntype Manifest = Record<string, { ref: string | string[] }>\n\nconst isUrl = (ref: string) => /^(https?:|data:|blob:)/.test(ref)\n\n/** The origins whose `/api/assets/<id>/file` is a hosted file of ours. */\nconst homeOrigins = (origin: string) => [\n origin,\n 'https://vos.so',\n 'https://www.vos.so',\n]\n\n/** `assets/.hosted.json`: which hosted file each file brought home is. */\nconst HOME_INDEX = '.hosted.json'\n\n/** A deep copy of the manifest, so a caller's rewrite never reaches disk. */\nfunction cloneManifest(config: Record<string, unknown>): Manifest {\n return JSON.parse(JSON.stringify(config.assets ?? {})) as Manifest\n}\n\nfunction setRef(\n manifest: Manifest,\n name: string,\n index: number | null,\n next: string,\n): void {\n const decl = manifest[name]\n if (index === null) decl.ref = next\n else (decl.ref as string[])[index] = next\n}\n\nconst where = (name: string, index: number | null) =>\n index === null ? `assets.${name}` : `assets.${name}[${index}]`\n\n/**\n * Upload the manifest's local files and point every entry at its hosted\n * asset as `asset:<id>`; a hosted file spelled as a URL takes the same\n * spelling. `config.assets` is REPLACED on the object (the file on disk is\n * untouched), so everything composed from it afterwards carries the hosted\n * refs. Uploads are content-addressed: a re-push sends nothing.\n */\nexport async function uploadManifest(\n config: Record<string, unknown>,\n dir: string,\n target: UploadTarget,\n log: (line: string) => void,\n): Promise<{ uploaded: number }> {\n const refs = manifestRefs(config)\n if (refs.length === 0) return { uploaded: 0 }\n const manifest = cloneManifest(config)\n const local: DocMediaRef[] = []\n for (const { name, index, ref } of refs) {\n const hosted = hostedAssetId(ref, homeOrigins(target.origin))\n if (hosted) {\n setRef(manifest, name, index, `asset:${hosted}`)\n continue\n }\n if (isUrl(ref)) continue\n // A push uploads what the manifest names, so it holds the same line a\n // render does: only a file inside the program's own directory.\n const found = localFile(ref, dir)\n if ('refused' in found) {\n throw new Error(`${where(name, index)} names ${ref}, ${found.refused}`)\n }\n local.push({\n where: where(name, index),\n // The upload helper reads keys against the directory.\n key: relative(realpathSync(dir), found.file),\n set: (url) => {\n const id = hostedAssetId(url, homeOrigins(target.origin))\n setRef(manifest, name, index, id ? `asset:${id}` : url)\n },\n })\n }\n await uploadDocRefs(local, dir, target, log)\n config.assets = manifest\n return { uploaded: local.length }\n}\n\n/** The manifest entries that are still files on this machine. */\nexport function localManifestRefs(config: Record<string, unknown>): string[] {\n return manifestRefs(config)\n .filter(({ ref }) => !isUrl(ref) && !hostedAssetId(ref, homeOrigins('')))\n .map(({ name, index }) => where(name, index))\n}\n\n/**\n * Bring a fetched program's hosted files home: each `asset:<id>` lands as\n * `assets/<name>.<ext>` beside the config (the bytes choose the extension)\n * and its entry is re-pointed at that file.\n *\n * A file already there is kept only when it IS that hosted file\n * (`assets/.hosted.json` remembers which id each one came from). Kept by\n * name alone, a second fetch after someone changed `logo` left the old\n * picture under the new manifest, and the next push stored the old one\n * again without a word.\n *\n * Returns the rewritten manifest, or null when nothing in it is hosted.\n */\nexport async function pullManifest(\n ctx: { origin: string; key: string | null },\n dir: string,\n config: Record<string, unknown>,\n log: (line: string) => void,\n): Promise<{ assets: Manifest; files: string[] } | null> {\n const home = homeOrigins(ctx.origin)\n const hosted = manifestRefs(config).filter(({ ref }) =>\n hostedAssetId(ref, home),\n )\n if (hosted.length === 0) return null\n const manifest = cloneManifest(config)\n const indexPath = join(dir, 'assets', HOME_INDEX)\n let index: Record<string, string> = {}\n try {\n const parsed: unknown = JSON.parse(readFileSync(indexPath, 'utf8'))\n if (parsed && typeof parsed === 'object') {\n index = parsed as Record<string, string>\n }\n } catch {\n // no index yet: nothing here is known to be a hosted file\n }\n const files: string[] = []\n for (const { name, index: at, ref } of hosted) {\n const id = hostedAssetId(ref, home)!\n // The manifest name is the config's word and becomes a file name here,\n // so it is held to a plain path segment (the asset id stands in).\n const stem = safePathName(at === null ? name : `${name}-${at}`, id)\n const known = Object.keys(index).find(\n (f) => f === stem || f.startsWith(`${stem}.`),\n )\n let file: string\n if (\n known &&\n index[known] === id &&\n existsSync(join(dir, 'assets', known))\n ) {\n file = `assets/${known}`\n } else {\n // A different hosted file under this name: the old copy goes first,\n // or a changed extension would leave both lying side by side.\n if (known) {\n rmSync(join(dir, 'assets', known), { force: true })\n delete index[known]\n }\n const got = await downloadMedia(ctx, `/api/assets/${id}/file`, {\n dir,\n subdir: 'assets',\n stem,\n fallbackExt: '',\n describe: `${where(name, at)} (asset ${id})`,\n notFoundHint: 'a private file needs a content key of its owner',\n })\n file = got.file\n index[file.slice('assets/'.length)] = id\n log(\n ` ${file} ← ${where(name, at)}, asset ${id} (${Math.round(got.bytes / 1024)} kB)`,\n )\n }\n files.push(file)\n setRef(manifest, name, at, `./${file}`)\n }\n writeFileSafeSync(indexPath, JSON.stringify(index, null, 2) + '\\n')\n return { assets: manifest, files }\n}\n\nconst HOSTED_FILE_LITERAL =\n /(?:asset:|\\/api\\/assets\\/)([0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12})/g\nconst FUNCTION_FIELDS = ['setup', 'createContent', 'createTimeline', 'onFrame']\n\n/**\n * A hosted file named inside a function instead of in the manifest. The\n * platform cannot give a string in code a URL per surface, so the file does\n * not reach a render of a private program and does not follow a remix.\n * One line per function and file, in words that say the fix.\n */\nexport function hostedLiteralWarnings(\n config: Record<string, unknown>,\n): string[] {\n const out: string[] = []\n const scan = (label: string, src: unknown) => {\n if (typeof src !== 'string') return\n const seen = new Set<string>()\n for (const m of src.matchAll(HOSTED_FILE_LITERAL)) {\n if (seen.has(m[1])) continue\n seen.add(m[1])\n out.push(\n `${label} names hosted file ${m[1]} in code. Declare it instead: \"assets\": { \"name\": { \"ref\": \"asset:${m[1]}\" } }, and read ctx.assets.name, so it loads on every render and follows a remix`,\n )\n }\n }\n for (const field of FUNCTION_FIELDS) scan(field, config[field])\n const stack = Array.isArray(config.stack) ? config.stack : []\n for (const entry of stack as Record<string, unknown>[]) {\n for (const field of ['setup', 'createContent', 'onFrame']) {\n scan(`stack.${String(entry.id)}.${field}`, entry[field])\n }\n }\n return out\n}\n","/**\n * A name the server chose (a slug, a title, a file name) as a path segment\n * on this disk. The server's word is never trusted as a path: a separator,\n * a `..`, a control character or a leading dot is dropped, everything else\n * collapses to `[A-Za-z0-9._-]`, and a name with nothing left falls back to\n * the id, which is ours to shape.\n */\nexport function safePathName(\n name: string | undefined,\n fallback: string,\n): string {\n const cleaned = (name ?? '')\n // eslint-disable-next-line no-control-regex\n .replace(/[\\u0000-\\u001f\\u007f]/g, '')\n .split(/[\\\\/]/)\n .filter((part) => part !== '' && part !== '.' && part !== '..')\n .join('-')\n .replace(/[^A-Za-z0-9._-]+/g, '-')\n .replace(/^[.-]+/, '')\n .replace(/-+$/, '')\n .slice(0, 80)\n if (cleaned) return cleaned\n const id = fallback.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^[.-]+/, '')\n return id || 'file'\n}\n","import { isKeyedOverlay } from '@vosjs/studio-core'\n/**\n * A hosted take comes home: download the recording (and the\n * mic/cam sidecars when the doc carries them) beside a pulled doc.json, and\n * re-anchor the doc's keys to the local files, so `vos digest`, `frames` and\n * `render` run on a pulled take exactly as on a local one. Also writes the\n * take-dir markers (meta.json, cursor.json) from the doc when absent, so the\n * directory IS a take directory to every other verb.\n *\n * Same consent shape as the upload direction: nothing moves without the verb\n * the user ran (`--media`). A file already on disk is kept, never re-fetched.\n */\nimport { createHash } from 'node:crypto'\nimport { createWriteStream, existsSync } from 'node:fs'\nimport {\n mkdir,\n open,\n readFile,\n readdir,\n rename,\n rm,\n stat,\n} from 'node:fs/promises'\nimport { basename, dirname, join } from 'node:path'\nimport { Readable } from 'node:stream'\nimport { pipeline } from 'node:stream/promises'\nimport { localFile } from '../programAssets'\nimport { refuseSymlink, writeFileSafe } from './safeWrite'\nimport { takeMediaFiles, writeJson } from './take'\nimport {\n MEDIA_HEAD_BYTES,\n extensionFor,\n nameForType,\n resolveMediaType,\n} from './container'\nimport { uploadAsset } from './uploadAsset'\nimport { pullPasteFiles } from './pasteFiles'\nimport { bearerFor, writeSyncState } from './platform'\nimport type { UploadTarget, UploadedAsset } from './uploadAsset'\nimport type {\n AudioClip,\n ObjectClip,\n OverlayClip,\n ProjectDoc,\n} from '@vosjs/studio-core'\n\n/** The names the recorders write their sidecars under. */\nexport const MIC_NAME = 'mic.webm'\nexport const CAM_NAME = 'cam.webm'\n\n/**\n * A take's own media: the stem each one lives under, and the container to\n * assume when the bytes that come back say nothing. The EXTENSION is not part\n * of the identity — a pulled recording wears whatever its hosted asset holds.\n */\nconst SOURCE_MEDIA = [\n { key: 'videoKey', stem: 'recording', fallbackExt: '.webm' },\n { key: 'micKey', stem: 'mic', fallbackExt: '.webm' },\n { key: 'camKey', stem: 'cam', fallbackExt: '.webm' },\n] as const\n\n/** A take-relative media key: neither a URL, a blob, nor a hosted asset path. */\nexport function isTakeRelativeKey(key: string | undefined): key is string {\n // `media:<id>` names a document media (a layer that shows one); the media\n // itself rides the upload door, so the reference is never a file.\n return (\n !!key &&\n !/^(https?:|blob:|data:|media:|\\/\\/)/.test(key) &&\n !key.startsWith('/api/')\n )\n}\n\n/** The file a take-relative key names, under the take directory. */\nexport const takeRelativeFile = (key: string): string => key.replace(/^\\/+/, '')\n\nexport interface DocMediaRef {\n /** Where the key lives, in words (for a log line). */\n where: string\n key: string\n /** Replace the key in the document. */\n set: (next: string) => void\n}\n\n/**\n * A take's own sidecars, when they are local files: the voice (`micKey`)\n * and the webcam (`camKey`). A push used to DROP them (\"local-only\"), while\n * `vos fetch --media` writes them beside the take as `mic.webm` and\n * `cam.webm`: fetch, then push, and the voice and the webcam were gone from\n * the hosted take, in one log line nobody reads.\n */\nexport function sourceSidecarRefs(\n doc: ProjectDoc,\n keep: (key: string | undefined) => boolean = isTakeRelativeKey,\n): DocMediaRef[] {\n const out: DocMediaRef[] = []\n for (const [field, where] of [\n ['micKey', 'mic track'],\n ['camKey', 'cam track'],\n ] as const) {\n const key = doc.source[field]\n if (!key || !keep(key)) continue\n out.push({\n where,\n key,\n set: (next) => {\n doc.source[field] = next\n },\n })\n }\n return out\n}\n\n/** The 3D props a document places: each names a model file (a GLB). */\nexport function docObjectRefs(\n doc: { objects?: ObjectClip[] },\n keep: (key: string | undefined) => boolean = isTakeRelativeKey,\n): DocMediaRef[] {\n const out: DocMediaRef[] = []\n for (const clip of doc.objects ?? []) {\n const asset = clip.asset as { kind?: string; key?: string }\n if (asset.kind !== 'gltf' || !keep(asset.key)) continue\n out.push({\n where: `object ${clip.id}`,\n key: asset.key as string,\n set: (next) => {\n asset.key = next\n },\n })\n }\n return out\n}\n\n/** The picture and video layers a document places, by key. */\nexport function docOverlayRefs(\n doc: { overlays?: OverlayClip[] },\n keep: (key: string | undefined) => boolean = isTakeRelativeKey,\n): DocMediaRef[] {\n const out: DocMediaRef[] = []\n for (const clip of doc.overlays ?? []) {\n // An HTML layer has no key: its content IS its source. Its only files are\n // the faces it brings (`fonts`), each a url the page fetches, so a face\n // still beside the document crosses like any other layer file.\n if (clip.kind === 'html') {\n for (const [i, font] of (clip.fonts ?? []).entries()) {\n if (!keep(font.url)) continue\n out.push({\n where: `overlay ${clip.id} font ${i}`,\n key: font.url,\n set: (next) => {\n font.url = next\n },\n })\n }\n continue\n }\n if (!isKeyedOverlay(clip) || !keep(clip.key)) continue\n out.push({\n where: `overlay ${clip.id}`,\n key: clip.key,\n set: (next) => {\n clip.key = next\n },\n })\n }\n return out\n}\n\n/**\n * The shared layers either kind of document carries, as files: picture and\n * video overlays, 3D props, and added sound. A program document has no\n * recording, frame or end card, so this is all it can name; a take reads\n * the same three through `docMediaRefs`.\n */\nexport function docLayerRefs(\n doc: {\n overlays?: OverlayClip[]\n objects?: ObjectClip[]\n audio?: AudioClip[]\n },\n keep: (key: string | undefined) => boolean = isTakeRelativeKey,\n): DocMediaRef[] {\n return [\n ...docOverlayRefs(doc, keep),\n ...docObjectRefs(doc, keep),\n ...docAudioRefs(doc, keep),\n ]\n}\n\n/**\n * Every media key a document carries beside its recording, mic and cam:\n * image and video overlay clips (a poster's mark), 3D props, the end card's\n * mark, a background image or loop, added sound. The recording's own keys\n * are `KEYS` and handled by their own path. A push rehosts these; a pull\n * brings them home.\n */\nexport function docMediaRefs(\n doc: ProjectDoc,\n keep: (key: string | undefined) => boolean = isTakeRelativeKey,\n): DocMediaRef[] {\n const out: DocMediaRef[] = []\n // The take's OTHER media (concat): each recording, and its sidecars,\n // rides the upload door like the primary's; a pull brings them home.\n for (const m of doc.media ?? []) {\n if (keep(m.videoKey))\n out.push({\n where: `media ${m.id}`,\n key: m.videoKey,\n set: (next) => {\n m.videoKey = next\n },\n })\n const mic = m.micKey\n if (mic && keep(mic))\n out.push({\n where: `media ${m.id} mic`,\n key: mic,\n set: (next) => {\n m.micKey = next\n },\n })\n const cam = m.camKey\n if (cam && keep(cam))\n out.push({\n where: `media ${m.id} cam`,\n key: cam,\n set: (next) => {\n m.camKey = next\n },\n })\n }\n out.push(...docOverlayRefs(doc, keep))\n // A prop's model was never walked: a take with a GLB pushed a document\n // whose object keyed a file only the pusher's disk held.\n out.push(...docObjectRefs(doc, keep))\n const mark = doc.endCard?.mark\n if (mark && keep(mark.key))\n out.push({\n where: 'end card mark',\n key: mark.key,\n set: (next) => {\n mark.key = next\n },\n })\n const bg = doc.frame.backgroundMedia\n if (bg && keep(bg.key))\n out.push({\n where: 'background',\n key: bg.key,\n set: (next) => {\n bg.key = next\n },\n })\n out.push(...docAudioRefs(doc, keep))\n return out\n}\n\n/**\n * The sound a document adds (`doc.audio`: a score, a voice-over, an effect),\n * for either kind of document. A take reads it through `docMediaRefs`; a\n * program document, which has no recording or frame, reads it here. A\n * catalog track is an absolute URL and is never walked.\n */\nexport function docAudioRefs(\n doc: { audio?: AudioClip[] },\n keep: (key: string | undefined) => boolean = isTakeRelativeKey,\n): DocMediaRef[] {\n const out: DocMediaRef[] = []\n for (const clip of doc.audio ?? []) {\n if (!keep(clip.key)) continue\n out.push({\n where: `audio ${clip.id}`,\n key: clip.key,\n set: (next) => {\n // The same bytes under a new name (uploaded, or brought home): a\n // measurement taken for the old name holds for the new one.\n if (clip.loudness?.src === clip.key) clip.loudness.src = next\n clip.key = next\n },\n })\n }\n return out\n}\n\n/**\n * Upload the files a document names and point its keys at the hosted\n * assets. Each file rides the content-addressed upload door, so a re-push\n * reuses it; the bytes name the type, and the uploaded filename is corrected\n * to match so the asset is never self-contradictory. A key whose file is\n * missing is said and left as it is (the hosted render will 404 on it,\n * honestly). Take pushes and program pushes share it.\n */\nexport async function uploadDocRefs(\n refs: DocMediaRef[],\n dir: string,\n target: UploadTarget,\n log: (line: string) => void,\n): Promise<void> {\n for (const ref of refs) {\n // The key names a file in the take directory or below, never outside\n // it: a fetched or pasted document is not trusted with the disk.\n const local = localFile(takeRelativeFile(ref.key), dir)\n if ('refused' in local) {\n log(\n `warning ${ref.where}: ${ref.key} is not beside the document, so it was not uploaded`,\n )\n continue\n }\n const file = local.file\n const media = await readFile(file)\n const mediaHash = createHash('sha256').update(media).digest('hex')\n const { type } = resolveMediaType({\n head: media.subarray(0, MEDIA_HEAD_BYTES),\n filename: file,\n })\n const name = nameForType(basename(file), type)\n if (name !== basename(file)) {\n log(` ${ref.where}: ${basename(file)} holds ${type} — sent as ${name}`)\n }\n let put: UploadedAsset\n try {\n put = await uploadAsset(target, new Uint8Array(media), {\n filename: name,\n contentType: type,\n contentHash: mediaHash,\n })\n } catch (e) {\n throw new Error(\n `${ref.where} upload failed: ${e instanceof Error ? e.message : String(e)}`,\n )\n }\n ref.set(put.url)\n log(\n ` ${ref.where}: ${ref.key} → asset ${put.id}${put.reused ? ' (reused)' : ''}`,\n )\n for (const note of put.notes) log(` ${ref.where}: ${note}`)\n }\n}\n\n// The extension↔type table and the byte sniffer live in `container.ts`; these\n// stay the door every media caller already knows.\nexport { extensionFor, mediaContentType } from './container'\n\n/** An asset file URL (relative `/api/assets/{id}/file` or absolute). */\nexport function assetIdOf(url: string | undefined): string | null {\n if (!url) return null\n const m = /\\/api\\/assets\\/([A-Za-z0-9_-]+)\\/file/.exec(url)\n return m ? m[1] : null\n}\n\nexport interface MediaPullResult {\n downloaded: { file: string; assetId: string; bytes: number }[]\n kept: string[]\n /** Every hosted file now beside the take, downloaded or kept: its path\n * here and the address it came from. */\n hosted: { file: string; url: string }[]\n}\n\n/** The first bytes of a file, enough for every container signature. */\nexport async function readMediaHead(file: string): Promise<Uint8Array> {\n const handle = await open(file, 'r')\n try {\n const buf = Buffer.alloc(MEDIA_HEAD_BYTES)\n const { bytesRead } = await handle.read(buf, 0, MEDIA_HEAD_BYTES, 0)\n return new Uint8Array(buf.subarray(0, bytesRead))\n } finally {\n await handle.close()\n }\n}\n\nexport interface DownloadTarget {\n dir: string\n /** Directory under the take dir, when the file is filed away. */\n subdir?: string\n /** The name without its extension — the extension is the BYTES' to choose. */\n stem: string\n /** The container to assume when the bytes say nothing. */\n fallbackExt: string\n /** What is being fetched, in words, for an error or a log line. */\n describe: string\n /** Added to a 404, where the reason is usually the credential. */\n notFoundHint?: string\n}\n\n/**\n * One media file, brought home under the name its BYTES earn.\n *\n * Streamed to a `.part` sibling first, then sniffed and renamed, so a take\n * never holds a file whose extension lies — whatever the server declared, and\n * whatever the hosted asset happened to be called. A push of a pulled take\n * re-uploads that file, so a wrong name here becomes a wrong Content-Type on\n * the asset, and a render that dies inside the video element.\n */\n/**\n * Where a document key is fetched from, and whether the content key rides\n * along. The key goes ONLY to the configured origin: a hosted file named\n * as a file-door address on some other host is read from the home door by\n * its id, and any other URL is fetched bare, so a stranger's document\n * can never send the reader's credential elsewhere.\n */\nexport function downloadPlan(\n ctx: { origin: string },\n url: string,\n): { url: string; bearer: boolean } {\n const home = new URL(ctx.origin).origin\n if (!/^https?:/.test(url)) return { url: `${home}${url}`, bearer: true }\n let abs: URL\n try {\n abs = new URL(url)\n } catch {\n return { url, bearer: false }\n }\n if (abs.origin === home) return { url: abs.href, bearer: true }\n const id = assetIdOf(url)\n if (id) return { url: `${home}/api/assets/${id}/file`, bearer: true }\n return { url: abs.href, bearer: false }\n}\n\nexport async function downloadMedia(\n ctx: { origin: string; key: string | null },\n url: string,\n target: DownloadTarget,\n): Promise<{ file: string; bytes: number }> {\n const plan = downloadPlan(ctx, url)\n const res = await fetch(plan.url, {\n headers: plan.bearer ? bearerFor(ctx.key, plan.url) : {},\n })\n if (!res.ok || !res.body) {\n throw new Error(\n `download of ${target.describe} failed (${res.status})${\n res.status === 404 && target.notFoundHint\n ? ` — ${target.notFoundHint}`\n : ''\n }`,\n )\n }\n const into = target.subdir ? join(target.dir, target.subdir) : target.dir\n const partial = join(into, `${target.stem}.part`)\n await mkdir(dirname(partial), { recursive: true })\n // A link already at the path would carry the bytes wherever it points.\n await refuseSymlink(partial)\n try {\n await pipeline(\n Readable.fromWeb(res.body as never),\n createWriteStream(partial),\n )\n const { type } = resolveMediaType({\n head: await readMediaHead(partial),\n declared: res.headers.get('content-type'),\n filename: `${target.stem}${target.fallbackExt}`,\n })\n const name = `${target.stem}${extensionFor(type) || target.fallbackExt}`\n await refuseSymlink(join(into, name))\n await rename(partial, join(into, name))\n const file = target.subdir ? `${target.subdir}/${name}` : name\n return { file, bytes: (await stat(join(target.dir, file))).size }\n } finally {\n // A rename leaves nothing behind; a failed transfer must not.\n await rm(partial, { force: true })\n }\n}\n\nexport async function pullMedia(\n ctx: { origin: string; key: string | null },\n dir: string,\n doc: ProjectDoc,\n log: (line: string) => void,\n): Promise<MediaPullResult> {\n const result: MediaPullResult = { downloaded: [], kept: [], hosted: [] }\n for (const { key, stem, fallbackExt } of SOURCE_MEDIA) {\n const url = doc.source[key]\n const assetId = assetIdOf(url)\n if (!assetId) continue\n // A file already here is kept whatever container it wears, so a second\n // pull never re-fetches and never leaves two of the same footage.\n const present = takeMediaFiles(dir, stem)[0]\n if (present) {\n result.kept.push(basename(present))\n result.hosted.push({ file: basename(present), url: url! })\n doc.source[key] = basename(present)\n continue\n }\n const got = await downloadMedia(ctx, url!, {\n dir,\n stem,\n fallbackExt,\n describe: stem,\n notFoundHint: 'a private recording needs a content key of its owner',\n })\n result.downloaded.push({ file: got.file, assetId, bytes: got.bytes })\n log(` ${got.file} ← asset ${assetId} (${Math.round(got.bytes / 1024)} kB)`)\n result.hosted.push({ file: got.file, url: url! })\n doc.source[key] = got.file\n }\n // The document's other media (a poster's mark, a pasted ground): a hosted\n // asset comes home under media/<assetId>.<ext> and the key re-anchors to\n // it, so the pulled poster renders and re-pushes as a whole.\n for (const ref of docMediaRefs(doc, (k) => assetIdOf(k) !== null)) {\n const assetId = assetIdOf(ref.key)!\n const existing = existsSync(join(dir, 'media'))\n ? (await readdir(join(dir, 'media'))).find(\n (f) => f === assetId || f.startsWith(`${assetId}.`),\n )\n : undefined\n if (existing) {\n const file = `media/${existing}`\n result.kept.push(file)\n result.hosted.push({ file, url: ref.key })\n ref.set(file)\n continue\n }\n const got = await downloadMedia(ctx, ref.key, {\n dir,\n subdir: 'media',\n stem: assetId,\n // A type with no extension here (an svg served as such) keeps the bare\n // asset id, exactly as before.\n fallbackExt: '',\n describe: `${ref.where} (${ref.key})`,\n })\n result.downloaded.push({ file: got.file, assetId, bytes: got.bytes })\n log(\n ` ${got.file} ← ${ref.where}, asset ${assetId} (${Math.round(got.bytes / 1024)} kB)`,\n )\n result.hosted.push({ file: got.file, url: ref.key })\n ref.set(got.file)\n }\n // The files the take's script pastes come home beside it, under the\n // names it pastes them by, so the take records again from here.\n const pasted = await pullPasteFiles(ctx, dir, doc, log)\n for (const w of pasted.written) {\n const assetId = assetIdOf(w.url)\n if (assetId)\n result.downloaded.push({ file: w.file, assetId, bytes: w.bytes })\n }\n for (const h of pasted.hosted) result.hosted.push(h)\n result.kept.push(...pasted.kept)\n // The take-dir markers: meta.json and cursor.json live on the doc for a\n // hosted take; write them out so loadTake() recognizes the directory.\n const metaPath = join(dir, 'meta.json')\n if (!existsSync(metaPath)) await writeJson(metaPath, doc.source.meta, true)\n const cursorPath = join(dir, 'cursor.json')\n if (!existsSync(cursorPath)) await writeJson(cursorPath, doc.source.cursor)\n await writeFileSafe(join(dir, 'doc.json'), JSON.stringify(doc, null, 2))\n return result\n}\n\n/** The sha256 of a file beside the take, hex. */\nexport async function fileSha256(file: string): Promise<string> {\n return createHash('sha256')\n .update(await readFile(file))\n .digest('hex')\n}\n\n/**\n * Write what a pull brought home into vos.json (`hostedMedia`): each file's\n * path here, the address it came from, and its sha256 now. A claimable push\n * of the take names those originals instead of uploading, and only while\n * the bytes are the ones that were fetched.\n */\nexport async function recordHostedMedia(\n dir: string,\n vosId: string,\n pulled: MediaPullResult,\n): Promise<void> {\n if (pulled.hosted.length === 0) return\n const hostedMedia: Record<string, { ref: string; sha256: string }> = {}\n for (const { file, url } of pulled.hosted) {\n hostedMedia[file] = { ref: url, sha256: await fileSha256(join(dir, file)) }\n }\n writeSyncState(dir, { vosId, hostedMedia })\n}\n","/**\n * The take directory — the CLI's unit of work, human-inspectable on purpose:\n * take/\n * recording.webm encoded footage (CFR; .mp4 when a pull brought it home\n * in that container, so readers RESOLVE the name)\n * frames/ raw screencast JPEGs + frames.json (kept for re-encode)\n * cursor.json synthesized CursorTrack\n * meta.json RecordingMeta (producer: 'cli')\n * actions.json the script that produced it (replay recipe)\n * doc.json ProjectDoc — the agent-editable surface\n */\nimport { mkdir, readFile, rename, rm } from 'node:fs/promises'\nimport { existsSync, readdirSync, statSync } from 'node:fs'\nimport { writeFileSafe } from './safeWrite'\nimport { basename, join } from 'node:path'\nimport { TAKE_MEDIA_EXTENSIONS } from './container'\nimport type { CursorTrack, ProjectDoc, RecordingMeta } from '@vosjs/studio-core'\nimport type { ActionsFile } from './actions'\n\n/** The name `record` writes its footage under — its encoder emits WebM. */\nexport const RECORDING_NAME = 'recording.webm'\n\n/**\n * A take's own media under `dir`, whatever container it came home in.\n *\n * `record` writes WebM, but a PULLED take wears the container its hosted asset\n * actually holds, so nothing may spell `recording.webm` at a read site: a take\n * whose footage is mp4 must still render, digest and push. Ordered by\n * `TAKE_MEDIA_EXTENSIONS`, so the answer is deterministic if a stale sibling\n * survives.\n */\nexport function takeMediaFiles(dir: string, stem: string): string[] {\n return TAKE_MEDIA_EXTENSIONS.map((ext) => join(dir, `${stem}${ext}`)).filter(\n (file) => existsSync(file),\n )\n}\n\n/**\n * The one file a take's media stem names, falling back to what `record` would\n * write — so a caller can `existsSync` the answer and get an honest\n * \"no footage here\" rather than a path that never existed.\n */\nexport function takeMediaFile(\n dir: string,\n stem: string,\n fallback: string,\n): string {\n return takeMediaFiles(dir, stem)[0] ?? join(dir, fallback)\n}\n\n/** The previous cut, preserved by a re-record. */\nexport const PREV_DOC_NAME = 'doc.prev.json'\n\nexport interface TakePaths {\n dir: string\n recording: string\n /** The recording's bare filename — what a doc key or a served URL spells. */\n recordingName: string\n framesDir: string\n framesIndex: string\n cursor: string\n meta: string\n actions: string\n doc: string\n}\n\nexport function takePaths(dir: string): TakePaths {\n const recording = takeMediaFile(dir, 'recording', RECORDING_NAME)\n return {\n dir,\n recording,\n recordingName: basename(recording),\n framesDir: join(dir, 'frames'),\n framesIndex: join(dir, 'frames.json'),\n cursor: join(dir, 'cursor.json'),\n meta: join(dir, 'meta.json'),\n actions: join(dir, 'actions.json'),\n doc: join(dir, 'doc.json'),\n }\n}\n\nexport async function ensureTakeDir(dir: string): Promise<TakePaths> {\n const p = takePaths(dir)\n await mkdir(p.framesDir, { recursive: true })\n return p\n}\n\n/**\n * Drop the screencast JPEGs once the recording they were encoded into is on\n * disk. They are the bulk of a take (one hero take: 437 MB of a 452 MB\n * directory) and nothing needs them afterwards: the digest reads frames\n * from them WHEN PRESENT and from the video otherwise, which is the path\n * every pulled take already runs on. Refuses unless the recording exists and\n * has bytes, so a failed encode never costs the only copy of the footage.\n * Returns the bytes freed, 0 when it left everything alone.\n */\nexport async function dropScreencastFrames(p: TakePaths): Promise<number> {\n if (!existsSync(p.recording) || statSync(p.recording).size === 0) return 0\n if (!existsSync(p.framesDir)) return 0\n let freed = 0\n for (const name of readdirSync(p.framesDir)) {\n try {\n freed += statSync(join(p.framesDir, name)).size\n } catch {\n // a file gone between the listing and the stat frees nothing\n }\n }\n await rm(p.framesDir, { recursive: true, force: true })\n await rm(p.framesIndex, { force: true })\n return freed\n}\n\n/**\n * Re-record into an existing take WITHOUT destroying the cut (the\n * re-render loop's first rung; `rm -rf` here is what made \"re-renders on the\n * next version\" a false claim). What the old footage produced is cleared\n * (recording, cursor, meta, raw frames, stills/, digest/ — all derived and\n * regenerable); what the human's work lives in survives: `doc.json` moves to\n * `doc.prev.json` — the explicit reuse base `vos plan --reuse` re-anchors\n * onto the new recording — and `actions.json` + `vos.json` stay put. The doc\n * is MOVED, never left in place: a doc.json embeds its footage's cursor and\n * meta (`doc.source`), so leaving it beside new footage would be a document\n * lying about its source, and the auto-plan after recording would replan\n * from the OLD cursor. Returns what was kept, for the terminal to say.\n */\nexport async function prepareReRecord(\n dir: string,\n): Promise<{ prevDoc: boolean; kept: string[] }> {\n const p = takePaths(dir)\n const kept: string[] = []\n let prevDoc = false\n if (existsSync(p.doc)) {\n await rename(p.doc, join(dir, PREV_DOC_NAME))\n prevDoc = true\n kept.push(PREV_DOC_NAME)\n }\n if (existsSync(p.actions)) kept.push('actions.json')\n if (existsSync(join(dir, 'vos.json'))) kept.push('vos.json')\n for (const stale of [\n // Every container, not just the one `record` writes: a pulled take's\n // footage may be mp4, and a survivor would outrank the new recording.\n ...takeMediaFiles(dir, 'recording'),\n ...takeMediaFiles(dir, 'mic'),\n ...takeMediaFiles(dir, 'cam'),\n p.cursor,\n p.meta,\n p.framesIndex,\n p.framesDir,\n join(dir, 'stills'),\n join(dir, 'digest'),\n // What the last run's download steps kept: this run downloads again.\n join(dir, 'outputs'),\n // The sound the last run's page played: this run hears it again.\n join(dir, 'page-sound'),\n ]) {\n await rm(stale, { recursive: true, force: true })\n }\n return { prevDoc, kept }\n}\n\nexport async function readJson<T>(file: string): Promise<T> {\n return JSON.parse(await readFile(file, 'utf8')) as T\n}\n\nexport async function writeJson(\n file: string,\n value: unknown,\n pretty = false,\n): Promise<void> {\n await writeFileSafe(\n file,\n pretty ? JSON.stringify(value, null, 2) : JSON.stringify(value),\n )\n}\n\nexport interface TakeData {\n paths: TakePaths\n cursor: CursorTrack\n meta: RecordingMeta\n actions: ActionsFile | null\n doc: ProjectDoc | null\n}\n\n/** Load a take directory; cursor+meta are required, doc/actions optional. */\nexport async function loadTake(dir: string): Promise<TakeData> {\n const paths = takePaths(dir)\n if (!existsSync(paths.meta)) {\n throw new Error(\n `${dir} is not a take directory (missing meta.json) — run record first`,\n )\n }\n const meta = await readJson<RecordingMeta>(paths.meta)\n const cursor = existsSync(paths.cursor)\n ? await readJson<CursorTrack>(paths.cursor)\n : []\n const actions = existsSync(paths.actions)\n ? await readJson<ActionsFile>(paths.actions)\n : null\n const doc = existsSync(paths.doc)\n ? await readJson<ProjectDoc>(paths.doc)\n : null\n return { paths, cursor, meta, actions, doc }\n}\n","/**\n * Uploading a file to vos.so: one door, for every kind and every size.\n *\n * The file is declared first, sent as parts, and sealed. Three reasons it\n * is never one request:\n *\n * - A request body has a ceiling the platform does not set and cannot\n * raise from inside a handler: the edge refuses anything past it before\n * the request arrives, with its own error page rather than a worded\n * refusal. A screen recording of any real length clears that ceiling.\n * - Everything the platform may refuse a file for (its kind, its size, a\n * daily rate, the account's storage) is weighed at the declaring call,\n * before a byte moves, so a refusal costs nothing and arrives in words.\n * - The declaring call is content-addressed: the same bytes pushed twice\n * are recognised and never re-sent, which is what keeps an iterating\n * push from re-uploading a take on every round.\n *\n * The part size is NOT decided here. The platform returns it from the\n * declaring call, so the split can change server-side without a CLI\n * release.\n *\n * What the file IS comes back from the sealing call: the platform reads\n * the stored bytes and answers its kind, the name it is stored under and\n * anything worth saying about it. Nothing here decides what is accepted;\n * `GET /api/limits` lists the kinds and their caps.\n */\nimport { apiJson } from './platform'\nimport type { ApiResult } from './platform'\n\n/** Attempts per part. A part commits nothing until the upload is sealed,\n * so it is always safe to send again. */\nconst PART_ATTEMPTS = 3\n\nexport interface PartPlan {\n /** Part numbers are 1-based. */\n partNumber: number\n start: number\n /** One past the last byte. */\n end: number\n}\n\n/**\n * The slices to send, in order. Every part but the last is exactly\n * `partBytes`; the last is the remainder. The platform holds each part to\n * exactly this length, so the split has to agree with the size that was\n * declared — which is why `partBytes` comes from the platform rather than\n * from a constant here.\n */\nexport function planParts(totalBytes: number, partBytes: number): PartPlan[] {\n if (totalBytes <= 0 || partBytes <= 0) return []\n const parts: PartPlan[] = []\n for (let start = 0; start < totalBytes; start += partBytes) {\n parts.push({\n partNumber: parts.length + 1,\n start,\n end: Math.min(start + partBytes, totalBytes),\n })\n }\n return parts\n}\n\nexport interface UploadedAsset {\n id: string\n /** The path a document names the file by. */\n url: string\n size: number\n /** The platform already had these exact bytes; nothing was sent. */\n reused: boolean\n /** What the bytes are, as the platform read them (video, audio, image,\n * vector, model, font, hdr, recipe, captions). */\n kind: string\n /** The name it is stored under: the extension follows the bytes. */\n filename: string\n /** The frontmatter summary of a recipe, a picture's size, a clip's codec. */\n metadata: Record<string, unknown> | null\n /** What the platform did to the file or wants said about it. */\n notes: string[]\n /** The reference a program's manifest takes. */\n ref: string\n /** Seconds, for what plays. */\n duration: number | null\n}\n\nexport interface UploadTarget {\n origin: string\n /** The bearer: a content key, or a claimable push's session token. */\n key: string\n /**\n * The door's path. The account's by default; a claimable push sends its\n * files through `/api/claim/uploads/files`, which speaks the same\n * protocol under its session token.\n */\n door?: string\n}\n\nexport interface UploadOptions {\n filename: string\n /** Only a hint: the platform types a file by its bytes. It settles the\n * one thing bytes cannot, a WebM that holds sound alone. */\n contentType?: string\n /** sha256 hex — the platform dedupes on it, per owner. */\n contentHash: string\n /** The take's length, so a refusal for one past the hosted cap arrives\n * before anything is sent. The platform measures the file either way. */\n durationSeconds?: number\n /**\n * The bytes are about to be sent: the platform does not hold them yet.\n * Never called for bytes it already holds, so a caller says \"uploading\"\n * here and never before.\n */\n onSending?: (parts: number) => void\n /** Parts landed, for a caller that reports a long upload's progress. */\n onPart?: (done: number, total: number) => void\n /** File the asset into one of the caller's folders. */\n folderId?: string\n /**\n * `library`: a file added to keep (`vos asset push`, a recipe).\n * `attached` (the default): a file a document is about to name, which\n * the platform collects if nothing ever does.\n */\n intent?: 'library' | 'attached'\n /**\n * Where the file came from, as the uploader states it: kept beside the\n * file and shown with it. The platform does not check it.\n */\n provenance?: { sourceUrl?: string; license?: string; attribution?: string }\n}\n\nfunction failed(what: string, r: ApiResult): Error {\n const detail = typeof r.body.error === 'string' ? r.body.error : ''\n const hint = typeof r.body.hint === 'string' ? ` ${r.body.hint}` : ''\n return new Error(`${what} (${r.status})${detail ? `: ${detail}${hint}` : ''}`)\n}\n\nfunction asAsset(\n body: Record<string, unknown>,\n reused: boolean,\n): UploadedAsset {\n const asset = (body.asset ?? {}) as Record<string, unknown>\n return {\n id: String(asset.id),\n url: String(asset.fileUrl),\n size: typeof asset.size === 'number' ? asset.size : 0,\n reused,\n kind: typeof asset.kind === 'string' ? asset.kind : '',\n filename: typeof asset.filename === 'string' ? asset.filename : '',\n metadata:\n asset.metadata && typeof asset.metadata === 'object'\n ? (asset.metadata as Record<string, unknown>)\n : null,\n notes: Array.isArray(body.notes)\n ? body.notes.filter((n): n is string => typeof n === 'string')\n : [],\n ref:\n typeof asset.ref === 'string' ? asset.ref : `asset:${String(asset.id)}`,\n duration: typeof asset.duration === 'number' ? asset.duration : null,\n }\n}\n\n/** Worth sending again: the platform was busy or briefly unreachable. A\n * refusal is a decision and will read the same on a second try. */\nfunction transient(status: number): boolean {\n return status >= 500 || status === 429\n}\n\nconst wait = (ms: number) => new Promise((r) => setTimeout(r, ms))\n\n/** Upload one file as an owned asset: declare it, send its parts, seal it. */\nexport async function uploadAsset(\n target: UploadTarget,\n bytes: Uint8Array,\n opts: UploadOptions,\n): Promise<UploadedAsset> {\n // Declare it first. Every quota is weighed here, before a byte moves, so\n // a refusal costs nothing and arrives in words — and the dedupe answers\n // here too, which is what makes re-pushing a hosted take free.\n const door = target.door ?? '/api/assets/uploads'\n const begun = await apiJson(target.origin, door, {\n method: 'POST',\n key: target.key,\n body: {\n filename: opts.filename,\n ...(opts.contentType ? { contentType: opts.contentType } : {}),\n size: bytes.length,\n sha256: opts.contentHash,\n ...(opts.durationSeconds && opts.durationSeconds > 0\n ? { durationSeconds: opts.durationSeconds }\n : {}),\n ...(opts.folderId ? { folderId: opts.folderId } : {}),\n ...(opts.intent ? { intent: opts.intent } : {}),\n },\n })\n if (begun.status !== 201 && begun.status !== 200) {\n throw failed('upload failed', begun)\n }\n // The platform already holds these bytes: nothing to send.\n if (begun.body.asset) return asAsset(begun.body, true)\n\n const uploadId = String(begun.body.uploadId)\n const partBytes = Number(begun.body.partBytes)\n if (!Number.isFinite(partBytes) || partBytes <= 0) {\n throw new Error('upload failed: the platform returned no part size')\n }\n const parts = planParts(bytes.length, partBytes)\n const etags: { partNumber: number; etag: string }[] = []\n opts.onSending?.(parts.length)\n\n try {\n for (const part of parts) {\n const chunk = bytes.subarray(part.start, part.end)\n let sent: ApiResult | null = null\n for (let attempt = 1; attempt <= PART_ATTEMPTS; attempt++) {\n try {\n sent = await apiJson(\n target.origin,\n `${door}/${uploadId}/parts/${part.partNumber}`,\n {\n method: 'PUT',\n key: target.key,\n headers: {\n 'Content-Type': 'application/octet-stream',\n 'Content-Length': String(chunk.length),\n },\n raw: chunk,\n },\n )\n } catch (networkError) {\n // Dropped connection, a sleeping machine: worth another try.\n if (attempt === PART_ATTEMPTS) throw networkError\n await wait(500 * 2 ** (attempt - 1))\n continue\n }\n if (sent.status === 200) break\n if (!transient(sent.status) || attempt === PART_ATTEMPTS) {\n throw failed(`part ${part.partNumber} failed`, sent)\n }\n await wait(500 * 2 ** (attempt - 1))\n }\n if (!sent || sent.status !== 200) {\n throw new Error(`part ${part.partNumber} failed`)\n }\n etags.push({\n partNumber: Number(sent.body.partNumber),\n etag: String(sent.body.etag),\n })\n opts.onPart?.(etags.length, parts.length)\n }\n\n // Sealing is where the platform reads the file. A refusal here means\n // the bytes are not what the name said, and it has already taken them\n // back out.\n const sealed = await apiJson(\n target.origin,\n `${door}/${uploadId}/complete`,\n {\n method: 'POST',\n key: target.key,\n body: {\n parts: etags,\n ...(opts.provenance ? { provenance: opts.provenance } : {}),\n },\n },\n )\n if (sealed.status !== 201 && sealed.status !== 200) {\n throw failed('upload failed', sealed)\n }\n // The platform hashes what it stored: bytes this account already held\n // come back as the file it had, whatever was declared.\n return asAsset(sealed.body, sealed.body.reused === true)\n } catch (err) {\n // Hand the parts back rather than leaving them held server-side.\n await apiJson(target.origin, `${door}/${uploadId}`, {\n method: 'DELETE',\n key: target.key,\n }).catch(() => undefined)\n throw err\n }\n}\n","/**\n * A pasted file travels with its take. A `paste` step names a file beside\n * the script (the take keeps it as `paste-<name>`), and the script rides\n * the pushed document (`source.script`), so a take fetched from vos.so came\n * home with a script naming a file that was not there: a re-record from\n * anywhere but the recording machine refused at its first paste.\n *\n * So `vos push` sends each paste file with the take and the document lists\n * them (`source.scriptFiles`: the name the script uses → where the bytes\n * are), and `vos fetch --media` / `vos pull --media` write each one back\n * beside `actions.json` under that name. A picture (PNG, JPEG, WebP, GIF,\n * SVG) rides the same upload door as every other file the document names;\n * a text file (words, HTML, JSON, CSV) is not a kind the platform stores,\n * so it rides inside the document as a `data:` address, at most\n * `SCRIPT_TEXT_MAX_BYTES`. A drag's path needs nothing of this: the\n * recorder writes its points inline into the take's script.\n *\n * Every name is a plain file name the script itself pastes, read from the\n * take directory or below (never through a link out of it), and written\n * back only where nothing is: a file on disk is the person's.\n */\nimport { createHash } from 'node:crypto'\nimport { existsSync } from 'node:fs'\nimport { readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport {\n fileNameProblem,\n pasteTypeOf,\n scriptFiles,\n} from '@vosjs/render-core/record'\nimport { localFile } from '../programAssets'\nimport { assetIdOf, downloadPlan } from './media'\nimport { bearerFor } from './platform'\nimport { writeFileSafe } from './safeWrite'\nimport { uploadAsset } from './uploadAsset'\nimport type { UploadTarget } from './uploadAsset'\nimport type { ProjectDoc } from '@vosjs/studio-core'\n\n/** The most bytes a pasted TEXT file carries inside a document. */\nexport const SCRIPT_TEXT_MAX_BYTES = 256 * 1024\n\n/** The files a stored script pastes, by the plain name it uses, once each. */\nexport function pasteNames(script: unknown): string[] {\n if (!script || typeof script !== 'object') return []\n const steps = (script as { steps?: unknown }).steps\n if (!Array.isArray(steps)) return []\n const names = scriptFiles({ steps: steps as never })\n .filter((f) => f.as === 'paste')\n .map((f) => f.name)\n return [...new Set(names)]\n}\n\n/** A refusal the person can fix in the take: exit 2, said before any upload. */\nexport class PasteFileRefusal extends Error {}\n\n/** A paste file found beside the take: its bytes, or why it is not there. */\ntype Found =\n | { name: string; bytes: Uint8Array; type: string }\n | { name: string; missing: true }\n\n/**\n * Read every file the take's script pastes, before anything is sent. A\n * name that is not plain, or a file that resolves outside the take\n * directory (a link out of it), is REFUSED in words: the push would carry\n * bytes from somewhere the take does not hold. A file that is not there is\n * only missing (a take fetched before paste files travelled): said, and\n * the push goes on without it.\n */\nexport async function readPasteFiles(\n script: unknown,\n dir: string,\n): Promise<Found[]> {\n const out: Found[] = []\n for (const name of pasteNames(script)) {\n const bad = fileNameProblem(name)\n if (bad)\n throw new PasteFileRefusal(\n `actions.json pastes \"${name}\", which ${bad.replace(/^\"[^\"]*\" /, '')}: a pasted file sits beside the script, in the take directory`,\n )\n if (!existsSync(join(dir, name))) {\n out.push({ name, missing: true })\n continue\n }\n const local = localFile(name, dir)\n if ('refused' in local)\n throw new PasteFileRefusal(\n `actions.json pastes ${name}, which resolves outside the take directory (${dir}), so the push would send a file the take does not hold. Copy the file into the take as ${name} (not a link) and push again`,\n )\n out.push({\n name,\n bytes: new Uint8Array(await readFile(local.file)),\n type: pasteTypeOf(name) ?? 'application/octet-stream',\n })\n }\n return out\n}\n\n/** Bytes as a `data:` address of the given type. */\nconst dataUrl = (type: string, bytes: Uint8Array): string =>\n `data:${type};base64,${Buffer.from(bytes).toString('base64')}`\n\n/**\n * Send the paste files with the take and list them on the document about\n * to be pushed (`source.scriptFiles`). A picture rides the upload door; a\n * text file rides the document. A missing file is said and left out, so\n * a re-record of the fetched take names what to put back.\n */\nexport async function sendPasteFiles(\n doc: ProjectDoc,\n found: Found[],\n target: UploadTarget,\n log: (line: string) => void,\n): Promise<void> {\n const listed: Record<string, string> = {}\n // A file the take no longer holds but the document already lists (it\n // came with a fetch, and was moved aside since) keeps its listing.\n const prior = Object.fromEntries(listedFiles(doc))\n for (const f of found) {\n if ('missing' in f && prior[f.name]) {\n listed[f.name] = prior[f.name]\n log(` pasted file ${f.name}: not in the take, kept as listed`)\n continue\n }\n if ('missing' in f) {\n log(\n ` warning: actions.json pastes ${f.name}, which is not in the take, so it does not travel with the push: a re-record of the pushed take needs it put beside actions.json again`,\n )\n continue\n }\n if (!f.type.startsWith('image/')) {\n if (f.bytes.length > SCRIPT_TEXT_MAX_BYTES)\n throw new PasteFileRefusal(\n `actions.json pastes ${f.name} (${Math.round(f.bytes.length / 1024)} kB), and a pasted text file rides inside the document, at most ${SCRIPT_TEXT_MAX_BYTES / 1024} kB: paste less, or paste the words as text in the step`,\n )\n listed[f.name] = dataUrl(f.type, f.bytes)\n log(` pasted file ${f.name}: carried in the document`)\n continue\n }\n let put\n try {\n put = await uploadAsset(target, f.bytes, {\n filename: f.name,\n contentType: f.type,\n contentHash: createHash('sha256').update(f.bytes).digest('hex'),\n })\n } catch (e) {\n throw new Error(\n `pasted file ${f.name} upload failed: ${e instanceof Error ? e.message : String(e)}`,\n )\n }\n listed[f.name] = put.url\n log(\n ` pasted file ${f.name} → asset ${put.id}${put.reused ? ' (reused)' : ''}`,\n )\n for (const note of put.notes) log(` pasted file ${f.name}: ${note}`)\n }\n if (Object.keys(listed).length) doc.source.scriptFiles = listed\n else delete doc.source.scriptFiles\n}\n\n/** The listed paste files a fetched document carries, read defensively. */\nfunction listedFiles(doc: Pick<ProjectDoc, 'source'>): [string, string][] {\n const listed = doc.source.scriptFiles\n if (!listed || typeof listed !== 'object' || Array.isArray(listed)) return []\n return Object.entries(listed).filter(\n (e): e is [string, string] => typeof e[1] === 'string',\n )\n}\n\n/** A `data:` address's bytes, or null when it is not one this reads. */\nfunction fromDataUrl(url: string): Uint8Array | null {\n const m = /^data:[^,;]*(;base64)?,(.*)$/s.exec(url)\n if (!m) return null\n return m[1]\n ? new Uint8Array(Buffer.from(m[2], 'base64'))\n : new TextEncoder().encode(decodeURIComponent(m[2]))\n}\n\nexport interface PasteFilesPulled {\n /** Written beside actions.json, with where each came from. */\n written: { file: string; url: string; bytes: number }[]\n /** Already on disk, kept. */\n kept: string[]\n /** Every hosted one now beside the take, written or kept, and its address. */\n hosted: { file: string; url: string }[]\n}\n\n/**\n * Bring a fetched take's paste files home beside its script, under the\n * names the script pastes. Only a name the document's own script pastes\n * is written, only where nothing is, only from the platform's file door\n * (with the reader's key, sent home only) or a `data:` address. Anything\n * else the list names is said and skipped: a stranger's document does not\n * choose what this machine fetches or where it lands.\n */\nexport async function pullPasteFiles(\n ctx: { origin: string; key: string | null },\n dir: string,\n doc: Pick<ProjectDoc, 'source'>,\n log: (line: string) => void,\n): Promise<PasteFilesPulled> {\n const result: PasteFilesPulled = { written: [], kept: [], hosted: [] }\n const wanted = new Set(pasteNames(doc.source.script))\n for (const [name, url] of listedFiles(doc)) {\n if (!wanted.has(name) || fileNameProblem(name)) {\n log(\n ` skipped ${JSON.stringify(name).slice(0, 80)}: the document lists it as a pasted file, and its script pastes no file by that name`,\n )\n continue\n }\n const path = join(dir, name)\n const door = assetIdOf(url) !== null\n if (existsSync(path)) {\n result.kept.push(name)\n if (door) result.hosted.push({ file: name, url })\n continue\n }\n let bytes: Uint8Array | null = null\n if (url.startsWith('data:')) bytes = fromDataUrl(url)\n else if (door) {\n const plan = downloadPlan(ctx, url)\n const res = await fetch(plan.url, {\n headers: plan.bearer ? bearerFor(ctx.key, plan.url) : {},\n })\n if (!res.ok)\n throw new Error(\n `download of the pasted file ${name} failed (${res.status})`,\n )\n bytes = new Uint8Array(await res.arrayBuffer())\n }\n if (!bytes) {\n log(\n ` skipped ${name}: the document names it at an address that is neither a hosted file nor carried in the document`,\n )\n continue\n }\n await writeFileSafe(path, bytes)\n result.written.push({ file: name, url, bytes: bytes.length })\n if (door) result.hosted.push({ file: name, url })\n log(` ${name} ← the pasted file (${Math.round(bytes.length / 1024)} kB)`)\n }\n return result\n}\n\n/**\n * A claimable push carries the paste files it may name: a text file inside\n * the document, a picture this take fetched and left unchanged by its\n * hosted address. Anything else is said and left out, because a claimable\n * push uploads nothing.\n */\nexport async function claimablePasteFiles(\n doc: ProjectDoc,\n found: Found[],\n hosted: Record<string, { ref: string; sha256: string }>,\n log: (line: string) => void,\n): Promise<void> {\n const listed: Record<string, string> = {}\n for (const f of found) {\n if ('missing' in f) continue\n if (!f.type.startsWith('image/')) {\n if (f.bytes.length <= SCRIPT_TEXT_MAX_BYTES)\n listed[f.name] = dataUrl(f.type, f.bytes)\n continue\n }\n const origin = hosted[f.name]\n const sha = createHash('sha256').update(f.bytes).digest('hex')\n if (origin && origin.sha256 === sha) listed[f.name] = origin.ref\n else\n log(\n ` note: the pasted file ${f.name} is not one this take fetched, and a claimable push uploads nothing, so it does not travel: a re-record of the claimed take needs it put back`,\n )\n }\n if (Object.keys(listed).length) doc.source.scriptFiles = listed\n else delete doc.source.scriptFiles\n}\n"],"mappings":"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","names":["existsSync","join","createHash","existsSync","mkdir","readFile","rename","rm","basename","join","existsSync","readFile","join","existsSync","join","readFile","where","readFile","createHash","basename","join","mkdir","rename","rm","existsSync","join","existsSync"]}