@docyrus/animaguru 0.19.0 → 0.22.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.
Files changed (32) hide show
  1. package/README.md +61 -0
  2. package/dist/animaguru.js +366 -261
  3. package/package.json +1 -1
  4. package/skills/animaguru/SKILL.md +9 -1
  5. package/skills/animaguru/references/sharing.md +3 -0
  6. package/skills/animaguru-branching-story/SKILL.md +72 -0
  7. package/skills/animaguru-general-video/SKILL.md +1 -0
  8. package/skills/animaguru-general-video/references/branching-story.md +13 -0
  9. package/skills/animaguru-quiz/SKILL.md +80 -0
  10. package/skills/animaguru-video/SKILL.md +9 -1
  11. package/templates/video/AGENTS.md +2 -0
  12. package/templates/video/README.md +8 -0
  13. package/templates/video/WORKSPACE-AGENTS.md +3 -1
  14. package/templates/video/scripts/build.mjs +4 -1
  15. package/templates/video/scripts/export-mp4.mjs +2 -0
  16. package/templates/video/scripts/html-export.mjs +12 -4
  17. package/templates/video/scripts/project-context.mjs +20 -3
  18. package/templates/video/scripts/quiz.d.mts +31 -0
  19. package/templates/video/scripts/quiz.mjs +126 -0
  20. package/templates/video/scripts/review.mjs +9 -1
  21. package/templates/video/scripts/story.d.mts +36 -0
  22. package/templates/video/scripts/story.mjs +139 -0
  23. package/templates/video/scripts/workspace-plugin.mjs +9 -4
  24. package/templates/video/shared/locales/en/player.json +29 -1
  25. package/templates/video/src/kit/index.ts +1 -0
  26. package/templates/video/src/kit/story.ts +19 -0
  27. package/templates/video/src/main.ts +6 -1
  28. package/templates/video/src/player-types.ts +6 -0
  29. package/templates/video/src/player.ts +213 -17
  30. package/templates/video/src/quiz.ts +315 -0
  31. package/templates/video/src/story.ts +112 -0
  32. package/templates/video/src/style.css +106 -3
package/dist/animaguru.js CHANGED
@@ -2312,8 +2312,8 @@ var require_lib = __commonJS({
2312
2312
  if (typeof cb !== "function") {
2313
2313
  opts = cb;
2314
2314
  cb = null;
2315
- deferred = new this.Promise(function(resolve17, reject) {
2316
- deferredResolve = resolve17;
2315
+ deferred = new this.Promise(function(resolve18, reject) {
2316
+ deferredResolve = resolve18;
2317
2317
  deferredReject = reject;
2318
2318
  });
2319
2319
  }
@@ -2461,17 +2461,17 @@ var require_lib = __commonJS({
2461
2461
  if (typeof cb === "function") {
2462
2462
  fnx(cb);
2463
2463
  } else {
2464
- return new this.Promise(function(resolve17, reject) {
2464
+ return new this.Promise(function(resolve18, reject) {
2465
2465
  if (fnx.length === 1) {
2466
2466
  fnx(function(err2, ret) {
2467
2467
  if (err2) {
2468
2468
  reject(err2);
2469
2469
  } else {
2470
- resolve17(ret);
2470
+ resolve18(ret);
2471
2471
  }
2472
2472
  });
2473
2473
  } else {
2474
- resolve17(fnx());
2474
+ resolve18(fnx());
2475
2475
  }
2476
2476
  });
2477
2477
  }
@@ -6862,7 +6862,7 @@ var require_pify = __commonJS({
6862
6862
  "use strict";
6863
6863
  var processFn = (fn, options) => function(...args) {
6864
6864
  const P = options.promiseModule;
6865
- return new P((resolve17, reject) => {
6865
+ return new P((resolve18, reject) => {
6866
6866
  if (options.multiArgs) {
6867
6867
  args.push((...result) => {
6868
6868
  if (options.errorFirst) {
@@ -6870,10 +6870,10 @@ var require_pify = __commonJS({
6870
6870
  reject(result);
6871
6871
  } else {
6872
6872
  result.shift();
6873
- resolve17(result);
6873
+ resolve18(result);
6874
6874
  }
6875
6875
  } else {
6876
- resolve17(result);
6876
+ resolve18(result);
6877
6877
  }
6878
6878
  });
6879
6879
  } else if (options.errorFirst) {
@@ -6881,11 +6881,11 @@ var require_pify = __commonJS({
6881
6881
  if (error62) {
6882
6882
  reject(error62);
6883
6883
  } else {
6884
- resolve17(result);
6884
+ resolve18(result);
6885
6885
  }
6886
6886
  });
6887
6887
  } else {
6888
- args.push(resolve17);
6888
+ args.push(resolve18);
6889
6889
  }
6890
6890
  fn.apply(this, args);
6891
6891
  });
@@ -11400,7 +11400,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
11400
11400
  } catch (err22) {
11401
11401
  if (err22 === null || err22.code === "EEXIST") return;
11402
11402
  if (err22.code === "ENOENT" && attempt < retries) {
11403
- await new Promise((resolve17) => setTimeout(resolve17, 0));
11403
+ await new Promise((resolve18) => setTimeout(resolve18, 0));
11404
11404
  continue;
11405
11405
  }
11406
11406
  throw err22;
@@ -14616,9 +14616,9 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
14616
14616
  throw Error("You cannot write to a FIFO that has already been ended!");
14617
14617
  }
14618
14618
  if (this._waiting) {
14619
- const resolve17 = this._waiting;
14619
+ const resolve18 = this._waiting;
14620
14620
  this._waiting = null;
14621
- resolve17({ value: chunk });
14621
+ resolve18({ value: chunk });
14622
14622
  } else {
14623
14623
  this._queue.push(chunk);
14624
14624
  }
@@ -14626,9 +14626,9 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
14626
14626
  end() {
14627
14627
  this._ended = true;
14628
14628
  if (this._waiting) {
14629
- const resolve17 = this._waiting;
14629
+ const resolve18 = this._waiting;
14630
14630
  this._waiting = null;
14631
- resolve17({ done: true });
14631
+ resolve18({ done: true });
14632
14632
  }
14633
14633
  }
14634
14634
  destroy(err2) {
@@ -14647,8 +14647,8 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
14647
14647
  "You cannot call read until the previous call to read has returned!"
14648
14648
  );
14649
14649
  }
14650
- return new Promise((resolve17) => {
14651
- this._waiting = resolve17;
14650
+ return new Promise((resolve18) => {
14651
+ this._waiting = resolve18;
14652
14652
  });
14653
14653
  }
14654
14654
  };
@@ -14803,7 +14803,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
14803
14803
  const acks = [];
14804
14804
  let nak = false;
14805
14805
  let done = false;
14806
- return new Promise((resolve17, reject) => {
14806
+ return new Promise((resolve18, reject) => {
14807
14807
  forAwait2(packetlines, (data) => {
14808
14808
  const line = data.toString("utf8").trim();
14809
14809
  if (line.startsWith("shallow")) {
@@ -14830,11 +14830,11 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
14830
14830
  nak = true;
14831
14831
  }
14832
14832
  if (done) {
14833
- stream.error ? reject(stream.error) : resolve17({ shallows, unshallows, acks, nak, packfile, progress });
14833
+ stream.error ? reject(stream.error) : resolve18({ shallows, unshallows, acks, nak, packfile, progress });
14834
14834
  }
14835
14835
  }).finally(() => {
14836
14836
  if (!done) {
14837
- stream.error ? reject(stream.error) : resolve17({ shallows, unshallows, acks, nak, packfile, progress });
14837
+ stream.error ? reject(stream.error) : resolve18({ shallows, unshallows, acks, nak, packfile, progress });
14838
14838
  }
14839
14839
  });
14840
14840
  });
@@ -19761,7 +19761,7 @@ var require_ignore2 = __commonJS({
19761
19761
  // cli/package.json
19762
19762
  var package_default = {
19763
19763
  name: "@docyrus/animaguru",
19764
- version: "0.19.0",
19764
+ version: "0.22.0",
19765
19765
  description: "Agent-friendly AnimaGuru CLI built with CrustJS",
19766
19766
  type: "module",
19767
19767
  bin: {
@@ -24295,8 +24295,8 @@ var version = defineExtension(VERSION).factory((extension, value, options = {})
24295
24295
 
24296
24296
  // cli/src/commands.ts
24297
24297
  import { spawn as spawn3 } from "node:child_process";
24298
- import { mkdir as mkdir15, readFile as readFile18, writeFile as writeFile16 } from "node:fs/promises";
24299
- import { dirname as dirname14, resolve as resolve16 } from "node:path";
24298
+ import { mkdir as mkdir15, readFile as readFile19, writeFile as writeFile16 } from "node:fs/promises";
24299
+ import { dirname as dirname14, resolve as resolve17 } from "node:path";
24300
24300
  import { setTimeout as delay6 } from "node:timers/promises";
24301
24301
 
24302
24302
  // node_modules/fflate/esm/index.mjs
@@ -25339,13 +25339,14 @@ var SPRITE_IMAGE_MODEL_IDS = SPRITE_IMAGE_MODELS.map((m) => m.id);
25339
25339
  function spriteImageModel(id2) {
25340
25340
  return SPRITE_IMAGE_MODELS.find((m) => m.id === id2) ?? SPRITE_IMAGE_MODELS[0];
25341
25341
  }
25342
- var VIDEO_WORKFLOWS = ["image-to-video", "text-to-video", "extend-video", "reference-to-video", "lip-sync", "camera-controls", "insert-video", "recast", "relight", "3d-to-video", "style"];
25342
+ var VIDEO_WORKFLOWS = ["image-to-video", "text-to-video", "extend-video", "reference-to-video", "lip-sync", "camera-controls", "insert-video", "recast", "relight", "3d-to-video", "style", "keyframes-to-video"];
25343
25343
  var STUDIO_VIDEO_WORKFLOWS = ["image-to-video", "text-to-video", "extend-video"];
25344
25344
  var MEDIA_LENGTH_WORKFLOWS = ["lip-sync", "recast", "relight", "3d-to-video"];
25345
25345
  var lengthFromMedia = (workflow) => MEDIA_LENGTH_WORKFLOWS.includes(workflow);
25346
25346
  var VIDEO_STYLES = ["vhs", "retro-toon-70s", "low-poly", "hand-drawn", "16bit-pixel"];
25347
25347
  var VHS_DAMAGE_LEVELS = ["light", "medium", "heavy"];
25348
- var VIDEO_RESOLUTIONS = ["480p", "768p", "1080p", "2k"];
25348
+ var VIDEO_RESOLUTIONS = ["480p", "720p", "768p", "1080p", "2k"];
25349
+ var H3_RESOLUTIONS = ["480p", "768p", "1080p", "2k"];
25349
25350
  var VIDEO_ASPECT_RATIOS = ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16"];
25350
25351
  var PROMPT_EXPANSION_MODES = ["disabled", "balanced", "quality"];
25351
25352
  var EXTEND_OUTPUTS = ["extended", "continuation"];
@@ -25359,7 +25360,10 @@ var MAX_RECAST_PEOPLE = 4;
25359
25360
  var MAX_3D_REFERENCES = 8;
25360
25361
  var MAX_CAMERA_KEYFRAMES = 12;
25361
25362
  var MAX_CAMERA_TURNS = 32;
25363
+ var MAX_IMAGE_KEYFRAMES = 10;
25364
+ var KEYFRAME_FPS = 24;
25362
25365
  var H3_MAX = "minimax/h3-max";
25366
+ var FLUX_3_MAX = "blackforestlabs/flux-3-max";
25363
25367
  var h3Endpoints = (route) => ({ "image-to-video": `${route}/image-to-video`, "text-to-video": `${route}/text-to-video`, "extend-video": `${route}/extend-video` });
25364
25368
  var SPRITE_VIDEO_MODELS = [
25365
25369
  {
@@ -25388,7 +25392,8 @@ var SPRITE_VIDEO_MODELS = [
25388
25392
  id: "fal/minimax-h3-max",
25389
25393
  label: "MiniMax H3 Max (fal.ai)",
25390
25394
  provider: "fal",
25391
- workflows: VIDEO_WORKFLOWS,
25395
+ // Every workflow but FLUX 3 Max's keyframes.
25396
+ workflows: VIDEO_WORKFLOWS.filter((w) => w !== "keyframes-to-video"),
25392
25397
  endpoints: {
25393
25398
  ...h3Endpoints(H3_MAX),
25394
25399
  "reference-to-video": `${H3_MAX}/reference-to-video`,
@@ -25402,11 +25407,11 @@ var SPRITE_VIDEO_MODELS = [
25402
25407
  },
25403
25408
  resolutions: ["480p", "768p", "1080p"],
25404
25409
  workflowOptions: {
25405
- "extend-video": { resolutions: VIDEO_RESOLUTIONS },
25406
- "lip-sync": { resolutions: VIDEO_RESOLUTIONS, seconds: { min: 5, max: 900, fractional: true } },
25410
+ "extend-video": { resolutions: H3_RESOLUTIONS },
25411
+ "lip-sync": { resolutions: H3_RESOLUTIONS, seconds: { min: 5, max: 900, fractional: true } },
25407
25412
  "insert-video": { resolutions: ["480p", "768p"], seconds: { min: 5, max: 13, fractional: true }, creditsPerSecond: { "480p": 5, "768p": 6 } },
25408
25413
  recast: { resolutions: ["768p", "1080p"], seconds: { min: 5, max: 30, fractional: true }, creditsPerSecond: { "768p": 30, "1080p": 45 } },
25409
- relight: { resolutions: VIDEO_RESOLUTIONS, seconds: { min: 2.33, max: 15, fractional: true } },
25414
+ relight: { resolutions: H3_RESOLUTIONS, seconds: { min: 2.33, max: 15, fractional: true } },
25410
25415
  "3d-to-video": { seconds: { min: 1, max: 15, fractional: true } },
25411
25416
  style: { resolutions: ["768p"], seconds: { min: 5, max: 15 } }
25412
25417
  },
@@ -25426,7 +25431,7 @@ var SPRITE_VIDEO_MODELS = [
25426
25431
  workflows: STUDIO_VIDEO_WORKFLOWS,
25427
25432
  endpoints: h3Endpoints("minimax/h3-max-turbo"),
25428
25433
  resolutions: ["480p", "768p", "1080p"],
25429
- workflowOptions: { "extend-video": { resolutions: VIDEO_RESOLUTIONS } },
25434
+ workflowOptions: { "extend-video": { resolutions: H3_RESOLUTIONS } },
25430
25435
  modes: ["speed"],
25431
25436
  lastFrame: true,
25432
25437
  endFrameOnly: true,
@@ -25435,6 +25440,21 @@ var SPRITE_VIDEO_MODELS = [
25435
25440
  audio: true,
25436
25441
  estimatedCreditsPerSecond: { "480p": { speed: 2.5 }, "768p": { speed: 4 }, "1080p": { speed: 8 }, "2k": { speed: 16 } },
25437
25442
  seconds: { min: 1, max: 15, fractional: true }
25443
+ },
25444
+ {
25445
+ id: "fal/flux-3-max",
25446
+ label: "FLUX 3 Max (fal.ai)",
25447
+ provider: "fal",
25448
+ workflows: ["image-to-video", "text-to-video", "keyframes-to-video"],
25449
+ endpoints: { "image-to-video": `${FLUX_3_MAX}/image-to-video`, "text-to-video": `${FLUX_3_MAX}/text-to-video`, "keyframes-to-video": `${FLUX_3_MAX}/keyframes-to-video` },
25450
+ falInputs: "flux",
25451
+ resolutions: ["720p", "1080p"],
25452
+ modes: ["quality"],
25453
+ lastFrame: true,
25454
+ seed: true,
25455
+ promptExpansion: ["balanced", "quality"],
25456
+ estimatedCreditsPerSecond: { "720p": { quality: 17 }, "1080p": { quality: 29 } },
25457
+ seconds: { min: 5, max: 20 }
25438
25458
  }
25439
25459
  ];
25440
25460
  var SPRITE_VIDEO_MODEL_IDS = SPRITE_VIDEO_MODELS.map((m) => m.id);
@@ -30496,7 +30516,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
30496
30516
  var NONE = 0;
30497
30517
  var ASSUMED = 1;
30498
30518
  var PROVEN = 2;
30499
- function isRecursive(inst, stack, resolve17) {
30519
+ function isRecursive(inst, stack, resolve18) {
30500
30520
  const cached2 = recursive.get(inst);
30501
30521
  if (cached2 !== void 0)
30502
30522
  return cached2 ? PROVEN : NONE;
@@ -30506,7 +30526,7 @@ function isRecursive(inst, stack, resolve17) {
30506
30526
  let result = NONE;
30507
30527
  const check2 = (child) => {
30508
30528
  if (result !== PROVEN && child?._zod) {
30509
- const answer = isRecursive(child, stack, resolve17);
30529
+ const answer = isRecursive(child, stack, resolve18);
30510
30530
  if (answer > result)
30511
30531
  result = answer;
30512
30532
  }
@@ -30517,7 +30537,7 @@ function isRecursive(inst, stack, resolve17) {
30517
30537
  const desc = Object.getOwnPropertyDescriptor(sh, key);
30518
30538
  if (spread && !desc.enumerable)
30519
30539
  continue;
30520
- const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve17) : NONE;
30540
+ const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve18) : NONE;
30521
30541
  if (child > answer)
30522
30542
  answer = child;
30523
30543
  }
@@ -30581,7 +30601,7 @@ function isRecursive(inst, stack, resolve17) {
30581
30601
  break;
30582
30602
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
30583
30603
  case "lazy": {
30584
- const inner = def._cachedInner ?? (resolve17 ? inst._zod.innerType : void 0);
30604
+ const inner = def._cachedInner ?? (resolve18 ? inst._zod.innerType : void 0);
30585
30605
  merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
30586
30606
  break;
30587
30607
  }
@@ -45199,6 +45219,12 @@ async function saveWorkspace(origin, workspaceId) {
45199
45219
  credentials[origin] = { ...saved, workspaceId };
45200
45220
  await writeCredentials(credentials);
45201
45221
  }
45222
+ async function sessionToken(origin) {
45223
+ const saved = (await readCredentials())[origin];
45224
+ const token3 = process.env.ANIMAGURU_TOKEN ?? (saved && saved.expiresAt > Date.now() ? saved.token : void 0);
45225
+ if (!token3) throw new Error("Sign in with animaguru auth login, or set ANIMAGURU_TOKEN.");
45226
+ return token3;
45227
+ }
45202
45228
  var ServiceError = class extends Error {
45203
45229
  constructor(status, code, message) {
45204
45230
  super(message);
@@ -45216,12 +45242,7 @@ var Client = class {
45216
45242
  signal;
45217
45243
  origin;
45218
45244
  async request(path, options = {}) {
45219
- let token3;
45220
- if (options.authenticated !== false) {
45221
- const saved = (await readCredentials())[this.origin];
45222
- token3 = process.env.ANIMAGURU_TOKEN ?? (saved && saved.expiresAt > Date.now() ? saved.token : void 0);
45223
- if (!token3) throw new Error("Sign in with animaguru auth login, or set ANIMAGURU_TOKEN.");
45224
- }
45245
+ const token3 = options.authenticated !== false ? await sessionToken(this.origin) : void 0;
45225
45246
  const generation = options.method === "POST" && /^\/api\/v1\/sprites\/[^/]+\/(jobs|assets|avatars|avatars\/looks|avatars\/[^/]+\/consent)$/.test(path);
45226
45247
  const idempotency = generation ? { "Idempotency-Key": generationRequestId(path, options.body) } : {};
45227
45248
  const response = await fetch(`${this.origin}${path}`, {
@@ -45347,14 +45368,7 @@ var animation_styles_default = {
45347
45368
  "Masked reveals",
45348
45369
  "Speech & beat timing"
45349
45370
  ],
45350
- previewCaption: "Type takes the lead through scale, alignment and a measured reading rhythm.",
45351
- studyCopy: [
45352
- "Make",
45353
- "every",
45354
- "word",
45355
- "move.",
45356
- "TYPE / RHYTHM / EMPHASIS"
45357
- ],
45371
+ previewCaption: "Words slam, mask and split into letters on a 120 bpm beat across four color fields, then settle into a lockup. Drawn entirely in code.",
45358
45372
  family: "graphic",
45359
45373
  example: false
45360
45374
  },
@@ -45371,11 +45385,7 @@ var animation_styles_default = {
45371
45385
  "Connected transitions",
45372
45386
  "Designed easing"
45373
45387
  ],
45374
- previewCaption: "A geometric system assembles, connects and resolves into a clear composition.",
45375
- studyCopy: [
45376
- "A shape. An idea.",
45377
- "One connected system."
45378
- ],
45388
+ previewCaption: "A bouncing ball splits and morphs into hills and a sun, a skyline rises, and a cyclist rides into dusk before an iris closes. Every shape is drawn and morphed in code.",
45379
45389
  family: "graphic",
45380
45390
  example: false
45381
45391
  },
@@ -45392,13 +45402,7 @@ var animation_styles_default = {
45392
45402
  "Drawn connections",
45393
45403
  "Readable drawing holds"
45394
45404
  ],
45395
- previewCaption: "Ordered pen strokes build an idea, then hold the completed drawing for reading.",
45396
- studyCopy: [
45397
- "Draw the connection.",
45398
- "Imagine",
45399
- "Connect",
45400
- "Create"
45401
- ],
45405
+ previewCaption: "A generated hand draws an AnimaGuru-generated marker illustration stroke by stroke, colors it in, titles it and wipes the board clean.",
45402
45406
  family: "graphic",
45403
45407
  example: false
45404
45408
  },
@@ -45415,13 +45419,7 @@ var animation_styles_default = {
45415
45419
  "Travelling path markers",
45416
45420
  "Controlled camera movement"
45417
45421
  ],
45418
- previewCaption: "Platforms rise into place and a travelling marker connects their stations.",
45419
- studyCopy: [
45420
- "A world, connected.",
45421
- "Collect",
45422
- "Shape",
45423
- "Share"
45424
- ],
45422
+ previewCaption: "An isometric town assembles tile by tile: buildings extrude, trees pop, cars and a train start running and the windows light up at dusk. Built from projected geometry in code.",
45425
45423
  family: "technical",
45426
45424
  example: false
45427
45425
  },
@@ -45438,14 +45436,7 @@ var animation_styles_default = {
45438
45436
  "Counted values",
45439
45437
  "Comparison highlights"
45440
45438
  ],
45441
- previewCaption: "Illustrative data only: three sample values reveal and settle at 24, 48 and 72.",
45442
- studyCopy: [
45443
- "Make the comparison clear.",
45444
- "A",
45445
- "B",
45446
- "C",
45447
- "ILLUSTRATIVE DATA / SAMPLE VALUES"
45448
- ],
45439
+ previewCaption: "A line chart is drawn, counted and annotated, then becomes ranked bars beside a platform donut. Drawn entirely in code from sample values.",
45449
45440
  family: "technical",
45450
45441
  example: false
45451
45442
  },
@@ -45462,12 +45453,7 @@ var animation_styles_default = {
45462
45453
  "Parameter changes",
45463
45454
  "Semantic transformations"
45464
45455
  ],
45465
- previewCaption: "A changing radius connects a circle to its area formula and a corresponding plot.",
45466
- studyCopy: [
45467
- "See the relationship.",
45468
- "radius",
45469
- "area"
45470
- ],
45456
+ previewCaption: "A unit circle traces a sine wave, then becomes the largest of 90 rotating circles that draw \u03C0. Plotted from a real Fourier series.",
45471
45457
  family: "technical",
45472
45458
  example: false
45473
45459
  },
@@ -45484,19 +45470,7 @@ var animation_styles_default = {
45484
45470
  "Bounded zooms",
45485
45471
  "State transitions"
45486
45472
  ],
45487
- previewCaption: "A conceptual interface demonstrates focus, a precise click and its resulting state.",
45488
- studyCopy: [
45489
- "Focus on what matters.",
45490
- "CONCEPTUAL INTERFACE",
45491
- "Workspace",
45492
- "Overview",
45493
- "Projects",
45494
- "Library",
45495
- "New project",
45496
- "Project created",
45497
- "Create your next idea.",
45498
- "Ready to create"
45499
- ],
45473
+ previewCaption: "A cursor drives a dashboard: a live toggle, hover lift, drag-to-reorder, a modal with typing and loading, then a toast and a phone notification. Every element and interaction is coded.",
45500
45474
  family: "technical",
45501
45475
  example: false
45502
45476
  },
@@ -45513,10 +45487,7 @@ var animation_styles_default = {
45513
45487
  "Pose-to-pose timing",
45514
45488
  "Gesture & reaction"
45515
45489
  ],
45516
- previewCaption: "An original illustrated character points, pauses and reacts to a moving idea.",
45517
- studyCopy: [
45518
- "An idea comes to life."
45519
- ],
45490
+ previewCaption: "Pip, a cartoon character drawn and animated in AnimaGuru, walks a generated meadow and hops a log, with a following camera, squash and stretch, and dust on landing.",
45520
45491
  family: "character",
45521
45492
  example: false
45522
45493
  },
@@ -45533,11 +45504,7 @@ var animation_styles_default = {
45533
45504
  "Palette accents",
45534
45505
  "Pixel transitions"
45535
45506
  ],
45536
- previewCaption: "A small pixel world comes to life with grid-aligned motion and stepped poses.",
45537
- studyCopy: [
45538
- "SMALL PIXELS.",
45539
- "BIG POSSIBILITIES."
45540
- ],
45507
+ previewCaption: "Bit, a pixel-art sprite animated in AnimaGuru, runs a side-scrolling level built from generated parallax layers, with coins, sparkles and a score drawn in code.",
45541
45508
  family: "character",
45542
45509
  example: false
45543
45510
  },
@@ -45554,10 +45521,7 @@ var animation_styles_default = {
45554
45521
  "Foreground reveals",
45555
45522
  "Selective organic motion"
45556
45523
  ],
45557
- previewCaption: "An original brush-textured landscape moves through overlapping painted planes.",
45558
- studyCopy: [
45559
- "Let the picture breathe."
45560
- ],
45524
+ previewCaption: "Five AnimaGuru-generated oil-painted planes on a slow dolly, with haze between them, sun rays, drifting pollen and wind in the grass.",
45561
45525
  family: "material",
45562
45526
  example: false
45563
45527
  },
@@ -45574,13 +45538,8 @@ var animation_styles_default = {
45574
45538
  "Matched reveals",
45575
45539
  "Controlled depth"
45576
45540
  ],
45577
- studyCopy: [
45578
- "Bring the signal forward.",
45579
- "Concept stage",
45580
- "One clear focus"
45581
- ],
45582
45541
  family: "technical",
45583
- previewCaption: "Dark presentation stages, crisp components and controlled accent light with precise assembly and focused reveals. Original procedural 2.5D study.",
45542
+ previewCaption: "Glass interface panels assemble around a hero card on a reflective stage under sweeping light. Built in code with depth of field and floor reflections.",
45584
45543
  example: true
45585
45544
  },
45586
45545
  "studio-product-reveal": {
@@ -45596,13 +45555,8 @@ var animation_styles_default = {
45596
45555
  "Detail close-ups",
45597
45556
  "Layered part separation"
45598
45557
  ],
45599
- studyCopy: [
45600
- "Form. Material. Detail.",
45601
- "Illustrative product",
45602
- "Light reveals the shape"
45603
- ],
45604
45558
  family: "material",
45605
- previewCaption: "A product silhouette, material details and layered light revealed through deliberate close-ups in a controlled 2.5D studio. Original procedural 2.5D study.",
45559
+ previewCaption: "An unbranded fragrance bottle generated with AnimaGuru turns under a strip light: a macro start, a pull-back, anamorphic glints and a title lockup.",
45606
45560
  example: true
45607
45561
  },
45608
45562
  "risograph-print": {
@@ -45618,13 +45572,8 @@ var animation_styles_default = {
45618
45572
  "Registration shifts",
45619
45573
  "Held-drawing cadence"
45620
45574
  ],
45621
- studyCopy: [
45622
- "Two inks. More possibilities.",
45623
- "Separate plates",
45624
- "Meet in the overprint"
45625
- ],
45626
45575
  family: "print",
45627
- previewCaption: "Porous paper, limited spot inks and colored overprints with deliberate registration and plate reveals. Original procedural 2.5D study.",
45576
+ previewCaption: "An AnimaGuru-generated illustration separated in code into yellow, fluorescent pink and teal drums, then printed pass by pass with riso grain and slight misregistration.",
45628
45577
  example: true
45629
45578
  },
45630
45579
  "halftone-editorial": {
@@ -45640,13 +45589,8 @@ var animation_styles_default = {
45640
45589
  "Stamp impacts",
45641
45590
  "Layered panel changes"
45642
45591
  ],
45643
- studyCopy: [
45644
- "Give the idea a headline.",
45645
- "An original print study",
45646
- "Make the point readable"
45647
- ],
45648
45592
  family: "print",
45649
- previewCaption: "Bold editorial type, dot shading, graphic cutouts and stamps arranged with a strong information hierarchy. Original procedural 2.5D study.",
45593
+ previewCaption: "An AnimaGuru-generated stage photo screened in code into black and orange halftone dots at 45\xB0 and 15\xB0, pulling back from the dots to a magazine spread.",
45650
45594
  example: true
45651
45595
  },
45652
45596
  "silkscreen-poster": {
@@ -45662,13 +45606,8 @@ var animation_styles_default = {
45662
45606
  "Layer-by-layer builds",
45663
45607
  "Graphic holds"
45664
45608
  ],
45665
- studyCopy: [
45666
- "One ink. One impression.",
45667
- "Print the composition",
45668
- "Hold the finished image"
45669
- ],
45670
45609
  family: "print",
45671
- previewCaption: "Opaque ink layers, bold silhouette and stepped color bands revealed through successive printing pulls. Original procedural 2.5D study.",
45610
+ previewCaption: "AnimaGuru-generated poster art cut into five flat stencils in code and pulled screen by screen with a squeegee, ending on the finished print.",
45672
45611
  example: true
45673
45612
  },
45674
45613
  "art-deco-motion": {
@@ -45684,13 +45623,8 @@ var animation_styles_default = {
45684
45623
  "Sequenced lights",
45685
45624
  "Mechanical settling"
45686
45625
  ],
45687
- studyCopy: [
45688
- "Geometry catches the light.",
45689
- "Open from the center",
45690
- "A measured reveal"
45691
- ],
45692
45626
  family: "graphic",
45693
- previewCaption: "Geometric ornament, metallic keylines and lacquer-like planes opening through symmetry and mechanical motion. Original procedural 2.5D study.",
45627
+ previewCaption: "Gold keylines, stepped arches and a sunburst fan open symmetrically; chevrons and marquee lights run in sequence while a specular sweep crosses the metal. Drawn entirely in code.",
45694
45628
  example: true
45695
45629
  },
45696
45630
  "blueprint-drafting": {
@@ -45706,13 +45640,8 @@ var animation_styles_default = {
45706
45640
  "Exploded parts",
45707
45641
  "Anchored dimensions"
45708
45642
  ],
45709
- studyCopy: [
45710
- "See how the parts connect.",
45711
- "Illustrative drawing",
45712
- "Assemble the relationship"
45713
- ],
45714
45643
  family: "technical",
45715
- previewCaption: "Measured line work, construction marks, sections and dimensions on a drafting surface with progressive assembly. Original procedural 2.5D study.",
45644
+ previewCaption: "A drafting point traces an AnimaGuru-generated cyanotype line by line, then dimensions, callouts and a title block are drawn in.",
45716
45645
  example: true
45717
45646
  },
45718
45647
  "holographic-scan": {
@@ -45728,13 +45657,8 @@ var animation_styles_default = {
45728
45657
  "Target locking",
45729
45658
  "Local part separation"
45730
45659
  ],
45731
- studyCopy: [
45732
- "Scan. Focus. Understand.",
45733
- "Illustrative part model",
45734
- "Follow the detail"
45735
- ],
45736
45660
  family: "technical",
45737
- previewCaption: "Luminous projected geometry, traveling scans and accurate part callouts with restrained depth and local explosions. Original procedural 2.5D study.",
45661
+ previewCaption: "A wireframe turbofan grows under a moving scan, locks onto its parts and separates them with callouts. Projected 3D geometry drawn in code.",
45738
45662
  example: true
45739
45663
  },
45740
45664
  "paper-popup": {
@@ -45750,13 +45674,8 @@ var animation_styles_default = {
45750
45674
  "Back-to-front arrival",
45751
45675
  "Paper-stage transitions"
45752
45676
  ],
45753
- studyCopy: [
45754
- "A flat page gains a world.",
45755
- "An original paper mechanism",
45756
- "Fold. Open. Discover."
45757
- ],
45758
45677
  family: "paper",
45759
- previewCaption: "Hinged cardstock pieces, visible fronts and backs, fold mechanisms and layered paper stages in constrained perspective. Original procedural 2.5D study.",
45678
+ previewCaption: "A book opens and AnimaGuru-generated cardstock layers hinge up from the fold with shadows and spring, while a pull tab lifts the balloon and sun.",
45760
45679
  example: true
45761
45680
  },
45762
45681
  "shadow-theatre": {
@@ -45772,13 +45691,8 @@ var animation_styles_default = {
45772
45691
  "Jointed articulation",
45773
45692
  "Lamp-distance changes"
45774
45693
  ],
45775
- studyCopy: [
45776
- "A silhouette tells a thousand things.",
45777
- "Light and carved shape",
45778
- "Move with the material"
45779
- ],
45780
45694
  family: "paper",
45781
- previewCaption: "Articulated silhouettes or translucent carved puppets on a lit screen, with distance-driven softness and scale. Original procedural 2.5D study.",
45695
+ previewCaption: "Shadow puppets generated with AnimaGuru perform behind a lit linen screen, finished in code with the cloth's weave, a breathing lamp and a warm glow.",
45782
45696
  example: true
45783
45697
  },
45784
45698
  "engraved-relief-print": {
@@ -45794,13 +45708,8 @@ var animation_styles_default = {
45794
45708
  "Progressive hatching",
45795
45709
  "Successive impressions"
45796
45710
  ],
45797
- studyCopy: [
45798
- "Every mark has a direction.",
45799
- "Carve the form",
45800
- "Pull the impression"
45801
- ],
45802
45711
  family: "print",
45803
- previewCaption: "Form-following hatch marks and carved impressions revealed through stable mark schedules and layered print treatments. Original procedural 2.5D study.",
45712
+ previewCaption: "A generated woodblock print pulled in order: hatching carved first, then two color impressions rolled into register.",
45804
45713
  example: true
45805
45714
  },
45806
45715
  "ascii-crt": {
@@ -45816,13 +45725,8 @@ var animation_styles_default = {
45816
45725
  "Grid-scale reveals",
45817
45726
  "Analytic afterglow"
45818
45727
  ],
45819
- studyCopy: [
45820
- "A picture made of characters.",
45821
- "Signal on the grid",
45822
- "Pixels become language"
45823
- ],
45824
45728
  family: "cinematic",
45825
- previewCaption: "Real glyph-grid imagery, terminal text and restrained phosphor glow with deliberate typing and screen behavior. Original procedural 2.5D study.",
45729
+ previewCaption: "An AnimaGuru-generated jellyfish clip rendered as real ASCII on a 128-column green-phosphor terminal, with curvature, glow trails and a typed command.",
45826
45730
  example: true
45827
45731
  },
45828
45732
  "analog-film-tape": {
@@ -45838,13 +45742,8 @@ var animation_styles_default = {
45838
45742
  "Deterministic weave",
45839
45743
  "Tape-inspired transitions"
45840
45744
  ],
45841
- studyCopy: [
45842
- "A different kind of time.",
45843
- "Original scene / film variant",
45844
- "Texture follows the medium"
45845
- ],
45846
45745
  family: "cinematic",
45847
- previewCaption: "Projected-film cadence or camcorder texture over original scenes, with controlled weave, exposure and medium-specific transitions. Original procedural 2.5D study.",
45746
+ previewCaption: "Two generated summer scenes projected at 18 fps, with gate weave, grain, a slipped splice and a film burn.",
45848
45747
  example: true
45849
45748
  },
45850
45749
  "glass-mosaic": {
@@ -45860,13 +45759,8 @@ var animation_styles_default = {
45860
45759
  "Moving light bands",
45861
45760
  "Identity-preserving rearrangement"
45862
45761
  ],
45863
- studyCopy: [
45864
- "Pieces become a picture.",
45865
- "Original glass-like mosaic",
45866
- "Light passes through"
45867
- ],
45868
45762
  family: "material",
45869
- previewCaption: "Colored panes or loose glass-like tiles, strong seams and moving transmitted-light treatments in 2.5D. Original procedural 2.5D study.",
45763
+ previewCaption: "An AnimaGuru-generated peacock cut in code into leaded glass pieces that slot in one by one, then lit by a low sun crossing the window.",
45870
45764
  example: true
45871
45765
  },
45872
45766
  "portal-zoom": {
@@ -45882,13 +45776,8 @@ var animation_styles_default = {
45882
45776
  "Layered parallax",
45883
45777
  "Occluded scene handoffs"
45884
45778
  ],
45885
- studyCopy: [
45886
- "Every frame opens another.",
45887
- "An original nested world",
45888
- "Keep the journey continuous"
45889
- ],
45890
45779
  family: "cinematic",
45891
- previewCaption: "Nested compositions, matched apertures and continuous scale changes through layered scenes with planned parallax. Original procedural 2.5D study.",
45780
+ previewCaption: "A continuous zoom through four AnimaGuru-generated worlds, each framed inside the last, that ends exactly where it began.",
45892
45781
  example: true
45893
45782
  },
45894
45783
  "photo-footage-editorial": {
@@ -45904,13 +45793,8 @@ var animation_styles_default = {
45904
45793
  "Media-wall selection",
45905
45794
  "Focused detail reveals"
45906
45795
  ],
45907
- studyCopy: [
45908
- "Give every image its place.",
45909
- "Original illustrated media cards",
45910
- "Frame the detail"
45911
- ],
45912
45796
  family: "cinematic",
45913
- previewCaption: "Purposeful media frames, filmstrips and contact-sheet layouts with selective focus and continuous framing relationships. Original procedural 2.5D study.",
45797
+ previewCaption: "Six generated photographs on a contact sheet. The circled frame becomes footage, then returns to its strip.",
45914
45798
  example: true
45915
45799
  },
45916
45800
  "stop-motion-objects": {
@@ -45926,13 +45810,8 @@ var animation_styles_default = {
45926
45810
  "Physical-looking assembly",
45927
45811
  "Held-drawing timing"
45928
45812
  ],
45929
- studyCopy: [
45930
- "Build it one piece at a time.",
45931
- "Illustrative tactile objects",
45932
- "Deliberate held poses"
45933
- ],
45934
45813
  family: "material",
45935
- previewCaption: "Tactile tabletop objects and deliberate held poses assembled with attached contact shadows in a 2.5D treatment. Original procedural 2.5D study.",
45814
+ previewCaption: "Clay footage generated with AnimaGuru, held on twos at 12 fps, with a nudge of the set and a flicker of the lamp between poses.",
45936
45815
  example: true
45937
45816
  },
45938
45817
  "miniature-diorama": {
@@ -45948,13 +45827,8 @@ var animation_styles_default = {
45948
45827
  "Elevated camera travel",
45949
45828
  "Optional time-lapse activity"
45950
45829
  ],
45951
- studyCopy: [
45952
- "A small world, carefully built.",
45953
- "An original 2.5D miniature",
45954
- "Let scale guide the view"
45955
- ],
45956
45830
  family: "material",
45957
- previewCaption: "Small constructed worlds seen from an elevated angle, with coherent scale, layered focus and controlled miniature activity. Original procedural 2.5D study.",
45831
+ previewCaption: "A generated harbor model with a slow elevated drift, a focus band on the quay and small time-lapsed activity.",
45958
45832
  example: true
45959
45833
  },
45960
45834
  "sand-drawing": {
@@ -45970,13 +45844,8 @@ var animation_styles_default = {
45970
45844
  "Authored reshape paths",
45971
45845
  "Material holds"
45972
45846
  ],
45973
- studyCopy: [
45974
- "A shape emerges from grains.",
45975
- "Original procedural sand study",
45976
- "Reveal the silhouette"
45977
- ],
45978
45847
  family: "material",
45979
- previewCaption: "Granular surface marks and swept negative space revealing coherent silhouettes through an original seeded 2D density recipe. Original procedural 2.5D study.",
45848
+ previewCaption: "A whale is swept into a seabird on a lit sand table, in one take. Footage generated with AnimaGuru.",
45980
45849
  example: true
45981
45850
  },
45982
45851
  "generative-light-signal": {
@@ -45992,20 +45861,14 @@ var animation_styles_default = {
45992
45861
  "Measured cue response",
45993
45862
  "Controlled pattern changes"
45994
45863
  ],
45995
- studyCopy: [
45996
- "Turn a signal into movement.",
45997
- "Original analytic field",
45998
- "Follow the rhythm"
45999
- ],
46000
45864
  family: "graphic",
46001
- previewCaption: "Luminous traces, geometric fields and seeded paths choreographed by time or precomputed audio cues. Original procedural 2.5D study.",
45865
+ previewCaption: "Seeded light particles stream along braided ribbons, flaring as timed pulses pass. A seamless loop rendered in code.",
46002
45866
  example: true
46003
45867
  }
46004
45868
  },
46005
45869
  browseStyles: "Browse animation styles",
46006
45870
  styleCount_one: "{{count}} visual style, guided by your brief",
46007
45871
  styleCount_other: "{{count}} visual styles, guided by your brief",
46008
- previewBrand: "ANIMAGURU / MOTION STUDIES",
46009
45872
  families: {
46010
45873
  all: "All styles",
46011
45874
  graphic: "Graphic & type",
@@ -46078,6 +45941,10 @@ var animation_styles_default = {
46078
45941
  "visual-essay": {
46079
45942
  name: "Visual Essay & Podcast",
46080
45943
  description: "Add accurate graphics and chapters to source text or recorded speech."
45944
+ },
45945
+ "branching-story": {
45946
+ name: "Branching Story",
45947
+ description: "Let viewers choose what happens next and reach one of several endings; plays as an interactive HTML5 film."
46081
45948
  }
46082
45949
  },
46083
45950
  artMotionLink: "Explore Art Motion styles \u2192"
@@ -46134,7 +46001,8 @@ var VIDEO_WORKFLOW_IDS = [
46134
46001
  "music-lyrics",
46135
46002
  "titles-event",
46136
46003
  "ambient-loop",
46137
- "visual-essay"
46004
+ "visual-essay",
46005
+ "branching-story"
46138
46006
  ];
46139
46007
  var VIDEO_WORKFLOWS2 = VIDEO_WORKFLOW_IDS.map((id2, index2) => ({
46140
46008
  id: id2,
@@ -46200,6 +46068,37 @@ async function installDependencies(directory, options) {
46200
46068
  child.once("close", (code) => code === 0 ? resolveInstall() : reject(new Error(`npm install exited with code ${code}.`)));
46201
46069
  });
46202
46070
  }
46071
+ var STARTER_STORY = {
46072
+ version: 1,
46073
+ start: "opening",
46074
+ segments: [
46075
+ {
46076
+ id: "opening",
46077
+ label: "The spark",
46078
+ start: 0,
46079
+ end: 8,
46080
+ prompt: "Where should the story go?",
46081
+ choiceAt: 5,
46082
+ choices: [{ label: "Show the wonder", to: "wonder" }, { label: "Make the invitation", to: "invitation" }]
46083
+ },
46084
+ { id: "wonder", label: "The possibility", start: 8, end: 16, ending: "A world worth exploring" },
46085
+ { id: "invitation", label: "The invitation", start: 16, end: 24, ending: "Your next story starts here" }
46086
+ ]
46087
+ };
46088
+ var writeStarterStory = (directory) => writeFile6(join6(directory, "story.json"), JSON.stringify(STARTER_STORY, null, 2) + "\n", { flag: "wx" });
46089
+ function starterQuiz(story = false) {
46090
+ return {
46091
+ version: 1,
46092
+ checkpoints: [{ id: "spark", at: story ? 4.5 : 7.5, title: "Quick check", questions: [
46093
+ { prompt: "Where does every great story begin?", options: ["With a big budget", "With a little spark", "With the ending"], answer: 1, explanation: "The opening scene says it: every great story begins with a little spark." }
46094
+ ] }],
46095
+ final: { title: "Quiz", pass: 50, questions: [
46096
+ { prompt: "What does a quiz film do at a checkpoint?", options: ["Skips to the end", "Pauses and asks a question", "Plays faster"], answer: 1, explanation: "A checkpoint pauses the film on its own frame, and the film goes on once the viewer answers." },
46097
+ { prompt: "Which of these can a question have?", options: ["One right answer", "Several right answers", "A countdown timer"], answer: [0, 1], explanation: "A question has one right answer, or several when it asks to select all that apply. Questions wait for the viewer." }
46098
+ ] }
46099
+ };
46100
+ }
46101
+ var writeStarterQuiz = (directory, story = false) => writeFile6(join6(directory, "quiz.json"), JSON.stringify(starterQuiz(story), null, 2) + "\n", { flag: "wx" });
46203
46102
  async function createVideoProject(options, installer = installDependencies) {
46204
46103
  if (!options.directory.trim()) throw new Error("Choose a new project folder, for example: animaguru create my-video");
46205
46104
  const directory = resolve5(options.directory);
@@ -46223,7 +46122,7 @@ async function createVideoProject(options, installer = installDependencies) {
46223
46122
  const manifest = JSON.parse(await readFile4(packagePath, "utf8"));
46224
46123
  manifest.name = basename2(directory).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 100) || "animaguru-video";
46225
46124
  await writeFile6(packagePath, JSON.stringify(manifest, null, 2) + "\n");
46226
- const skills = [.../* @__PURE__ */ new Set(["animaguru", "animaguru-video", "animaguru-brand-kit", "animaguru-cutcraft", "animaguru-art-motion", ...VIDEO_PURPOSE_SKILLS, ...VIDEO_TEMPLATES.map((template) => template.skill)])];
46125
+ const skills = [.../* @__PURE__ */ new Set(["animaguru", "animaguru-video", "animaguru-brand-kit", "animaguru-cutcraft", "animaguru-art-motion", "animaguru-branching-story", "animaguru-quiz", ...VIDEO_PURPOSE_SKILLS, ...VIDEO_TEMPLATES.map((template) => template.skill)])];
46227
46126
  const pixiSkillsRoot = join6(packageRoot, "vendor/pixijs-skills/skills");
46228
46127
  const pixiSkills = (await readdir2(pixiSkillsRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
46229
46128
  for (const agent of [".agents", ".claude"]) {
@@ -46247,6 +46146,8 @@ async function createVideoProject(options, installer = installDependencies) {
46247
46146
  animaguruProjectId: null
46248
46147
  }, null, 2) + "\n");
46249
46148
  await writeFile6(join6(directory, "animaguru.templates.json"), JSON.stringify(listTemplates(), null, 2) + "\n");
46149
+ if (options.story) await writeStarterStory(directory);
46150
+ if (options.quiz) await writeStarterQuiz(directory, options.story);
46250
46151
  if (options.install !== false) {
46251
46152
  options.log?.("Installing PixiJS, Vite, TypeScript, and the MP4 tools\u2026");
46252
46153
  try {
@@ -46369,7 +46270,11 @@ async function createAnimation(options) {
46369
46270
  await mkdir6(join6(directory, "public/assets"), { recursive: true });
46370
46271
  await mkdir6(join6(directory, "production"), { recursive: true });
46371
46272
  await writeFile6(join6(directory, "animaguru.project.json"), JSON.stringify({ schemaVersion: 4, cliVersion: package_default.version, workspace: "../..", category: null, template: null, workflow: null, skill: null, setupStage: "category", galleryUrl: ANIMATION_STYLE_GALLERY_URL, interview: "pending", animaguruProjectId: null, branding: "inherit" }, null, 2) + "\n");
46372
- options.log?.(`Animation ready: ${directory}. It uses the workspace's branding, player and export tools.`);
46273
+ if (options.story) await writeStarterStory(directory);
46274
+ if (options.quiz) await writeStarterQuiz(directory, options.story);
46275
+ const kind = options.story && options.quiz ? "Branching story with a quiz" : options.story ? "Branching story" : options.quiz ? "Quiz film" : "Animation";
46276
+ options.log?.(`${kind} ready: ${directory}. It uses the workspace's branding, player and export tools.`);
46277
+ const skills = [...options.story ? ["animaguru-branching-story"] : [], ...options.quiz ? ["animaguru-quiz"] : []];
46373
46278
  return {
46374
46279
  type: "animation",
46375
46280
  path: directory,
@@ -46377,7 +46282,9 @@ async function createAnimation(options) {
46377
46282
  name: options.name,
46378
46283
  category: null,
46379
46284
  template: null,
46380
- nextSteps: [`Describe the film in Codex or Claude Code, working in animations/${options.name}.`, `Run npm run dev to preview, or npm run export:html -- --animation ${options.name}.`]
46285
+ ...options.story ? { story: "story.json" } : {},
46286
+ ...options.quiz ? { quiz: "quiz.json" } : {},
46287
+ nextSteps: skills.length ? [`Describe the ${options.story ? "story" : "film"} in Codex or Claude Code, working in animations/${options.name}; the agent follows the ${skills.join(" and ")} skill${skills.length > 1 ? "s" : ""} and replaces the starter ${[...options.story ? ["story.json"] : [], ...options.quiz ? ["quiz.json"] : []].join(" and ")}.`, `Run npm run dev to play it; ${options.story ? "a branching story" : "a quiz film"} plays as HTML5 only, delivered with npm run export:html -- --animation ${options.name} or animaguru submit.`] : [`Describe the film in Codex or Claude Code, working in animations/${options.name}.`, `Run npm run dev to preview, or npm run export:html -- --animation ${options.name}.`]
46381
46288
  };
46382
46289
  }
46383
46290
 
@@ -46408,6 +46315,7 @@ var cameraKeyframe = external_exports.object({
46408
46315
  /** Distance from the subject in scene units. */
46409
46316
  distance: external_exports.number().gt(0).max(100)
46410
46317
  });
46318
+ var imageKeyframe = external_exports.object({ image: blob, seconds: external_exports.number().min(0).max(20) });
46411
46319
  var videoAsset = external_exports.object({
46412
46320
  name: base.name,
46413
46321
  /** Lip-sync and relight take no prompt, and camera-controls, insert-video, recast and 3d-to-video may leave it out. */
@@ -46434,6 +46342,8 @@ var videoAsset = external_exports.object({
46434
46342
  referenceVideos: external_exports.array(blob).max(MAX_REFERENCE_VIDEOS).optional(),
46435
46343
  /** The camera's path around the reference image: 2–12 keyframes. */
46436
46344
  camera: external_exports.array(cameraKeyframe).min(2).max(MAX_CAMERA_KEYFRAMES).optional(),
46345
+ /** Keyframes-to-video: 1–10 images pinned to moments of the clip, each on a frame of its own. */
46346
+ keyframes: external_exports.array(imageKeyframe).min(1).max(MAX_IMAGE_KEYFRAMES).optional(),
46437
46347
  /** Seconds into the source where the inserted scene starts, and where the source resumes after it. */
46438
46348
  insertAt: external_exports.number().min(1.625).max(60).optional(),
46439
46349
  resumeAt: external_exports.number().gt(1.625).max(60).optional(),
@@ -46477,6 +46387,7 @@ var TAKES = {
46477
46387
  references: ["reference-to-video", "insert-video", "recast", "3d-to-video"],
46478
46388
  referenceVideos: ["reference-to-video", "insert-video"],
46479
46389
  camera: ["camera-controls"],
46390
+ keyframes: ["keyframes-to-video"],
46480
46391
  insertAt: ["insert-video"],
46481
46392
  resumeAt: ["insert-video"],
46482
46393
  colorMatch: ["insert-video"],
@@ -46487,8 +46398,8 @@ var TAKES = {
46487
46398
  generatedReferences: ["3d-to-video"],
46488
46399
  style: ["style"],
46489
46400
  damage: ["style"],
46490
- aspectRatio: ["text-to-video", "extend-video", "reference-to-video", "relight", "style"],
46491
- promptExpansion: ["image-to-video", "text-to-video", "extend-video", "reference-to-video", "camera-controls", "insert-video"],
46401
+ aspectRatio: ["text-to-video", "extend-video", "reference-to-video", "relight", "style", "keyframes-to-video"],
46402
+ promptExpansion: ["image-to-video", "text-to-video", "extend-video", "reference-to-video", "camera-controls", "insert-video", "keyframes-to-video"],
46492
46403
  seed: ALL.filter((w) => w !== "3d-to-video"),
46493
46404
  extendOutput: ["extend-video"]
46494
46405
  };
@@ -46499,7 +46410,8 @@ var NEEDS = {
46499
46410
  recast: ["source", "references", "subjectConsent"],
46500
46411
  relight: ["source", "lighting"],
46501
46412
  "3d-to-video": ["source"],
46502
- style: ["style"]
46413
+ style: ["style"],
46414
+ "keyframes-to-video": ["keyframes"]
46503
46415
  };
46504
46416
  var LABELS = {
46505
46417
  reference: "a starting image",
@@ -46512,6 +46424,7 @@ var LABELS = {
46512
46424
  references: "reference images",
46513
46425
  referenceVideos: "reference videos",
46514
46426
  camera: "camera keyframes",
46427
+ keyframes: "keyframe images",
46515
46428
  insertAt: "insertAt",
46516
46429
  resumeAt: "resumeAt",
46517
46430
  colorMatch: "colorMatch",
@@ -46597,6 +46510,11 @@ function videoChecks(p, ctx) {
46597
46510
  const travel = p.camera.slice(1).reduce((sum, k, i2) => sum + Math.abs(k.azimuth - p.camera[i2].azimuth), 0);
46598
46511
  if (travel > 360 * MAX_CAMERA_TURNS) issue2("camera", `The camera may travel at most ${MAX_CAMERA_TURNS} full turns around the subject.`);
46599
46512
  }
46513
+ if (p.keyframes) {
46514
+ const frames = p.keyframes.map((k) => Math.round(k.seconds * KEYFRAME_FPS));
46515
+ if (new Set(frames).size !== frames.length) issue2("keyframes", `Put each keyframe on a frame of its own; the video has ${KEYFRAME_FPS} frames a second.`);
46516
+ if (p.keyframes.some((k) => k.seconds > seconds2)) issue2("keyframes", "Put each keyframe within the video's length.");
46517
+ }
46600
46518
  }
46601
46519
  var defaultSeconds = (model, workflow) => {
46602
46520
  const r = videoSeconds(model, workflow);
@@ -46703,9 +46621,10 @@ var videoDetails = external_exports.object({
46703
46621
  agreement: videoAgreementAcceptance.optional()
46704
46622
  }).refine((value) => value.visibility !== "public" || !value.recipients.length, "Public videos do not use a recipient list.");
46705
46623
  var videoAnimationName = external_exports.string().regex(/^[a-z0-9][a-z0-9-]{0,79}$/, "Use the animation's folder name.");
46624
+ var videoWorkspace = external_exports.union([external_exports.literal("me"), external_exports.string().regex(/^team_[a-f0-9]{32}$/)]);
46706
46625
  var videoSubmission = videoDetails.and(external_exports.object({
46707
46626
  /** The workspace that keeps the video; a project's video goes to the project's workspace. */
46708
- workspace: external_exports.union([external_exports.literal("me"), external_exports.string().regex(/^team_[a-f0-9]{32}$/)]).optional(),
46627
+ workspace: videoWorkspace.optional(),
46709
46628
  project: external_exports.string().regex(/^prj_[A-Za-z0-9]{8,40}$/).optional(),
46710
46629
  animation: videoAnimationName.optional(),
46711
46630
  format: external_exports.enum(["html5", "mp4"]),
@@ -46714,6 +46633,7 @@ var videoSubmission = videoDetails.and(external_exports.object({
46714
46633
  height: external_exports.number().int().min(1).max(4096).default(1080),
46715
46634
  duration: external_exports.number().positive().max(7200).optional()
46716
46635
  })).refine((value) => value.bytes <= VIDEO_MAX_BYTES[value.format], "HTML5 ZIPs support up to 64 MiB; MP4s support up to 512 MiB.").refine((value) => !value.animation || value.project, "Name the project of the animation.");
46636
+ var videoMove = external_exports.object({ workspace: videoWorkspace }).strict();
46717
46637
  function videoPosterType(bytes) {
46718
46638
  if (bytes.length >= 8 && bytes.slice(0, 8).every((b, i2) => b === [137, 80, 78, 71, 13, 10, 26, 10][i2])) return "image/png";
46719
46639
  if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return "image/jpeg";
@@ -47066,7 +46986,7 @@ var CLOUD_PART_BYTES = 8 * 1024 * 1024;
47066
46986
  var CLOUD_MANIFEST_PATH = ".animaguru-cloud.json";
47067
46987
  var CLOUD_MANIFEST_MAX_BYTES = 16 * 1024 * 1024;
47068
46988
  var CLOUD_MAX_FILES = 5e4;
47069
- var CLOUD_STORAGE_BYTES = { pro: 20 * 1024 ** 3, team: 100 * 1024 ** 3 };
46989
+ var CLOUD_STORAGE_BYTES = { starter: 5 * 1024 ** 3, pro: 20 * 1024 ** 3, team: 100 * 1024 ** 3 };
47070
46990
  var cloudProjectId = external_exports.string().regex(/^prj_[A-Za-z0-9]{8,40}$/, "Use a project ID such as prj_\u2026.");
47071
46991
  var sha256Hex = external_exports.string().regex(/^[a-f0-9]{64}$/, "Use a lowercase SHA-256 hex digest.");
47072
46992
  var gitObjectId = external_exports.string().regex(/^[a-f0-9]{40}$/, "Use a full 40-character version ID.");
@@ -47820,6 +47740,31 @@ async function changeVideoSubtitles(client, id2, options) {
47820
47740
  parseSubtitles(content.toString("utf8"));
47821
47741
  return client.request(path + language.data, { method: "PUT", body: subtitlesBody(content) });
47822
47742
  }
47743
+ async function listVideos(client, options) {
47744
+ const scope = options.scope ?? "mine";
47745
+ const page = options.page ?? 1;
47746
+ if (options.workspace !== void 0 && scope !== "mine") throw new Error("--workspace chooses a library; use it with --scope mine.");
47747
+ if (scope === "public") return client.request(`/api/videos?page=${page}`, { authenticated: false });
47748
+ if (scope !== "mine") return client.request(`/api/v1/videos?scope=${scope}&page=${page}`);
47749
+ const workspace3 = options.workspace === void 0 ? await selectedWorkspace(client.origin) : libraryWorkspace(options.workspace);
47750
+ return client.request(`/api/v1/videos?scope=mine&page=${page}${workspace3 === "me" ? "" : `&workspace=${encodeURIComponent(workspace3)}`}`);
47751
+ }
47752
+ async function moveVideos(client, ids, options) {
47753
+ if (!options.yes) throw new Error("Pass --yes to authorize moving these videos. Everyone in a team can watch the videos in its library.");
47754
+ if (!ids.length) throw new Error("Name the videos to move with their IDs from animaguru videos list.");
47755
+ if (options.workspace === void 0) throw new Error("Pass --workspace personal or a team ID from animaguru workspaces list.");
47756
+ const workspace3 = libraryWorkspace(options.workspace);
47757
+ const moved = [];
47758
+ for (const [index2, id2] of ids.entries()) {
47759
+ try {
47760
+ const { video } = await client.request(`/api/v1/videos/${encodeURIComponent(id2)}/workspace`, { method: "PATCH", body: { workspace: workspace3 } });
47761
+ moved.push({ id: video.id, title: video.title, workspaceId: video.workspaceId });
47762
+ } catch (error62) {
47763
+ return { moved, error: `${id2} was not moved: ${error62 instanceof Error ? error62.message : String(error62)}`, notMoved: ids.slice(index2) };
47764
+ }
47765
+ }
47766
+ return { moved };
47767
+ }
47823
47768
 
47824
47769
  // cli/src/assets.ts
47825
47770
  import { mkdir as mkdir8, readFile as readFile9, stat as stat4, writeFile as writeFile8 } from "node:fs/promises";
@@ -47846,6 +47791,7 @@ var extras = {
47846
47791
  "middle-reference",
47847
47792
  "middle-seconds",
47848
47793
  "camera",
47794
+ "keyframes",
47849
47795
  "insert-at",
47850
47796
  "resume-at",
47851
47797
  "color-match",
@@ -47880,11 +47826,16 @@ var regions = (value) => value?.split(";").filter((region) => region.trim()).map
47880
47826
  if (rest.length || [x2, y, width, height].some((n) => !Number.isFinite(n))) throw new Error(`--mask regions are x,y,width,height in fractions of Image 1, separated by semicolons; "${region.trim()}" is not.`);
47881
47827
  return { x: x2, y, width, height };
47882
47828
  });
47883
- var keyframes = (value) => value?.split(";").filter((frame) => frame.trim()).map((frame) => {
47829
+ var cameraKeyframes = (value) => value?.split(";").filter((frame) => frame.trim()).map((frame) => {
47884
47830
  const [time3, azimuth, elevation, distance, ...rest] = frame.split(",").map((n) => n.trim() ? Number(n) : NaN);
47885
47831
  if (rest.length || [time3, azimuth, elevation, distance].some((n) => !Number.isFinite(n))) throw new Error(`--camera keyframes are time,azimuth,elevation,distance (time 0\u20131, angles in degrees), separated by semicolons; "${frame.trim()}" is not.`);
47886
47832
  return { time: time3, azimuth, elevation, distance };
47887
47833
  });
47834
+ var imageKeyframes = (value) => value?.split(";").filter((frame) => frame.trim()).map((frame) => {
47835
+ const [seconds2, image, ...rest] = frame.split(",").map((part) => part.trim());
47836
+ if (rest.length || !image || !seconds2 || !Number.isFinite(Number(seconds2))) throw new Error(`--keyframes are seconds,image SHA pairs separated by semicolons, e.g. "0,SHA;5,SHA"; "${frame.trim()}" is not.`);
47837
+ return { image, seconds: Number(seconds2) };
47838
+ });
47888
47839
  function assetFromFlags(flags) {
47889
47840
  if (flags.type === "image" && flags.model) throw new Error("Image models are managed by staff. Omit --model.");
47890
47841
  for (const name2 of Object.values(extras).flat()) {
@@ -47907,7 +47858,8 @@ function assetFromFlags(flags) {
47907
47858
  referenceVideos: shas(flags["reference-videos"]),
47908
47859
  middleReference: flags["middle-reference"],
47909
47860
  middleSeconds: flags["middle-seconds"],
47910
- camera: keyframes(flags.camera),
47861
+ camera: cameraKeyframes(flags.camera),
47862
+ keyframes: imageKeyframes(flags.keyframes),
47911
47863
  insertAt: flags["insert-at"],
47912
47864
  resumeAt: flags["resume-at"],
47913
47865
  colorMatch: flags["color-match"],
@@ -48617,8 +48569,8 @@ async function youtubeLogin(options) {
48617
48569
  let receive;
48618
48570
  let fail;
48619
48571
  let used = false;
48620
- const callback = new Promise((resolve17, reject) => {
48621
- receive = resolve17;
48572
+ const callback = new Promise((resolve18, reject) => {
48573
+ receive = resolve18;
48622
48574
  fail = reject;
48623
48575
  });
48624
48576
  void callback.catch(() => {
@@ -48656,9 +48608,9 @@ async function youtubeLogin(options) {
48656
48608
  response.end("Authorization received. Return to your AnimaGuru terminal.");
48657
48609
  receive(code);
48658
48610
  });
48659
- await new Promise((resolve17, reject) => {
48611
+ await new Promise((resolve18, reject) => {
48660
48612
  server.once("error", reject);
48661
- server.listen(0, "127.0.0.1", resolve17);
48613
+ server.listen(0, "127.0.0.1", resolve18);
48662
48614
  });
48663
48615
  const address = server.address();
48664
48616
  if (!address || typeof address === "string") throw new Error("Cannot open the local YouTube authorization listener.");
@@ -48693,7 +48645,7 @@ async function youtubeLogin(options) {
48693
48645
  clearTimeout(timer);
48694
48646
  options.signal?.removeEventListener("abort", abort);
48695
48647
  server.closeAllConnections();
48696
- await new Promise((resolve17) => server.close(() => resolve17()));
48648
+ await new Promise((resolve18) => server.close(() => resolve18()));
48697
48649
  }
48698
48650
  });
48699
48651
  }
@@ -49797,6 +49749,114 @@ async function referenceGet(client, id2, options) {
49797
49749
  return report(options.wait ? await finished(client, analysis, options.log) : analysis, options);
49798
49750
  }
49799
49751
 
49752
+ // cli/src/curl.ts
49753
+ import { createWriteStream as createWriteStream2, openAsBlob as openAsBlob2 } from "node:fs";
49754
+ import { readFile as readFile18, unlink as unlink3 } from "node:fs/promises";
49755
+ import { extname as extname4, resolve as resolve16 } from "node:path";
49756
+ import { Readable as Readable2 } from "node:stream";
49757
+ import { pipeline as pipeline2 } from "node:stream/promises";
49758
+ function curlUrl(server, target2) {
49759
+ const origin = serverUrl(server);
49760
+ const url3 = target2.startsWith("/") && !target2.startsWith("//") ? new URL(target2, origin) : URL.canParse(target2) ? new URL(target2) : null;
49761
+ if (!url3 || url3.origin !== origin || url3.username || url3.password) throw new Error(`animaguru curl calls only ${origin}. Pass a path such as /api/v1/me.`);
49762
+ return url3;
49763
+ }
49764
+ function curlHeaders(lines = []) {
49765
+ const headers = new Headers();
49766
+ for (const line of lines) {
49767
+ const colon = line.indexOf(":");
49768
+ const name2 = colon > 0 ? line.slice(0, colon).trim() : "";
49769
+ if (!name2) throw new Error(`Write headers as "Name: value", not "${line}".`);
49770
+ if (/^(authorization|host)$/i.test(name2)) throw new Error(`Leave out the ${name2} header; animaguru curl sends the signed-in session's.`);
49771
+ headers.append(name2, line.slice(colon + 1).trim());
49772
+ }
49773
+ return headers;
49774
+ }
49775
+ var UPLOAD_TYPES = {
49776
+ ".mp4": "video/mp4",
49777
+ ".webm": "video/webm",
49778
+ ".mov": "video/quicktime",
49779
+ ".jpg": "image/jpeg",
49780
+ ".jpeg": "image/jpeg",
49781
+ ".png": "image/png",
49782
+ ".webp": "image/webp",
49783
+ ".gif": "image/gif",
49784
+ ".json": "application/json",
49785
+ ".txt": "text/plain"
49786
+ };
49787
+ var isText = (type) => /^(text\/|application\/([\w.+-]*\+)?(json|xml)\b|application\/(javascript|x-www-form-urlencoded)\b)/i.test(type);
49788
+ var readStdin = async () => {
49789
+ const chunks = [];
49790
+ for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
49791
+ return Buffer.concat(chunks);
49792
+ };
49793
+ async function bodyValue(value, io) {
49794
+ if (value === "@-") return (io.stdin ?? readStdin)();
49795
+ return value.startsWith("@") ? readFile18(resolve16(value.slice(1))) : value;
49796
+ }
49797
+ var parsesAsJson = (body) => {
49798
+ try {
49799
+ JSON.parse(body.toString());
49800
+ return true;
49801
+ } catch {
49802
+ return false;
49803
+ }
49804
+ };
49805
+ async function curl(options, io) {
49806
+ const url3 = curlUrl(options.server, options.target);
49807
+ const headers = curlHeaders(options.header);
49808
+ if ([options.data, options.json, options.uploadFile].filter((value) => value !== void 0).length > 1) throw new Error("Use one of --data, --json and --upload-file.");
49809
+ let body;
49810
+ if (options.uploadFile !== void 0) {
49811
+ const file2 = resolve16(options.uploadFile);
49812
+ body = await openAsBlob2(file2);
49813
+ if (!headers.has("content-type")) headers.set("Content-Type", UPLOAD_TYPES[extname4(file2).toLowerCase()] ?? "application/octet-stream");
49814
+ } else if (options.json !== void 0) {
49815
+ body = await bodyValue(options.json, io);
49816
+ if (!headers.has("content-type")) headers.set("Content-Type", "application/json");
49817
+ if (!headers.has("accept")) headers.set("Accept", "application/json");
49818
+ } else if (options.data !== void 0) {
49819
+ body = await bodyValue(options.data, io);
49820
+ if (!headers.has("content-type")) headers.set("Content-Type", parsesAsJson(body) ? "application/json" : "application/x-www-form-urlencoded");
49821
+ }
49822
+ headers.set("Authorization", `Bearer ${await sessionToken(url3.origin)}`);
49823
+ if (!headers.has("origin")) headers.set("Origin", url3.origin);
49824
+ const method = (options.request ?? (options.uploadFile !== void 0 ? "PUT" : body !== void 0 ? "POST" : "GET")).toUpperCase();
49825
+ const timeout = AbortSignal.timeout(options.maxTime * 1e3);
49826
+ const response = await fetch(url3, { method, headers, body, redirect: "manual", signal: io.signal ? AbortSignal.any([io.signal, timeout]) : timeout });
49827
+ if (options.include) io.stdout([`HTTP ${response.status} ${response.statusText}`.trim(), ...[...response.headers].map(([name2, value]) => `${name2}: ${value}`), ""].join("\n"));
49828
+ const type = response.headers.get("content-type") ?? "";
49829
+ const failed = options.fail && response.status >= 400;
49830
+ if (options.output !== void 0 && !failed) {
49831
+ const file2 = resolve16(options.output);
49832
+ let bytes = 0;
49833
+ try {
49834
+ await pipeline2(
49835
+ response.body ? Readable2.fromWeb(response.body) : Readable2.from([]),
49836
+ async function* (chunks) {
49837
+ for await (const chunk of chunks) {
49838
+ bytes += chunk.length;
49839
+ yield chunk;
49840
+ }
49841
+ },
49842
+ createWriteStream2(file2, { flags: "wx" })
49843
+ );
49844
+ } catch (error62) {
49845
+ if (error62.code === "EEXIST") throw new Error(`${options.output} already exists; choose another --output.`);
49846
+ await unlink3(file2).catch(() => void 0);
49847
+ throw error62;
49848
+ }
49849
+ io.stdout(JSON.stringify({ status: response.status, contentType: type || null, bytes, file: file2 }));
49850
+ } else if (!response.body || isText(type) || failed) {
49851
+ const text2 = await response.text();
49852
+ if (text2) io.stdout(text2.replace(/\n$/, ""));
49853
+ } else {
49854
+ await response.body.cancel();
49855
+ throw new Error(`The response is ${type || "binary"} (HTTP ${response.status}). Save it with -o FILE.`);
49856
+ }
49857
+ if (failed) throw new Error(`HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""} from ${method} ${url3.pathname}.`);
49858
+ }
49859
+
49800
49860
  // cli/src/commands.ts
49801
49861
  var assetModelIds = [...SPRITE_VIDEO_MODEL_IDS, ...MUSIC_MODELS.map((m) => m.id), ...SPEECH_MODELS.map((m) => m.id), ...SFX_MODELS.map((m) => m.id)];
49802
49862
  var assetAuthorizationFlags = [
@@ -49847,12 +49907,12 @@ var workspaceQuery = async (client) => {
49847
49907
  async function openBrowser(url3) {
49848
49908
  const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32" : "xdg-open";
49849
49909
  const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url3] : [url3];
49850
- await new Promise((resolve17) => {
49910
+ await new Promise((resolve18) => {
49851
49911
  const child = spawn3(command, args, { stdio: "ignore", detached: true });
49852
- child.once("error", () => resolve17());
49912
+ child.once("error", () => resolve18());
49853
49913
  child.once("spawn", () => {
49854
49914
  child.unref();
49855
- resolve17();
49915
+ resolve18();
49856
49916
  });
49857
49917
  });
49858
49918
  }
@@ -49902,10 +49962,17 @@ function createCli() {
49902
49962
  { name: "out", type: "string", description: "Write the analysis to this file instead" },
49903
49963
  { name: "wait", type: "boolean", description: "Wait until the analysis finishes" },
49904
49964
  urlFlag
49905
- ).action(async ({ args, flags, stdout, stderr, signal }) => out(stdout, await referenceGet(new Client(flags.url, signal), args.id, { ...flags, log: stderr })))))).command("create", (creation) => creation.args({ name: "name", type: "string", description: "Legacy standalone project folder; prefer create workspace NAME" }).action(async ({ args, stdout, stderr, signal }) => {
49965
+ ).action(async ({ args, flags, stdout, stderr, signal }) => out(stdout, await referenceGet(new Client(flags.url, signal), args.id, { ...flags, log: stderr })))))).command("create", (creation) => creation.args({ name: "name", type: "string", description: "Legacy standalone project folder; prefer create workspace NAME" }).flags(
49966
+ { name: "story", type: "boolean", description: "Start a branching story: viewers choose its path, and it plays as HTML5 only" },
49967
+ { name: "quiz", type: "boolean", description: "Start a quiz film: questions pause the film at checkpoints and a final quiz follows it (HTML5 only, never MP4)" }
49968
+ ).action(async ({ args, flags, stdout, stderr, signal }) => {
49906
49969
  if (!args.name) throw new Error("Use animaguru create workspace NAME, or animaguru create animation NAME inside a workspace.");
49907
- out(stdout, await createVideoProject({ directory: args.name, log: stderr, signal }));
49908
- }).add(defineCommand("workspace", { description: "Create a shared-brand workspace with one player, dependencies, skills and export toolchain" }, (command) => command.args({ name: "name", type: "string", required: true }).flags({ name: "skip-install", type: "boolean", default: false, description: "Create source files without installing dependencies" }).action(async ({ args, flags, stdout, stderr, signal }) => out(stdout, await createWorkspace({ directory: args.name, install: !flags["skip-install"], log: stderr, signal }))))).add(defineCommand("animation", { description: "Add an animation to a workspace without copying shared runtime, dependencies or skills" }, (command) => command.args({ name: "name", type: "string", required: true }).flags({ name: "workspace", type: "string", description: "Workspace folder; otherwise finds it from the current directory" }).action(async ({ args, flags, stdout, stderr, signal }) => out(stdout, await createAnimation({ name: args.name, workspace: flags.workspace, log: stderr, signal })))))).add(defineCommand("submit", { description: "Upload a finished HTML5 animation or MP4 to AnimaGuru; private unless --visibility public" }, (command) => command.args({ name: "directory", type: "string", default: ".", description: "Video project folder; defaults to current folder" }).flags(
49970
+ out(stdout, await createVideoProject({ directory: args.name, story: flags.story, quiz: flags.quiz, log: stderr, signal }));
49971
+ }).add(defineCommand("workspace", { description: "Create a shared-brand workspace with one player, dependencies, skills and export toolchain" }, (command) => command.args({ name: "name", type: "string", required: true }).flags({ name: "skip-install", type: "boolean", default: false, description: "Create source files without installing dependencies" }).action(async ({ args, flags, stdout, stderr, signal }) => out(stdout, await createWorkspace({ directory: args.name, install: !flags["skip-install"], log: stderr, signal }))))).add(defineCommand("animation", { description: "Add an animation to a workspace without copying shared runtime, dependencies or skills" }, (command) => command.args({ name: "name", type: "string", required: true }).flags(
49972
+ { name: "workspace", type: "string", description: "Workspace folder; otherwise finds it from the current directory" },
49973
+ { name: "story", type: "boolean", description: "Start a branching story: viewers choose its path at choices, and it plays as HTML5 only (never MP4)" },
49974
+ { name: "quiz", type: "boolean", description: "Start a quiz film (an educational explainer): checkpoints pause the film to ask questions, a final quiz and score follow its end; it plays as HTML5 only (never MP4)" }
49975
+ ).action(async ({ args, flags, stdout, stderr, signal }) => out(stdout, await createAnimation({ name: args.name, workspace: flags.workspace, story: flags.story, quiz: flags.quiz, log: stderr, signal })))))).add(defineCommand("submit", { description: "Upload a finished HTML5 animation or MP4 to AnimaGuru; private unless --visibility public" }, (command) => command.args({ name: "directory", type: "string", default: ".", description: "Video project folder; defaults to current folder" }).flags(
49909
49976
  { name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } },
49910
49977
  { name: "animation", type: "string", description: "Animation name when submitting from a multi-animation workspace" },
49911
49978
  { name: "workspace", type: "string", description: "Video library: personal or a team ID; defaults to the selected workspace, or a synced project's" },
@@ -49919,11 +49986,24 @@ function createCli() {
49919
49986
  { name: "subtitle-language", type: "string", description: "Subtitle language code such as en or tr; defaults to the MP4's export receipt or film.config.json" },
49920
49987
  { name: "yes", type: "boolean", description: "Authorize upload and chosen sharing" },
49921
49988
  { name: "dry-run", type: "boolean", description: "Inspect locally without uploading" }
49922
- ).action(async ({ args, flags, stdout, stderr, signal }) => out(stdout, await submitVideo(new Client(flags.url, signal), { ...flags, directory: args.directory, log: stderr, signal }))))).command("videos", (videos) => videos.add(defineCommand("list", { description: "List your videos, privately shared videos, or the public gallery" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } }, { name: "scope", type: "string", choices: ["mine", "shared", "public"], default: "mine" }, { name: "page", type: "number", default: 1 }).action(async ({ flags, stdout, signal }) => out(stdout, await new Client(flags.url, signal).request(flags.scope === "public" ? `/api/videos?page=${flags.page}` : `/api/v1/videos?scope=${flags.scope}&page=${flags.page}`, { authenticated: flags.scope !== "public" }))))).add(defineCommand("get", { description: "Get a video's details or the state of your unfinished upload" }, (command) => command.args({ name: "id", type: "string", required: true }).flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } }).action(async ({ args, flags, stdout, signal }) => out(stdout, await new Client(flags.url, signal).request(`/api/v1/videos/${encodeURIComponent(args.id)}`))))).add(defineCommand("complete", { description: "Retry completion of an already uploaded animation" }, (command) => command.args({ name: "id", type: "string", required: true }).flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } }).action(async ({ args, flags, stdout, signal }) => out(stdout, await new Client(flags.url, signal).request(`/api/v1/videos/${encodeURIComponent(args.id)}/complete`, { method: "POST", timeout: 3e5 }))))).add(defineCommand("share", { description: "Replace a video's visibility and recipient list" }, (command) => command.args({ name: "id", type: "string", required: true }).flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } }, { name: "visibility", type: "string", required: true, choices: ["private", "public"] }, { name: "share", type: "string" }, { name: "yes", type: "boolean", required: true }).action(async ({ args, flags, stdout, signal }) => {
49989
+ ).action(async ({ args, flags, stdout, stderr, signal }) => out(stdout, await submitVideo(new Client(flags.url, signal), { ...flags, directory: args.directory, log: stderr, signal }))))).command("videos", (videos) => videos.add(defineCommand("list", { description: "List a video library (the selected workspace's by default), privately shared videos, or the public gallery" }, (command) => command.flags(
49990
+ { name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } },
49991
+ { name: "scope", type: "string", choices: ["mine", "shared", "public"], default: "mine" },
49992
+ { name: "page", type: "number", default: 1 },
49993
+ { name: "workspace", type: "string", description: "The library to list with --scope mine: 'personal' or a team ID from workspaces list" }
49994
+ ).action(async ({ flags, stdout, signal }) => out(stdout, await listVideos(new Client(flags.url, signal), flags))))).add(defineCommand("get", { description: "Get a video's details or the state of your unfinished upload" }, (command) => command.args({ name: "id", type: "string", required: true }).flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } }).action(async ({ args, flags, stdout, signal }) => out(stdout, await new Client(flags.url, signal).request(`/api/v1/videos/${encodeURIComponent(args.id)}`))))).add(defineCommand("complete", { description: "Retry completion of an already uploaded animation" }, (command) => command.args({ name: "id", type: "string", required: true }).flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } }).action(async ({ args, flags, stdout, signal }) => out(stdout, await new Client(flags.url, signal).request(`/api/v1/videos/${encodeURIComponent(args.id)}/complete`, { method: "POST", timeout: 3e5 }))))).add(defineCommand("share", { description: "Replace a video's visibility and recipient list" }, (command) => command.args({ name: "id", type: "string", required: true }).flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } }, { name: "visibility", type: "string", required: true, choices: ["private", "public"] }, { name: "share", type: "string" }, { name: "yes", type: "boolean", required: true }).action(async ({ args, flags, stdout, signal }) => {
49923
49995
  if (!flags.yes) throw new Error("Pass --yes to authorize changing who can see this video.");
49924
49996
  const client = new Client(flags.url, signal);
49925
49997
  const { video } = await client.request(`/api/videos/${encodeURIComponent(args.id)}`);
49926
49998
  out(stdout, await client.request(`/api/v1/videos/${encodeURIComponent(args.id)}`, { method: "PATCH", body: videoDetails.parse({ ...video, visibility: flags.visibility, recipients: sharingEmails(flags.share) }) }));
49999
+ }))).add(defineCommand("move", { description: "Move your videos to another library, Personal or a team you belong to; their links, subtitles and sharing stay" }, (command) => command.args({ name: "ids", type: "string", variadic: true, description: "Video IDs from videos list" }).flags(
50000
+ { name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } },
50001
+ { name: "workspace", type: "string", required: true, description: "'personal' or a team ID from workspaces list" },
50002
+ { name: "yes", type: "boolean", required: true }
50003
+ ).action(async ({ args, flags, stdout, signal }) => {
50004
+ const result = await moveVideos(new Client(flags.url, signal), args.ids ?? [], flags);
50005
+ out(stdout, result);
50006
+ if (result.error) throw new Error(result.error);
49927
50007
  }))).add(defineCommand("subtitles", { description: "Add or replace an MP4's subtitles in one language, or remove a language" }, (command) => command.args({ name: "id", type: "string", required: true }).flags(
49928
50008
  { name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } },
49929
50009
  { name: "file", type: "string", description: ".srt or .vtt subtitle file" },
@@ -49933,7 +50013,7 @@ function createCli() {
49933
50013
  ).action(async ({ args, flags, stdout, signal }) => out(stdout, await changeVideoSubtitles(new Client(flags.url, signal), args.id, flags))))).add(defineCommand("delete", { description: "Remove a video and its hosted files" }, (command) => command.args({ name: "id", type: "string", required: true }).flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } }, { name: "yes", type: "boolean", required: true }).action(async ({ args, flags, stdout, signal }) => {
49934
50014
  if (!flags.yes) throw new Error("Pass --yes to delete the video.");
49935
50015
  out(stdout, await new Client(flags.url, signal).request(`/api/v1/videos/${encodeURIComponent(args.id)}`, { method: "DELETE" }));
49936
- })))).command("cloud", (cloud) => cloud.add(defineCommand("list", { description: "List projects with synced animations in the selected workspace, with plan and storage (Individual Pro and Team)" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } }).action(async ({ flags, stdout, signal }) => {
50016
+ })))).command("cloud", (cloud) => cloud.add(defineCommand("list", { description: "List projects with synced animations in the selected workspace, with plan and storage (Individual Starter, Individual Pro and Team)" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } }).action(async ({ flags, stdout, signal }) => {
49937
50017
  const client = new Client(flags.url, signal);
49938
50018
  out(stdout, await client.request(`/api/v1/cloud${await workspaceQuery(client)}`));
49939
50019
  }))).add(defineCommand("link", { description: "Sync this video workspace with a project's animations: --project for an existing project, or a new project in the selected workspace" }, (command) => command.flags(
@@ -49951,7 +50031,31 @@ function createCli() {
49951
50031
  }))).add(defineCommand("restore", { description: "Make the folder, or --path parts of it, match an earlier version; push afterwards to save it as the newest" }, (command) => command.args({ name: "version", type: "string", required: true, description: "Version ID or its first characters" }).flags({ name: "dir", type: "string", default: "." }, { name: "path", type: "string", multiple: true, description: "File or folder to restore; repeatable" }, { name: "yes", type: "boolean", description: "Replace unsaved local changes (copies are kept)" }).action(async ({ args, flags, stdout, stderr, signal }) => out(stdout, await restoreCloud({ dir: flags.dir, version: args.version, paths: flags.path, yes: flags.yes, log: stderr, signal }))))).add(defineCommand("clone", { description: "Download the newest version of a project's animations into a new folder" }, (command) => command.args({ name: "id", type: "string", required: true, description: "Project ID" }, { name: "directory", type: "string", description: "New folder; defaults to the project name" }).flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } }).action(async ({ args, flags, stdout, stderr, signal }) => out(stdout, await cloneCloud(new Client(flags.url, signal), { id: args.id, dir: args.directory, log: stderr, signal }))))).add(defineCommand("unlink", { description: "Stop syncing this folder; the project's animations and versions stay" }, (command) => command.flags({ name: "dir", type: "string", default: "." }).action(async ({ flags, stdout }) => out(stdout, await unlinkCloud({ dir: flags.dir }))))).add(defineCommand("delete", { description: "Delete a project's animations with all their versions, drafts and stored files; the project and its assets stay. Requires --yes" }, (command) => command.args({ name: "id", type: "string", required: true, description: "Project ID" }).flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } }, { name: "yes", type: "boolean", required: true }).action(async ({ args, flags, stdout, signal }) => {
49952
50032
  if (!flags.yes) throw new Error("Pass --yes to delete the project's animations and every saved version.");
49953
50033
  out(stdout, await new Client(flags.url, signal).request(`/api/v1/cloud/${encodeURIComponent(args.id)}`, { method: "DELETE" }));
49954
- })))).command("auth", (auth) => auth.add(defineCommand("login", { description: "Approve a browser sign-in and save a seven-day CLI session" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" }, description: "Server origin" }).flags({ name: "browser", type: "boolean", default: true, description: "Open the browser; --no-browser prints the URL only" }).action(async ({ flags, stdout, stderr, signal }) => {
50034
+ })))).add(defineCommand("curl", { description: "Call an AnimaGuru API path as the signed-in user with curl's flags; the server and the session's token are added for you. Prints the response body, or saves it with -o" }, (command) => command.args({ name: "path", type: "string", required: true, description: "API path such as /api/v1/me (with any query string), or a full URL on the server" }).flags(
50035
+ { name: "request", short: "X", type: "string", description: "HTTP method; GET by default, POST with --data or --json, PUT with --upload-file" },
50036
+ { name: "header", short: "H", type: "string", multiple: true, description: '"Name: value" request header; repeatable. Authorization is added for you' },
50037
+ { name: "data", short: "d", type: "string", aliases: ["data-binary"], description: "Request body: text, @FILE or @- for standard input. JSON is sent as application/json" },
50038
+ { name: "json", type: "string", description: "JSON body (text, @FILE or @-) with JSON Content-Type and Accept headers" },
50039
+ { name: "upload-file", short: "T", type: "string", description: "Stream a file as the body (PUT unless -X); Content-Type follows its extension unless -H sets one" },
50040
+ { name: "output", short: "o", type: "string", description: "Save the response body to a new file and print a JSON summary; needed for video and images" },
50041
+ { name: "include", short: "i", type: "boolean", noNegate: true, description: "Print the status line and response headers first" },
50042
+ { name: "fail", short: "f", type: "boolean", default: true, description: "Exit non-zero on HTTP 4xx and 5xx after printing the body; --no-fail exits 0 like plain curl" },
50043
+ { name: "silent", short: "s", type: "boolean", noNegate: true, description: "Accepted for curl compatibility; nothing else is printed anyway" },
50044
+ { name: "max-time", short: "m", type: "number", default: 600, description: "Maximum seconds for the whole request" },
50045
+ { name: "server", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" }, description: "Server origin" }
50046
+ ).action(async ({ args, flags, stdout, signal }) => curl({
50047
+ server: flags.server,
50048
+ target: args.path,
50049
+ request: flags.request,
50050
+ header: flags.header,
50051
+ data: flags.data,
50052
+ json: flags.json,
50053
+ uploadFile: flags["upload-file"],
50054
+ output: flags.output,
50055
+ include: flags.include,
50056
+ fail: flags.fail,
50057
+ maxTime: flags["max-time"]
50058
+ }, { stdout, signal })))).command("auth", (auth) => auth.add(defineCommand("login", { description: "Approve a browser sign-in and save a seven-day CLI session" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" }, description: "Server origin" }).flags({ name: "browser", type: "boolean", default: true, description: "Open the browser; --no-browser prints the URL only" }).action(async ({ flags, stdout, stderr, signal }) => {
49955
50059
  const client = new Client(flags.url, signal);
49956
50060
  const device = await client.request("/api/auth/device/code", { method: "POST", authenticated: false, body: { client_id: CLI_CLIENT_ID } });
49957
50061
  stderr(`Open ${device.verification_uri_complete}
@@ -50073,11 +50177,11 @@ Confirm code: ${device.user_code}`);
50073
50177
  { name: "name", type: "string", required: true },
50074
50178
  { name: "prompt", type: "string", description: "Description to generate, or verbatim transcript for speech; video lip-sync and relight take none, and camera-controls, insert-video, recast and 3d-to-video may leave it out" },
50075
50179
  { name: "model", type: "string", choices: assetModelIds, description: "Model for the chosen asset type" },
50076
- { name: "aspect-ratio", type: "string", choices: [.../* @__PURE__ */ new Set([...IMAGE_ASPECT_RATIOS, "auto", ...VIDEO_ASPECT_RATIOS])], description: "Image: 1:1 (default), 3:2, 2:3, 16:9 or 9:16; a masked edit keeps Image 1's shape. Video from text or a style without --reference: 21:9\u20139:16 (default 16:9). Extension, reference-to-video and relight: auto (default, the inputs' shape) or a ratio to crop to" },
50180
+ { name: "aspect-ratio", type: "string", choices: [.../* @__PURE__ */ new Set([...IMAGE_ASPECT_RATIOS, "auto", ...VIDEO_ASPECT_RATIOS])], description: "Image: 1:1 (default), 3:2, 2:3, 16:9 or 9:16; a masked edit keeps Image 1's shape. Video from text or a style without --reference: 21:9\u20139:16 (default 16:9). Extension, reference-to-video, relight and keyframes-to-video: auto (default, the inputs' shape) or a ratio to crop to" },
50077
50181
  { name: "transparent", type: "boolean", description: "Image: transparent background" },
50078
50182
  { name: "references", type: "string", description: "Comma-separated blob SHAs of this project's images or sprite art, named Image 1, Image 2\u2026 in the prompt. Image: up to 4 to follow, such as a character sheet. Video: subjects or looks to keep (reference-to-video and insert-video up to 9, 3d-to-video up to 8), or recast's new people, one photo each (up to 4)" },
50079
50183
  { name: "mask", type: "string", description: "Image: edit only these rectangles of Image 1 (the first --references image), as x,y,width,height fractions of its size from the top left, up to 8 separated by semicolons; the rest is kept" },
50080
- { name: "workflow", type: "string", choices: VIDEO_WORKFLOWS, description: "Video: image-to-video (default), text-to-video or extend-video; H3 Max also reference-to-video, lip-sync, camera-controls, insert-video, recast, relight, 3d-to-video and style, as the model's videoProfiles allow" },
50184
+ { name: "workflow", type: "string", choices: VIDEO_WORKFLOWS, description: "Video: image-to-video (default), text-to-video or extend-video; H3 Max also reference-to-video, lip-sync, camera-controls, insert-video, recast, relight, 3d-to-video and style, FLUX 3 Max keyframes-to-video, as the model's videoProfiles allow" },
50081
50185
  { name: "reference", type: "string", description: "Video: starting image blob SHA from this project's assets or sprites; the speaking face for lip-sync, the image the camera moves around for camera-controls" },
50082
50186
  { name: "end-reference", type: "string", description: "Video: ending image blob SHA; models with endFrameOnly accept it without --reference" },
50083
50187
  { name: "middle-reference", type: "string", description: "Reference-to-video: image blob SHA reached at --middle-seconds; needs --reference and --end-reference, 480p or 768p" },
@@ -50087,6 +50191,7 @@ Confirm code: ${device.user_code}`);
50087
50191
  { name: "audio-references", type: "string", description: "Extension or reference-to-video: up to 3 comma-separated audio asset blob SHAs (2\u201315 s each, 15 s total) guiding the sound; refer to them as Audio 1, Audio 2\u2026 in the prompt" },
50088
50192
  { name: "reference-videos", type: "string", description: "Reference-to-video or insert-video: up to 3 comma-separated video asset blob SHAs (2\u201315 s each, 15 s total) guiding motion, named Video 1, Video 2\u2026 in the prompt" },
50089
50193
  { name: "camera", type: "string", description: 'Camera-controls: 2\u201312 keyframes time,azimuth,elevation,distance (time 0\u20131, angles in degrees, elevation \u221290\u201390) separated by semicolons, e.g. "0,0,0,1;1,90,15,1.2"' },
50194
+ { name: "keyframes", type: "string", description: 'Keyframes-to-video (FLUX 3 Max): 1\u201310 seconds,image-SHA pairs separated by semicolons, e.g. "0,SHA;2.5,SHA;5,SHA"; each image is reached at its second, on a frame of its own of the 24 fps clip, within --seconds' },
50090
50195
  { name: "insert-at", type: "number", description: "Insert-video: seconds into --source where the new scene starts (1.625\u201360)" },
50091
50196
  { name: "resume-at", type: "number", description: "Insert-video: seconds into --source where it resumes after the scene" },
50092
50197
  { name: "color-match", type: "boolean", description: "Insert-video: match the scene's colors to the source (default on; --no-color-match turns it off)" },
@@ -50097,10 +50202,10 @@ Confirm code: ${device.user_code}`);
50097
50202
  { name: "generated-references", type: "number", description: "3d-to-video without --references: how many reference images it may draw, 1\u20138 (default 2)" },
50098
50203
  { name: "style", type: "string", choices: VIDEO_STYLES, description: "Style workflow: the preset look, at 768p for 5\u201315 whole seconds" },
50099
50204
  { name: "damage", type: "string", choices: VHS_DAMAGE_LEVELS, description: "VHS style: tape damage (fal's default medium)" },
50100
- { name: "seconds", type: "number", description: "Video or SFX duration in seconds (default 5); H3 takes fractions (6.5); extensions add this many seconds, insert-video's scene is 5\u201313; lip-sync, recast, relight and 3d-to-video take their input's length; SFX supports 0.5\u201330" },
50205
+ { name: "seconds", type: "number", description: "Video or SFX duration in seconds (default 5); H3 takes fractions (6.5), FLUX 3 Max 5\u201320 whole seconds; extensions add this many seconds, insert-video's scene is 5\u201313; lip-sync, recast, relight and 3d-to-video take their input's length; SFX supports 0.5\u201330" },
50101
50206
  { name: "resolution", type: "string", choices: VIDEO_RESOLUTIONS, description: "Video resolution supported by the selected model and workflow (default its first)" },
50102
50207
  { name: "mode", type: "string", choices: ["cost", "speed", "quality"], description: "Video generation mode supported by the selected model (default its first)" },
50103
- { name: "prompt-expansion", type: "string", choices: PROMPT_EXPANSION_MODES, description: "Video: how much the model may rewrite the prompt (default disabled); extensions treat any other value as on" },
50208
+ { name: "prompt-expansion", type: "string", choices: PROMPT_EXPANSION_MODES, description: "Video: how much the model may rewrite the prompt (default disabled); extensions treat any other value as on. FLUX 3 Max always reasons about the prompt: quality thoroughly, any other value lightly" },
50104
50209
  { name: "seed", type: "number", description: "Video: seed for repeatable results, where the model takes one" },
50105
50210
  { name: "extend-output", type: "string", choices: EXTEND_OUTPUTS, description: "Extension: extended (default, source plus new footage) or continuation (a 1.625 s overlap, then the new footage)" },
50106
50211
  { name: "voice", type: "string", description: "Gemini voice name or ElevenLabs voice ID (defaults: Kore / George)" },
@@ -50152,7 +50257,7 @@ Confirm code: ${device.user_code}`);
50152
50257
  out(stdout, await new Client(flags.url, signal).request(flags.look ? `${avatarsPath(args.id)}/looks/${encodeURIComponent(flags.look)}` : `${avatarsPath(args.id)}/${encodeURIComponent(flags.group)}`, { method: "DELETE" }));
50153
50258
  })))).command("jobs", (jobs) => jobs.add(defineCommand("list", { description: "List the latest generation jobs" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" }, description: "Server origin" }).args(id).action(async ({ args, flags, stdout, signal }) => out(stdout, await new Client(flags.url, signal).request(`${projectPath(args.id)}/jobs`))))).add(defineCommand("submit", { description: "Submit a typed JSON job: character, animate, custom or redraw; spends credits" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" }, description: "Server origin" }).args(id).flags(...modelFlags).flags({ name: "file", type: "string", required: true, description: "JSON job file" }, { name: "yes", type: "boolean", required: true, description: "Authorize spending credits" }).action(async ({ args, flags, stdout, signal }) => {
50154
50259
  if (!flags.yes) throw new Error("Pass --yes to authorize spending credits.");
50155
- const body = spriteJobBody.parse({ ...JSON.parse(await readFile18(flags.file, "utf8")), ...selectedModels(flags) });
50260
+ const body = spriteJobBody.parse({ ...JSON.parse(await readFile19(flags.file, "utf8")), ...selectedModels(flags) });
50156
50261
  out(stdout, await new Client(flags.url, signal).request(`${projectPath(args.id)}/jobs`, { method: "POST", body }));
50157
50262
  }))).add(defineCommand("wait", { description: "Wait for a specific job; fail if generation fails or times out" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" }, description: "Server origin" }).args(id, { name: "job", type: "string", required: true }).flags({ name: "timeout", type: "number", default: 600, description: "Maximum seconds to wait" }).action(async ({ args, flags, stdout, signal }) => out(stdout, { job: await waitForJob(new Client(flags.url, signal), args.id, args.job, flags.timeout, signal) }))))).add(defineCommand("models", { description: "Set the default video model for future project jobs" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" }, description: "Server origin" }).args(id).flags(...modelFlags).action(async ({ args, flags, stdout, signal }) => {
50158
50263
  const body = selectedModels(flags);
@@ -50167,7 +50272,7 @@ Confirm code: ${device.user_code}`);
50167
50272
  const extension = flags.file.split(".").at(-1)?.toLowerCase();
50168
50273
  const type = extension === "png" ? "image/png" : extension === "jpg" || extension === "jpeg" ? "image/jpeg" : extension === "webp" ? "image/webp" : void 0;
50169
50274
  if (!type) throw new Error("Use a PNG, JPEG or WebP image.");
50170
- const bytes = await readFile18(flags.file);
50275
+ const bytes = await readFile19(flags.file);
50171
50276
  if (bytes.length > 10 * 1024 * 1024) throw new Error("Image must be at most 10 MB.");
50172
50277
  const form = new FormData();
50173
50278
  form.set("image", new Blob([bytes], { type }), `reference.${extension}`);
@@ -50183,7 +50288,7 @@ Confirm code: ${device.user_code}`);
50183
50288
  if (!file2) throw new Error(`No file at ${args.path} in this project. List them with animaguru files list.`);
50184
50289
  if (!/^[a-f0-9]{64}$/.test(file2.sha)) throw new Error("Invalid blob hash in manifest.");
50185
50290
  const response = await client.request(`/api/v1/blobs/${encodeURIComponent(manifest.ownerId)}/${file2.sha}`, { raw: true });
50186
- const path = resolve16(flags.output);
50291
+ const path = resolve17(flags.output);
50187
50292
  await mkdir15(dirname14(path), { recursive: true });
50188
50293
  const bytes = new Uint8Array(await response.arrayBuffer());
50189
50294
  await writeFile16(path, bytes, { flag: "wx" });
@@ -50198,7 +50303,7 @@ Confirm code: ${device.user_code}`);
50198
50303
  const response = await client.request(`/api/v1/blobs/${encodeURIComponent(manifest.ownerId)}/${file2.sha}`, { raw: true });
50199
50304
  entries[path2] = new Uint8Array(await response.arrayBuffer());
50200
50305
  }
50201
- const path = resolve16(flags.output);
50306
+ const path = resolve17(flags.output);
50202
50307
  await writeFile16(path, zipSync(entries, { level: 0 }), { flag: "wx" });
50203
50308
  out(stdout, { path, files: manifest.files.length });
50204
50309
  })))).extend(version(package_default.version, { format: "plain" })).extend(help()).extend(completion());