@docyrus/animaguru 0.27.0 → 0.29.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 (33) hide show
  1. package/README.md +28 -2
  2. package/dist/animaguru.js +933 -664
  3. package/package.json +1 -1
  4. package/skills/animaguru/SKILL.md +4 -1
  5. package/skills/animaguru-3d-models/SKILL.md +86 -0
  6. package/skills/animaguru-character-stage-3d/SKILL.md +35 -0
  7. package/skills/animaguru-character-stage-3d/references/recipe.md +26 -0
  8. package/skills/animaguru-exploded-teardown-3d/SKILL.md +38 -0
  9. package/skills/animaguru-exploded-teardown-3d/references/recipe.md +20 -0
  10. package/skills/animaguru-product-turntable-3d/SKILL.md +38 -0
  11. package/skills/animaguru-product-turntable-3d/references/recipe.md +27 -0
  12. package/skills/animaguru-quiz/SKILL.md +1 -1
  13. package/skills/animaguru-toy-diorama-3d/SKILL.md +33 -0
  14. package/skills/animaguru-toy-diorama-3d/references/recipe.md +26 -0
  15. package/skills/animaguru-video/SKILL.md +2 -2
  16. package/templates/video/AGENTS.md +1 -1
  17. package/templates/video/README.md +5 -1
  18. package/templates/video/package.json +3 -1
  19. package/templates/video/scripts/export-mp4.mjs +2 -2
  20. package/templates/video/scripts/install-example.mjs +10 -6
  21. package/templates/video/shared/locales/en/style-studies.json +35 -0
  22. package/templates/video/shared/locales/en/timeline.json +1 -0
  23. package/templates/video/src/kit/index.ts +1 -0
  24. package/templates/video/src/kit/three-stage.ts +262 -0
  25. package/templates/video/src/resource-timeline.ts +1 -0
  26. package/templates/video/src/studies/three/character-stage-3d.ts +172 -0
  27. package/templates/video/src/studies/three/exploded-teardown-3d.ts +214 -0
  28. package/templates/video/src/studies/three/index.ts +16 -0
  29. package/templates/video/src/studies/three/product-turntable-3d.ts +160 -0
  30. package/templates/video/src/studies/three/toy-diorama-3d.ts +245 -0
  31. package/templates/video/src/studies/three-film.ts +42 -0
  32. package/templates/video/src/style.css +2 -1
  33. package/templates/video/src/timeline-model.ts +1 -1
package/dist/animaguru.js CHANGED
@@ -369,19 +369,19 @@ function jsonInstabilityIssues(document2) {
369
369
  return issues;
370
370
  }
371
371
  function createStore(options) {
372
- const { dirPath, name: name2, fields, pruneUnknown, access: access3 } = options;
372
+ const { dirPath, name: name2, fields, pruneUnknown, access: access4 } = options;
373
373
  const validators = /* @__PURE__ */ new Map();
374
374
  for (const [key, def] of Object.entries(fields)) if (def.schema !== void 0) {
375
375
  for (const option of ["default", "validate"]) if (def[option] !== void 0) throw new CrustStoreError("DEFINITION", `field "${key}" mixes "schema" with "${option}" \u2014 the schema exclusively owns validation, transformation, defaults, and optionality`);
376
376
  validators.set(key, makeSchemaValidator(def.schema));
377
377
  } else if (def.validate !== void 0) validators.set(key, def.validate);
378
378
  const filePath = resolveStorePath(dirPath, name2);
379
- const writeOptions = access3 === "private" ? {
379
+ const writeOptions = access4 === "private" ? {
380
380
  fileMode: 384,
381
381
  directoryMode: 448
382
- } : access3 === void 0 || access3 === "default" ? {} : {
383
- fileMode: access3.file,
384
- directoryMode: access3.directory
382
+ } : access4 === void 0 || access4 === "default" ? {} : {
383
+ fileMode: access4.file,
384
+ directoryMode: access4.directory
385
385
  };
386
386
  function normalizeStateTypes(state) {
387
387
  const normalized = { ...state };
@@ -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(resolve18, reject) {
2316
- deferredResolve = resolve18;
2315
+ deferred = new this.Promise(function(resolve19, reject) {
2316
+ deferredResolve = resolve19;
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(resolve18, reject) {
2464
+ return new this.Promise(function(resolve19, 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
- resolve18(ret);
2470
+ resolve19(ret);
2471
2471
  }
2472
2472
  });
2473
2473
  } else {
2474
- resolve18(fnx());
2474
+ resolve19(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((resolve18, reject) => {
6865
+ return new P((resolve19, 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
- resolve18(result);
6873
+ resolve19(result);
6874
6874
  }
6875
6875
  } else {
6876
- resolve18(result);
6876
+ resolve19(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
- resolve18(result);
6884
+ resolve19(result);
6885
6885
  }
6886
6886
  });
6887
6887
  } else {
6888
- args.push(resolve18);
6888
+ args.push(resolve19);
6889
6889
  }
6890
6890
  fn.apply(this, args);
6891
6891
  });
@@ -8083,17 +8083,17 @@ If you're a developer and you believe this is a bug in isomorphic-git, please fi
8083
8083
  const length = Math.ceil((62 + bpath.length + 1) / 8) * 8;
8084
8084
  const written = Buffer.alloc(length);
8085
8085
  const writer = new BufferCursor(written);
8086
- const stat11 = normalizeStats(entry);
8087
- writer.writeUInt32BE(stat11.ctimeSeconds);
8088
- writer.writeUInt32BE(stat11.ctimeNanoseconds);
8089
- writer.writeUInt32BE(stat11.mtimeSeconds);
8090
- writer.writeUInt32BE(stat11.mtimeNanoseconds);
8091
- writer.writeUInt32BE(stat11.dev);
8092
- writer.writeUInt32BE(stat11.ino);
8093
- writer.writeUInt32BE(stat11.mode);
8094
- writer.writeUInt32BE(stat11.uid);
8095
- writer.writeUInt32BE(stat11.gid);
8096
- writer.writeUInt32BE(stat11.size);
8086
+ const stat12 = normalizeStats(entry);
8087
+ writer.writeUInt32BE(stat12.ctimeSeconds);
8088
+ writer.writeUInt32BE(stat12.ctimeNanoseconds);
8089
+ writer.writeUInt32BE(stat12.mtimeSeconds);
8090
+ writer.writeUInt32BE(stat12.mtimeNanoseconds);
8091
+ writer.writeUInt32BE(stat12.dev);
8092
+ writer.writeUInt32BE(stat12.ino);
8093
+ writer.writeUInt32BE(stat12.mode);
8094
+ writer.writeUInt32BE(stat12.uid);
8095
+ writer.writeUInt32BE(stat12.gid);
8096
+ writer.writeUInt32BE(stat12.size);
8097
8097
  writer.write(entry.oid, 20, "hex");
8098
8098
  writer.writeUInt16BE(renderCacheEntryFlags(entry));
8099
8099
  writer.write(entry.path, bpath.length, "utf8");
@@ -8145,13 +8145,13 @@ If you're a developer and you believe this is a bug in isomorphic-git, please fi
8145
8145
  };
8146
8146
  }
8147
8147
  async function updateCachedIndexFile(fs2, filepath, cache) {
8148
- const [stat11, rawIndexFile] = await Promise.all([
8148
+ const [stat12, rawIndexFile] = await Promise.all([
8149
8149
  fs2.lstat(filepath),
8150
8150
  fs2.read(filepath)
8151
8151
  ]);
8152
8152
  const index3 = await GitIndex.from(rawIndexFile);
8153
8153
  cache.map.set(filepath, index3);
8154
- cache.stats.set(filepath, stat11);
8154
+ cache.stats.set(filepath, stat12);
8155
8155
  }
8156
8156
  async function isIndexStale(fs2, filepath, cache) {
8157
8157
  const savedStats = cache.stats.get(filepath);
@@ -8208,7 +8208,7 @@ If you're a developer and you believe this is a bug in isomorphic-git, please fi
8208
8208
  }
8209
8209
  return path;
8210
8210
  }
8211
- function dirname15(path) {
8211
+ function dirname16(path) {
8212
8212
  const last = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
8213
8213
  if (last === -1) return ".";
8214
8214
  if (last === 0) return "/";
@@ -8216,7 +8216,7 @@ If you're a developer and you believe this is a bug in isomorphic-git, please fi
8216
8216
  }
8217
8217
  function flatFileListToDirectoryStructure(files) {
8218
8218
  const inodes = /* @__PURE__ */ new Map();
8219
- const mkdir16 = function(name2) {
8219
+ const mkdir17 = function(name2) {
8220
8220
  if (!inodes.has(name2)) {
8221
8221
  const dir = {
8222
8222
  type: "tree",
@@ -8226,7 +8226,7 @@ If you're a developer and you believe this is a bug in isomorphic-git, please fi
8226
8226
  children: []
8227
8227
  };
8228
8228
  inodes.set(name2, dir);
8229
- dir.parent = mkdir16(dirname15(name2));
8229
+ dir.parent = mkdir17(dirname16(name2));
8230
8230
  if (dir.parent && dir.parent !== dir) dir.parent.children.push(dir);
8231
8231
  }
8232
8232
  return inodes.get(name2);
@@ -8239,7 +8239,7 @@ If you're a developer and you believe this is a bug in isomorphic-git, please fi
8239
8239
  basename: basename8(name2),
8240
8240
  metadata,
8241
8241
  // This recursively generates any missing parent folders.
8242
- parent: mkdir16(dirname15(name2)),
8242
+ parent: mkdir17(dirname16(name2)),
8243
8243
  children: []
8244
8244
  };
8245
8245
  if (file2.parent) file2.parent.children.push(file2);
@@ -8247,7 +8247,7 @@ If you're a developer and you believe this is a bug in isomorphic-git, please fi
8247
8247
  }
8248
8248
  return inodes.get(name2);
8249
8249
  };
8250
- mkdir16(".");
8250
+ mkdir17(".");
8251
8251
  for (const file2 of files) {
8252
8252
  mkfile(file2.path, file2);
8253
8253
  }
@@ -8650,7 +8650,7 @@ If you're a developer and you believe this is a bug in isomorphic-git, please fi
8650
8650
  }
8651
8651
  return isAbsolute2 ? `/${normalized}` : normalized;
8652
8652
  }
8653
- function join19(...args) {
8653
+ function join20(...args) {
8654
8654
  if (args.length === 0) return ".";
8655
8655
  let joined;
8656
8656
  for (let i2 = 0; i2 < args.length; ++i2) {
@@ -9052,7 +9052,7 @@ If you're a developer and you believe this is a bug in isomorphic-git, please fi
9052
9052
  for (const [key, value] of actualRefsToWrite) {
9053
9053
  await acquireLock(
9054
9054
  key,
9055
- async () => fs2.write(join19(gitdir, key), `${value.trim()}
9055
+ async () => fs2.write(join20(gitdir, key), `${value.trim()}
9056
9056
  `, "utf8")
9057
9057
  );
9058
9058
  }
@@ -9076,7 +9076,7 @@ If you're a developer and you believe this is a bug in isomorphic-git, please fi
9076
9076
  }
9077
9077
  await acquireLock(
9078
9078
  ref,
9079
- async () => fs2.write(join19(gitdir, ref), `${value.trim()}
9079
+ async () => fs2.write(join20(gitdir, ref), `${value.trim()}
9080
9080
  `, "utf8")
9081
9081
  );
9082
9082
  }
@@ -9094,7 +9094,7 @@ If you're a developer and you believe this is a bug in isomorphic-git, please fi
9094
9094
  assertWritableRef(ref);
9095
9095
  await acquireLock(
9096
9096
  ref,
9097
- async () => fs2.write(join19(gitdir, ref), `ref: ${value.trim()}
9097
+ async () => fs2.write(join20(gitdir, ref), `ref: ${value.trim()}
9098
9098
  `, "utf8")
9099
9099
  );
9100
9100
  }
@@ -9121,7 +9121,7 @@ If you're a developer and you believe this is a bug in isomorphic-git, please fi
9121
9121
  */
9122
9122
  static async deleteRefs({ fs: fs2, gitdir, refs }) {
9123
9123
  refs.forEach(assertWritableRef);
9124
- await Promise.all(refs.map((ref) => fs2.rm(join19(gitdir, ref))));
9124
+ await Promise.all(refs.map((ref) => fs2.rm(join20(gitdir, ref))));
9125
9125
  let text2 = await acquireLock(
9126
9126
  "packed-refs",
9127
9127
  async () => fs2.read(`${gitdir}/packed-refs`, { encoding: "utf8" })
@@ -10208,7 +10208,7 @@ If you're a developer and you believe this is a bug in isomorphic-git, please fi
10208
10208
  format = "content",
10209
10209
  getExternalRefDelta
10210
10210
  }) {
10211
- let list3 = await fs2.readdir(join19(gitdir, "objects/pack"));
10211
+ let list3 = await fs2.readdir(join20(gitdir, "objects/pack"));
10212
10212
  list3 = list3.filter((x2) => x2.endsWith(".idx"));
10213
10213
  for (const filename of list3) {
10214
10214
  const indexFile = `${gitdir}/objects/pack/${filename}`;
@@ -11065,9 +11065,9 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
11065
11065
  }
11066
11066
  const tree = GitTree.from(object2);
11067
11067
  for (const entry2 of tree) {
11068
- map2.set(join19(filepath, entry2.path), entry2);
11068
+ map2.set(join20(filepath, entry2.path), entry2);
11069
11069
  }
11070
- return tree.entries().map((entry2) => join19(filepath, entry2.path));
11070
+ return tree.entries().map((entry2) => join20(filepath, entry2.path));
11071
11071
  }
11072
11072
  async type(entry) {
11073
11073
  if (entry._type === false) {
@@ -11160,9 +11160,9 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
11160
11160
  if (await entry.type() !== "tree") return null;
11161
11161
  const filepath = entry._fullpath;
11162
11162
  const { fs: fs2, dir } = this;
11163
- const names = await fs2.readdir(join19(dir, filepath));
11163
+ const names = await fs2.readdir(join20(dir, filepath));
11164
11164
  if (names === null) return null;
11165
- return names.map((name2) => join19(filepath, name2));
11165
+ return names.map((name2) => join20(filepath, name2));
11166
11166
  }
11167
11167
  async type(entry) {
11168
11168
  if (entry._type === false) {
@@ -11179,23 +11179,23 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
11179
11179
  async stat(entry) {
11180
11180
  if (entry._stat === false) {
11181
11181
  const { fs: fs2, dir } = this;
11182
- let stat11 = await fs2.lstat(`${dir}/${entry._fullpath}`);
11183
- if (!stat11) {
11182
+ let stat12 = await fs2.lstat(`${dir}/${entry._fullpath}`);
11183
+ if (!stat12) {
11184
11184
  throw new Error(
11185
11185
  `ENOENT: no such file or directory, lstat '${entry._fullpath}'`
11186
11186
  );
11187
11187
  }
11188
- let type = stat11.isDirectory() ? "tree" : "blob";
11189
- if (type === "blob" && !stat11.isFile() && !stat11.isSymbolicLink()) {
11188
+ let type = stat12.isDirectory() ? "tree" : "blob";
11189
+ if (type === "blob" && !stat12.isFile() && !stat12.isSymbolicLink()) {
11190
11190
  type = "special";
11191
11191
  }
11192
11192
  entry._type = type;
11193
- stat11 = normalizeStats(stat11);
11194
- entry._mode = stat11.mode;
11195
- if (stat11.size === -1 && entry._actualSize) {
11196
- stat11.size = entry._actualSize;
11193
+ stat12 = normalizeStats(stat12);
11194
+ entry._mode = stat12.mode;
11195
+ if (stat12.size === -1 && entry._actualSize) {
11196
+ stat12.size = entry._actualSize;
11197
11197
  }
11198
- entry._stat = stat11;
11198
+ entry._stat = stat12;
11199
11199
  }
11200
11200
  return entry._stat;
11201
11201
  }
@@ -11383,24 +11383,24 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
11383
11383
  };
11384
11384
  return walk2(root);
11385
11385
  }
11386
- async function mkdirp(mkdir16, filepath, retries = 10) {
11386
+ async function mkdirp(mkdir17, filepath, retries = 10) {
11387
11387
  try {
11388
- await mkdir16(filepath);
11388
+ await mkdir17(filepath);
11389
11389
  } catch (err2) {
11390
11390
  if (err2 === null) return;
11391
11391
  if (err2.code === "EEXIST") return;
11392
11392
  if (err2.code !== "ENOENT") throw err2;
11393
- const parent = dirname15(filepath);
11393
+ const parent = dirname16(filepath);
11394
11394
  if (parent === "." || parent === "/" || parent === filepath) throw err2;
11395
- await mkdirp(mkdir16, parent, retries);
11395
+ await mkdirp(mkdir17, parent, retries);
11396
11396
  for (let attempt = 0; ; attempt++) {
11397
11397
  try {
11398
- await mkdir16(filepath);
11398
+ await mkdir17(filepath);
11399
11399
  return;
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((resolve18) => setTimeout(resolve18, 0));
11403
+ await new Promise((resolve19) => setTimeout(resolve19, 0));
11404
11404
  continue;
11405
11405
  }
11406
11406
  throw err22;
@@ -11415,10 +11415,10 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
11415
11415
  } else if (entries.length) {
11416
11416
  await Promise.all(
11417
11417
  entries.map((entry) => {
11418
- const subpath = join19(filepath, entry);
11419
- return fs2.lstat(subpath).then((stat11) => {
11420
- if (!stat11) return;
11421
- return stat11.isDirectory() ? rmRecursive(fs2, subpath) : fs2.rm(subpath);
11418
+ const subpath = join20(filepath, entry);
11419
+ return fs2.lstat(subpath).then((stat12) => {
11420
+ if (!stat12) return;
11421
+ return stat12.isDirectory() ? rmRecursive(fs2, subpath) : fs2.rm(subpath);
11422
11422
  });
11423
11423
  })
11424
11424
  ).then(() => fs2.rmdir(filepath));
@@ -11554,7 +11554,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
11554
11554
  try {
11555
11555
  await this._writeFile(filepath, contents, options);
11556
11556
  } catch (err2) {
11557
- await this.mkdir(dirname15(filepath));
11557
+ await this.mkdir(dirname16(filepath));
11558
11558
  await this._writeFile(filepath, contents, options);
11559
11559
  }
11560
11560
  }
@@ -11700,7 +11700,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
11700
11700
  if (isAbsolute(submoduleGitdir)) {
11701
11701
  return submoduleGitdir;
11702
11702
  }
11703
- const gitdir = join19(dirname15(dotgit), submoduleGitdir);
11703
+ const gitdir = join20(dirname16(dotgit), submoduleGitdir);
11704
11704
  return gitdir;
11705
11705
  });
11706
11706
  } else {
@@ -11722,7 +11722,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
11722
11722
  async function abortMerge({
11723
11723
  fs: _fs,
11724
11724
  dir,
11725
- gitdir = join19(dir, ".git"),
11725
+ gitdir = join20(dir, ".git"),
11726
11726
  commit: commit2 = "HEAD",
11727
11727
  cache = {}
11728
11728
  }) {
@@ -11803,17 +11803,17 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
11803
11803
  * @param {string} args.filepath - The path of the file to check.
11804
11804
  * @returns {Promise<boolean>} - `true` if the file is ignored, `false` otherwise.
11805
11805
  */
11806
- static async isIgnored({ fs: fs2, dir, gitdir = join19(dir, ".git"), filepath }) {
11806
+ static async isIgnored({ fs: fs2, dir, gitdir = join20(dir, ".git"), filepath }) {
11807
11807
  if (basename8(filepath) === ".git") return true;
11808
11808
  if (filepath === ".") return false;
11809
11809
  let excludes = "";
11810
- const excludesFile = join19(gitdir, "info", "exclude");
11810
+ const excludesFile = join20(gitdir, "info", "exclude");
11811
11811
  if (await fs2.exists(excludesFile)) {
11812
11812
  excludes = await fs2.read(excludesFile, "utf8");
11813
11813
  }
11814
11814
  const pairs = [
11815
11815
  {
11816
- gitignore: join19(dir, ".gitignore"),
11816
+ gitignore: join20(dir, ".gitignore"),
11817
11817
  filepath
11818
11818
  }
11819
11819
  ];
@@ -11822,7 +11822,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
11822
11822
  const folder = pieces.slice(0, i2).join("/");
11823
11823
  const file2 = pieces.slice(i2).join("/");
11824
11824
  pairs.push({
11825
- gitignore: join19(dir, folder, ".gitignore"),
11825
+ gitignore: join20(dir, folder, ".gitignore"),
11826
11826
  filepath: file2
11827
11827
  });
11828
11828
  }
@@ -11836,7 +11836,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
11836
11836
  }
11837
11837
  const ign = ignore2().add(excludes);
11838
11838
  ign.add(file2);
11839
- const parentdir = dirname15(p.filepath);
11839
+ const parentdir = dirname16(p.filepath);
11840
11840
  if (parentdir !== "." && ign.ignores(parentdir)) return true;
11841
11841
  if (ignoredStatus) {
11842
11842
  ignoredStatus = !ign.test(p.filepath).unignored;
@@ -11909,7 +11909,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
11909
11909
  async function add({
11910
11910
  fs: _fs,
11911
11911
  dir,
11912
- gitdir = join19(dir, ".git"),
11912
+ gitdir = join20(dir, ".git"),
11913
11913
  filepath,
11914
11914
  cache = {},
11915
11915
  force = false,
@@ -11973,17 +11973,17 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
11973
11973
  });
11974
11974
  if (ignored) return;
11975
11975
  }
11976
- const stats = await fs2.lstat(join19(dir, currentFilepath));
11976
+ const stats = await fs2.lstat(join20(dir, currentFilepath));
11977
11977
  if (!stats) throw new NotFoundError(currentFilepath);
11978
11978
  if (stats.isDirectory()) {
11979
- const children = await fs2.readdir(join19(dir, currentFilepath));
11979
+ const children = await fs2.readdir(join20(dir, currentFilepath));
11980
11980
  if (parallel) {
11981
11981
  const promises3 = children.map(
11982
11982
  (child) => addToIndex({
11983
11983
  dir,
11984
11984
  gitdir,
11985
11985
  fs: fs2,
11986
- filepath: [join19(currentFilepath, child)],
11986
+ filepath: [join20(currentFilepath, child)],
11987
11987
  index: index3,
11988
11988
  force,
11989
11989
  parallel,
@@ -11997,7 +11997,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
11997
11997
  dir,
11998
11998
  gitdir,
11999
11999
  fs: fs2,
12000
- filepath: [join19(currentFilepath, child)],
12000
+ filepath: [join20(currentFilepath, child)],
12001
12001
  index: index3,
12002
12002
  force,
12003
12003
  parallel,
@@ -12006,7 +12006,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
12006
12006
  }
12007
12007
  }
12008
12008
  } else {
12009
- const object2 = stats.isSymbolicLink() ? await fs2.readlink(join19(dir, currentFilepath)).then(posixifyPathBuffer) : await fs2.read(join19(dir, currentFilepath), { autocrlf });
12009
+ const object2 = stats.isSymbolicLink() ? await fs2.readlink(join20(dir, currentFilepath)).then(posixifyPathBuffer) : await fs2.read(join20(dir, currentFilepath), { autocrlf });
12010
12010
  if (object2 === null) throw new NotFoundError(currentFilepath);
12011
12011
  const oid = await _writeObject({ fs: fs2, gitdir, type: "blob", object: object2 });
12012
12012
  index3.insert({ filepath: currentFilepath, stats, oid });
@@ -12440,7 +12440,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
12440
12440
  fs: _fs,
12441
12441
  onSign,
12442
12442
  dir,
12443
- gitdir = join19(dir, ".git"),
12443
+ gitdir = join20(dir, ".git"),
12444
12444
  ref = "refs/notes/commits",
12445
12445
  oid,
12446
12446
  note,
@@ -12510,7 +12510,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
12510
12510
  async function addRemote({
12511
12511
  fs: fs2,
12512
12512
  dir,
12513
- gitdir = join19(dir, ".git"),
12513
+ gitdir = join20(dir, ".git"),
12514
12514
  remote,
12515
12515
  url: url3,
12516
12516
  force = false
@@ -12580,7 +12580,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
12580
12580
  fs: _fs,
12581
12581
  onSign,
12582
12582
  dir,
12583
- gitdir = join19(dir, ".git"),
12583
+ gitdir = join20(dir, ".git"),
12584
12584
  ref,
12585
12585
  tagger: _tagger,
12586
12586
  message = ref,
@@ -12661,7 +12661,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
12661
12661
  async function branch({
12662
12662
  fs: fs2,
12663
12663
  dir,
12664
- gitdir = join19(dir, ".git"),
12664
+ gitdir = join20(dir, ".git"),
12665
12665
  ref,
12666
12666
  object: object2,
12667
12667
  checkout: checkout2 = false,
@@ -13325,7 +13325,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
13325
13325
  onProgress,
13326
13326
  onPostCheckout,
13327
13327
  dir,
13328
- gitdir = join19(dir, ".git"),
13328
+ gitdir = join20(dir, ".git"),
13329
13329
  remote = "origin",
13330
13330
  ref: _ref,
13331
13331
  filepaths,
@@ -13406,7 +13406,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
13406
13406
  fs: fs2,
13407
13407
  cache,
13408
13408
  dir,
13409
- gitdir = join19(dir, ".git"),
13409
+ gitdir = join20(dir, ".git"),
13410
13410
  index: index3,
13411
13411
  ourOid,
13412
13412
  baseOid,
@@ -13676,7 +13676,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
13676
13676
  workdir: WORKDIR
13677
13677
  };
13678
13678
  async function checkAndWriteBlob(fs2, gitdir, dir, filepath, oid = null) {
13679
- const currentFilepath = join19(dir, filepath);
13679
+ const currentFilepath = join20(dir, filepath);
13680
13680
  const stats = await fs2.lstat(currentFilepath);
13681
13681
  if (!stats) throw new NotFoundError(currentFilepath);
13682
13682
  if (stats.isDirectory())
@@ -13845,7 +13845,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
13845
13845
  stageUpdated.push({
13846
13846
  filepath,
13847
13847
  oid,
13848
- stats: await fs2.lstat(join19(dir, filepath))
13848
+ stats: await fs2.lstat(join20(dir, filepath))
13849
13849
  });
13850
13850
  return {
13851
13851
  method: "write",
@@ -13858,7 +13858,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
13858
13858
  });
13859
13859
  await acquireLock(gitdir, async () => {
13860
13860
  for (const op of ops) {
13861
- const currentFilepath = join19(dir, op.filepath);
13861
+ const currentFilepath = join20(dir, op.filepath);
13862
13862
  switch (op.method) {
13863
13863
  case "rmdir":
13864
13864
  await fs2.rmdir(currentFilepath);
@@ -13992,7 +13992,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
13992
13992
  async function cherryPick({
13993
13993
  fs: _fs,
13994
13994
  dir,
13995
- gitdir = join19(dir, ".git"),
13995
+ gitdir = join20(dir, ".git"),
13996
13996
  oid,
13997
13997
  cache = {},
13998
13998
  committer,
@@ -14497,7 +14497,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
14497
14497
  * @returns {Promise<Set<string>>} - A set of shallow object IDs.
14498
14498
  */
14499
14499
  static async read({ fs: fs2, gitdir }) {
14500
- const filepath = join19(gitdir, "shallow");
14500
+ const filepath = join20(gitdir, "shallow");
14501
14501
  const oids = /* @__PURE__ */ new Set();
14502
14502
  await acquireLock(filepath, async function() {
14503
14503
  const text2 = await fs2.read(filepath, { encoding: "utf8" });
@@ -14518,7 +14518,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
14518
14518
  * @returns {Promise<void>}
14519
14519
  */
14520
14520
  static async write({ fs: fs2, gitdir, oids }) {
14521
- const filepath = join19(gitdir, "shallow");
14521
+ const filepath = join20(gitdir, "shallow");
14522
14522
  if (oids.size > 0) {
14523
14523
  const text2 = [...oids].join("\n") + "\n";
14524
14524
  await acquireLock(filepath, async function() {
@@ -14544,7 +14544,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
14544
14544
  oid,
14545
14545
  getExternalRefDelta
14546
14546
  }) {
14547
- let list3 = await fs2.readdir(join19(gitdir, "objects/pack"));
14547
+ let list3 = await fs2.readdir(join20(gitdir, "objects/pack"));
14548
14548
  list3 = list3.filter((x2) => x2.endsWith(".idx"));
14549
14549
  for (const filename of list3) {
14550
14550
  const indexFile = `${gitdir}/objects/pack/${filename}`;
@@ -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 resolve18 = this._waiting;
14619
+ const resolve19 = this._waiting;
14620
14620
  this._waiting = null;
14621
- resolve18({ value: chunk });
14621
+ resolve19({ 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 resolve18 = this._waiting;
14629
+ const resolve19 = this._waiting;
14630
14630
  this._waiting = null;
14631
- resolve18({ done: true });
14631
+ resolve19({ 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((resolve18) => {
14651
- this._waiting = resolve18;
14650
+ return new Promise((resolve19) => {
14651
+ this._waiting = resolve19;
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((resolve18, reject) => {
14806
+ return new Promise((resolve19, 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) : resolve18({ shallows, unshallows, acks, nak, packfile, progress });
14833
+ stream.error ? reject(stream.error) : resolve19({ shallows, unshallows, acks, nak, packfile, progress });
14834
14834
  }
14835
14835
  }).finally(() => {
14836
14836
  if (!done) {
14837
- stream.error ? reject(stream.error) : resolve18({ shallows, unshallows, acks, nak, packfile, progress });
14837
+ stream.error ? reject(stream.error) : resolve19({ shallows, unshallows, acks, nak, packfile, progress });
14838
14838
  }
14839
14839
  });
14840
14840
  });
@@ -15135,7 +15135,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
15135
15135
  }
15136
15136
  if (packfileSha !== "" && !emptyPackfile(packfile)) {
15137
15137
  res.packfile = `objects/pack/pack-${packfileSha}.pack`;
15138
- const fullpath = join19(gitdir, res.packfile);
15138
+ const fullpath = join20(gitdir, res.packfile);
15139
15139
  await fs2.write(fullpath, packfile);
15140
15140
  const getExternalRefDelta = (oid2) => _readObject({ fs: fs2, cache, gitdir, oid: oid2 });
15141
15141
  const idx = await GitPackIndex.fromPack({
@@ -15151,7 +15151,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
15151
15151
  fs: fs2,
15152
15152
  bare = false,
15153
15153
  dir,
15154
- gitdir = bare ? dir : join19(dir, ".git"),
15154
+ gitdir = bare ? dir : join20(dir, ".git"),
15155
15155
  defaultBranch = "master"
15156
15156
  }) {
15157
15157
  if (await fs2.exists(gitdir + "/config")) return;
@@ -15265,7 +15265,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
15265
15265
  onAuthFailure,
15266
15266
  onPostCheckout,
15267
15267
  dir,
15268
- gitdir = join19(dir, ".git"),
15268
+ gitdir = join20(dir, ".git"),
15269
15269
  url: url3,
15270
15270
  corsProxy = void 0,
15271
15271
  ref = void 0,
@@ -15328,7 +15328,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
15328
15328
  fs: _fs,
15329
15329
  onSign,
15330
15330
  dir,
15331
- gitdir = join19(dir, ".git"),
15331
+ gitdir = join20(dir, ".git"),
15332
15332
  message,
15333
15333
  author,
15334
15334
  committer,
@@ -15377,7 +15377,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
15377
15377
  async function currentBranch({
15378
15378
  fs: fs2,
15379
15379
  dir,
15380
- gitdir = join19(dir, ".git"),
15380
+ gitdir = join20(dir, ".git"),
15381
15381
  fullname = false,
15382
15382
  test = false
15383
15383
  }) {
@@ -15418,7 +15418,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
15418
15418
  async function deleteBranch({
15419
15419
  fs: fs2,
15420
15420
  dir,
15421
- gitdir = join19(dir, ".git"),
15421
+ gitdir = join20(dir, ".git"),
15422
15422
  ref
15423
15423
  }) {
15424
15424
  try {
@@ -15436,7 +15436,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
15436
15436
  throw err2;
15437
15437
  }
15438
15438
  }
15439
- async function deleteRef({ fs: fs2, dir, gitdir = join19(dir, ".git"), ref }) {
15439
+ async function deleteRef({ fs: fs2, dir, gitdir = join20(dir, ".git"), ref }) {
15440
15440
  try {
15441
15441
  assertParameter("fs", fs2);
15442
15442
  assertParameter("ref", ref);
@@ -15456,7 +15456,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
15456
15456
  async function deleteRemote({
15457
15457
  fs: fs2,
15458
15458
  dir,
15459
- gitdir = join19(dir, ".git"),
15459
+ gitdir = join20(dir, ".git"),
15460
15460
  remote
15461
15461
  }) {
15462
15462
  try {
@@ -15478,7 +15478,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
15478
15478
  ref = ref.startsWith("refs/tags/") ? ref : `refs/tags/${ref}`;
15479
15479
  await GitRefManager.deleteRef({ fs: fs2, gitdir, ref });
15480
15480
  }
15481
- async function deleteTag({ fs: fs2, dir, gitdir = join19(dir, ".git"), ref }) {
15481
+ async function deleteTag({ fs: fs2, dir, gitdir = join20(dir, ".git"), ref }) {
15482
15482
  try {
15483
15483
  assertParameter("fs", fs2);
15484
15484
  assertParameter("ref", ref);
@@ -15507,7 +15507,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
15507
15507
  getExternalRefDelta
15508
15508
  }) {
15509
15509
  const results = [];
15510
- let list3 = await fs2.readdir(join19(gitdir, "objects/pack"));
15510
+ let list3 = await fs2.readdir(join20(gitdir, "objects/pack"));
15511
15511
  list3 = list3.filter((x2) => x2.endsWith(".idx"));
15512
15512
  for (const filename of list3) {
15513
15513
  const indexFile = `${gitdir}/objects/pack/${filename}`;
@@ -15550,7 +15550,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
15550
15550
  async function expandOid({
15551
15551
  fs: fs2,
15552
15552
  dir,
15553
- gitdir = join19(dir, ".git"),
15553
+ gitdir = join20(dir, ".git"),
15554
15554
  oid,
15555
15555
  cache = {}
15556
15556
  }) {
@@ -15571,7 +15571,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
15571
15571
  throw err2;
15572
15572
  }
15573
15573
  }
15574
- async function expandRef({ fs: fs2, dir, gitdir = join19(dir, ".git"), ref }) {
15574
+ async function expandRef({ fs: fs2, dir, gitdir = join20(dir, ".git"), ref }) {
15575
15575
  try {
15576
15576
  assertParameter("fs", fs2);
15577
15577
  assertParameter("gitdir", gitdir);
@@ -15862,7 +15862,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
15862
15862
  onAuthSuccess,
15863
15863
  onAuthFailure,
15864
15864
  dir,
15865
- gitdir = join19(dir, ".git"),
15865
+ gitdir = join20(dir, ".git"),
15866
15866
  ref,
15867
15867
  url: url3,
15868
15868
  remote,
@@ -15920,7 +15920,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
15920
15920
  onAuthSuccess,
15921
15921
  onAuthFailure,
15922
15922
  dir,
15923
- gitdir = join19(dir, ".git"),
15923
+ gitdir = join20(dir, ".git"),
15924
15924
  ref,
15925
15925
  remote,
15926
15926
  remoteRef,
@@ -15976,7 +15976,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
15976
15976
  async function findMergeBase({
15977
15977
  fs: fs2,
15978
15978
  dir,
15979
- gitdir = join19(dir, ".git"),
15979
+ gitdir = join20(dir, ".git"),
15980
15980
  oids,
15981
15981
  cache = {}
15982
15982
  }) {
@@ -15998,10 +15998,10 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
15998
15998
  }
15999
15999
  }
16000
16000
  async function _findRoot({ fs: fs2, filepath }) {
16001
- if (await fs2.exists(join19(filepath, ".git"))) {
16001
+ if (await fs2.exists(join20(filepath, ".git"))) {
16002
16002
  return filepath;
16003
16003
  } else {
16004
- const parent = dirname15(filepath);
16004
+ const parent = dirname16(filepath);
16005
16005
  if (parent === filepath) {
16006
16006
  throw new NotFoundError(`git root for ${filepath}`);
16007
16007
  }
@@ -16018,7 +16018,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
16018
16018
  throw err2;
16019
16019
  }
16020
16020
  }
16021
- async function getConfig({ fs: fs2, dir, gitdir = join19(dir, ".git"), path }) {
16021
+ async function getConfig({ fs: fs2, dir, gitdir = join20(dir, ".git"), path }) {
16022
16022
  try {
16023
16023
  assertParameter("fs", fs2);
16024
16024
  assertParameter("gitdir", gitdir);
@@ -16042,7 +16042,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
16042
16042
  async function getConfigAll({
16043
16043
  fs: fs2,
16044
16044
  dir,
16045
- gitdir = join19(dir, ".git"),
16045
+ gitdir = join20(dir, ".git"),
16046
16046
  path
16047
16047
  }) {
16048
16048
  try {
@@ -16231,7 +16231,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
16231
16231
  filepath
16232
16232
  }) {
16233
16233
  try {
16234
- filepath = join19(dir, filepath);
16234
+ filepath = join20(dir, filepath);
16235
16235
  const pack = await fs2.read(filepath);
16236
16236
  const getExternalRefDelta = (oid) => _readObject({ fs: fs2, cache, gitdir, oid });
16237
16237
  const idx = await GitPackIndex.fromPack({
@@ -16252,7 +16252,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
16252
16252
  fs: fs2,
16253
16253
  onProgress,
16254
16254
  dir,
16255
- gitdir = join19(dir, ".git"),
16255
+ gitdir = join20(dir, ".git"),
16256
16256
  filepath,
16257
16257
  cache = {}
16258
16258
  }) {
@@ -16280,7 +16280,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
16280
16280
  fs: fs2,
16281
16281
  bare = false,
16282
16282
  dir,
16283
- gitdir = bare ? dir : join19(dir, ".git"),
16283
+ gitdir = bare ? dir : join20(dir, ".git"),
16284
16284
  defaultBranch = "master"
16285
16285
  }) {
16286
16286
  try {
@@ -16354,7 +16354,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
16354
16354
  async function isDescendent({
16355
16355
  fs: fs2,
16356
16356
  dir,
16357
- gitdir = join19(dir, ".git"),
16357
+ gitdir = join20(dir, ".git"),
16358
16358
  oid,
16359
16359
  ancestor,
16360
16360
  depth = -1,
@@ -16383,7 +16383,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
16383
16383
  async function isIgnored({
16384
16384
  fs: fs2,
16385
16385
  dir,
16386
- gitdir = join19(dir, ".git"),
16386
+ gitdir = join20(dir, ".git"),
16387
16387
  filepath
16388
16388
  }) {
16389
16389
  try {
@@ -16407,7 +16407,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
16407
16407
  async function listBranches({
16408
16408
  fs: fs2,
16409
16409
  dir,
16410
- gitdir = join19(dir, ".git"),
16410
+ gitdir = join20(dir, ".git"),
16411
16411
  remote
16412
16412
  }) {
16413
16413
  try {
@@ -16464,17 +16464,17 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
16464
16464
  gitdir,
16465
16465
  oid: entry.oid,
16466
16466
  filenames,
16467
- prefix: join19(prefix, entry.path)
16467
+ prefix: join20(prefix, entry.path)
16468
16468
  });
16469
16469
  } else {
16470
- filenames.push(join19(prefix, entry.path));
16470
+ filenames.push(join20(prefix, entry.path));
16471
16471
  }
16472
16472
  }
16473
16473
  }
16474
16474
  async function listFiles({
16475
16475
  fs: fs2,
16476
16476
  dir,
16477
- gitdir = join19(dir, ".git"),
16477
+ gitdir = join20(dir, ".git"),
16478
16478
  ref,
16479
16479
  cache = {}
16480
16480
  }) {
@@ -16518,7 +16518,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
16518
16518
  async function listNotes({
16519
16519
  fs: fs2,
16520
16520
  dir,
16521
- gitdir = join19(dir, ".git"),
16521
+ gitdir = join20(dir, ".git"),
16522
16522
  ref = "refs/notes/commits",
16523
16523
  cache = {}
16524
16524
  }) {
@@ -16542,7 +16542,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
16542
16542
  async function listRefs({
16543
16543
  fs: fs2,
16544
16544
  dir,
16545
- gitdir = join19(dir, ".git"),
16545
+ gitdir = join20(dir, ".git"),
16546
16546
  filepath
16547
16547
  }) {
16548
16548
  try {
@@ -16567,7 +16567,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
16567
16567
  );
16568
16568
  return remotes;
16569
16569
  }
16570
- async function listRemotes({ fs: fs2, dir, gitdir = join19(dir, ".git") }) {
16570
+ async function listRemotes({ fs: fs2, dir, gitdir = join20(dir, ".git") }) {
16571
16571
  try {
16572
16572
  assertParameter("fs", fs2);
16573
16573
  assertParameter("gitdir", gitdir);
@@ -16666,7 +16666,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
16666
16666
  throw err2;
16667
16667
  }
16668
16668
  }
16669
- async function listTags({ fs: fs2, dir, gitdir = join19(dir, ".git") }) {
16669
+ async function listTags({ fs: fs2, dir, gitdir = join20(dir, ".git") }) {
16670
16670
  try {
16671
16671
  assertParameter("fs", fs2);
16672
16672
  assertParameter("gitdir", gitdir);
@@ -16719,7 +16719,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
16719
16719
  const walks = tree.entries().map(function(entry) {
16720
16720
  let result;
16721
16721
  if (entry.oid === fileId) {
16722
- result = join19(parentPath, entry.path);
16722
+ result = join20(parentPath, entry.path);
16723
16723
  filepaths.push(result);
16724
16724
  } else if (entry.type === "tree") {
16725
16725
  result = _readObject({
@@ -16736,7 +16736,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
16736
16736
  fileId,
16737
16737
  oid,
16738
16738
  filepaths,
16739
- parentPath: join19(parentPath, entry.path)
16739
+ parentPath: join20(parentPath, entry.path)
16740
16740
  });
16741
16741
  });
16742
16742
  }
@@ -16911,7 +16911,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
16911
16911
  async function log({
16912
16912
  fs: fs2,
16913
16913
  dir,
16914
- gitdir = join19(dir, ".git"),
16914
+ gitdir = join20(dir, ".git"),
16915
16915
  filepath,
16916
16916
  ref = "HEAD",
16917
16917
  depth,
@@ -16949,7 +16949,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
16949
16949
  fs: _fs,
16950
16950
  onSign,
16951
16951
  dir,
16952
- gitdir = join19(dir, ".git"),
16952
+ gitdir = join20(dir, ".git"),
16953
16953
  ours,
16954
16954
  theirs,
16955
16955
  fastForward: fastForward2 = true,
@@ -17026,7 +17026,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
17026
17026
  fs: fs2,
17027
17027
  cache,
17028
17028
  dir,
17029
- gitdir = join19(dir, ".git"),
17029
+ gitdir = join20(dir, ".git"),
17030
17030
  oids
17031
17031
  }) {
17032
17032
  const hash2 = new Hash();
@@ -17069,7 +17069,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
17069
17069
  const packfileSha = packfile.slice(-20).toString("hex");
17070
17070
  const filename = `pack-${packfileSha}.pack`;
17071
17071
  if (write) {
17072
- await fs2.write(join19(gitdir, `objects/pack/${filename}`), packfile);
17072
+ await fs2.write(join20(gitdir, `objects/pack/${filename}`), packfile);
17073
17073
  return { filename };
17074
17074
  }
17075
17075
  return {
@@ -17080,7 +17080,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
17080
17080
  async function packObjects({
17081
17081
  fs: fs2,
17082
17082
  dir,
17083
- gitdir = join19(dir, ".git"),
17083
+ gitdir = join20(dir, ".git"),
17084
17084
  oids,
17085
17085
  write = false,
17086
17086
  cache = {}
@@ -17112,7 +17112,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
17112
17112
  onAuthSuccess,
17113
17113
  onAuthFailure,
17114
17114
  dir,
17115
- gitdir = join19(dir, ".git"),
17115
+ gitdir = join20(dir, ".git"),
17116
17116
  ref,
17117
17117
  url: url3,
17118
17118
  remote,
@@ -17182,7 +17182,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
17182
17182
  fs: fs2,
17183
17183
  cache,
17184
17184
  dir,
17185
- gitdir = join19(dir, ".git"),
17185
+ gitdir = join20(dir, ".git"),
17186
17186
  start,
17187
17187
  finish
17188
17188
  }) {
@@ -17230,7 +17230,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
17230
17230
  fs: fs2,
17231
17231
  cache,
17232
17232
  dir,
17233
- gitdir = join19(dir, ".git"),
17233
+ gitdir = join20(dir, ".git"),
17234
17234
  oids
17235
17235
  }) {
17236
17236
  const visited = /* @__PURE__ */ new Set();
@@ -17533,7 +17533,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
17533
17533
  onAuthFailure,
17534
17534
  onPrePush,
17535
17535
  dir,
17536
- gitdir = join19(dir, ".git"),
17536
+ gitdir = join20(dir, ".git"),
17537
17537
  ref,
17538
17538
  remoteRef,
17539
17539
  remote = "origin",
@@ -17609,7 +17609,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
17609
17609
  async function readBlob({
17610
17610
  fs: fs2,
17611
17611
  dir,
17612
- gitdir = join19(dir, ".git"),
17612
+ gitdir = join20(dir, ".git"),
17613
17613
  oid,
17614
17614
  filepath,
17615
17615
  cache = {}
@@ -17635,7 +17635,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
17635
17635
  async function readCommit({
17636
17636
  fs: fs2,
17637
17637
  dir,
17638
- gitdir = join19(dir, ".git"),
17638
+ gitdir = join20(dir, ".git"),
17639
17639
  oid,
17640
17640
  cache = {}
17641
17641
  }) {
@@ -17676,7 +17676,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
17676
17676
  async function readNote({
17677
17677
  fs: fs2,
17678
17678
  dir,
17679
- gitdir = join19(dir, ".git"),
17679
+ gitdir = join20(dir, ".git"),
17680
17680
  ref = "refs/notes/commits",
17681
17681
  oid,
17682
17682
  cache = {}
@@ -17703,7 +17703,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
17703
17703
  async function readObject({
17704
17704
  fs: _fs,
17705
17705
  dir,
17706
- gitdir = join19(dir, ".git"),
17706
+ gitdir = join20(dir, ".git"),
17707
17707
  oid,
17708
17708
  format = "parsed",
17709
17709
  filepath = void 0,
@@ -17792,7 +17792,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
17792
17792
  async function readTag({
17793
17793
  fs: fs2,
17794
17794
  dir,
17795
- gitdir = join19(dir, ".git"),
17795
+ gitdir = join20(dir, ".git"),
17796
17796
  oid,
17797
17797
  cache = {}
17798
17798
  }) {
@@ -17816,7 +17816,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
17816
17816
  async function readTree({
17817
17817
  fs: fs2,
17818
17818
  dir,
17819
- gitdir = join19(dir, ".git"),
17819
+ gitdir = join20(dir, ".git"),
17820
17820
  oid,
17821
17821
  filepath = void 0,
17822
17822
  cache = {}
@@ -17842,7 +17842,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
17842
17842
  async function remove2({
17843
17843
  fs: _fs,
17844
17844
  dir,
17845
- gitdir = join19(dir, ".git"),
17845
+ gitdir = join20(dir, ".git"),
17846
17846
  filepath,
17847
17847
  cache = {}
17848
17848
  }) {
@@ -17915,7 +17915,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
17915
17915
  fs: _fs,
17916
17916
  onSign,
17917
17917
  dir,
17918
- gitdir = join19(dir, ".git"),
17918
+ gitdir = join20(dir, ".git"),
17919
17919
  ref = "refs/notes/commits",
17920
17920
  oid,
17921
17921
  author: _author,
@@ -18003,7 +18003,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18003
18003
  async function renameBranch({
18004
18004
  fs: fs2,
18005
18005
  dir,
18006
- gitdir = join19(dir, ".git"),
18006
+ gitdir = join20(dir, ".git"),
18007
18007
  ref,
18008
18008
  oldref,
18009
18009
  checkout: checkout2 = false
@@ -18033,7 +18033,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18033
18033
  async function resetIndex({
18034
18034
  fs: _fs,
18035
18035
  dir,
18036
- gitdir = join19(dir, ".git"),
18036
+ gitdir = join20(dir, ".git"),
18037
18037
  filepath,
18038
18038
  ref,
18039
18039
  cache = {}
@@ -18080,7 +18080,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18080
18080
  gid: 0,
18081
18081
  size: 0
18082
18082
  };
18083
- const object2 = dir && await fs2.read(join19(dir, filepath));
18083
+ const object2 = dir && await fs2.read(join20(dir, filepath));
18084
18084
  if (object2) {
18085
18085
  workdirOid = await hashObject$1({
18086
18086
  gitdir: updatedGitdir,
@@ -18088,7 +18088,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18088
18088
  object: object2
18089
18089
  });
18090
18090
  if (oid === workdirOid) {
18091
- stats = await fs2.lstat(join19(dir, filepath));
18091
+ stats = await fs2.lstat(join20(dir, filepath));
18092
18092
  }
18093
18093
  }
18094
18094
  await GitIndexManager.acquire(
@@ -18108,7 +18108,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18108
18108
  async function resolveRef2({
18109
18109
  fs: fs2,
18110
18110
  dir,
18111
- gitdir = join19(dir, ".git"),
18111
+ gitdir = join20(dir, ".git"),
18112
18112
  ref,
18113
18113
  depth
18114
18114
  }) {
@@ -18133,7 +18133,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18133
18133
  async function setConfig({
18134
18134
  fs: _fs,
18135
18135
  dir,
18136
- gitdir = join19(dir, ".git"),
18136
+ gitdir = join20(dir, ".git"),
18137
18137
  path,
18138
18138
  value,
18139
18139
  append = false
@@ -18201,7 +18201,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18201
18201
  * @param {string} args.dir - The working directory.
18202
18202
  * @param {string}[args.gitdir=join(dir, '.git')] - [required] The [git directory](dir-vs-gitdir.md) path
18203
18203
  */
18204
- constructor({ fs: fs2, dir, gitdir = join19(dir, ".git") }) {
18204
+ constructor({ fs: fs2, dir, gitdir = join20(dir, ".git") }) {
18205
18205
  Object.assign(this, {
18206
18206
  fs: fs2,
18207
18207
  dir,
@@ -18231,7 +18231,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18231
18231
  * @returns {string} - The file path for the stash reference.
18232
18232
  */
18233
18233
  get refStashPath() {
18234
- return join19(this.gitdir, _GitStashManager.refStash);
18234
+ return join20(this.gitdir, _GitStashManager.refStash);
18235
18235
  }
18236
18236
  /**
18237
18237
  * Gets the file path for the stash reflogs.
@@ -18239,7 +18239,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18239
18239
  * @returns {string} - The file path for the stash reflogs.
18240
18240
  */
18241
18241
  get refLogsStashPath() {
18242
- return join19(this.gitdir, _GitStashManager.refLogsStash);
18242
+ return join20(this.gitdir, _GitStashManager.refLogsStash);
18243
18243
  }
18244
18244
  /**
18245
18245
  * Retrieves the author information for the stash.
@@ -18545,7 +18545,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18545
18545
  async function stash({
18546
18546
  fs: fs2,
18547
18547
  dir,
18548
- gitdir = join19(dir, ".git"),
18548
+ gitdir = join20(dir, ".git"),
18549
18549
  op = "push",
18550
18550
  message = "",
18551
18551
  refIdx = 0
@@ -18568,7 +18568,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18568
18568
  const _fs = new FileSystem(fs2);
18569
18569
  const updatedGitdir = await discoverGitdir({ fsp: _fs, dotgit: gitdir });
18570
18570
  const folders = ["refs", "logs", "logs/refs"];
18571
- folders.map((f) => join19(updatedGitdir, f)).forEach(async (folder) => {
18571
+ folders.map((f) => join20(updatedGitdir, f)).forEach(async (folder) => {
18572
18572
  if (!await _fs.exists(folder)) {
18573
18573
  await _fs.mkdir(folder);
18574
18574
  }
@@ -18598,7 +18598,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18598
18598
  async function status({
18599
18599
  fs: _fs,
18600
18600
  dir,
18601
- gitdir = join19(dir, ".git"),
18601
+ gitdir = join20(dir, ".git"),
18602
18602
  filepath,
18603
18603
  cache = {},
18604
18604
  refresh: refresh2 = true
@@ -18637,7 +18637,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18637
18637
  return "ignored";
18638
18638
  }
18639
18639
  }
18640
- const stats = await fs2.lstat(join19(dir, filepath));
18640
+ const stats = await fs2.lstat(join20(dir, filepath));
18641
18641
  const H = treeOid !== null;
18642
18642
  const I = indexEntry !== null;
18643
18643
  const W = stats !== null;
@@ -18647,7 +18647,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18647
18647
  } else {
18648
18648
  const config2 = await GitConfigManager.get({ fs: fs2, gitdir: updatedGitdir });
18649
18649
  const autocrlf = await config2.get("core.autocrlf");
18650
- const object2 = await fs2.read(join19(dir, filepath), { autocrlf });
18650
+ const object2 = await fs2.read(join20(dir, filepath), { autocrlf });
18651
18651
  const workdirOid = await hashObject$1({
18652
18652
  gitdir: updatedGitdir,
18653
18653
  type: "blob",
@@ -18696,9 +18696,9 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18696
18696
  }
18697
18697
  async function getOidAtPath({ fs: fs2, cache, gitdir: updatedGitdir, tree, path }) {
18698
18698
  if (typeof path === "string") path = path.split("/");
18699
- const dirname16 = path.shift();
18699
+ const dirname17 = path.shift();
18700
18700
  for (const entry of tree) {
18701
- if (entry.path === dirname16) {
18701
+ if (entry.path === dirname17) {
18702
18702
  if (path.length === 0) {
18703
18703
  return entry.oid;
18704
18704
  }
@@ -18738,7 +18738,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18738
18738
  async function statusMatrix({
18739
18739
  fs: _fs,
18740
18740
  dir,
18741
- gitdir = join19(dir, ".git"),
18741
+ gitdir = join20(dir, ".git"),
18742
18742
  ref = "HEAD",
18743
18743
  filepaths = ["."],
18744
18744
  filter,
@@ -18811,7 +18811,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18811
18811
  async function tag({
18812
18812
  fs: _fs,
18813
18813
  dir,
18814
- gitdir = join19(dir, ".git"),
18814
+ gitdir = join20(dir, ".git"),
18815
18815
  ref,
18816
18816
  object: object2,
18817
18817
  force = false
@@ -18843,7 +18843,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18843
18843
  async function updateIndex$1({
18844
18844
  fs: _fs,
18845
18845
  dir,
18846
- gitdir = join19(dir, ".git"),
18846
+ gitdir = join20(dir, ".git"),
18847
18847
  cache = {},
18848
18848
  filepath,
18849
18849
  oid,
@@ -18863,7 +18863,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18863
18863
  { fs: fs2, gitdir: updatedGitdir, cache },
18864
18864
  async function(index3) {
18865
18865
  if (!force) {
18866
- const fileStats2 = await fs2.lstat(join19(dir, filepath));
18866
+ const fileStats2 = await fs2.lstat(join20(dir, filepath));
18867
18867
  if (fileStats2) {
18868
18868
  if (fileStats2.isDirectory()) {
18869
18869
  throw new InvalidFilepathError("directory");
@@ -18881,7 +18881,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18881
18881
  }
18882
18882
  let fileStats;
18883
18883
  if (!oid) {
18884
- fileStats = await fs2.lstat(join19(dir, filepath));
18884
+ fileStats = await fs2.lstat(join20(dir, filepath));
18885
18885
  if (!fileStats) {
18886
18886
  throw new NotFoundError(
18887
18887
  `file at "${filepath}" on disk and "remove" not set`
@@ -18902,7 +18902,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18902
18902
  let stats;
18903
18903
  if (!oid) {
18904
18904
  stats = fileStats;
18905
- const object2 = stats.isSymbolicLink() ? await fs2.readlink(join19(dir, filepath)) : await fs2.read(join19(dir, filepath));
18905
+ const object2 = stats.isSymbolicLink() ? await fs2.readlink(join20(dir, filepath)) : await fs2.read(join20(dir, filepath));
18906
18906
  oid = await _writeObject({
18907
18907
  fs: fs2,
18908
18908
  gitdir: updatedGitdir,
@@ -18946,7 +18946,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18946
18946
  async function walk({
18947
18947
  fs: fs2,
18948
18948
  dir,
18949
- gitdir = join19(dir, ".git"),
18949
+ gitdir = join20(dir, ".git"),
18950
18950
  trees,
18951
18951
  map: map2,
18952
18952
  reduce,
@@ -18974,7 +18974,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18974
18974
  throw err2;
18975
18975
  }
18976
18976
  }
18977
- async function writeBlob({ fs: fs2, dir, gitdir = join19(dir, ".git"), blob: blob2 }) {
18977
+ async function writeBlob({ fs: fs2, dir, gitdir = join20(dir, ".git"), blob: blob2 }) {
18978
18978
  try {
18979
18979
  assertParameter("fs", fs2);
18980
18980
  assertParameter("gitdir", gitdir);
@@ -18996,7 +18996,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
18996
18996
  async function writeCommit({
18997
18997
  fs: fs2,
18998
18998
  dir,
18999
- gitdir = join19(dir, ".git"),
18999
+ gitdir = join20(dir, ".git"),
19000
19000
  commit: commit2
19001
19001
  }) {
19002
19002
  try {
@@ -19018,7 +19018,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
19018
19018
  async function writeObject({
19019
19019
  fs: _fs,
19020
19020
  dir,
19021
- gitdir = join19(dir, ".git"),
19021
+ gitdir = join20(dir, ".git"),
19022
19022
  type,
19023
19023
  object: object2,
19024
19024
  format = "parsed",
@@ -19064,7 +19064,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
19064
19064
  async function writeRef({
19065
19065
  fs: _fs,
19066
19066
  dir,
19067
- gitdir = join19(dir, ".git"),
19067
+ gitdir = join20(dir, ".git"),
19068
19068
  ref,
19069
19069
  value,
19070
19070
  force = false,
@@ -19119,7 +19119,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
19119
19119
  });
19120
19120
  return oid;
19121
19121
  }
19122
- async function writeTag({ fs: fs2, dir, gitdir = join19(dir, ".git"), tag: tag2 }) {
19122
+ async function writeTag({ fs: fs2, dir, gitdir = join20(dir, ".git"), tag: tag2 }) {
19123
19123
  try {
19124
19124
  assertParameter("fs", fs2);
19125
19125
  assertParameter("gitdir", gitdir);
@@ -19136,7 +19136,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
19136
19136
  throw err2;
19137
19137
  }
19138
19138
  }
19139
- async function writeTree2({ fs: fs2, dir, gitdir = join19(dir, ".git"), tree }) {
19139
+ async function writeTree2({ fs: fs2, dir, gitdir = join20(dir, ".git"), tree }) {
19140
19140
  try {
19141
19141
  assertParameter("fs", fs2);
19142
19142
  assertParameter("gitdir", gitdir);
@@ -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.27.0",
19764
+ version: "0.29.0",
19765
19765
  description: "Agent-friendly AnimaGuru CLI built with CrustJS",
19766
19766
  type: "module",
19767
19767
  bin: {
@@ -19978,14 +19978,14 @@ function normalizeFlag(name2, def) {
19978
19978
  name: name2,
19979
19979
  reason: "empty-env"
19980
19980
  });
19981
- for (const [field, delimiter2] of [["delimiter", def.delimiter], ["env.delimiter", def.env?.delimiter]]) {
19982
- if (delimiter2 === void 0) continue;
19981
+ for (const [field, delimiter3] of [["delimiter", def.delimiter], ["env.delimiter", def.env?.delimiter]]) {
19982
+ if (delimiter3 === void 0) continue;
19983
19983
  if (!def.multiple) throw new CrustError("DEFINITION", `Flag "${name2}" ${field} requires multiple: true`, {
19984
19984
  subject: "flag",
19985
19985
  name: name2,
19986
19986
  reason: "delimiter-without-multiple"
19987
19987
  });
19988
- if (delimiter2 === "") throw new CrustError("DEFINITION", `Flag "${name2}" ${field} must be non-empty`, {
19988
+ if (delimiter3 === "") throw new CrustError("DEFINITION", `Flag "${name2}" ${field} must be non-empty`, {
19989
19989
  subject: "flag",
19990
19990
  name: name2,
19991
19991
  reason: "empty-delimiter"
@@ -20457,9 +20457,9 @@ function resolveAliases(tokens, spellings) {
20457
20457
  }
20458
20458
  return canonical;
20459
20459
  }
20460
- function splitOccurrences(values, delimiter2) {
20461
- if (delimiter2 === void 0) return [...values];
20462
- return values.flatMap((value) => value.split(delimiter2)).filter((value) => value !== "");
20460
+ function splitOccurrences(values, delimiter3) {
20461
+ if (delimiter3 === void 0) return [...values];
20462
+ return values.flatMap((value) => value.split(delimiter3)).filter((value) => value !== "");
20463
20463
  }
20464
20464
  function envFlagValue(name2, def, raw) {
20465
20465
  const occurrences = def.multiple ? splitOccurrences([raw], def.env?.delimiter) : [raw];
@@ -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 readFile19, writeFile as writeFile16 } from "node:fs/promises";
24299
- import { dirname as dirname14, resolve as resolve17 } from "node:path";
24298
+ import { mkdir as mkdir16, readFile as readFile20, writeFile as writeFile16 } from "node:fs/promises";
24299
+ import { dirname as dirname15, resolve as resolve18 } from "node:path";
24300
24300
  import { setTimeout as delay6 } from "node:timers/promises";
24301
24301
 
24302
24302
  // node_modules/fflate/esm/index.mjs
@@ -25288,208 +25288,6 @@ function unzipSync(data, opts) {
25288
25288
  // shared/auth.ts
25289
25289
  var CLI_CLIENT_ID = "animaguru-cli";
25290
25290
 
25291
- // shared/sprites/styles.ts
25292
- var GAME_SPRITE_STYLES = ["hd-cartoon", "pixel", "painted", "vector"];
25293
- var VIDEO_SPRITE_STYLES = [
25294
- "photoreal-cutout",
25295
- "photo-collage",
25296
- "paper-cutout",
25297
- "photoreal-stop-motion",
25298
- "cutout-motion-graphics"
25299
- ];
25300
- var SPRITE_STYLES = [...GAME_SPRITE_STYLES, ...VIDEO_SPRITE_STYLES];
25301
-
25302
- // shared/credits.ts
25303
- var UNITS_PER_CREDIT = 10;
25304
- var toCredits = (units) => Math.round(units) / UNITS_PER_CREDIT;
25305
- var toUnits = (credits) => Math.round(credits * UNITS_PER_CREDIT);
25306
-
25307
- // shared/sprites/types.ts
25308
- var SPRITE_KINDS = ["character", "vehicle", "machine", "effect"];
25309
- var SPRITE_ACTIONS = ["idle", "walk", "run", "skid-stop", "shoot", "jump", "hurt", "death", "slide", "punch", "kick"];
25310
- var SPRITE_KIND_ACTIONS = {
25311
- character: SPRITE_ACTIONS,
25312
- vehicle: ["idle", "move", "fire", "hurt", "destroyed"],
25313
- machine: ["idle", "work", "activate", "fire", "hurt", "break"],
25314
- effect: ["projectile", "charge", "impact", "explosion", "aura"]
25315
- };
25316
- var ALL_SPRITE_ACTIONS = [...new Set(Object.values(SPRITE_KIND_ACTIONS).flat())];
25317
- var SPRITE_TOP_ACTIONS = {
25318
- vehicle: ["hull-move", "hull-destroyed", "turret-fire"],
25319
- machine: ["hull-break", "turret-fire"]
25320
- };
25321
- var ALL_TOP_ACTIONS = [...new Set(Object.values(SPRITE_TOP_ACTIONS).flat())];
25322
- var CUSTOM_FRAMES = { min: 2, max: 12 };
25323
-
25324
- // shared/sprites/models.ts
25325
- var DEFAULT_SPRITE_IMAGE_MODEL = "fal/gpt-image-2.5-medium";
25326
- var SPRITE_IMAGE_MODELS = [
25327
- { id: DEFAULT_SPRITE_IMAGE_MODEL, label: "GPT Image 2.5 \xB7 medium (fal)", provider: "fal", edit: true, nativeAlpha: true, maxImages: 16, relCost: 0.3, quality: "medium", endpoints: { generate: "openai/gpt-image-2.5/flare/text-to-image", edit: "openai/gpt-image-2.5/flare/edit" } },
25328
- { id: "fal/gpt-image-2-low", label: "GPT Image 2 \xB7 low (fal)", provider: "fal", edit: true, spriteBackupCutout: true, maxImages: 16, relCost: 0.2, quality: "low", endpoints: { generate: "openai/gpt-image-2", edit: "openai/gpt-image-2/edit" } },
25329
- { id: "fal/muse-image", label: "Muse Image (fal)", provider: "fal", edit: true, maxImages: 10, relCost: 0.2, endpoints: { generate: "meta/muse-image/text-to-image", edit: "meta/muse-image/edit" } },
25330
- { id: "fal/krea-2-turbo", label: "Krea 2 Turbo (fal)", provider: "fal", edit: false, maxImages: 0, relCost: 0.16, endpoints: { generate: "fal-ai/krea-2/turbo" } },
25331
- { id: "gpt-image-2.5", label: "GPT Image 2.5 (OpenAI)", provider: "openai", edit: true, nativeAlpha: true, maxImages: 16, relCost: 1 },
25332
- { id: "black-forest-labs/flux-2-max", label: "FLUX.2 [max]", provider: "workers-ai", edit: true, maxImages: 8, relCost: 3 },
25333
- { id: "bytedance/seedream-5-pro", label: "Seedream 5 Pro", provider: "workers-ai", edit: true, maxImages: 10, relCost: 1.5 },
25334
- { id: "xai/grok-imagine-image-2.0", label: "Grok Imagine Image 2.0", provider: "workers-ai", edit: true, maxImages: 5, relCost: 1 },
25335
- { id: "alibaba/qwen-image-3.0-pro", label: "Qwen Image 3.0 Pro", provider: "workers-ai", edit: false, maxImages: 0, relCost: 0.5 },
25336
- { id: "krea/krea-2-medium-turbo", label: "Krea 2 Medium Turbo", provider: "workers-ai", edit: false, maxImages: 0, relCost: 0.25 }
25337
- ];
25338
- var SPRITE_IMAGE_MODEL_IDS = SPRITE_IMAGE_MODELS.map((m) => m.id);
25339
- function spriteImageModel(id2) {
25340
- return SPRITE_IMAGE_MODELS.find((m) => m.id === id2) ?? SPRITE_IMAGE_MODELS[0];
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", "keyframes-to-video"];
25343
- var STUDIO_VIDEO_WORKFLOWS = ["image-to-video", "text-to-video", "extend-video"];
25344
- var MEDIA_LENGTH_WORKFLOWS = ["lip-sync", "recast", "relight", "3d-to-video"];
25345
- var lengthFromMedia = (workflow) => MEDIA_LENGTH_WORKFLOWS.includes(workflow);
25346
- var VIDEO_STYLES = ["vhs", "retro-toon-70s", "low-poly", "hand-drawn", "16bit-pixel"];
25347
- var VHS_DAMAGE_LEVELS = ["light", "medium", "heavy"];
25348
- var VIDEO_RESOLUTIONS = ["480p", "720p", "768p", "1080p", "2k"];
25349
- var H3_RESOLUTIONS = ["480p", "768p", "1080p", "2k"];
25350
- var VIDEO_ASPECT_RATIOS = ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16"];
25351
- var PROMPT_EXPANSION_MODES = ["disabled", "balanced", "quality"];
25352
- var EXTEND_OUTPUTS = ["extended", "continuation"];
25353
- var SOUNDTRACK_MAX_BYTES = 15 * 1024 * 1024;
25354
- var EXTEND_SOURCE_MAX_BYTES = 50 * 1024 * 1024;
25355
- var MAX_AUDIO_REFERENCES = 3;
25356
- var MAX_VIDEO_REFERENCE_IMAGES = 9;
25357
- var MAX_REFERENCE_VIDEOS = 3;
25358
- var MAX_REFERENCE_FILES = 12;
25359
- var MAX_RECAST_PEOPLE = 4;
25360
- var MAX_3D_REFERENCES = 8;
25361
- var MAX_CAMERA_KEYFRAMES = 12;
25362
- var MAX_CAMERA_TURNS = 32;
25363
- var MAX_IMAGE_KEYFRAMES = 10;
25364
- var KEYFRAME_FPS = 24;
25365
- var H3_MAX = "minimax/h3-max";
25366
- var FLUX_3_MAX = "blackforestlabs/flux-3-max";
25367
- var h3Endpoints = (route) => ({ "image-to-video": `${route}/image-to-video`, "text-to-video": `${route}/text-to-video`, "extend-video": `${route}/extend-video` });
25368
- var SPRITE_VIDEO_MODELS = [
25369
- {
25370
- id: "pruna/p-video-2-pro",
25371
- label: "Pruna P-Video 2 Pro",
25372
- provider: "pruna",
25373
- workflows: ["image-to-video"],
25374
- resolutions: ["480p", "768p"],
25375
- modes: ["cost", "speed", "quality"],
25376
- lastFrame: true,
25377
- estimatedCreditsPerSecond: { "480p": { cost: 1, speed: 2, quality: 4 }, "768p": { cost: 2.5, speed: 3.5, quality: 7.5 } },
25378
- seconds: { min: 5, max: 15 }
25379
- },
25380
- {
25381
- id: "heygen/heygen-video-1",
25382
- label: "HeyGen Video 1",
25383
- provider: "heygen",
25384
- workflows: ["image-to-video"],
25385
- resolutions: ["480p", "768p"],
25386
- modes: ["cost"],
25387
- lastFrame: false,
25388
- estimatedCreditsPerSecond: { "480p": { cost: 1 }, "768p": { cost: 1.5 } },
25389
- seconds: { min: 5, max: 15 }
25390
- },
25391
- {
25392
- id: "fal/minimax-h3-max",
25393
- label: "MiniMax H3 Max (fal.ai)",
25394
- provider: "fal",
25395
- // Every workflow but FLUX 3 Max's keyframes.
25396
- workflows: VIDEO_WORKFLOWS.filter((w) => w !== "keyframes-to-video"),
25397
- endpoints: {
25398
- ...h3Endpoints(H3_MAX),
25399
- "reference-to-video": `${H3_MAX}/reference-to-video`,
25400
- "lip-sync": `${H3_MAX}/lip-sync/image-to-video`,
25401
- "camera-controls": `${H3_MAX}/camera-controls`,
25402
- "insert-video": `${H3_MAX}/insert-video`,
25403
- recast: `${H3_MAX}/recast`,
25404
- relight: `${H3_MAX}/relight`,
25405
- "3d-to-video": `${H3_MAX}/3d-to-video`,
25406
- style: `${H3_MAX}/styles`
25407
- },
25408
- resolutions: ["480p", "768p", "1080p"],
25409
- workflowOptions: {
25410
- "extend-video": { resolutions: H3_RESOLUTIONS },
25411
- "lip-sync": { resolutions: H3_RESOLUTIONS, seconds: { min: 5, max: 900, fractional: true } },
25412
- "insert-video": { resolutions: ["480p", "768p"], seconds: { min: 5, max: 13, fractional: true }, creditsPerSecond: { "480p": 5, "768p": 6 } },
25413
- recast: { resolutions: ["768p", "1080p"], seconds: { min: 5, max: 30, fractional: true }, creditsPerSecond: { "768p": 30, "1080p": 45 } },
25414
- relight: { resolutions: H3_RESOLUTIONS, seconds: { min: 2.33, max: 15, fractional: true } },
25415
- "3d-to-video": { seconds: { min: 1, max: 15, fractional: true } },
25416
- style: { resolutions: ["768p"], seconds: { min: 5, max: 15 } }
25417
- },
25418
- modes: ["quality"],
25419
- lastFrame: true,
25420
- endFrameOnly: true,
25421
- seed: true,
25422
- promptExpansion: PROMPT_EXPANSION_MODES,
25423
- audio: true,
25424
- estimatedCreditsPerSecond: { "480p": { quality: 5 }, "768p": { quality: 8 }, "1080p": { quality: 16 }, "2k": { quality: 32 } },
25425
- seconds: { min: 1, max: 15, fractional: true }
25426
- },
25427
- {
25428
- id: "fal/minimax-h3-max-turbo",
25429
- label: "MiniMax H3 Max Turbo (fal.ai)",
25430
- provider: "fal",
25431
- workflows: STUDIO_VIDEO_WORKFLOWS,
25432
- endpoints: h3Endpoints("minimax/h3-max-turbo"),
25433
- resolutions: ["480p", "768p", "1080p"],
25434
- workflowOptions: { "extend-video": { resolutions: H3_RESOLUTIONS } },
25435
- modes: ["speed"],
25436
- lastFrame: true,
25437
- endFrameOnly: true,
25438
- seed: true,
25439
- promptExpansion: PROMPT_EXPANSION_MODES,
25440
- audio: true,
25441
- estimatedCreditsPerSecond: { "480p": { speed: 2.5 }, "768p": { speed: 4 }, "1080p": { speed: 8 }, "2k": { speed: 16 } },
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 }
25458
- }
25459
- ];
25460
- var SPRITE_VIDEO_MODEL_IDS = SPRITE_VIDEO_MODELS.map((m) => m.id);
25461
- var DEFAULT_SPRITE_VIDEO_MODEL = "pruna/p-video-2-pro";
25462
- function spriteVideoModel(id2) {
25463
- return SPRITE_VIDEO_MODELS.find((m) => m.id === id2) ?? SPRITE_VIDEO_MODELS[0];
25464
- }
25465
- function videoResolutions(model, workflow = "image-to-video") {
25466
- return model.workflowOptions?.[workflow]?.resolutions ?? model.resolutions;
25467
- }
25468
- function videoSeconds(model, workflow = "image-to-video") {
25469
- return model.workflowOptions?.[workflow]?.seconds ?? model.seconds;
25470
- }
25471
- var INCLUDED_REFERENCE_TOKENS = 4096;
25472
- var imageReferenceTokens = (size) => size && size[0] > 0 && size[1] > 0 ? 32 * Math.ceil(32 * Math.max(...size) / Math.min(...size)) : 1824;
25473
- var clipReferenceTokens = (seconds2, resolution) => Math.ceil(Math.max(0, seconds2) * (resolution === "480p" ? 2700 : 6900));
25474
- var audioReferenceTokens = (seconds2) => Math.ceil(Math.max(0, seconds2) * 80);
25475
- var DEFAULT_GENERATED_REFERENCES = 2;
25476
- function billedVideoSeconds(model, workflow, seconds2) {
25477
- const s = videoSeconds(model, workflow).fractional ? seconds2 : Math.round(seconds2);
25478
- if (workflow === "3d-to-video") return Math.max(5, s);
25479
- return workflow === "lip-sync" && s > 15 ? s * 1.2 : s;
25480
- }
25481
- function videoCreditsPerSecond(model, p) {
25482
- const override = model.workflowOptions?.[p.workflow ?? "image-to-video"]?.creditsPerSecond?.[p.resolution];
25483
- if (override !== void 0) return override;
25484
- const byMode = model.estimatedCreditsPerSecond[p.resolution] ?? model.estimatedCreditsPerSecond[model.resolutions[0]];
25485
- return byMode[p.mode] ?? byMode[model.modes[0]] ?? 0;
25486
- }
25487
- function spriteVideoEstimate(model, p, extras2 = {}) {
25488
- const seconds2 = billedVideoSeconds(model, p.workflow ?? "image-to-video", p.seconds);
25489
- const beyond = Math.max(0, (extras2.referenceTokens ?? 0) - INCLUDED_REFERENCE_TOKENS) * 2e-3 + (extras2.generatedReferences ?? 0) * 3;
25490
- return Math.max(1, Math.ceil((videoCreditsPerSecond(model, p) * seconds2 + beyond) * UNITS_PER_CREDIT));
25491
- }
25492
-
25493
25291
  // node_modules/zod/v4/classic/external.js
25494
25292
  var external_exports = {};
25495
25293
  __export(external_exports, {
@@ -27487,8 +27285,8 @@ function emoji2() {
27487
27285
  }
27488
27286
  var ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
27489
27287
  var ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
27490
- var mac = (delimiter2) => {
27491
- const escapedDelim = escapeRegex(delimiter2 ?? ":");
27288
+ var mac = (delimiter3) => {
27289
+ const escapedDelim = escapeRegex(delimiter3 ?? ":");
27492
27290
  return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
27493
27291
  };
27494
27292
  var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
@@ -30516,7 +30314,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
30516
30314
  var NONE = 0;
30517
30315
  var ASSUMED = 1;
30518
30316
  var PROVEN = 2;
30519
- function isRecursive(inst, stack, resolve18) {
30317
+ function isRecursive(inst, stack, resolve19) {
30520
30318
  const cached2 = recursive.get(inst);
30521
30319
  if (cached2 !== void 0)
30522
30320
  return cached2 ? PROVEN : NONE;
@@ -30526,7 +30324,7 @@ function isRecursive(inst, stack, resolve18) {
30526
30324
  let result = NONE;
30527
30325
  const check2 = (child) => {
30528
30326
  if (result !== PROVEN && child?._zod) {
30529
- const answer = isRecursive(child, stack, resolve18);
30327
+ const answer = isRecursive(child, stack, resolve19);
30530
30328
  if (answer > result)
30531
30329
  result = answer;
30532
30330
  }
@@ -30537,7 +30335,7 @@ function isRecursive(inst, stack, resolve18) {
30537
30335
  const desc = Object.getOwnPropertyDescriptor(sh, key);
30538
30336
  if (spread && !desc.enumerable)
30539
30337
  continue;
30540
- const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve18) : NONE;
30338
+ const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve19) : NONE;
30541
30339
  if (child > answer)
30542
30340
  answer = child;
30543
30341
  }
@@ -30601,7 +30399,7 @@ function isRecursive(inst, stack, resolve18) {
30601
30399
  break;
30602
30400
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
30603
30401
  case "lazy": {
30604
- const inner = def._cachedInner ?? (resolve18 ? inst._zod.innerType : void 0);
30402
+ const inner = def._cachedInner ?? (resolve19 ? inst._zod.innerType : void 0);
30605
30403
  merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
30606
30404
  break;
30607
30405
  }
@@ -45158,6 +44956,220 @@ function date4(params) {
45158
44956
  return _coercedDate(ZodDate, params);
45159
44957
  }
45160
44958
 
44959
+ // shared/showcase.ts
44960
+ var SHOWCASE_SECTORS = ["cafe", "restaurant", "food", "retail", "beauty", "fitness", "workspace", "hospitality", "realestate", "education", "health", "events", "technology", "services"];
44961
+ var SHOWCASE_TAGS = ["social", "website", "product", "space", "offer", "brand", "explainer", "event"];
44962
+ var SHOWCASE_LOCALES = ["", "en", "tr", "sv"];
44963
+ var showcaseEntry = external_exports.object({
44964
+ sector: external_exports.enum(SHOWCASE_SECTORS),
44965
+ tags: external_exports.array(external_exports.enum(SHOWCASE_TAGS)).max(SHOWCASE_TAGS.length).default([]).transform((tags) => [...new Set(tags)]),
44966
+ locale: external_exports.enum(SHOWCASE_LOCALES).default(""),
44967
+ /** Lower comes first; films with the same position show newest first. */
44968
+ position: external_exports.number().int().min(0).max(1e3).default(0)
44969
+ }).strict();
44970
+
44971
+ // shared/sprites/styles.ts
44972
+ var GAME_SPRITE_STYLES = ["hd-cartoon", "pixel", "painted", "vector"];
44973
+ var VIDEO_SPRITE_STYLES = [
44974
+ "photoreal-cutout",
44975
+ "photo-collage",
44976
+ "paper-cutout",
44977
+ "photoreal-stop-motion",
44978
+ "cutout-motion-graphics"
44979
+ ];
44980
+ var SPRITE_STYLES = [...GAME_SPRITE_STYLES, ...VIDEO_SPRITE_STYLES];
44981
+
44982
+ // shared/credits.ts
44983
+ var UNITS_PER_CREDIT = 10;
44984
+ var toCredits = (units) => Math.round(units) / UNITS_PER_CREDIT;
44985
+ var toUnits = (credits) => Math.round(credits * UNITS_PER_CREDIT);
44986
+
44987
+ // shared/sprites/types.ts
44988
+ var SPRITE_KINDS = ["character", "vehicle", "machine", "effect"];
44989
+ var SPRITE_ACTIONS = ["idle", "walk", "run", "skid-stop", "shoot", "jump", "hurt", "death", "slide", "punch", "kick"];
44990
+ var SPRITE_KIND_ACTIONS = {
44991
+ character: SPRITE_ACTIONS,
44992
+ vehicle: ["idle", "move", "fire", "hurt", "destroyed"],
44993
+ machine: ["idle", "work", "activate", "fire", "hurt", "break"],
44994
+ effect: ["projectile", "charge", "impact", "explosion", "aura"]
44995
+ };
44996
+ var ALL_SPRITE_ACTIONS = [...new Set(Object.values(SPRITE_KIND_ACTIONS).flat())];
44997
+ var SPRITE_TOP_ACTIONS = {
44998
+ vehicle: ["hull-move", "hull-destroyed", "turret-fire"],
44999
+ machine: ["hull-break", "turret-fire"]
45000
+ };
45001
+ var ALL_TOP_ACTIONS = [...new Set(Object.values(SPRITE_TOP_ACTIONS).flat())];
45002
+ var CUSTOM_FRAMES = { min: 2, max: 12 };
45003
+
45004
+ // shared/sprites/models.ts
45005
+ var DEFAULT_SPRITE_IMAGE_MODEL = "fal/gpt-image-2.5-medium";
45006
+ var SPRITE_IMAGE_MODELS = [
45007
+ { id: DEFAULT_SPRITE_IMAGE_MODEL, label: "GPT Image 2.5 \xB7 medium (fal)", provider: "fal", edit: true, nativeAlpha: true, maxImages: 16, relCost: 0.3, quality: "medium", endpoints: { generate: "openai/gpt-image-2.5/flare/text-to-image", edit: "openai/gpt-image-2.5/flare/edit" } },
45008
+ { id: "fal/gpt-image-2-low", label: "GPT Image 2 \xB7 low (fal)", provider: "fal", edit: true, spriteBackupCutout: true, maxImages: 16, relCost: 0.2, quality: "low", endpoints: { generate: "openai/gpt-image-2", edit: "openai/gpt-image-2/edit" } },
45009
+ { id: "fal/muse-image", label: "Muse Image (fal)", provider: "fal", edit: true, maxImages: 10, relCost: 0.2, endpoints: { generate: "meta/muse-image/text-to-image", edit: "meta/muse-image/edit" } },
45010
+ { id: "fal/krea-2-turbo", label: "Krea 2 Turbo (fal)", provider: "fal", edit: false, maxImages: 0, relCost: 0.16, endpoints: { generate: "fal-ai/krea-2/turbo" } },
45011
+ { id: "gpt-image-2.5", label: "GPT Image 2.5 (OpenAI)", provider: "openai", edit: true, nativeAlpha: true, maxImages: 16, relCost: 1 },
45012
+ { id: "black-forest-labs/flux-2-max", label: "FLUX.2 [max]", provider: "workers-ai", edit: true, maxImages: 8, relCost: 3 },
45013
+ { id: "bytedance/seedream-5-pro", label: "Seedream 5 Pro", provider: "workers-ai", edit: true, maxImages: 10, relCost: 1.5 },
45014
+ { id: "xai/grok-imagine-image-2.0", label: "Grok Imagine Image 2.0", provider: "workers-ai", edit: true, maxImages: 5, relCost: 1 },
45015
+ { id: "alibaba/qwen-image-3.0-pro", label: "Qwen Image 3.0 Pro", provider: "workers-ai", edit: false, maxImages: 0, relCost: 0.5 },
45016
+ { id: "krea/krea-2-medium-turbo", label: "Krea 2 Medium Turbo", provider: "workers-ai", edit: false, maxImages: 0, relCost: 0.25 }
45017
+ ];
45018
+ var SPRITE_IMAGE_MODEL_IDS = SPRITE_IMAGE_MODELS.map((m) => m.id);
45019
+ function spriteImageModel(id2) {
45020
+ return SPRITE_IMAGE_MODELS.find((m) => m.id === id2) ?? SPRITE_IMAGE_MODELS[0];
45021
+ }
45022
+ 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"];
45023
+ var STUDIO_VIDEO_WORKFLOWS = ["image-to-video", "text-to-video", "extend-video"];
45024
+ var MEDIA_LENGTH_WORKFLOWS = ["lip-sync", "recast", "relight", "3d-to-video"];
45025
+ var lengthFromMedia = (workflow) => MEDIA_LENGTH_WORKFLOWS.includes(workflow);
45026
+ var VIDEO_STYLES = ["vhs", "retro-toon-70s", "low-poly", "hand-drawn", "16bit-pixel"];
45027
+ var VHS_DAMAGE_LEVELS = ["light", "medium", "heavy"];
45028
+ var VIDEO_RESOLUTIONS = ["480p", "720p", "768p", "1080p", "2k"];
45029
+ var H3_RESOLUTIONS = ["480p", "768p", "1080p", "2k"];
45030
+ var VIDEO_ASPECT_RATIOS = ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16"];
45031
+ var PROMPT_EXPANSION_MODES = ["disabled", "balanced", "quality"];
45032
+ var EXTEND_OUTPUTS = ["extended", "continuation"];
45033
+ var SOUNDTRACK_MAX_BYTES = 15 * 1024 * 1024;
45034
+ var EXTEND_SOURCE_MAX_BYTES = 50 * 1024 * 1024;
45035
+ var MAX_AUDIO_REFERENCES = 3;
45036
+ var MAX_VIDEO_REFERENCE_IMAGES = 9;
45037
+ var MAX_REFERENCE_VIDEOS = 3;
45038
+ var MAX_REFERENCE_FILES = 12;
45039
+ var MAX_RECAST_PEOPLE = 4;
45040
+ var MAX_3D_REFERENCES = 8;
45041
+ var MAX_CAMERA_KEYFRAMES = 12;
45042
+ var MAX_CAMERA_TURNS = 32;
45043
+ var MAX_IMAGE_KEYFRAMES = 10;
45044
+ var KEYFRAME_FPS = 24;
45045
+ var H3_MAX = "minimax/h3-max";
45046
+ var FLUX_3_MAX = "blackforestlabs/flux-3-max";
45047
+ var h3Endpoints = (route) => ({ "image-to-video": `${route}/image-to-video`, "text-to-video": `${route}/text-to-video`, "extend-video": `${route}/extend-video` });
45048
+ var SPRITE_VIDEO_MODELS = [
45049
+ {
45050
+ id: "pruna/p-video-2-pro",
45051
+ label: "Pruna P-Video 2 Pro",
45052
+ provider: "pruna",
45053
+ workflows: ["image-to-video"],
45054
+ resolutions: ["480p", "768p"],
45055
+ modes: ["cost", "speed", "quality"],
45056
+ lastFrame: true,
45057
+ estimatedCreditsPerSecond: { "480p": { cost: 1, speed: 2, quality: 4 }, "768p": { cost: 2.5, speed: 3.5, quality: 7.5 } },
45058
+ seconds: { min: 5, max: 15 }
45059
+ },
45060
+ {
45061
+ id: "heygen/heygen-video-1",
45062
+ label: "HeyGen Video 1",
45063
+ provider: "heygen",
45064
+ workflows: ["image-to-video"],
45065
+ resolutions: ["480p", "768p"],
45066
+ modes: ["cost"],
45067
+ lastFrame: false,
45068
+ estimatedCreditsPerSecond: { "480p": { cost: 1 }, "768p": { cost: 1.5 } },
45069
+ seconds: { min: 5, max: 15 }
45070
+ },
45071
+ {
45072
+ id: "fal/minimax-h3-max",
45073
+ label: "MiniMax H3 Max (fal.ai)",
45074
+ provider: "fal",
45075
+ // Every workflow but FLUX 3 Max's keyframes.
45076
+ workflows: VIDEO_WORKFLOWS.filter((w) => w !== "keyframes-to-video"),
45077
+ endpoints: {
45078
+ ...h3Endpoints(H3_MAX),
45079
+ "reference-to-video": `${H3_MAX}/reference-to-video`,
45080
+ "lip-sync": `${H3_MAX}/lip-sync/image-to-video`,
45081
+ "camera-controls": `${H3_MAX}/camera-controls`,
45082
+ "insert-video": `${H3_MAX}/insert-video`,
45083
+ recast: `${H3_MAX}/recast`,
45084
+ relight: `${H3_MAX}/relight`,
45085
+ "3d-to-video": `${H3_MAX}/3d-to-video`,
45086
+ style: `${H3_MAX}/styles`
45087
+ },
45088
+ resolutions: ["480p", "768p", "1080p"],
45089
+ workflowOptions: {
45090
+ "extend-video": { resolutions: H3_RESOLUTIONS },
45091
+ "lip-sync": { resolutions: H3_RESOLUTIONS, seconds: { min: 5, max: 900, fractional: true } },
45092
+ "insert-video": { resolutions: ["480p", "768p"], seconds: { min: 5, max: 13, fractional: true }, creditsPerSecond: { "480p": 5, "768p": 6 } },
45093
+ recast: { resolutions: ["768p", "1080p"], seconds: { min: 5, max: 30, fractional: true }, creditsPerSecond: { "768p": 30, "1080p": 45 } },
45094
+ relight: { resolutions: H3_RESOLUTIONS, seconds: { min: 2.33, max: 15, fractional: true } },
45095
+ "3d-to-video": { seconds: { min: 1, max: 15, fractional: true } },
45096
+ style: { resolutions: ["768p"], seconds: { min: 5, max: 15 } }
45097
+ },
45098
+ modes: ["quality"],
45099
+ lastFrame: true,
45100
+ endFrameOnly: true,
45101
+ seed: true,
45102
+ promptExpansion: PROMPT_EXPANSION_MODES,
45103
+ audio: true,
45104
+ estimatedCreditsPerSecond: { "480p": { quality: 5 }, "768p": { quality: 8 }, "1080p": { quality: 16 }, "2k": { quality: 32 } },
45105
+ seconds: { min: 1, max: 15, fractional: true }
45106
+ },
45107
+ {
45108
+ id: "fal/minimax-h3-max-turbo",
45109
+ label: "MiniMax H3 Max Turbo (fal.ai)",
45110
+ provider: "fal",
45111
+ workflows: STUDIO_VIDEO_WORKFLOWS,
45112
+ endpoints: h3Endpoints("minimax/h3-max-turbo"),
45113
+ resolutions: ["480p", "768p", "1080p"],
45114
+ workflowOptions: { "extend-video": { resolutions: H3_RESOLUTIONS } },
45115
+ modes: ["speed"],
45116
+ lastFrame: true,
45117
+ endFrameOnly: true,
45118
+ seed: true,
45119
+ promptExpansion: PROMPT_EXPANSION_MODES,
45120
+ audio: true,
45121
+ estimatedCreditsPerSecond: { "480p": { speed: 2.5 }, "768p": { speed: 4 }, "1080p": { speed: 8 }, "2k": { speed: 16 } },
45122
+ seconds: { min: 1, max: 15, fractional: true }
45123
+ },
45124
+ {
45125
+ id: "fal/flux-3-max",
45126
+ label: "FLUX 3 Max (fal.ai)",
45127
+ provider: "fal",
45128
+ workflows: ["image-to-video", "text-to-video", "keyframes-to-video"],
45129
+ 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` },
45130
+ falInputs: "flux",
45131
+ resolutions: ["720p", "1080p"],
45132
+ modes: ["quality"],
45133
+ lastFrame: true,
45134
+ seed: true,
45135
+ promptExpansion: ["balanced", "quality"],
45136
+ estimatedCreditsPerSecond: { "720p": { quality: 17 }, "1080p": { quality: 29 } },
45137
+ seconds: { min: 5, max: 20 }
45138
+ }
45139
+ ];
45140
+ var SPRITE_VIDEO_MODEL_IDS = SPRITE_VIDEO_MODELS.map((m) => m.id);
45141
+ var DEFAULT_SPRITE_VIDEO_MODEL = "pruna/p-video-2-pro";
45142
+ function spriteVideoModel(id2) {
45143
+ return SPRITE_VIDEO_MODELS.find((m) => m.id === id2) ?? SPRITE_VIDEO_MODELS[0];
45144
+ }
45145
+ function videoResolutions(model, workflow = "image-to-video") {
45146
+ return model.workflowOptions?.[workflow]?.resolutions ?? model.resolutions;
45147
+ }
45148
+ function videoSeconds(model, workflow = "image-to-video") {
45149
+ return model.workflowOptions?.[workflow]?.seconds ?? model.seconds;
45150
+ }
45151
+ var INCLUDED_REFERENCE_TOKENS = 4096;
45152
+ var imageReferenceTokens = (size) => size && size[0] > 0 && size[1] > 0 ? 32 * Math.ceil(32 * Math.max(...size) / Math.min(...size)) : 1824;
45153
+ var clipReferenceTokens = (seconds2, resolution) => Math.ceil(Math.max(0, seconds2) * (resolution === "480p" ? 2700 : 6900));
45154
+ var audioReferenceTokens = (seconds2) => Math.ceil(Math.max(0, seconds2) * 80);
45155
+ var DEFAULT_GENERATED_REFERENCES = 2;
45156
+ function billedVideoSeconds(model, workflow, seconds2) {
45157
+ const s = videoSeconds(model, workflow).fractional ? seconds2 : Math.round(seconds2);
45158
+ if (workflow === "3d-to-video") return Math.max(5, s);
45159
+ return workflow === "lip-sync" && s > 15 ? s * 1.2 : s;
45160
+ }
45161
+ function videoCreditsPerSecond(model, p) {
45162
+ const override = model.workflowOptions?.[p.workflow ?? "image-to-video"]?.creditsPerSecond?.[p.resolution];
45163
+ if (override !== void 0) return override;
45164
+ const byMode = model.estimatedCreditsPerSecond[p.resolution] ?? model.estimatedCreditsPerSecond[model.resolutions[0]];
45165
+ return byMode[p.mode] ?? byMode[model.modes[0]] ?? 0;
45166
+ }
45167
+ function spriteVideoEstimate(model, p, extras2 = {}) {
45168
+ const seconds2 = billedVideoSeconds(model, p.workflow ?? "image-to-video", p.seconds);
45169
+ const beyond = Math.max(0, (extras2.referenceTokens ?? 0) - INCLUDED_REFERENCE_TOKENS) * 2e-3 + (extras2.generatedReferences ?? 0) * 3;
45170
+ return Math.max(1, Math.ceil((videoCreditsPerSecond(model, p) * seconds2 + beyond) * UNITS_PER_CREDIT));
45171
+ }
45172
+
45161
45173
  // shared/sprites/generation-models.ts
45162
45174
  var generationModelShape = {
45163
45175
  videoModel: external_exports.enum(SPRITE_VIDEO_MODEL_IDS).optional()
@@ -45293,10 +45305,62 @@ async function initializeSkill(directory = process.cwd()) {
45293
45305
  return { path, created };
45294
45306
  }
45295
45307
 
45308
+ // cli/src/skills.ts
45309
+ import { spawn as nodeSpawn } from "node:child_process";
45310
+ import { access, mkdir as mkdir6, readFile as readFile4, readdir as readdir2, stat } from "node:fs/promises";
45311
+ import { delimiter, dirname as dirname5, join as join6, resolve as resolve5 } from "node:path";
45312
+ var SKILLS_CLI = "skills@1.7.4";
45313
+ var SKILL_AGENTS = { "claude-code": ".claude/skills", codex: ".agents/skills" };
45314
+ var skillsSource = (server) => `${new URL(server).origin}/cli/skills`;
45315
+ async function skillsInstallCommand(source, platform = process.platform, execPath = process.execPath) {
45316
+ const args = ["-y", SKILLS_CLI, "add", source, "--skill", "*", ...Object.keys(SKILL_AGENTS).flatMap((agent) => ["--agent", agent]), "--copy", "--yes"];
45317
+ const adjacentNpx = resolve5(dirname5(execPath), "../lib/node_modules/npm/bin/npx-cli.js");
45318
+ try {
45319
+ await access(adjacentNpx);
45320
+ return { command: execPath, args: [adjacentNpx, ...args], shell: false };
45321
+ } catch {
45322
+ return { command: platform === "win32" ? "npx.cmd" : "npx", args, shell: platform === "win32" };
45323
+ }
45324
+ }
45325
+ async function lockedSkills(directory, source) {
45326
+ try {
45327
+ const lock = JSON.parse(await readFile4(join6(directory, "skills-lock.json"), "utf8"));
45328
+ return Object.entries(lock.skills ?? {}).filter(([, skill]) => skill.sourceUrl === source).map(([name2]) => name2).sort();
45329
+ } catch {
45330
+ return [];
45331
+ }
45332
+ }
45333
+ async function installSkills(options) {
45334
+ const directory = resolve5(options.dir ?? ".");
45335
+ await mkdir6(directory, { recursive: true });
45336
+ if (!(await stat(directory)).isDirectory()) throw new Error(`${directory} is not a folder.`);
45337
+ const source = skillsSource(options.url);
45338
+ const { command, args, shell } = await skillsInstallCommand(source);
45339
+ options.log?.(`Installing AnimaGuru skills from ${source} into ${directory}\u2026`);
45340
+ await new Promise((done, reject) => {
45341
+ const child = (options.spawn ?? nodeSpawn)(command, args, {
45342
+ cwd: directory,
45343
+ signal: options.signal,
45344
+ stdio: ["ignore", "pipe", "pipe"],
45345
+ shell,
45346
+ // The skills CLI reports installs to skills.sh unless told not to.
45347
+ env: { ...process.env, DISABLE_TELEMETRY: "1", DO_NOT_TRACK: "1", PATH: `${dirname5(process.execPath)}${delimiter}${process.env.PATH ?? ""}` }
45348
+ });
45349
+ child.stdout?.on("data", (chunk) => options.log?.(chunk.toString().trimEnd()));
45350
+ child.stderr?.on("data", (chunk) => options.log?.(chunk.toString().trimEnd()));
45351
+ child.once("error", (error62) => reject(error62.code === "ENOENT" ? new Error("Installing skills needs npx, which comes with npm and Node.js 24. Install Node.js, or run the AnimaGuru installer again.") : error62));
45352
+ child.once("close", (code) => code === 0 ? done() : reject(new Error(`npx ${SKILLS_CLI} add exited with code ${code}; see its output above.`)));
45353
+ });
45354
+ const installed = await lockedSkills(directory, source);
45355
+ const folders = await Promise.all(Object.values(SKILL_AGENTS).map(async (folder) => (await readdir2(join6(directory, folder)).catch(() => [])).length));
45356
+ if (!installed.length || folders.some((count) => count === 0)) throw new Error("The skills CLI finished without installing AnimaGuru skills; see its output above.");
45357
+ return { directory, source, skills: installed.length, installed, agents: SKILL_AGENTS, lockfile: join6(directory, "skills-lock.json"), update: "npx skills update --project --yes" };
45358
+ }
45359
+
45296
45360
  // cli/src/create.ts
45297
45361
  import { spawn } from "node:child_process";
45298
- import { cp, mkdir as mkdir6, readFile as readFile4, readdir as readdir2, access, writeFile as writeFile6, rename as rename3 } from "node:fs/promises";
45299
- import { basename as basename2, dirname as dirname5, delimiter, join as join6, resolve as resolve5 } from "node:path";
45362
+ import { cp, mkdir as mkdir7, readFile as readFile5, readdir as readdir3, access as access2, writeFile as writeFile6, rename as rename3 } from "node:fs/promises";
45363
+ import { basename as basename2, dirname as dirname6, delimiter as delimiter2, join as join7, resolve as resolve6 } from "node:path";
45300
45364
  import { fileURLToPath } from "node:url";
45301
45365
 
45302
45366
  // shared/locales/en/animation-styles.json
@@ -46275,6 +46339,74 @@ var animation_styles_default = {
46275
46339
  previewCaption: "One champagne line draws a sun on the horizon, three arches and a woman's profile without lifting, closes a ring that fills with gold from the point where it met, then pulls back to the wordmark. Invented brand, drawn entirely in code.",
46276
46340
  family: "graphic",
46277
46341
  example: true
46342
+ },
46343
+ "product-turntable-3d": {
46344
+ name: "3D Product Turntable",
46345
+ description: "A real 3D product turns on a studio set while the camera orbits and pushes in, studio light slides across its materials and callouts stay pinned to its parts.",
46346
+ materials: [
46347
+ "Generated PBR product model",
46348
+ "Studio HDR reflections",
46349
+ "Shallow depth of field"
46350
+ ],
46351
+ motion: [
46352
+ "Turntable orbit and push-in",
46353
+ "Rack focus between details",
46354
+ "Callouts that track parts"
46355
+ ],
46356
+ previewCaption: "Generated headphones turn on a stone plinth as the camera pulls back from a macro of the brushed yoke, focus racking from part to part under pinned callouts, ending on a lockup for an invented brand. Model generated with Tripo H3.1, rendered in three.js.",
46357
+ family: "3d",
46358
+ example: true
46359
+ },
46360
+ "character-stage-3d": {
46361
+ name: "3D Character Stage",
46362
+ description: "A rigged 3D character walks, turns and gestures in front of a painted 2D backdrop, casting real shadows onto the painting while captions carry the story.",
46363
+ materials: [
46364
+ "Rigged character model",
46365
+ "Painted 2D backdrop",
46366
+ "Shadows cast onto the painting"
46367
+ ],
46368
+ motion: [
46369
+ "Walk cycles at true speed",
46370
+ "Crossfaded gestures",
46371
+ "A camera that follows the character"
46372
+ ],
46373
+ previewCaption: "A generated, auto-rigged baker walks across a painted square, turns to camera, waves and explains, his shadow falling over the painting. Character made with Tripo H3.1 and Meshy's auto-rig, rendered in three.js.",
46374
+ family: "3d",
46375
+ example: true
46376
+ },
46377
+ "toy-diorama-3d": {
46378
+ name: "3D Toy Diorama",
46379
+ description: "A miniature 3D world on a tabletop, seen through tilt-shift focus as a free camera glides over tiny props and characters going about their day.",
46380
+ materials: [
46381
+ "Toy-scale models",
46382
+ "Tilt-shift depth of field",
46383
+ "Warm tabletop light"
46384
+ ],
46385
+ motion: [
46386
+ "Free camera glides",
46387
+ "Little cars and figures on loops",
46388
+ "Focus pulled through the scene"
46389
+ ],
46390
+ previewCaption: "A camera glides over a toy harbour town as little cars and boats run their loops, everything sharp only in a thin band of focus. Props generated with Tripo H3.1, rendered in three.js.",
46391
+ family: "3d",
46392
+ example: true
46393
+ },
46394
+ "exploded-teardown-3d": {
46395
+ name: "3D Exploded Teardown",
46396
+ description: "A 3D product orbits and comes apart along its assembly axes, each part followed by a leader label that stays with it through the turn.",
46397
+ materials: [
46398
+ "Part-by-part 3D model",
46399
+ "Leader labels and callouts",
46400
+ "Clean studio grey"
46401
+ ],
46402
+ motion: [
46403
+ "Orbit while the parts separate",
46404
+ "Parts sliding along their axes",
46405
+ "Labels that follow each part"
46406
+ ],
46407
+ previewCaption: "An invented handheld console comes apart along its assembly axes as the camera orbits, every part keeping its label. Modelled and rendered in code.",
46408
+ family: "3d",
46409
+ example: true
46278
46410
  }
46279
46411
  },
46280
46412
  browseStyles: "Browse animation styles",
@@ -46290,7 +46422,8 @@ var animation_styles_default = {
46290
46422
  material: "Materials & depth",
46291
46423
  cinematic: "Cinema & media",
46292
46424
  evidence: "Evidence & data",
46293
- documentary: "Documentary & broadcast"
46425
+ documentary: "Documentary & broadcast",
46426
+ "3d": "3D scenes"
46294
46427
  },
46295
46428
  searchLabel: "Search visual styles",
46296
46429
  searchPlaceholder: "Try paper, product, typography\u2026",
@@ -46423,7 +46556,11 @@ var VIDEO_STYLE_IDS = [
46423
46556
  "synthwave-y2k",
46424
46557
  "sticker-explainer",
46425
46558
  "variety-captions",
46426
- "one-line-art"
46559
+ "one-line-art",
46560
+ "product-turntable-3d",
46561
+ "character-stage-3d",
46562
+ "toy-diorama-3d",
46563
+ "exploded-teardown-3d"
46427
46564
  ];
46428
46565
  var VIDEO_WORKFLOW_IDS = [
46429
46566
  "product-proof",
@@ -46483,10 +46620,10 @@ function listTemplates() {
46483
46620
  return videoTemplateCatalog();
46484
46621
  }
46485
46622
  async function installDependencies(directory, options) {
46486
- const adjacentNpm = resolve5(dirname5(process.execPath), "../lib/node_modules/npm/bin/npm-cli.js");
46623
+ const adjacentNpm = resolve6(dirname6(process.execPath), "../lib/node_modules/npm/bin/npm-cli.js");
46487
46624
  let npmScript;
46488
46625
  try {
46489
- await access(adjacentNpm);
46626
+ await access2(adjacentNpm);
46490
46627
  npmScript = adjacentNpm;
46491
46628
  } catch {
46492
46629
  }
@@ -46497,7 +46634,7 @@ async function installDependencies(directory, options) {
46497
46634
  signal: options.signal,
46498
46635
  stdio: ["ignore", "pipe", "pipe"],
46499
46636
  shell: !npmScript && process.platform === "win32",
46500
- env: { ...process.env, PATH: `${dirname5(process.execPath)}${delimiter}${process.env.PATH ?? ""}` }
46637
+ env: { ...process.env, PATH: `${dirname6(process.execPath)}${delimiter2}${process.env.PATH ?? ""}` }
46501
46638
  });
46502
46639
  child.stdout.on("data", (chunk) => options.log?.(chunk.toString().trimEnd()));
46503
46640
  child.stderr.on("data", (chunk) => options.log?.(chunk.toString().trimEnd()));
@@ -46522,7 +46659,7 @@ var STARTER_STORY = {
46522
46659
  { id: "invitation", label: "The invitation", start: 16, end: 24, ending: "Your next story starts here" }
46523
46660
  ]
46524
46661
  };
46525
- var writeStarterStory = (directory) => writeFile6(join6(directory, "story.json"), JSON.stringify(STARTER_STORY, null, 2) + "\n", { flag: "wx" });
46662
+ var writeStarterStory = (directory) => writeFile6(join7(directory, "story.json"), JSON.stringify(STARTER_STORY, null, 2) + "\n", { flag: "wx" });
46526
46663
  function starterQuiz(story = false) {
46527
46664
  return {
46528
46665
  version: 1,
@@ -46535,42 +46672,42 @@ function starterQuiz(story = false) {
46535
46672
  ] }
46536
46673
  };
46537
46674
  }
46538
- var writeStarterQuiz = (directory, story = false) => writeFile6(join6(directory, "quiz.json"), JSON.stringify(starterQuiz(story), null, 2) + "\n", { flag: "wx" });
46675
+ var writeStarterQuiz = (directory, story = false) => writeFile6(join7(directory, "quiz.json"), JSON.stringify(starterQuiz(story), null, 2) + "\n", { flag: "wx" });
46539
46676
  async function createVideoProject(options, installer = installDependencies) {
46540
46677
  if (!options.directory.trim()) throw new Error("Choose a new project folder, for example: animaguru create my-video");
46541
- const directory = resolve5(options.directory);
46678
+ const directory = resolve6(options.directory);
46542
46679
  options.signal?.throwIfAborted();
46543
46680
  try {
46544
- await mkdir6(directory);
46681
+ await mkdir7(directory);
46545
46682
  } catch (error62) {
46546
46683
  if (error62.code === "EEXIST") throw new Error(`Destination already exists: ${directory}. Choose a new folder; existing files were not changed.`);
46547
46684
  throw error62;
46548
46685
  }
46549
46686
  options.log?.(`Creating a video workspace in ${directory}\u2026`);
46550
- const templateRoot = join6(packageRoot, "templates/video");
46551
- for (const entry of await readdir2(templateRoot)) {
46687
+ const templateRoot = join7(packageRoot, "templates/video");
46688
+ for (const entry of await readdir3(templateRoot)) {
46552
46689
  if (entry === "WORKSPACE-AGENTS.md") continue;
46553
- await cp(join6(templateRoot, entry), join6(directory, entry), { recursive: true, errorOnExist: true, force: false });
46690
+ await cp(join7(templateRoot, entry), join7(directory, entry), { recursive: true, errorOnExist: true, force: false });
46554
46691
  }
46555
- await rename3(join6(directory, "gitignore"), join6(directory, ".gitignore"));
46556
- await mkdir6(join6(directory, "public/assets"), { recursive: true });
46557
- await mkdir6(join6(directory, "production"), { recursive: true });
46558
- const packagePath = join6(directory, "package.json");
46559
- const manifest = JSON.parse(await readFile4(packagePath, "utf8"));
46692
+ await rename3(join7(directory, "gitignore"), join7(directory, ".gitignore"));
46693
+ await mkdir7(join7(directory, "public/assets"), { recursive: true });
46694
+ await mkdir7(join7(directory, "production"), { recursive: true });
46695
+ const packagePath = join7(directory, "package.json");
46696
+ const manifest = JSON.parse(await readFile5(packagePath, "utf8"));
46560
46697
  manifest.name = basename2(directory).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 100) || "animaguru-video";
46561
46698
  await writeFile6(packagePath, JSON.stringify(manifest, null, 2) + "\n");
46562
- 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)])];
46563
- const pixiSkillsRoot = join6(packageRoot, "vendor/pixijs-skills/skills");
46564
- const pixiSkills = (await readdir2(pixiSkillsRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
46699
+ const skills = [.../* @__PURE__ */ new Set(["animaguru", "animaguru-video", "animaguru-brand-kit", "animaguru-cutcraft", "animaguru-art-motion", "animaguru-branching-story", "animaguru-quiz", "animaguru-3d-models", ...VIDEO_PURPOSE_SKILLS, ...VIDEO_TEMPLATES.map((template) => template.skill)])];
46700
+ const pixiSkillsRoot = join7(packageRoot, "vendor/pixijs-skills/skills");
46701
+ const pixiSkills = (await readdir3(pixiSkillsRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
46565
46702
  for (const agent of [".agents", ".claude"]) {
46566
- const skillRoot = join6(directory, agent, "skills");
46567
- await mkdir6(skillRoot, { recursive: true });
46568
- for (const skill of skills) await cp(join6(packageRoot, "skills", skill), join6(skillRoot, skill), { recursive: true });
46569
- for (const skill of pixiSkills) await cp(join6(pixiSkillsRoot, skill), join6(skillRoot, skill), { recursive: true });
46570
- }
46571
- await cp(join6(packageRoot, "vendor/pixijs-skills/LICENSE"), join6(directory, "PIXIJS-SKILLS-LICENSE.txt"));
46572
- await cp(join6(packageRoot, "vendor/pixijs-skills/provenance.json"), join6(directory, "production/pixijs-skills-provenance.json"));
46573
- await writeFile6(join6(directory, "animaguru.project.json"), JSON.stringify({
46703
+ const skillRoot = join7(directory, agent, "skills");
46704
+ await mkdir7(skillRoot, { recursive: true });
46705
+ for (const skill of skills) await cp(join7(packageRoot, "skills", skill), join7(skillRoot, skill), { recursive: true });
46706
+ for (const skill of pixiSkills) await cp(join7(pixiSkillsRoot, skill), join7(skillRoot, skill), { recursive: true });
46707
+ }
46708
+ await cp(join7(packageRoot, "vendor/pixijs-skills/LICENSE"), join7(directory, "PIXIJS-SKILLS-LICENSE.txt"));
46709
+ await cp(join7(packageRoot, "vendor/pixijs-skills/provenance.json"), join7(directory, "production/pixijs-skills-provenance.json"));
46710
+ await writeFile6(join7(directory, "animaguru.project.json"), JSON.stringify({
46574
46711
  schemaVersion: 3,
46575
46712
  cliVersion: package_default.version,
46576
46713
  category: null,
@@ -46582,7 +46719,7 @@ async function createVideoProject(options, installer = installDependencies) {
46582
46719
  interview: "pending",
46583
46720
  animaguruProjectId: null
46584
46721
  }, null, 2) + "\n");
46585
- await writeFile6(join6(directory, "animaguru.templates.json"), JSON.stringify(listTemplates(), null, 2) + "\n");
46722
+ await writeFile6(join7(directory, "animaguru.templates.json"), JSON.stringify(listTemplates(), null, 2) + "\n");
46586
46723
  if (options.story) await writeStarterStory(directory);
46587
46724
  if (options.quiz) await writeStarterQuiz(directory, options.story);
46588
46725
  if (options.install !== false) {
@@ -46609,28 +46746,28 @@ ${options.install === false ? "Run npm install, then " : "Run "}npm run dev insi
46609
46746
  }
46610
46747
  async function createWorkspace(options, installer = installDependencies) {
46611
46748
  const result = await createVideoProject({ ...options, install: false, log: (message) => {
46612
- if (message.startsWith("Creating")) options.log?.(`Creating a shared-brand workspace in ${resolve5(options.directory)}\u2026`);
46749
+ if (message.startsWith("Creating")) options.log?.(`Creating a shared-brand workspace in ${resolve6(options.directory)}\u2026`);
46613
46750
  } }, installer);
46614
46751
  const directory = result.path;
46615
46752
  const { rm: rm3 } = await import("node:fs/promises");
46616
- await rename3(join6(directory, "src"), join6(directory, "runtime"));
46617
- await mkdir6(join6(directory, "templates/animation/src"), { recursive: true });
46618
- await mkdir6(join6(directory, "templates/animation/shared/locales/en"), { recursive: true });
46753
+ await rename3(join7(directory, "src"), join7(directory, "runtime"));
46754
+ await mkdir7(join7(directory, "templates/animation/src"), { recursive: true });
46755
+ await mkdir7(join7(directory, "templates/animation/shared/locales/en"), { recursive: true });
46619
46756
  for (const file2 of ["film.ts", "film-resources.ts"]) {
46620
- const original = await readFile4(join6(directory, "runtime", file2), "utf8");
46757
+ const original = await readFile5(join7(directory, "runtime", file2), "utf8");
46621
46758
  const rewritten = original.replace('"./player-types"', '"../../../runtime/player-types"').replace('"./timeline-model"', '"../../../runtime/timeline-model"').replace('"./kit"', '"../../../runtime/kit"').replace('"../shared/locales/en/timeline.json"', '"../../../shared/locales/en/timeline.json"');
46622
- await writeFile6(join6(directory, "templates/animation/src", file2), rewritten);
46623
- await rm3(join6(directory, "runtime", file2));
46759
+ await writeFile6(join7(directory, "templates/animation/src", file2), rewritten);
46760
+ await rm3(join7(directory, "runtime", file2));
46624
46761
  }
46625
- await rm3(join6(directory, "runtime/main.ts"));
46626
- await rename3(join6(directory, "film.config.json"), join6(directory, "templates/animation/film.config.json"));
46627
- await rename3(join6(directory, "shared/locales/en/film.json"), join6(directory, "templates/animation/shared/locales/en/film.json"));
46762
+ await rm3(join7(directory, "runtime/main.ts"));
46763
+ await rename3(join7(directory, "film.config.json"), join7(directory, "templates/animation/film.config.json"));
46764
+ await rename3(join7(directory, "shared/locales/en/film.json"), join7(directory, "templates/animation/shared/locales/en/film.json"));
46628
46765
  const { createHash: createHash7 } = await import("node:crypto");
46629
- await writeFile6(join6(directory, "templates/animation/starter-film.sha256"), createHash7("sha256").update(await readFile4(join6(directory, "templates/animation/src/film.ts"))).digest("hex") + "\n");
46630
- await rm3(join6(directory, "animaguru.project.json"));
46631
- await mkdir6(join6(directory, "animations"));
46632
- await mkdir6(join6(directory, "branding"));
46633
- await writeFile6(join6(directory, "branding/brand.json"), JSON.stringify({
46766
+ await writeFile6(join7(directory, "templates/animation/starter-film.sha256"), createHash7("sha256").update(await readFile5(join7(directory, "templates/animation/src/film.ts"))).digest("hex") + "\n");
46767
+ await rm3(join7(directory, "animaguru.project.json"));
46768
+ await mkdir7(join7(directory, "animations"));
46769
+ await mkdir7(join7(directory, "branding"));
46770
+ await writeFile6(join7(directory, "branding/brand.json"), JSON.stringify({
46634
46771
  name: "Your brand",
46635
46772
  tagline: "Your next story starts here",
46636
46773
  logo: null,
@@ -46638,18 +46775,18 @@ async function createWorkspace(options, installer = installDependencies) {
46638
46775
  fonts: { heading: { family: "Arial, sans-serif", file: null }, body: { family: "Arial, sans-serif", file: null } },
46639
46776
  assets: []
46640
46777
  }, null, 2) + "\n");
46641
- await writeFile6(join6(directory, "branding/README.md"), "# Shared branding\n\nThe starter brand.json contains placeholders. On the first creative request, the agent asks for your official website or brand guide unless you already supplied one. It researches the identity and records sources and usage notes in brand-guide.md. You can also provide logos, fonts and guides here or proceed without a website. Register reusable web assets in brand.json with workspace-relative paths, such as branding/logo.svg. All animations inherit this brand. Only registered web assets are included in exports; guides and other local documents remain in this workspace.\n");
46642
- await writeFile6(join6(directory, "animaguru.workspace.json"), JSON.stringify({ schemaVersion: 1, cliVersion: package_default.version, name: basename2(directory), animationsDirectory: "animations", branding: "branding/brand.json" }, null, 2) + "\n");
46643
- const pkg = JSON.parse(await readFile4(join6(directory, "package.json"), "utf8"));
46778
+ await writeFile6(join7(directory, "branding/README.md"), "# Shared branding\n\nThe starter brand.json contains placeholders. On the first creative request, the agent asks for your official website or brand guide unless you already supplied one. It researches the identity and records sources and usage notes in brand-guide.md. You can also provide logos, fonts and guides here or proceed without a website. Register reusable web assets in brand.json with workspace-relative paths, such as branding/logo.svg. All animations inherit this brand. Only registered web assets are included in exports; guides and other local documents remain in this workspace.\n");
46779
+ await writeFile6(join7(directory, "animaguru.workspace.json"), JSON.stringify({ schemaVersion: 1, cliVersion: package_default.version, name: basename2(directory), animationsDirectory: "animations", branding: "branding/brand.json" }, null, 2) + "\n");
46780
+ const pkg = JSON.parse(await readFile5(join7(directory, "package.json"), "utf8"));
46644
46781
  pkg.scripts.dev = "node scripts/dev.mjs";
46645
- await writeFile6(join6(directory, "package.json"), JSON.stringify(pkg, null, 2) + "\n");
46646
- const tsconfig = JSON.parse(await readFile4(join6(directory, "tsconfig.json"), "utf8"));
46782
+ await writeFile6(join7(directory, "package.json"), JSON.stringify(pkg, null, 2) + "\n");
46783
+ const tsconfig = JSON.parse(await readFile5(join7(directory, "tsconfig.json"), "utf8"));
46647
46784
  tsconfig.include = ["runtime", "animations/*/src"];
46648
- await writeFile6(join6(directory, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
46649
- await writeFile6(join6(directory, "AGENTS.md"), await readFile4(join6(packageRoot, "templates/video/WORKSPACE-AGENTS.md"), "utf8"));
46650
- await writeFile6(join6(directory, "CLAUDE.md"), "# AnimaGuru workspace\n\nRead AGENTS.md for the multi-animation workflow, shared branding and commands. Claude skills are in .claude/skills. For an incomplete shared kit, read .claude/skills/animaguru-brand-kit/SKILL.md and ask for the official website or brand guide if the user did not supply one. Select the animation folder before editing; reuse shared runtime/ and scripts/.\n");
46651
- await rm3(join6(directory, "WORKSPACE-AGENTS.md"), { force: true });
46652
- await writeFile6(join6(directory, "README.md"), "# Your AnimaGuru workspace\n\nOpen this root folder in Codex or Claude Code and describe a film. If you have an official website or brand guide, include it; otherwise the agent asks for it while setting up the shared branding/ kit. You can also provide local brand assets or proceed without a website. Create a film with animaguru create animation my-video.\n\nRun npm run dev for the workspace hub. Run npm run build -- --animation my-video, npm run export:html -- --animation my-video, or npm run export:mp4 -- --animation my-video. Player code, dependencies, skills and export tools are shared once; animation folders contain only their own production files.\n");
46785
+ await writeFile6(join7(directory, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
46786
+ await writeFile6(join7(directory, "AGENTS.md"), await readFile5(join7(packageRoot, "templates/video/WORKSPACE-AGENTS.md"), "utf8"));
46787
+ await writeFile6(join7(directory, "CLAUDE.md"), "# AnimaGuru workspace\n\nRead AGENTS.md for the multi-animation workflow, shared branding and commands. Claude skills are in .claude/skills. For an incomplete shared kit, read .claude/skills/animaguru-brand-kit/SKILL.md and ask for the official website or brand guide if the user did not supply one. Select the animation folder before editing; reuse shared runtime/ and scripts/.\n");
46788
+ await rm3(join7(directory, "WORKSPACE-AGENTS.md"), { force: true });
46789
+ await writeFile6(join7(directory, "README.md"), "# Your AnimaGuru workspace\n\nOpen this root folder in Codex or Claude Code and describe a film. If you have an official website or brand guide, include it; otherwise the agent asks for it while setting up the shared branding/ kit. You can also provide local brand assets or proceed without a website. Create a film with animaguru create animation my-video.\n\nRun npm run dev for the workspace hub. Run npm run build -- --animation my-video, npm run export:html -- --animation my-video, or npm run export:mp4 -- --animation my-video. Player code, dependencies, skills and export tools are shared once; animation folders contain only their own production files.\n");
46653
46790
  if (options.install !== false) {
46654
46791
  try {
46655
46792
  await installer(directory, options);
@@ -46665,23 +46802,23 @@ async function createWorkspace(options, installer = installDependencies) {
46665
46802
  skills: result.skills,
46666
46803
  agents: result.agents,
46667
46804
  installed: options.install !== false,
46668
- branding: join6(directory, "branding"),
46669
- animations: join6(directory, "animations"),
46805
+ branding: join7(directory, "branding"),
46806
+ animations: join7(directory, "animations"),
46670
46807
  nextSteps: ["Open this workspace in Codex or Claude Code and provide your website or brand guide; the agent asks if it is missing.", "Build the shared brand kit in branding/.", "Run animaguru create animation my-video.", "Run npm run dev for the animation hub."]
46671
46808
  };
46672
46809
  }
46673
46810
  async function findWorkspace(start = process.cwd()) {
46674
46811
  const { realpath } = await import("node:fs/promises");
46675
- let directory = await realpath(resolve5(start));
46812
+ let directory = await realpath(resolve6(start));
46676
46813
  while (true) {
46677
46814
  try {
46678
- const info = JSON.parse(await readFile4(join6(directory, "animaguru.workspace.json"), "utf8"));
46815
+ const info = JSON.parse(await readFile5(join7(directory, "animaguru.workspace.json"), "utf8"));
46679
46816
  if (info.schemaVersion !== 1 || info.animationsDirectory !== "animations" || info.branding !== "branding/brand.json") throw new Error("Unsupported AnimaGuru workspace.");
46680
46817
  return directory;
46681
46818
  } catch (error62) {
46682
46819
  if (error62.code !== "ENOENT") throw error62;
46683
46820
  }
46684
- const parent = dirname5(directory);
46821
+ const parent = dirname6(directory);
46685
46822
  if (parent === directory) throw new Error("Run this inside an AnimaGuru workspace, or pass --workspace PATH. Create one with animaguru create workspace NAME.");
46686
46823
  directory = parent;
46687
46824
  }
@@ -46690,23 +46827,23 @@ async function createAnimation(options) {
46690
46827
  if (!/^[a-z0-9][a-z0-9-]{0,79}$/.test(options.name)) throw new Error("Choose a lowercase, hyphenated animation name, such as product-launch.");
46691
46828
  options.signal?.throwIfAborted();
46692
46829
  const root = await findWorkspace(options.workspace);
46693
- const directory = join6(root, "animations", options.name);
46830
+ const directory = join7(root, "animations", options.name);
46694
46831
  try {
46695
- await mkdir6(directory);
46832
+ await mkdir7(directory);
46696
46833
  } catch (error62) {
46697
46834
  if (error62.code === "EEXIST") throw new Error(`Animation already exists: ${options.name}. Existing files were not changed.`);
46698
46835
  throw error62;
46699
46836
  }
46700
- for (const file2 of await readdir2(join6(root, "templates/animation"))) {
46701
- if (file2 !== "starter-film.sha256") await cp(join6(root, "templates/animation", file2), join6(directory, file2), { recursive: true, errorOnExist: true, force: false });
46837
+ for (const file2 of await readdir3(join7(root, "templates/animation"))) {
46838
+ if (file2 !== "starter-film.sha256") await cp(join7(root, "templates/animation", file2), join7(directory, file2), { recursive: true, errorOnExist: true, force: false });
46702
46839
  }
46703
- const config2 = JSON.parse(await readFile4(join6(directory, "film.config.json"), "utf8"));
46840
+ const config2 = JSON.parse(await readFile5(join7(directory, "film.config.json"), "utf8"));
46704
46841
  config2.slug = options.name;
46705
46842
  config2.title = options.name.replaceAll("-", " ").replace(/^./, (value) => value.toUpperCase());
46706
- await writeFile6(join6(directory, "film.config.json"), JSON.stringify(config2, null, 2) + "\n");
46707
- await mkdir6(join6(directory, "public/assets"), { recursive: true });
46708
- await mkdir6(join6(directory, "production"), { recursive: true });
46709
- 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");
46843
+ await writeFile6(join7(directory, "film.config.json"), JSON.stringify(config2, null, 2) + "\n");
46844
+ await mkdir7(join7(directory, "public/assets"), { recursive: true });
46845
+ await mkdir7(join7(directory, "production"), { recursive: true });
46846
+ await writeFile6(join7(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");
46710
46847
  if (options.story) await writeStarterStory(directory);
46711
46848
  if (options.quiz) await writeStarterQuiz(directory, options.story);
46712
46849
  const kind = options.story && options.quiz ? "Branching story with a quiz" : options.story ? "Branching story" : options.quiz ? "Quiz film" : "Animation";
@@ -46725,8 +46862,65 @@ async function createAnimation(options) {
46725
46862
  };
46726
46863
  }
46727
46864
 
46865
+ // shared/model-assets.ts
46866
+ var MODEL_GENERATORS = [
46867
+ {
46868
+ id: "tripo-h3.1",
46869
+ label: "Tripo H3.1",
46870
+ endpoints: { image: "tripo3d/h3.1/image-to-3d", text: "tripo3d/h3.1/text-to-3d" },
46871
+ faces: { min: 1e3, max: 2e6 },
46872
+ /** The direction the generated model faces; saved models are turned to +Z. */
46873
+ facing: "+x"
46874
+ },
46875
+ {
46876
+ id: "hunyuan-3d-3.1-pro",
46877
+ label: "Hunyuan3D 3.1 Pro",
46878
+ endpoints: { image: "fal-ai/hunyuan-3d/v3.1/pro/image-to-3d", text: "fal-ai/hunyuan-3d/v3.1/pro/text-to-3d" },
46879
+ faces: { min: 4e4, max: 15e5 },
46880
+ facing: "+z"
46881
+ }
46882
+ ];
46883
+ var MODEL_GENERATOR_IDS = MODEL_GENERATORS.map((m) => m.id);
46884
+ var DEFAULT_MODEL_GENERATOR = "tripo-h3.1";
46885
+ var modelGenerator = (id2) => MODEL_GENERATORS.find((m) => m.id === id2) ?? MODEL_GENERATORS[0];
46886
+ var MAX_MODEL_CLIPS = 10;
46887
+ var MAX_RIG_FACES = 3e5;
46888
+ var MAX_MESHY_ACTION = 696;
46889
+ var MAX_MODEL_PROMPT = 1e3;
46890
+ var MODEL_ANIMATIONS = [
46891
+ { id: "idle", action: 0, label: "Idle" },
46892
+ { id: "idle-2", action: 11, label: "Idle 2" },
46893
+ { id: "idle-3", action: 12, label: "Idle 3" },
46894
+ { id: "walk", action: 30, label: "Casual walk" },
46895
+ { id: "walk-woman", action: 1, label: "Walking (woman)" },
46896
+ { id: "stage-walk", action: 55, label: "Stage walk" },
46897
+ { id: "thoughtful-walk", action: 121, label: "Thoughtful walk" },
46898
+ { id: "run", action: 14, label: "Run" },
46899
+ { id: "run-fast", action: 16, label: "Fast run" },
46900
+ { id: "wave", action: 28, label: "Big wave hello" },
46901
+ { id: "wave-one-hand", action: 290, label: "One-hand wave" },
46902
+ { id: "cheer", action: 59, label: "Victory cheer" },
46903
+ { id: "cheer-hands-up", action: 298, label: "Cheer with both hands up" },
46904
+ { id: "fist-pump", action: 403, label: "Victory fist pump" },
46905
+ { id: "talk", action: 313, label: "Talk with open hands" },
46906
+ { id: "talk-passionately", action: 308, label: "Talk passionately" },
46907
+ { id: "phone-call", action: 312, label: "Phone conversation" },
46908
+ { id: "agree", action: 25, label: "Agree gesture" },
46909
+ { id: "jump", action: 466, label: "Jump" },
46910
+ { id: "jump-arms-open", action: 460, label: "Jump with arms open" },
46911
+ { id: "dance", action: 22, label: "Funny dance" },
46912
+ { id: "dance-2", action: 64, label: "All-night dance" },
46913
+ { id: "shuffle", action: 63, label: "Arm-circle shuffle" }
46914
+ ];
46915
+ var MODEL_ANIMATION_IDS = MODEL_ANIMATIONS.map((a) => a.id);
46916
+ var DEFAULT_MODEL_ANIMATIONS = ["idle", "walk"];
46917
+ var animationAction = (clip) => typeof clip === "number" ? clip : MODEL_ANIMATIONS.find((a) => a.id === clip).action;
46918
+ var generationCostUsd = (p) => p.model === "hunyuan-3d-3.1-pro" ? 0.375 + (p.texture && p.pbr ? 0.15 : 0) + (p.faces !== void 0 ? 0.15 : 0) : p.texture ? 0.3 : 0.2;
46919
+ var rigCostUsd = (clips2) => 0.2 + 0.12 * clips2;
46920
+ var modelCostUsd = (p) => generationCostUsd(p) + (p.rig ? rigCostUsd(p.animations.length) : 0);
46921
+
46728
46922
  // shared/assets.ts
46729
- var ASSET_TYPES = ["image", "vector", "video", "music", "speech", "sfx"];
46923
+ var ASSET_TYPES = ["image", "vector", "video", "music", "speech", "sfx", "model"];
46730
46924
  var SPEECH_MODELS = [
46731
46925
  { id: "gemini-3.8-flash-tts", label: "Gemini 3.8 Flash TTS" },
46732
46926
  { id: "gemini-3.8-flash-lite-tts", label: "Gemini 3.8 Flash-Lite TTS" },
@@ -47022,6 +47216,43 @@ function vectorChecks(p, ctx) {
47022
47216
  if (p.references.length) issue2("references", "image-to-vector traces its source alone and takes no references.");
47023
47217
  }
47024
47218
  var vectorDefaults = (p) => p.workflow === "text-to-vector" ? { ...p, quality: p.quality ?? "medium", aspectRatio: p.aspectRatio ?? "auto" } : { ...p, shapes: p.shapes ?? "detailed" };
47219
+ var animationClip = external_exports.union([external_exports.enum(MODEL_ANIMATION_IDS), external_exports.number().int().min(0).max(MAX_MESHY_ACTION)]);
47220
+ var modelAsset = external_exports.object({
47221
+ name: base.name,
47222
+ /** Describes the model for text-to-3D; with a reference image it is only a note. */
47223
+ prompt: external_exports.string().trim().max(4e3).default(""),
47224
+ type: external_exports.literal("model"),
47225
+ model: external_exports.enum(MODEL_GENERATOR_IDS).default(DEFAULT_MODEL_GENERATOR),
47226
+ /** A saved image or sprite art the model is built from. */
47227
+ reference: blob.optional(),
47228
+ /** Textured (default), or a plain white model. */
47229
+ texture: external_exports.boolean().default(true),
47230
+ /** Metallic, roughness and normal maps with the texture, so scene lights shape the surface. */
47231
+ pbr: external_exports.boolean().default(true),
47232
+ /** Target face count, within the generator's range; Hunyuan3D charges extra for it. */
47233
+ faces: external_exports.number().int().optional(),
47234
+ /** Auto-rig a humanoid character standing in a T- or A-pose. */
47235
+ rig: external_exports.boolean().default(false),
47236
+ /** Clips the rig gets (idle and walk when none are named). */
47237
+ animations: external_exports.array(animationClip).max(MAX_MODEL_CLIPS).default([]),
47238
+ /** The rigged character's height in metres (default 1.7). */
47239
+ height: external_exports.number().min(0.3).max(5).optional()
47240
+ });
47241
+ function modelChecks(p, ctx) {
47242
+ const issue2 = (path, message) => ctx.addIssue({ code: "custom", message, path: [path] });
47243
+ const generator = modelGenerator(p.model);
47244
+ if (!p.reference && p.prompt.length < 3) issue2("prompt", "Describe the model in at least 3 characters, or choose a saved image to build it from.");
47245
+ if (!p.reference && p.prompt.length > MAX_MODEL_PROMPT) issue2("prompt", `Describe the model in at most ${MAX_MODEL_PROMPT.toLocaleString("en-US")} characters.`);
47246
+ if (p.faces !== void 0 && (p.faces < generator.faces.min || p.faces > generator.faces.max)) issue2("faces", `${generator.label} makes ${generator.faces.min.toLocaleString("en-US")}\u2013${generator.faces.max.toLocaleString("en-US")} faces.`);
47247
+ if (!p.rig && p.animations.length) issue2("animations", "Animation clips need rig: true.");
47248
+ if (!p.rig && p.height !== void 0) issue2("height", "height is the rigged character's height; it needs rig: true.");
47249
+ if (!p.rig) return;
47250
+ if (p.faces === void 0 && p.model === "hunyuan-3d-3.1-pro") issue2("faces", `Rigging takes at most ${MAX_RIG_FACES.toLocaleString("en-US")} faces, and Hunyuan3D makes 500,000 unless you set faces.`);
47251
+ else if (p.faces !== void 0 && p.faces > MAX_RIG_FACES) issue2("faces", `Rigging takes at most ${MAX_RIG_FACES.toLocaleString("en-US")} faces.`);
47252
+ const actions2 = p.animations.map(animationAction);
47253
+ if (new Set(actions2).size !== actions2.length) issue2("animations", "Choose each clip once.");
47254
+ }
47255
+ var modelDefaults = (p) => ({ ...p, animations: p.rig && !p.animations.length ? [...DEFAULT_MODEL_ANIMATIONS] : p.animations });
47025
47256
  var assetRequest = external_exports.discriminatedUnion("type", [
47026
47257
  imageAsset.superRefine(imageChecks),
47027
47258
  videoAsset.superRefine(videoChecks).transform(videoDefaults),
@@ -47031,7 +47262,8 @@ var assetRequest = external_exports.discriminatedUnion("type", [
47031
47262
  if (isElevenLabsSpeech(p.model) && p.delivery) ctx.addIssue({ code: "custom", message: "ElevenLabs uses audio tags in the transcript; separate delivery instructions are only supported by Gemini.", path: ["delivery"] });
47032
47263
  }).transform((p) => ({ ...p, voice: p.voice || (isElevenLabsSpeech(p.model) ? DEFAULT_ELEVENLABS_VOICE : "Kore") })),
47033
47264
  external_exports.object({ ...base, type: external_exports.literal("sfx"), model: external_exports.literal("eleven_text_to_sound_v2").default("eleven_text_to_sound_v2"), seconds: external_exports.number().min(0.5).max(30).default(5), loop: external_exports.boolean().default(false), promptInfluence: external_exports.number().min(0).max(1).default(0.3) }),
47034
- vectorAsset.superRefine(vectorChecks).transform(vectorDefaults)
47265
+ vectorAsset.superRefine(vectorChecks).transform(vectorDefaults),
47266
+ modelAsset.superRefine(modelChecks).transform(modelDefaults)
47035
47267
  ]);
47036
47268
  var assetGenerationRequest = external_exports.discriminatedUnion("type", [imageAsset.omit({ model: true }).superRefine(imageChecks), ...assetRequest.options.slice(1)]);
47037
47269
  var LIBRARY_AUDIO_MODEL = "library-audio";
@@ -47047,13 +47279,15 @@ function videoExtras(p, facts = {}) {
47047
47279
  if (w !== "reference-to-video" && w !== "insert-video" && w !== "3d-to-video") return {};
47048
47280
  const images = (p.references ?? []).reduce((sum, sha2) => sum + imageReferenceTokens(facts.size?.(sha2)), 0);
47049
47281
  if (w === "3d-to-video") return { referenceTokens: images + clipReferenceTokens(p.seconds, p.resolution), generatedReferences: p.references?.length ? 0 : p.generatedReferences ?? DEFAULT_GENERATED_REFERENCES };
47050
- const clips = Math.min(15, (p.referenceVideos ?? []).reduce((sum, sha2) => sum + Math.min(p.seconds, facts.seconds?.(sha2) ?? p.seconds), 0));
47282
+ const clips2 = Math.min(15, (p.referenceVideos ?? []).reduce((sum, sha2) => sum + Math.min(p.seconds, facts.seconds?.(sha2) ?? p.seconds), 0));
47051
47283
  const audio = Math.min(15, p.audioReferences.reduce((sum, sha2) => sum + (facts.seconds?.(sha2) ?? 15), 0));
47052
- return { referenceTokens: images + clipReferenceTokens(clips, p.resolution) + (w === "reference-to-video" ? audioReferenceTokens(audio) : 0) };
47284
+ return { referenceTokens: images + clipReferenceTokens(clips2, p.resolution) + (w === "reference-to-video" ? audioReferenceTokens(audio) : 0) };
47053
47285
  }
47286
+ var modelEstimate = (p) => Math.max(1, Math.ceil(modelCostUsd(p) / 1e-3));
47054
47287
  function assetEstimate(p, now = Date.now()) {
47055
47288
  if (p.type === "image") return imageEstimate(p);
47056
47289
  if (p.type === "vector") return vectorEstimate(p);
47290
+ if (p.type === "model") return modelEstimate(p);
47057
47291
  if (p.type === "video") return spriteVideoEstimate(spriteVideoModel(p.model), p, videoExtras(p));
47058
47292
  if (p.type === "music") return 80;
47059
47293
  const units = (credits) => Math.max(1, Math.ceil(credits * UNITS_PER_CREDIT));
@@ -47070,8 +47304,8 @@ function assetEstimate(p, now = Date.now()) {
47070
47304
  }
47071
47305
 
47072
47306
  // cli/src/videos.ts
47073
- import { open as open4, readFile as readFile8, stat as stat3 } from "node:fs/promises";
47074
- import { basename as basename4, join as join9, resolve as resolve9 } from "node:path";
47307
+ import { open as open4, readFile as readFile9, stat as stat4 } from "node:fs/promises";
47308
+ import { basename as basename4, join as join10, resolve as resolve10 } from "node:path";
47075
47309
  import { setTimeout as delay2 } from "node:timers/promises";
47076
47310
  import { spawn as spawn2 } from "node:child_process";
47077
47311
  import { createHash as createHash4 } from "node:crypto";
@@ -47114,7 +47348,9 @@ var videoSubmission = videoDetails.and(external_exports.object({
47114
47348
  bytes: external_exports.number().int().positive(),
47115
47349
  width: external_exports.number().int().min(1).max(4096).default(1920),
47116
47350
  height: external_exports.number().int().min(1).max(4096).default(1080),
47117
- duration: external_exports.number().positive().max(7200).optional()
47351
+ duration: external_exports.number().positive().max(7200).optional(),
47352
+ /** Admins only: adds the video to the showcase (shared/showcase.ts), where it shows once it is public and approved. */
47353
+ showcase: showcaseEntry.optional()
47118
47354
  })).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.");
47119
47355
  var videoMove = external_exports.object({ workspace: videoWorkspace }).strict();
47120
47356
  function videoPosterType(bytes) {
@@ -47167,12 +47403,12 @@ function parseSubtitles(input2, duration3) {
47167
47403
  }
47168
47404
 
47169
47405
  // cli/src/export-files.ts
47170
- import { open as open2, readFile as readFile5, stat } from "node:fs/promises";
47171
- import { basename as basename3, resolve as resolve6 } from "node:path";
47406
+ import { open as open2, readFile as readFile6, stat as stat2 } from "node:fs/promises";
47407
+ import { basename as basename3, resolve as resolve7 } from "node:path";
47172
47408
  import { createHash as createHash2 } from "node:crypto";
47173
47409
  async function exportReceipt(file2) {
47174
47410
  try {
47175
- const receipt = JSON.parse(await readFile5(file2.replace(/\.mp4$/i, ".json"), "utf8"));
47411
+ const receipt = JSON.parse(await readFile6(file2.replace(/\.mp4$/i, ".json"), "utf8"));
47176
47412
  if (receipt.file !== basename3(file2)) return {};
47177
47413
  return { title: typeof receipt.title === "string" ? receipt.title : void 0, language: typeof receipt.settings?.language === "string" ? receipt.settings.language : void 0 };
47178
47414
  } catch {
@@ -47182,22 +47418,22 @@ async function exportReceipt(file2) {
47182
47418
  async function readSubtitleFile(file2, maxBytes) {
47183
47419
  let info;
47184
47420
  try {
47185
- info = await stat(file2);
47421
+ info = await stat2(file2);
47186
47422
  } catch {
47187
47423
  throw new Error(`Cannot read the subtitle file ${file2}.`);
47188
47424
  }
47189
47425
  if (!info.isFile() || !/\.(?:srt|vtt)$/i.test(file2) || info.size < 1 || info.size > maxBytes) throw new Error(`Use a .srt or .vtt subtitle file up to ${maxBytes / 1024 / 1024} MiB, such as the one npm run export:mp4 writes beside the MP4.`);
47190
- const content = await readFile5(file2);
47426
+ const content = await readFile6(file2);
47191
47427
  const text2 = content.toString("utf8");
47192
47428
  if (text2.includes("\uFFFD") || !/\d\d:\d\d[,.]\d{3} --> /.test(text2)) throw new Error("Use a UTF-8 .srt or .vtt subtitle file with timed captions.");
47193
47429
  return { content, sha256: createHash2("sha256").update(content).digest("hex"), language: /^\uFEFF?WEBVTT[^\n]*\n(?:[^\n]+\n)*?Language:[ \t]*([^\s]+)/.exec(text2)?.[1] };
47194
47430
  }
47195
47431
  async function subtitleFile(root, mp4, choice, extensions) {
47196
47432
  if (choice === "none") return null;
47197
- if (choice !== "auto") return { file: resolve6(root, choice), explicit: true };
47433
+ if (choice !== "auto") return { file: resolve7(root, choice), explicit: true };
47198
47434
  for (const extension of extensions) {
47199
47435
  const file2 = mp4.replace(/\.mp4$/i, extension);
47200
- if (await stat(file2).then((info) => info.isFile(), () => false)) return { file: file2, explicit: false };
47436
+ if (await stat2(file2).then((info) => info.isFile(), () => false)) return { file: file2, explicit: false };
47201
47437
  }
47202
47438
  return null;
47203
47439
  }
@@ -47272,10 +47508,10 @@ async function mp4Info(file2) {
47272
47508
  }
47273
47509
 
47274
47510
  // cli/src/local-project.ts
47275
- import { readFile as readFile6, readdir as readdir3 } from "node:fs/promises";
47276
- import { join as join7, relative, resolve as resolve7, sep } from "node:path";
47511
+ import { readFile as readFile7, readdir as readdir4 } from "node:fs/promises";
47512
+ import { join as join8, relative, resolve as resolve8, sep } from "node:path";
47277
47513
  async function localProject(directory, selected) {
47278
- const root = resolve7(directory);
47514
+ const root = resolve8(directory);
47279
47515
  let workspace3;
47280
47516
  try {
47281
47517
  workspace3 = await findWorkspace(root);
@@ -47287,10 +47523,10 @@ async function localProject(directory, selected) {
47287
47523
  let animation = selected ?? (location[0] === "animations" ? location[1] : void 0);
47288
47524
  if (!animation) {
47289
47525
  const names = [];
47290
- for (const entry of await readdir3(join7(workspace3, "animations"), { withFileTypes: true })) {
47526
+ for (const entry of await readdir4(join8(workspace3, "animations"), { withFileTypes: true })) {
47291
47527
  if (!entry.isDirectory()) continue;
47292
47528
  try {
47293
- await readFile6(join7(workspace3, "animations", entry.name, "animaguru.project.json"));
47529
+ await readFile7(join8(workspace3, "animations", entry.name, "animaguru.project.json"));
47294
47530
  names.push(entry.name);
47295
47531
  } catch (error62) {
47296
47532
  if (error62.code !== "ENOENT") throw error62;
@@ -47300,17 +47536,17 @@ async function localProject(directory, selected) {
47300
47536
  animation = names[0];
47301
47537
  }
47302
47538
  if (!/^[a-z0-9][a-z0-9-]{0,79}$/.test(animation)) throw new Error("Use a lowercase, hyphenated animation name.");
47303
- const projectRoot = join7(workspace3, "animations", animation);
47304
- await readFile6(join7(projectRoot, "animaguru.project.json"));
47539
+ const projectRoot = join8(workspace3, "animations", animation);
47540
+ await readFile7(join8(projectRoot, "animaguru.project.json"));
47305
47541
  return { toolsRoot: workspace3, projectRoot, animation };
47306
47542
  }
47307
47543
 
47308
47544
  // cli/src/cloud-sync.ts
47309
47545
  var import_isomorphic_git = __toESM(require_isomorphic_git(), 1);
47310
47546
  import * as fs from "node:fs";
47311
- import { chmod as chmod3, lstat, mkdir as mkdir7, open as open3, readFile as readFile7, readdir as readdir4, rename as rename4, rm as rm2, stat as stat2, writeFile as writeFile7 } from "node:fs/promises";
47547
+ import { chmod as chmod3, lstat, mkdir as mkdir8, open as open3, readFile as readFile8, readdir as readdir5, rename as rename4, rm as rm2, stat as stat3, writeFile as writeFile7 } from "node:fs/promises";
47312
47548
  import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
47313
- import { dirname as dirname6, join as join8, resolve as resolve8 } from "node:path";
47549
+ import { dirname as dirname7, join as join9, resolve as resolve9 } from "node:path";
47314
47550
  import { Readable } from "node:stream";
47315
47551
  import { pipeline } from "node:stream/promises";
47316
47552
  import { setTimeout as delay } from "node:timers/promises";
@@ -47529,37 +47765,37 @@ var list = (paths, limit2 = 200) => paths.length > limit2 ? [...paths.slice(0, l
47529
47765
  var short = (oid) => oid.slice(0, 12);
47530
47766
  var executable = (mode) => (mode & 73) !== 0;
47531
47767
  async function cloudRootFor(start) {
47532
- let directory = resolve8(start);
47768
+ let directory = resolve9(start);
47533
47769
  while (true) {
47534
- if (await exists(join8(directory, LINK_FILE)) || await exists(join8(directory, "animaguru.workspace.json"))) return directory;
47770
+ if (await exists(join9(directory, LINK_FILE)) || await exists(join9(directory, "animaguru.workspace.json"))) return directory;
47535
47771
  try {
47536
- const project = JSON.parse(await readFile7(join8(directory, "animaguru.project.json"), "utf8"));
47772
+ const project = JSON.parse(await readFile8(join9(directory, "animaguru.project.json"), "utf8"));
47537
47773
  if (!project.workspace) return directory;
47538
47774
  } catch (error62) {
47539
47775
  if (error62.code !== "ENOENT" && !(error62 instanceof SyntaxError)) throw error62;
47540
47776
  }
47541
- const parent = dirname6(directory);
47777
+ const parent = dirname7(directory);
47542
47778
  if (parent === directory) throw new Error("Run this inside an AnimaGuru video workspace or project, or pass --dir. Create one with animaguru create workspace NAME.");
47543
47779
  directory = parent;
47544
47780
  }
47545
47781
  }
47546
47782
  async function readJson2(path) {
47547
47783
  try {
47548
- return JSON.parse(await readFile7(path, "utf8"));
47784
+ return JSON.parse(await readFile8(path, "utf8"));
47549
47785
  } catch (error62) {
47550
47786
  if (error62.code === "ENOENT") return null;
47551
47787
  throw error62;
47552
47788
  }
47553
47789
  }
47554
47790
  async function writeJson2(path, value) {
47555
- await mkdir7(dirname6(path), { recursive: true });
47791
+ await mkdir8(dirname7(path), { recursive: true });
47556
47792
  const temp = `${path}.${randomUUID2()}.tmp`;
47557
47793
  await writeFile7(temp, JSON.stringify(value, null, 2) + "\n", { mode: 384 });
47558
47794
  await rename4(temp, path);
47559
47795
  }
47560
47796
  async function withLock(root, run) {
47561
- const path = join8(root, SYNC_DIR, "lock");
47562
- await mkdir7(dirname6(path), { recursive: true });
47797
+ const path = join9(root, SYNC_DIR, "lock");
47798
+ await mkdir8(dirname7(path), { recursive: true });
47563
47799
  for (let attempt = 0; ; attempt++) {
47564
47800
  try {
47565
47801
  await writeFile7(path, JSON.stringify({ pid: process.pid, at: Date.now() }), { flag: "wx" });
@@ -47587,27 +47823,27 @@ async function withLock(root, run) {
47587
47823
  }
47588
47824
  async function openContext(dir, options, url3) {
47589
47825
  const root = await cloudRootFor(dir);
47590
- const link2 = await readJson2(join8(root, LINK_FILE));
47826
+ const link2 = await readJson2(join9(root, LINK_FILE));
47591
47827
  if (!link2) throw new Error("This folder is not linked to a project. Run animaguru cloud link, or animaguru cloud clone PROJECT_ID.");
47592
47828
  if (link2.schemaVersion !== 1 || !cloudProjectId.safeParse(link2.projectId).success) throw new Error(`Unsupported cloud link in ${LINK_FILE}. Run animaguru cloud unlink and link again.`);
47593
- const state = await readJson2(join8(root, STATE_FILE)) ?? { schemaVersion: 1, base: null, files: {} };
47594
- return { root, gitdir: join8(root, GIT_DIR), link: link2, state, client: new Client(url3 ?? link2.origin, options.signal), options };
47829
+ const state = await readJson2(join9(root, STATE_FILE)) ?? { schemaVersion: 1, base: null, files: {} };
47830
+ return { root, gitdir: join9(root, GIT_DIR), link: link2, state, client: new Client(url3 ?? link2.origin, options.signal), options };
47595
47831
  }
47596
47832
  async function cloudLink(dir) {
47597
- const link2 = await readJson2(join8(await cloudRootFor(dir), LINK_FILE));
47833
+ const link2 = await readJson2(join9(await cloudRootFor(dir), LINK_FILE));
47598
47834
  if (!link2) throw new Error("This folder is not linked to a project. Pass a project ID.");
47599
47835
  return link2;
47600
47836
  }
47601
- var saveState = (context) => writeJson2(join8(context.root, STATE_FILE), context.state);
47837
+ var saveState = (context) => writeJson2(join9(context.root, STATE_FILE), context.state);
47602
47838
  async function scanFiles(root) {
47603
47839
  const files = /* @__PURE__ */ new Map();
47604
47840
  const skipped = [];
47605
47841
  const tooLarge = [];
47606
47842
  async function walk(directory, prefix, rules) {
47607
- const entries = (await readdir4(directory, { withFileTypes: true })).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
47843
+ const entries = (await readdir5(directory, { withFileTypes: true })).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
47608
47844
  const scoped = [...rules];
47609
47845
  for (const name2 of [".gitignore", ".animaguruignore"]) {
47610
- if (entries.some((entry) => entry.name === name2 && entry.isFile())) scoped.push({ base: prefix, rules: (0, import_ignore.default)().add(await readFile7(join8(directory, name2), "utf8")) });
47846
+ if (entries.some((entry) => entry.name === name2 && entry.isFile())) scoped.push({ base: prefix, rules: (0, import_ignore.default)().add(await readFile8(join9(directory, name2), "utf8")) });
47611
47847
  }
47612
47848
  for (const entry of entries) {
47613
47849
  const path = prefix ? `${prefix}/${entry.name}` : entry.name;
@@ -47618,7 +47854,7 @@ async function scanFiles(root) {
47618
47854
  continue;
47619
47855
  }
47620
47856
  if (entry.isDirectory()) {
47621
- await walk(join8(directory, entry.name), path, scoped);
47857
+ await walk(join9(directory, entry.name), path, scoped);
47622
47858
  continue;
47623
47859
  }
47624
47860
  if (!entry.isFile()) continue;
@@ -47629,7 +47865,7 @@ async function scanFiles(root) {
47629
47865
  skipped.push(`${path} (unsupported name)`);
47630
47866
  continue;
47631
47867
  }
47632
- const info = await stat2(join8(directory, entry.name));
47868
+ const info = await stat3(join9(directory, entry.name));
47633
47869
  if (info.size > CLOUD_OBJECT_MAX_BYTES) {
47634
47870
  skipped.push(`${path} (larger than ${CLOUD_OBJECT_MAX_BYTES / 1024 ** 3} GB)`);
47635
47871
  continue;
@@ -47638,7 +47874,7 @@ async function scanFiles(root) {
47638
47874
  tooLarge.push(safe);
47639
47875
  continue;
47640
47876
  }
47641
- files.set(safe, { path: safe, absolute: join8(directory, entry.name), size: info.size, mtimeMs: info.mtimeMs, mode: executable(info.mode) ? "100755" : "100644" });
47877
+ files.set(safe, { path: safe, absolute: join9(directory, entry.name), size: info.size, mtimeMs: info.mtimeMs, mode: executable(info.mode) ? "100755" : "100644" });
47642
47878
  if (files.size > CLOUD_MAX_FILES) throw new Error(`Cloud sync supports up to ${CLOUD_MAX_FILES} files. Add generated folders to .animaguruignore.`);
47643
47879
  }
47644
47880
  }
@@ -47670,7 +47906,7 @@ async function describeFiles(context, scanned) {
47670
47906
  const sha256 = await fileSha256(file2.absolute);
47671
47907
  local.set(file2.path, { oid: (await import_isomorphic_git.default.hashBlob({ object: cloudPointer(sha256, file2.size) })).oid, mode: file2.mode, size: file2.size, mtimeMs: file2.mtimeMs, sha256 });
47672
47908
  } else {
47673
- local.set(file2.path, { oid: (await import_isomorphic_git.default.hashBlob({ object: await readFile7(file2.absolute) })).oid, mode: file2.mode, size: file2.size, mtimeMs: file2.mtimeMs });
47909
+ local.set(file2.path, { oid: (await import_isomorphic_git.default.hashBlob({ object: await readFile8(file2.absolute) })).oid, mode: file2.mode, size: file2.size, mtimeMs: file2.mtimeMs });
47674
47910
  }
47675
47911
  }
47676
47912
  return local;
@@ -47734,14 +47970,14 @@ ${entries.join(",\n")}
47734
47970
  }}
47735
47971
  `;
47736
47972
  }
47737
- async function token(context, access3) {
47738
- const result = await context.client.request(`/api/v1/cloud/${context.link.projectId}/token`, { method: "POST", body: { access: access3 } });
47973
+ async function token(context, access4) {
47974
+ const result = await context.client.request(`/api/v1/cloud/${context.link.projectId}/token`, { method: "POST", body: { access: access4 } });
47739
47975
  const remote = new URL(result.remote);
47740
47976
  if (remote.protocol !== "https:" && !(remote.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(remote.hostname))) throw new Error("The server returned an unsafe cloud remote.");
47741
47977
  return { ...result, onAuth: () => ({ username: "x", password: result.token.split("?expires=")[0] }) };
47742
47978
  }
47743
47979
  async function ensureRepo(gitdir) {
47744
- if (await exists(join8(gitdir, "HEAD"))) return;
47980
+ if (await exists(join9(gitdir, "HEAD"))) return;
47745
47981
  await import_isomorphic_git.default.init({ fs, gitdir, bare: true, defaultBranch: CLOUD_BRANCH });
47746
47982
  await import_isomorphic_git.default.setConfig({ fs, gitdir, path: "remote.origin.fetch", value: `+refs/heads/${CLOUD_BRANCH}:refs/remotes/origin/${CLOUD_BRANCH}` });
47747
47983
  }
@@ -47807,12 +48043,12 @@ async function target(root, path) {
47807
48043
  if (builtIn.ignores(path)) throw new Error(`Refusing to write ${path}: cloud sync never writes ignored or secret files.`);
47808
48044
  let current = root;
47809
48045
  for (const part of parts.slice(0, -1)) {
47810
- current = join8(current, part);
48046
+ current = join9(current, part);
47811
48047
  const info2 = await lstat(current).catch(() => null);
47812
- if (!info2) await mkdir7(current);
48048
+ if (!info2) await mkdir8(current);
47813
48049
  else if (info2.isSymbolicLink() || !info2.isDirectory()) throw new Error(`Cannot write ${path}: ${part} is not a folder.`);
47814
48050
  }
47815
- const file2 = join8(current, parts.at(-1));
48051
+ const file2 = join9(current, parts.at(-1));
47816
48052
  const info = await lstat(file2).catch(() => null);
47817
48053
  if (info && !info.isFile()) throw new Error(`Cannot replace ${path}: it is a link or folder.`);
47818
48054
  return file2;
@@ -47838,7 +48074,7 @@ async function materialize(context, path, entry) {
47838
48074
  } else await writeFile7(temp, blob2, { mode: 420 });
47839
48075
  await chmod3(temp, entry.mode === "100755" ? 493 : 420);
47840
48076
  await rename4(temp, file2);
47841
- const info = await stat2(file2);
48077
+ const info = await stat3(file2);
47842
48078
  return { ...entry, size: info.size, mtimeMs: info.mtimeMs, ...pointer ? { sha256: pointer.sha256 } : {} };
47843
48079
  } catch (error62) {
47844
48080
  await rm2(temp, { force: true });
@@ -47846,10 +48082,10 @@ async function materialize(context, path, entry) {
47846
48082
  }
47847
48083
  }
47848
48084
  async function backup(context, folder, path) {
47849
- const source = join8(context.root, path);
48085
+ const source = join9(context.root, path);
47850
48086
  if (!await exists(source)) return;
47851
- const destination = join8(context.root, SYNC_DIR, "backups", folder, path);
47852
- await mkdir7(dirname6(destination), { recursive: true });
48087
+ const destination = join9(context.root, SYNC_DIR, "backups", folder, path);
48088
+ await mkdir8(dirname7(destination), { recursive: true });
47853
48089
  await fs.promises.copyFile(source, destination);
47854
48090
  }
47855
48091
  async function remove(context, path) {
@@ -47869,24 +48105,24 @@ async function apply(context, local, writes, deletes) {
47869
48105
  }
47870
48106
  async function linkCloud(client, options) {
47871
48107
  const root = await cloudRootFor(options.dir);
47872
- if (await exists(join8(root, LINK_FILE))) throw new Error(`This folder is already linked. Run animaguru cloud status, or animaguru cloud unlink first.`);
48108
+ if (await exists(join9(root, LINK_FILE))) throw new Error(`This folder is already linked. Run animaguru cloud status, or animaguru cloud unlink first.`);
47873
48109
  const id2 = options.project ?? (await client.request("/api/v1/sprites", { method: "POST", body: { owner: options.workspace, name: (options.name ?? root.split(/[\\/]/).pop() ?? "Animations").slice(0, 80) } })).project.id;
47874
48110
  const { project } = await client.request("/api/v1/cloud", { method: "POST", body: { project: id2 } });
47875
- await writeJson2(join8(root, LINK_FILE), { schemaVersion: 1, origin: client.origin, projectId: project.id, name: project.name, workspace: project.workspaceId });
48111
+ await writeJson2(join9(root, LINK_FILE), { schemaVersion: 1, origin: client.origin, projectId: project.id, name: project.name, workspace: project.workspaceId });
47876
48112
  await keepOutOfGit(root);
47877
48113
  return { project, path: root, nextSteps: project.head ? ["Run animaguru cloud pull to combine the cloud versions with this folder."] : ['Run animaguru cloud push -m "First version" to save the first version.'] };
47878
48114
  }
47879
48115
  async function keepOutOfGit(root) {
47880
- const path = join8(root, ".gitignore");
47881
- const text2 = await readFile7(path, "utf8").catch(() => null);
48116
+ const path = join9(root, ".gitignore");
48117
+ const text2 = await readFile8(path, "utf8").catch(() => null);
47882
48118
  if (text2 !== null && !/^\/?\.animaguru\/(?:sync\/?)?$/m.test(text2)) await writeFile7(path, `${text2}${text2.endsWith("\n") || !text2 ? "" : "\n"}.animaguru/sync/
47883
48119
  `);
47884
48120
  }
47885
48121
  async function unlinkCloud(options) {
47886
48122
  const root = await cloudRootFor(options.dir);
47887
- if (!await exists(join8(root, LINK_FILE))) throw new Error("This folder is not linked to a project.");
48123
+ if (!await exists(join9(root, LINK_FILE))) throw new Error("This folder is not linked to a project.");
47888
48124
  await withLock(root, async () => {
47889
- for (const name2 of ["link.json", "state.json", "repo.git"]) await rm2(join8(root, SYNC_DIR, name2), { recursive: true, force: true });
48125
+ for (const name2 of ["link.json", "state.json", "repo.git"]) await rm2(join9(root, SYNC_DIR, name2), { recursive: true, force: true });
47890
48126
  });
47891
48127
  return { unlinked: true, path: root, note: "The project's animations and versions were not changed." };
47892
48128
  }
@@ -47936,13 +48172,13 @@ async function pushCloud(options) {
47936
48172
  if (!local.size) throw new Error("There are no files to save.");
47937
48173
  for (const [path, entry] of local) {
47938
48174
  if (base2.get(path)?.oid === entry.oid) continue;
47939
- const bytes = entry.sha256 ? new TextEncoder().encode(cloudPointer(entry.sha256, entry.size)) : await readFile7(join8(context.root, path));
48175
+ const bytes = entry.sha256 ? new TextEncoder().encode(cloudPointer(entry.sha256, entry.size)) : await readFile8(join9(context.root, path));
47940
48176
  if (await import_isomorphic_git.default.writeBlob({ fs, gitdir: context.gitdir, blob: bytes }) !== entry.oid) throw new Error(`${path} changed while saving. Run the push again.`);
47941
48177
  }
47942
- const uploaded = await uploadObjects(context, local, (path) => join8(context.root, path));
48178
+ const uploaded = await uploadObjects(context, local, (path) => join9(context.root, path));
47943
48179
  for (const [path, entry] of local) {
47944
48180
  if (!entry.sha256 || base2.get(path)?.oid === entry.oid) continue;
47945
- const info = await stat2(join8(context.root, path));
48181
+ const info = await stat3(join9(context.root, path));
47946
48182
  if (info.size !== entry.size || info.mtimeMs !== entry.mtimeMs) throw new Error(`${path} changed while saving. Run the push again.`);
47947
48183
  }
47948
48184
  const tree = await writeTree(context.gitdir, local, manifestText(local));
@@ -48017,10 +48253,10 @@ async function pullCloud(options) {
48017
48253
  async function cloneCloud(client, options) {
48018
48254
  const { project } = await client.request(`/api/v1/cloud/${encodeURIComponent(options.id)}`);
48019
48255
  if (!project) throw new Error("This project has no synced animations yet. Link a folder with animaguru cloud link --project ID, then push.");
48020
- const root = resolve8(options.dir ?? (project.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || project.id));
48021
- if (await exists(root) && (await readdir4(root)).length) throw new Error(`Destination is not empty: ${root}. Choose a new folder; existing files were not changed.`);
48022
- await mkdir7(root, { recursive: true });
48023
- await writeJson2(join8(root, LINK_FILE), { schemaVersion: 1, origin: client.origin, projectId: project.id, name: project.name, workspace: project.workspaceId });
48256
+ const root = resolve9(options.dir ?? (project.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || project.id));
48257
+ if (await exists(root) && (await readdir5(root)).length) throw new Error(`Destination is not empty: ${root}. Choose a new folder; existing files were not changed.`);
48258
+ await mkdir8(root, { recursive: true });
48259
+ await writeJson2(join9(root, LINK_FILE), { schemaVersion: 1, origin: client.origin, projectId: project.id, name: project.name, workspace: project.workspaceId });
48024
48260
  const pulled = await pullCloud({ ...options, dir: root });
48025
48261
  return { project, path: root, version: pulled.version, files: "written" in pulled ? pulled.written : 0, nextSteps: ["Run npm install in the folder before previewing or exporting."] };
48026
48262
  }
@@ -48060,6 +48296,17 @@ async function restoreCloud(options) {
48060
48296
  }
48061
48297
 
48062
48298
  // cli/src/videos.ts
48299
+ function showcaseOption(options) {
48300
+ const extra = options["showcase-tags"] !== void 0 || options["showcase-language"] !== void 0 || options["showcase-position"] !== void 0;
48301
+ if (!options.showcase) {
48302
+ if (extra) throw new Error("Pass --showcase SECTOR to add the video to the showcase.");
48303
+ return {};
48304
+ }
48305
+ const tags = options["showcase-tags"] ? options["showcase-tags"].split(",").map((tag) => tag.trim()).filter(Boolean) : [];
48306
+ const parsed = showcaseEntry.safeParse({ sector: options.showcase, tags, locale: options["showcase-language"] === "none" ? "" : options["showcase-language"] ?? "", position: options["showcase-position"] ?? 0 });
48307
+ if (!parsed.success) throw new Error(`Check the showcase tags: ${parsed.error.issues[0]?.message ?? "invalid"} (tags are ${SHOWCASE_TAGS.join(", ")}).`);
48308
+ return { showcase: parsed.data };
48309
+ }
48063
48310
  var subtitlesBody = (content) => new Blob([new Uint8Array(content)], { type: "text/plain; charset=utf-8" });
48064
48311
  function libraryWorkspace(value) {
48065
48312
  if (value === "personal" || value === "me") return "me";
@@ -48070,8 +48317,8 @@ function sharingEmails(value) {
48070
48317
  return value ? value.split(",").map((email3) => email3.trim()).filter(Boolean) : [];
48071
48318
  }
48072
48319
  async function exportHtml(root, animation, signal, log) {
48073
- const script = join9(root, "scripts/export-html.mjs");
48074
- await stat3(script).catch(() => {
48320
+ const script = join10(root, "scripts/export-html.mjs");
48321
+ await stat4(script).catch(() => {
48075
48322
  throw new Error("Choose an AnimaGuru video project, or pass --file for an exported HTML5 ZIP or MP4.");
48076
48323
  });
48077
48324
  log?.("Building the portable HTML5 animation\u2026");
@@ -48104,23 +48351,24 @@ async function exportHtml(root, animation, signal, log) {
48104
48351
  async function submitVideo(client, options) {
48105
48352
  if (!options.yes && !options["dry-run"]) throw new Error("Pass --yes to authorize uploading and sharing this animation, or --dry-run to inspect the submission.");
48106
48353
  const workspace3 = options.workspace === void 0 ? void 0 : libraryWorkspace(options.workspace);
48107
- const root = resolve9(options.directory ?? ".");
48354
+ const showcase = showcaseOption(options);
48355
+ const root = resolve10(options.directory ?? ".");
48108
48356
  const context = await localProject(root, options.animation);
48109
- const file2 = options.file ? resolve9(root, options.file) : await exportHtml(context.toolsRoot, context.animation, options.signal, options.log);
48110
- const info = await stat3(file2);
48357
+ const file2 = options.file ? resolve10(root, options.file) : await exportHtml(context.toolsRoot, context.animation, options.signal, options.log);
48358
+ const info = await stat4(file2);
48111
48359
  const format = file2.toLowerCase().endsWith(".zip") ? "html5" : file2.toLowerCase().endsWith(".mp4") ? "mp4" : null;
48112
48360
  if (!info.isFile() || !format) throw new Error("Submit an AnimaGuru HTML5 .zip export or an .mp4 file.");
48113
48361
  videoSubmission.parse({ title: options.title ?? "Video", format, bytes: info.size });
48114
48362
  let metadata = {};
48115
48363
  let receipt = {};
48116
48364
  if (format === "html5") {
48117
- const files = unzipSync(new Uint8Array(await readFile8(file2)), { filter: (entry) => entry.name === "manifest.json" && entry.originalSize <= 256 * 1024 });
48365
+ const files = unzipSync(new Uint8Array(await readFile9(file2)), { filter: (entry) => entry.name === "manifest.json" && entry.originalSize <= 256 * 1024 });
48118
48366
  if (!files["manifest.json"]) throw new Error("Use an AnimaGuru HTML5 export with manifest.json.");
48119
48367
  metadata = JSON.parse(strFromU8(files["manifest.json"]));
48120
48368
  if (metadata.format !== "spriteguru-html5" || metadata.version !== 1) throw new Error("Use an AnimaGuru HTML5 export.");
48121
48369
  } else {
48122
48370
  try {
48123
- metadata = JSON.parse(await readFile8(join9(context.projectRoot, "film.config.json"), "utf8"));
48371
+ metadata = JSON.parse(await readFile9(join10(context.projectRoot, "film.config.json"), "utf8"));
48124
48372
  } catch {
48125
48373
  }
48126
48374
  const handle2 = await open4(file2, "r");
@@ -48148,7 +48396,8 @@ async function submitVideo(client, options) {
48148
48396
  bytes: info.size,
48149
48397
  width: metadata.width,
48150
48398
  height: metadata.height,
48151
- duration: metadata.duration
48399
+ duration: metadata.duration,
48400
+ ...showcase
48152
48401
  });
48153
48402
  let subtitles = null;
48154
48403
  const choice = options.subtitles ?? "auto";
@@ -48163,10 +48412,10 @@ async function submitVideo(client, options) {
48163
48412
  }
48164
48413
  let poster;
48165
48414
  if (options.poster) {
48166
- const path = resolve9(root, options.poster);
48167
- const info2 = await stat3(path);
48415
+ const path = resolve10(root, options.poster);
48416
+ const info2 = await stat4(path);
48168
48417
  if (!info2.isFile() || info2.size > 2 * 1024 * 1024) throw new Error("Poster must be a PNG, JPEG or WebP up to 2 MiB.");
48169
- poster = await readFile8(path);
48418
+ poster = await readFile9(path);
48170
48419
  videoPosterType(poster);
48171
48420
  }
48172
48421
  if (options["dry-run"]) return { dryRun: true, file: file2, submission: body, poster: options.poster ?? null, subtitles: subtitles && { file: subtitles.file, language: subtitles.language } };
@@ -48216,7 +48465,7 @@ async function changeVideoSubtitles(client, id2, options) {
48216
48465
  return client.request(path + encodeURIComponent(options.remove), { method: "DELETE" });
48217
48466
  }
48218
48467
  if (!options.file) throw new Error("Pass --file with a .srt or .vtt subtitle file, or --remove LANGUAGE.");
48219
- const file2 = resolve9(options.file);
48468
+ const file2 = resolve10(options.file);
48220
48469
  const { content, language: header } = await readSubtitleFile(file2, SUBTITLE_MAX_BYTES);
48221
48470
  const language = subtitleLanguage.safeParse(options.language ?? header ?? (await exportReceipt(file2.replace(/\.(?:srt|vtt)$/i, ".mp4"))).language);
48222
48471
  if (!language.success) throw new Error("Pass --language with the subtitles' language code, such as en or tr.");
@@ -48250,8 +48499,8 @@ async function moveVideos(client, ids, options) {
48250
48499
  }
48251
48500
 
48252
48501
  // cli/src/assets.ts
48253
- import { mkdir as mkdir8, readFile as readFile9, stat as stat4, writeFile as writeFile8 } from "node:fs/promises";
48254
- import { dirname as dirname7, join as join10, resolve as resolve10 } from "node:path";
48502
+ import { mkdir as mkdir9, readFile as readFile10, stat as stat5, writeFile as writeFile8 } from "node:fs/promises";
48503
+ import { dirname as dirname8, join as join11, resolve as resolve11 } from "node:path";
48255
48504
  import { setTimeout as delay3 } from "node:timers/promises";
48256
48505
  var extras = {
48257
48506
  image: ["aspect-ratio", "transparent", "references", "mask"],
@@ -48289,6 +48538,7 @@ var extras = {
48289
48538
  music: [],
48290
48539
  speech: ["voice", "delivery"],
48291
48540
  sfx: ["seconds", "loop", "prompt-influence"],
48541
+ model: ["reference", "texture", "pbr", "faces", "rig", "animations", "height"],
48292
48542
  vector: ["workflow", "references", "source", "aspect-ratio", "quality", "shapes", "seed"]
48293
48543
  };
48294
48544
  var list2 = (items) => items.length < 2 ? items.join("") : `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
@@ -48310,6 +48560,7 @@ var regions = (value) => value?.split(";").filter((region) => region.trim()).map
48310
48560
  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.`);
48311
48561
  return { x: x2, y, width, height };
48312
48562
  });
48563
+ var clips = (value) => value?.split(",").map((clip) => clip.trim()).filter(Boolean).map((clip) => /^\d+$/.test(clip) ? Number(clip) : clip);
48313
48564
  var cameraKeyframes = (value) => value?.split(";").filter((frame) => frame.trim()).map((frame) => {
48314
48565
  const [time3, azimuth, elevation, distance, ...rest] = frame.split(",").map((n) => n.trim() ? Number(n) : NaN);
48315
48566
  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.`);
@@ -48324,9 +48575,10 @@ function assetFromFlags(flags) {
48324
48575
  if (flags.type === "image" && flags.model) throw new Error("Image models are managed by staff. Omit --model.");
48325
48576
  for (const name2 of Object.values(extras).flat()) {
48326
48577
  const value = flags[name2];
48327
- if (value !== void 0 && value !== false && !extras[flags.type]?.includes(name2)) throw new Error(`--${name2} is not an option for ${flags.type} assets. ${accepted(flags.type)}`.trim());
48578
+ const given2 = value !== void 0 && (value !== false || (name2 === "texture" || name2 === "pbr") && flags.type !== "model");
48579
+ if (given2 && !extras[flags.type]?.includes(name2)) throw new Error(`--${name2} is not an option for ${flags.type} assets. ${accepted(flags.type)}`.trim());
48328
48580
  }
48329
- if (flags.type !== "video" && flags.type !== "vector" && flags.prompt === void 0) throw new Error(`--prompt is required for ${flags.type} assets.`);
48581
+ if (flags.type !== "video" && flags.type !== "vector" && !(flags.type === "model" && flags.reference) && flags.prompt === void 0) throw new Error(`--prompt is required for ${flags.type} assets${flags.type === "model" ? " unless --reference names an image to build from" : ""}.`);
48330
48582
  const base2 = { type: flags.type, name: flags.name, prompt: flags.prompt, model: flags.model };
48331
48583
  if (flags.type === "image") return parseAsset({ ...base2, aspectRatio: flags["aspect-ratio"], transparent: flags.transparent, references: shas(flags.references), mask: regions(flags.mask) });
48332
48584
  if (flags.type === "video") {
@@ -48364,11 +48616,12 @@ function assetFromFlags(flags) {
48364
48616
  });
48365
48617
  }
48366
48618
  if (flags.type === "sfx") return parseAsset({ ...base2, seconds: flags.seconds, loop: flags.loop, promptInfluence: flags["prompt-influence"] });
48619
+ if (flags.type === "model") return parseAsset({ ...base2, reference: flags.reference, texture: flags.texture, pbr: flags.pbr, faces: flags.faces, rig: flags.rig, animations: clips(flags.animations), height: flags.height });
48367
48620
  if (flags.type === "vector") return parseAsset({ ...base2, workflow: flags.workflow, quality: flags.quality, aspectRatio: flags["aspect-ratio"], references: shas(flags.references), source: flags.source, shapes: flags.shapes, seed: flags.seed });
48368
48621
  return parseAsset({ ...base2, ...flags.type === "speech" ? { voice: flags.voice, delivery: flags.delivery } : {} });
48369
48622
  }
48370
48623
  async function assetFromFile(file2, model) {
48371
- const input2 = JSON.parse(await readFile9(file2, "utf8"));
48624
+ const input2 = JSON.parse(await readFile10(file2, "utf8"));
48372
48625
  if (!input2 || typeof input2 !== "object" || Array.isArray(input2)) throw new Error("The asset file must contain a JSON object.");
48373
48626
  return parseAsset({ ...input2, ...model ? { model } : {} });
48374
48627
  }
@@ -48376,7 +48629,7 @@ var MAX_BATCH = 50;
48376
48629
  var isObject2 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
48377
48630
  var fileName = (name2) => name2.normalize("NFKD").replace(/[^\w.-]+/g, "-").replace(/-{2,}/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, 80);
48378
48631
  async function assetsFromBatchFile(file2, model) {
48379
- const input2 = JSON.parse(await readFile9(file2, "utf8"));
48632
+ const input2 = JSON.parse(await readFile10(file2, "utf8"));
48380
48633
  let defaults = {};
48381
48634
  let items;
48382
48635
  if (Array.isArray(input2)) items = input2;
@@ -48462,18 +48715,19 @@ var MIME_EXTENSIONS = {
48462
48715
  "image/webp": "webp",
48463
48716
  "image/svg+xml": "svg",
48464
48717
  "video/mp4": "mp4",
48465
- "video/webm": "webm"
48718
+ "video/webm": "webm",
48719
+ "model/gltf-binary": "glb"
48466
48720
  };
48467
48721
  var extensionFor = (asset) => MIME_EXTENSIONS[asset.mimeType?.split(";")[0].trim().toLowerCase()] ?? /\.([a-z0-9]{1,5})$/i.exec(asset.file ?? "")?.[1]?.toLowerCase() ?? "bin";
48468
48722
  async function downloadTarget(output2, asset, folder = false) {
48469
- const path = resolve10(output2);
48470
- const isFolder = folder || /[\\/]$/.test(output2) || await stat4(path).then((info) => info.isDirectory(), () => false);
48723
+ const path = resolve11(output2);
48724
+ const isFolder = folder || /[\\/]$/.test(output2) || await stat5(path).then((info) => info.isDirectory(), () => false);
48471
48725
  if (!isFolder) {
48472
- await mkdir8(dirname7(path), { recursive: true });
48726
+ await mkdir9(dirname8(path), { recursive: true });
48473
48727
  return path;
48474
48728
  }
48475
- await mkdir8(path, { recursive: true });
48476
- return join10(path, `${fileName(asset.name) || asset.id}.${extensionFor(asset)}`);
48729
+ await mkdir9(path, { recursive: true });
48730
+ return join11(path, `${fileName(asset.name) || asset.id}.${extensionFor(asset)}`);
48477
48731
  }
48478
48732
  async function saveAsset(client, library, asset, path) {
48479
48733
  if (!library.ownerId || !/^[a-f0-9]{64}$/.test(asset.file)) throw new Error("Invalid asset download metadata.");
@@ -48497,7 +48751,7 @@ async function downloadAsset(client, project, assetId, output2) {
48497
48751
  const library = await client.request(assetsPath(project));
48498
48752
  const asset = library.assets.find((a) => a.id === assetId);
48499
48753
  if (!asset) throw new Error("Asset not found in this project. Run assets list to find its id.");
48500
- return saveAsset(client, library, asset, resolve10(output2));
48754
+ return saveAsset(client, library, asset, resolve11(output2));
48501
48755
  }
48502
48756
  async function generateAsset(client, project, body, options, signal) {
48503
48757
  const waiting = !options["dry-run"] && Boolean(options.wait || options.download);
@@ -48574,7 +48828,7 @@ async function submitAssetBatch(client, project, requests, options, signal) {
48574
48828
  }
48575
48829
 
48576
48830
  // cli/src/avatars.ts
48577
- import { readFile as readFile10, stat as stat5 } from "node:fs/promises";
48831
+ import { readFile as readFile11, stat as stat6 } from "node:fs/promises";
48578
48832
  import { extname } from "node:path";
48579
48833
  import { setTimeout as delay4 } from "node:timers/promises";
48580
48834
 
@@ -48680,8 +48934,8 @@ async function avatarFile2(value) {
48680
48934
  if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value)) throw new Error("Use an HTTPS URL, a project file SHA or a local file.");
48681
48935
  const mediaType = MEDIA_TYPES[extname(value).toLowerCase()];
48682
48936
  if (!mediaType) throw new Error("Use a PNG, JPEG or WebP image, or MP4, MOV or WebM footage.");
48683
- if ((await stat5(value)).size > AVATAR_INLINE_MAX_BYTES) throw new Error("Local files must be at most 8 MB. Use an HTTPS URL for larger footage.");
48684
- return { type: "base64", mediaType, data: (await readFile10(value)).toString("base64") };
48937
+ if ((await stat6(value)).size > AVATAR_INLINE_MAX_BYTES) throw new Error("Local files must be at most 8 MB. Use an HTTPS URL for larger footage.");
48938
+ return { type: "base64", mediaType, data: (await readFile11(value)).toString("base64") };
48685
48939
  }
48686
48940
  async function avatarFromFlags(flags) {
48687
48941
  const prompt = flags.type === "prompt";
@@ -48729,11 +48983,11 @@ async function waitForLook(client, project, lookId, timeout, signal, interval =
48729
48983
  // cli/src/youtube-auth.ts
48730
48984
  import { createServer } from "node:http";
48731
48985
  import { randomBytes, createHash as createHash5, timingSafeEqual } from "node:crypto";
48732
- import { readFile as readFile12, stat as stat6 } from "node:fs/promises";
48986
+ import { readFile as readFile13, stat as stat7 } from "node:fs/promises";
48733
48987
 
48734
48988
  // cli/src/youtube-store.ts
48735
- import { chmod as chmod4, lstat as lstat2, mkdir as mkdir9, open as open5, readFile as readFile11, readdir as readdir5, rename as rename5, unlink, writeFile as writeFile9 } from "node:fs/promises";
48736
- import { dirname as dirname8, join as join11 } from "node:path";
48989
+ import { chmod as chmod4, lstat as lstat2, mkdir as mkdir10, open as open5, readFile as readFile12, readdir as readdir6, rename as rename5, unlink, writeFile as writeFile9 } from "node:fs/promises";
48990
+ import { dirname as dirname9, join as join12 } from "node:path";
48737
48991
  import { randomUUID as randomUUID3 } from "node:crypto";
48738
48992
  function refuseInAnimator() {
48739
48993
  if (process.env.ANIMAGURU_CLOUD_WORKSPACE) throw new Error("YouTube uploads are not available in the cloud Animator. The user exports the MP4 from the draft page in their browser, then uploads it under Publishing \u2192 YouTube.");
@@ -48791,14 +49045,14 @@ var youtubeJob = external_exports.object({
48791
49045
  captions: youtubeCaptions.optional(),
48792
49046
  thumbnail: youtubeThumbnail.optional()
48793
49047
  }).strict();
48794
- var youtubeDirectory = () => join11(dirname8(credentialsPath()), "youtube");
48795
- var credentialFile = () => join11(youtubeDirectory(), "credentials.json");
49048
+ var youtubeDirectory = () => join12(dirname9(credentialsPath()), "youtube");
49049
+ var credentialFile = () => join12(youtubeDirectory(), "credentials.json");
48796
49050
  function jobFile(id2) {
48797
49051
  if (!external_exports.uuid().safeParse(id2).success) throw new Error("Use an upload ID returned by animaguru youtube uploads list.");
48798
- return join11(youtubeDirectory(), "uploads", `${id2}.json`);
49052
+ return join12(youtubeDirectory(), "uploads", `${id2}.json`);
48799
49053
  }
48800
49054
  async function privateDirectory(directory) {
48801
- await mkdir9(directory, { recursive: true, mode: 448 });
49055
+ await mkdir10(directory, { recursive: true, mode: 448 });
48802
49056
  const info = await lstat2(directory);
48803
49057
  if (info.isSymbolicLink() || !info.isDirectory()) throw new Error("YouTube configuration must use a real directory.");
48804
49058
  await chmod4(directory, 448);
@@ -48815,11 +49069,11 @@ async function existingDirectory(directory) {
48815
49069
  }
48816
49070
  async function readPrivate(file2) {
48817
49071
  try {
48818
- if (!await existingDirectory(youtubeDirectory()) || !await existingDirectory(dirname8(file2))) return null;
49072
+ if (!await existingDirectory(youtubeDirectory()) || !await existingDirectory(dirname9(file2))) return null;
48819
49073
  const info = await lstat2(file2);
48820
49074
  if (info.isSymbolicLink() || !info.isFile() || info.size > 65536) throw new Error("Invalid local YouTube state file.");
48821
49075
  await chmod4(file2, 384);
48822
- return JSON.parse(await readFile11(file2, "utf8"));
49076
+ return JSON.parse(await readFile12(file2, "utf8"));
48823
49077
  } catch (error62) {
48824
49078
  if (error62.code === "ENOENT") return null;
48825
49079
  throw new Error("Cannot read local YouTube state. Check your AnimaGuru configuration directory.");
@@ -48827,7 +49081,7 @@ async function readPrivate(file2) {
48827
49081
  }
48828
49082
  async function writePrivate(file2, value) {
48829
49083
  await privateDirectory(youtubeDirectory());
48830
- await privateDirectory(dirname8(file2));
49084
+ await privateDirectory(dirname9(file2));
48831
49085
  const temporary = `${file2}.${randomUUID3()}.tmp`;
48832
49086
  try {
48833
49087
  await writeFile9(temporary, JSON.stringify(value) + "\n", { flag: "wx", mode: 384 });
@@ -48859,11 +49113,11 @@ function youtubeJobSummary(job) {
48859
49113
  return { id: job.id, file: job.file, title: job.metadata.title, channelId: job.channelId, status: job.status, bytes: job.bytes, bytesUploaded: job.bytesUploaded, privacy: job.privacy, youtubeId: job.youtubeId, url: job.youtubeId ? `https://www.youtube.com/watch?v=${job.youtubeId}` : null, createdAt: job.createdAt, captions: job.captions ? { file: job.captions.file, language: job.captions.language, name: job.captions.name, status: job.captions.status } : null, thumbnail: job.thumbnail ? { file: job.thumbnail.file, status: job.thumbnail.status } : null };
48860
49114
  }
48861
49115
  async function localYouTubeJobs() {
48862
- const directory = join11(youtubeDirectory(), "uploads");
49116
+ const directory = join12(youtubeDirectory(), "uploads");
48863
49117
  if (!await existingDirectory(directory)) return [];
48864
49118
  let files;
48865
49119
  try {
48866
- files = await readdir5(directory);
49120
+ files = await readdir6(directory);
48867
49121
  } catch (error62) {
48868
49122
  if (error62.code === "ENOENT") return [];
48869
49123
  throw error62;
@@ -48880,10 +49134,10 @@ async function clearYouTubeData() {
48880
49134
  await unlink(credentialFile()).catch((error62) => {
48881
49135
  if (error62.code !== "ENOENT") throw error62;
48882
49136
  });
48883
- if (!await existingDirectory(join11(youtubeDirectory(), "uploads"))) return;
49137
+ if (!await existingDirectory(join12(youtubeDirectory(), "uploads"))) return;
48884
49138
  let files;
48885
49139
  try {
48886
- files = await readdir5(join11(youtubeDirectory(), "uploads"));
49140
+ files = await readdir6(join12(youtubeDirectory(), "uploads"));
48887
49141
  } catch (error62) {
48888
49142
  if (error62.code === "ENOENT") return;
48889
49143
  throw error62;
@@ -48893,7 +49147,7 @@ async function clearYouTubeData() {
48893
49147
  async function withYouTubeLock(operation) {
48894
49148
  refuseInAnimator();
48895
49149
  await privateDirectory(youtubeDirectory());
48896
- const file2 = join11(youtubeDirectory(), "operation.lock");
49150
+ const file2 = join12(youtubeDirectory(), "operation.lock");
48897
49151
  const lock = await open5(file2, "wx", 384).catch((error62) => {
48898
49152
  if (error62.code === "EEXIST") throw new Error(`Another YouTube command is running. If a previous command was killed, verify it has stopped before removing ${file2}.`);
48899
49153
  throw error62;
@@ -49041,11 +49295,11 @@ async function youtubeLogin(options) {
49041
49295
  if (existing && options.captions && !canAddCaptions(existing)) throw new Error("A YouTube channel is already connected without caption permission. Run youtube auth logout --yes, then youtube auth login --captions.");
49042
49296
  if (existing) throw new Error("A YouTube channel is already connected. Run youtube auth logout --yes before switching channels.");
49043
49297
  const file2 = options["client-file"];
49044
- const info = await stat6(file2);
49298
+ const info = await stat7(file2);
49045
49299
  if (!info.isFile() || info.size > 65536) throw new Error("Use Google's downloaded Desktop app OAuth client JSON.");
49046
49300
  let installed;
49047
49301
  try {
49048
- installed = JSON.parse(await readFile12(file2, "utf8")).installed;
49302
+ installed = JSON.parse(await readFile13(file2, "utf8")).installed;
49049
49303
  } catch {
49050
49304
  }
49051
49305
  if (!installed || typeof installed.client_id !== "string" || !installed.client_id.endsWith(".apps.googleusercontent.com") || installed.client_secret !== void 0 && typeof installed.client_secret !== "string") throw new Error("Use Google's Desktop app OAuth client JSON (installed), not a web client or API key.");
@@ -49055,8 +49309,8 @@ async function youtubeLogin(options) {
49055
49309
  let receive;
49056
49310
  let fail;
49057
49311
  let used = false;
49058
- const callback = new Promise((resolve18, reject) => {
49059
- receive = resolve18;
49312
+ const callback = new Promise((resolve19, reject) => {
49313
+ receive = resolve19;
49060
49314
  fail = reject;
49061
49315
  });
49062
49316
  void callback.catch(() => {
@@ -49094,9 +49348,9 @@ async function youtubeLogin(options) {
49094
49348
  response.end("Authorization received. Return to your AnimaGuru terminal.");
49095
49349
  receive(code);
49096
49350
  });
49097
- await new Promise((resolve18, reject) => {
49351
+ await new Promise((resolve19, reject) => {
49098
49352
  server.once("error", reject);
49099
- server.listen(0, "127.0.0.1", resolve18);
49353
+ server.listen(0, "127.0.0.1", resolve19);
49100
49354
  });
49101
49355
  const address = server.address();
49102
49356
  if (!address || typeof address === "string") throw new Error("Cannot open the local YouTube authorization listener.");
@@ -49131,7 +49385,7 @@ async function youtubeLogin(options) {
49131
49385
  clearTimeout(timer);
49132
49386
  options.signal?.removeEventListener("abort", abort);
49133
49387
  server.closeAllConnections();
49134
- await new Promise((resolve18) => server.close(() => resolve18()));
49388
+ await new Promise((resolve19) => server.close(() => resolve19()));
49135
49389
  }
49136
49390
  });
49137
49391
  }
@@ -49162,8 +49416,8 @@ async function youtubeLogout(options) {
49162
49416
 
49163
49417
  // cli/src/youtube-upload.ts
49164
49418
  import { createHash as createHash6, randomUUID as randomUUID4 } from "node:crypto";
49165
- import { open as open6, readFile as readFile13, stat as stat7 } from "node:fs/promises";
49166
- import { basename as basename5, join as join12, resolve as resolve11 } from "node:path";
49419
+ import { open as open6, readFile as readFile14, stat as stat8 } from "node:fs/promises";
49420
+ import { basename as basename5, join as join13, resolve as resolve12 } from "node:path";
49167
49421
  import { setTimeout as delay5 } from "node:timers/promises";
49168
49422
  var YOUTUBE_CHUNK_BYTES = 8 * 1024 * 1024;
49169
49423
  function safeYouTubeSession(value) {
@@ -49214,23 +49468,23 @@ async function planCaptions(root, mp4, options, fallbackLanguage) {
49214
49468
  async function readThumbnail(file2) {
49215
49469
  let info;
49216
49470
  try {
49217
- info = await stat7(file2);
49471
+ info = await stat8(file2);
49218
49472
  } catch {
49219
49473
  throw new Error(`Cannot read the thumbnail ${file2}.`);
49220
49474
  }
49221
49475
  if (!info.isFile() || info.size < 8 || info.size > 2 * 1024 * 1024) throw new Error("Use a JPEG or PNG thumbnail up to 2 MiB, such as the one npm run export:mp4 saves beside the MP4.");
49222
- const content = await readFile13(file2);
49476
+ const content = await readFile14(file2);
49223
49477
  const type = content[0] === 255 && content[1] === 216 && content[2] === 255 ? "image/jpeg" : content.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])) ? "image/png" : null;
49224
49478
  if (!type) throw new Error("Use a JPEG or PNG thumbnail.");
49225
49479
  return { content, type, sha256: createHash6("sha256").update(content).digest("hex") };
49226
49480
  }
49227
49481
  async function planThumbnail(root, mp4, choice = "auto") {
49228
49482
  if (choice === "none") return null;
49229
- let file2 = choice === "auto" ? void 0 : resolve11(root, choice);
49483
+ let file2 = choice === "auto" ? void 0 : resolve12(root, choice);
49230
49484
  if (!file2) {
49231
49485
  for (const extension of [".jpg", ".jpeg", ".png"]) {
49232
49486
  const candidate = mp4.replace(/\.mp4$/i, extension);
49233
- if (await stat7(candidate).then((info) => info.isFile(), () => false)) {
49487
+ if (await stat8(candidate).then((info) => info.isFile(), () => false)) {
49234
49488
  file2 = candidate;
49235
49489
  break;
49236
49490
  }
@@ -49415,12 +49669,12 @@ async function uploadYouTube(options) {
49415
49669
  refuseInAnimator();
49416
49670
  if (!options.yes && !options["dry-run"]) throw new Error("Pass --yes to authorize the YouTube upload, or --dry-run to inspect it locally.");
49417
49671
  if (!["yes", "no"].includes(options["made-for-kids"]) || !["yes", "no"].includes(options["synthetic-media"])) throw new Error("Set --made-for-kids yes|no and --synthetic-media yes|no explicitly.");
49418
- const root = resolve11(options.directory ?? ".");
49419
- const file2 = resolve11(root, options.file);
49672
+ const root = resolve12(options.directory ?? ".");
49673
+ const file2 = resolve12(root, options.file);
49420
49674
  const context = await localProject(root, options.animation);
49421
49675
  let config2 = {};
49422
49676
  try {
49423
- config2 = JSON.parse(await readFile13(join12(context.projectRoot, "film.config.json"), "utf8"));
49677
+ config2 = JSON.parse(await readFile14(join13(context.projectRoot, "film.config.json"), "utf8"));
49424
49678
  } catch {
49425
49679
  }
49426
49680
  const receipt = await exportReceipt(file2);
@@ -49497,8 +49751,8 @@ async function resumeYouTube(id2, options) {
49497
49751
  var listYouTubeUploads = () => withYouTubeLock(async () => ({ uploads: (await localYouTubeJobs()).map(youtubeJobSummary) }));
49498
49752
 
49499
49753
  // cli/src/motion.ts
49500
- import { access as access2, mkdir as mkdir10, writeFile as writeFile10 } from "node:fs/promises";
49501
- import { dirname as dirname9, join as join13, relative as relative2, resolve as resolve12, sep as sep2 } from "node:path";
49754
+ import { access as access3, mkdir as mkdir11, writeFile as writeFile10 } from "node:fs/promises";
49755
+ import { dirname as dirname10, join as join14, relative as relative2, resolve as resolve13, sep as sep2 } from "node:path";
49502
49756
 
49503
49757
  // shared/motion-library.ts
49504
49758
  var MOTION_CATEGORIES = [
@@ -49547,18 +49801,18 @@ async function searchMotion(client, query, flags) {
49547
49801
  if (flags.code) params.set("include_code", "true");
49548
49802
  return client.request(`/api/v1/motion-library/search?${params}`);
49549
49803
  }
49550
- var exists2 = (path) => access2(path).then(() => true, () => false);
49804
+ var exists2 = (path) => access3(path).then(() => true, () => false);
49551
49805
  async function workspaceRoot(start) {
49552
- for (let directory = start; ; directory = dirname9(directory)) {
49553
- if (await exists2(join13(directory, "animaguru.workspace.json"))) return directory;
49554
- if (dirname9(directory) === directory) return null;
49806
+ for (let directory = start; ; directory = dirname10(directory)) {
49807
+ if (await exists2(join14(directory, "animaguru.workspace.json"))) return directory;
49808
+ if (dirname10(directory) === directory) return null;
49555
49809
  }
49556
49810
  }
49557
49811
  async function writeMotionExample(example, directory, { force = false } = {}) {
49558
- const target2 = resolve12(directory);
49812
+ const target2 = resolve13(directory);
49559
49813
  const workspace3 = await workspaceRoot(target2);
49560
49814
  const inAnimation = Boolean(workspace3 && relative2(workspace3, target2).split(sep2)[0] === "animations");
49561
- const files = example.files.map((file2) => ({ path: join13(target2, safeProjectPath(file2.path)), code: inAnimation ? motionCodeForWorkspace(file2.code) : file2.code }));
49815
+ const files = example.files.map((file2) => ({ path: join14(target2, safeProjectPath(file2.path)), code: inAnimation ? motionCodeForWorkspace(file2.code) : file2.code }));
49562
49816
  if (!force) {
49563
49817
  const clashes = [];
49564
49818
  for (const [index2, file2] of files.entries()) if (index2 === 0 && await exists2(file2.path)) clashes.push(file2.path);
@@ -49570,7 +49824,7 @@ async function writeMotionExample(example, directory, { force = false } = {}) {
49570
49824
  kept.push(file2.path);
49571
49825
  continue;
49572
49826
  }
49573
- await mkdir10(dirname9(file2.path), { recursive: true });
49827
+ await mkdir11(dirname10(file2.path), { recursive: true });
49574
49828
  await writeFile10(file2.path, file2.code);
49575
49829
  written.push(file2.path);
49576
49830
  }
@@ -49578,8 +49832,8 @@ async function writeMotionExample(example, directory, { force = false } = {}) {
49578
49832
  }
49579
49833
 
49580
49834
  // cli/src/library.ts
49581
- import { mkdir as mkdir11, readFile as readFile14, writeFile as writeFile11 } from "node:fs/promises";
49582
- import { dirname as dirname10, join as join14, resolve as resolve13 } from "node:path";
49835
+ import { mkdir as mkdir12, readFile as readFile15, writeFile as writeFile11 } from "node:fs/promises";
49836
+ import { dirname as dirname11, join as join15, resolve as resolve14 } from "node:path";
49583
49837
 
49584
49838
  // shared/asset-library.ts
49585
49839
  var LIBRARY_ASSET_TYPES = ["sprite", "image", "video", "music", "speech", "sfx"];
@@ -49666,23 +49920,23 @@ function searchLibrary(client, query, flags) {
49666
49920
  var getLibraryAsset = (client, id2) => client.request(libraryPath(id2));
49667
49921
  async function downloadLibraryAsset(client, id2, output2, { force = false } = {}) {
49668
49922
  const asset = await getLibraryAsset(client, id2);
49669
- const root = resolve13(output2, asset.type === "sprite" ? safeProjectPath(asset.id) : ".");
49923
+ const root = resolve14(output2, asset.type === "sprite" ? safeProjectPath(asset.id) : ".");
49670
49924
  const written = [];
49671
49925
  for (const file2 of asset.files) {
49672
- const path = join14(root, safeProjectPath(file2.path));
49926
+ const path = join15(root, safeProjectPath(file2.path));
49673
49927
  const response = await client.request(`${libraryPath(asset.id)}/files/${file2.path.split("/").map(encodeURIComponent).join("/")}?download=1`, { raw: true, timeout: 3e5 });
49674
49928
  const bytes = new Uint8Array(await response.arrayBuffer());
49675
- await mkdir11(dirname10(path), { recursive: true });
49929
+ await mkdir12(dirname11(path), { recursive: true });
49676
49930
  await writeFile11(path, bytes, { flag: force ? "w" : "wx" }).catch((error62) => {
49677
49931
  throw error62.code === "EEXIST" ? new Error(`Already exists: ${path}. Pass --force to replace it.`) : error62;
49678
49932
  });
49679
49933
  written.push({ path, size: bytes.byteLength, mimeType: file2.mimeType });
49680
49934
  }
49681
- return { id: asset.id, type: asset.type, files: written, ...asset.type === "sprite" ? { animations: asset.details.animations?.map(({ action, sheet, data, frames, fps, loop }) => ({ action, sheet: join14(root, sheet), data: join14(root, data), frames, fps, loop })) } : {} };
49935
+ return { id: asset.id, type: asset.type, files: written, ...asset.type === "sprite" ? { animations: asset.details.animations?.map(({ action, sheet, data, frames, fps, loop }) => ({ action, sheet: join15(root, sheet), data: join15(root, data), frames, fps, loop })) } : {} };
49682
49936
  }
49683
49937
  var importLibraryAsset = (client, id2, project, name2) => client.request(`${libraryPath(id2)}/import`, { method: "POST", body: { projectId: project, ...name2 ? { name: name2 } : {} } });
49684
49938
  async function words(flags) {
49685
- const input2 = flags.file ? JSON.parse(await readFile14(flags.file, "utf8")) : {};
49939
+ const input2 = flags.file ? JSON.parse(await readFile15(flags.file, "utf8")) : {};
49686
49940
  if (!input2 || typeof input2 !== "object" || Array.isArray(input2)) throw new Error("The file must contain a JSON object.");
49687
49941
  return {
49688
49942
  ...input2,
@@ -49727,8 +49981,8 @@ async function moveWork(client, project, flags) {
49727
49981
  }
49728
49982
 
49729
49983
  // cli/src/release.ts
49730
- import { mkdir as mkdir12, readFile as readFile15, rename as rename6, unlink as unlink2, writeFile as writeFile12 } from "node:fs/promises";
49731
- import { dirname as dirname11, join as join15 } from "node:path";
49984
+ import { mkdir as mkdir13, readFile as readFile16, rename as rename6, unlink as unlink2, writeFile as writeFile12 } from "node:fs/promises";
49985
+ import { dirname as dirname12, join as join16 } from "node:path";
49732
49986
  import { randomUUID as randomUUID5 } from "node:crypto";
49733
49987
  var releaseUrl = "https://animaguru.com/cli/release.json";
49734
49988
  function versionParts(version3) {
@@ -49749,10 +50003,10 @@ function parseRelease(value) {
49749
50003
  versionParts(value.version);
49750
50004
  return { name: value.name, version: value.version };
49751
50005
  }
49752
- var cachePath = () => join15(dirname11(credentialsPath()), "cli-release.json");
50006
+ var cachePath = () => join16(dirname12(credentialsPath()), "cli-release.json");
49753
50007
  async function cachedRelease() {
49754
50008
  try {
49755
- return parseRelease(JSON.parse(await readFile15(cachePath(), "utf8")));
50009
+ return parseRelease(JSON.parse(await readFile16(cachePath(), "utf8")));
49756
50010
  } catch {
49757
50011
  return void 0;
49758
50012
  }
@@ -49768,7 +50022,7 @@ async function latestRelease(signal) {
49768
50022
  const path = cachePath();
49769
50023
  const temp = `${path}.${randomUUID5()}.tmp`;
49770
50024
  try {
49771
- await mkdir12(dirname11(path), { recursive: true, mode: 448 });
50025
+ await mkdir13(dirname12(path), { recursive: true, mode: 448 });
49772
50026
  await writeFile12(temp, JSON.stringify(release) + "\n", { flag: "wx", mode: 384 });
49773
50027
  await rename6(temp, path);
49774
50028
  } catch {
@@ -49810,8 +50064,8 @@ async function requireCurrentRelease(argv, stderr) {
49810
50064
  }
49811
50065
 
49812
50066
  // cli/src/brands.ts
49813
- import { lstat as lstat3, mkdir as mkdir13, readFile as readFile16, stat as stat8, writeFile as writeFile13 } from "node:fs/promises";
49814
- import { basename as basename6, dirname as dirname12, extname as extname2, join as join16, resolve as resolve14 } from "node:path";
50067
+ import { lstat as lstat3, mkdir as mkdir14, readFile as readFile17, stat as stat9, writeFile as writeFile13 } from "node:fs/promises";
50068
+ import { basename as basename6, dirname as dirname13, extname as extname2, join as join17, resolve as resolve15 } from "node:path";
49815
50069
 
49816
50070
  // shared/brands.ts
49817
50071
  var brandId = external_exports.string().regex(/^br_[a-f0-9]{24}$/);
@@ -49864,6 +50118,7 @@ var brandCreate = brandFields.extend({ requestId: external_exports.string().uuid
49864
50118
  var brandUpdate = external_exports.object(fieldShape).partial().strict();
49865
50119
  var brandResearch = external_exports.object({ requestId: external_exports.string().uuid(), workspace, url: brandWebsite, name: external_exports.string().trim().min(1).max(120).optional() }).strict();
49866
50120
  var brandRefresh = external_exports.object({ requestId: external_exports.string().uuid(), url: brandWebsite.optional() }).strict();
50121
+ var brandTransfer = external_exports.object({ workspace }).strict();
49867
50122
  var DEFAULT_BRAND_COLORS = { primary: "#2563eb", background: "#ffffff", text: "#111827", accent: null, palette: [] };
49868
50123
  var DEFAULT_BRAND_FONTS = { heading: { family: "Inter, sans-serif", file: null }, body: { family: "Inter, sans-serif", file: null } };
49869
50124
  var brandDraftFields = brandFields.extend({
@@ -49930,7 +50185,7 @@ async function listBrands(client, selected) {
49930
50185
  }
49931
50186
  var getBrand = (client, id2) => client.request(brandPath(id2));
49932
50187
  async function jsonObject(file2) {
49933
- const value = JSON.parse(await readFile16(file2, "utf8"));
50188
+ const value = JSON.parse(await readFile17(file2, "utf8"));
49934
50189
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("The file must contain a JSON object.");
49935
50190
  return value;
49936
50191
  }
@@ -49957,12 +50212,12 @@ async function uploadBrandFile(client, id2, file2, flags) {
49957
50212
  const path = brandPath(id2);
49958
50213
  if (!flags.yes) throw new Error("Pass --yes to authorize uploading this file to the brand.");
49959
50214
  if (flags.kind && !BRAND_FILE_KINDS.includes(flags.kind)) throw new Error("Unknown brand file kind.");
49960
- const info = await stat8(file2);
50215
+ const info = await stat9(file2);
49961
50216
  if (!info.isFile() || !info.size || info.size > BRAND_FILE_BYTES) throw new Error("A brand file must be between 1 byte and 32 MiB.");
49962
50217
  const extension = extname2(file2).slice(1).toLowerCase().replace(/^jpeg$/, "jpg");
49963
50218
  const mimeType = flags["mime-type"] ?? Object.entries(BRAND_FILE_TYPES).find(([, ext]) => ext === extension)?.[0];
49964
50219
  if (!mimeType || !BRAND_FILE_TYPES[mimeType]) throw new Error("Unsupported brand file type. Use a web image, font, audio or video file, or specify --mime-type.");
49965
- const bytes = new Uint8Array(await readFile16(file2));
50220
+ const bytes = new Uint8Array(await readFile17(file2));
49966
50221
  if (!bytes.byteLength || bytes.byteLength > BRAND_FILE_BYTES) throw new Error("A brand file must be between 1 byte and 32 MiB.");
49967
50222
  const form = new FormData();
49968
50223
  form.set("file", new File([bytes], basename6(file2), { type: mimeType }));
@@ -49980,22 +50235,22 @@ async function downloadBrand(client, id2, output2, flags = {}) {
49980
50235
  `, mimeType: "application/json" },
49981
50236
  { path: "branding/brand-guide.md", content: brandGuide(brand), mimeType: "text/markdown" }
49982
50237
  ];
49983
- const root = resolve14(output2);
49984
- const targets = [...textFiles.map((file2) => file2.path), ...files.map(brandFilePath)].map((path) => join16(root, safeProjectPath(path)));
50238
+ const root = resolve15(output2);
50239
+ const targets = [...textFiles.map((file2) => file2.path), ...files.map(brandFilePath)].map((path) => join17(root, safeProjectPath(path)));
49985
50240
  if (new Set(targets).size !== targets.length) throw new Error("The brand contains conflicting file names.");
49986
- for (const path of [join16(root, "branding"), ...targets]) {
50241
+ for (const path of [join17(root, "branding"), ...targets]) {
49987
50242
  const info = await lstat3(path).catch((error62) => {
49988
50243
  if (error62.code !== "ENOENT") throw error62;
49989
50244
  return null;
49990
50245
  });
49991
50246
  if (info?.isSymbolicLink()) throw new Error(`Refusing to write through a symbolic link: ${path}`);
49992
- if (path !== join16(root, "branding") && info && (!flags.force || !info.isFile())) throw new Error(`Already exists: ${path}. Pass --force to replace files.`);
50247
+ if (path !== join17(root, "branding") && info && (!flags.force || !info.isFile())) throw new Error(`Already exists: ${path}. Pass --force to replace files.`);
49993
50248
  }
49994
- await mkdir13(join16(root, "branding"), { recursive: true });
50249
+ await mkdir14(join17(root, "branding"), { recursive: true });
49995
50250
  const written = [];
49996
50251
  const save = async (path, bytes, mimeType) => {
49997
- const target2 = join16(root, safeProjectPath(path));
49998
- await mkdir13(dirname12(target2), { recursive: true });
50252
+ const target2 = join17(root, safeProjectPath(path));
50253
+ await mkdir14(dirname13(target2), { recursive: true });
49999
50254
  await writeFile13(target2, bytes, { flag: flags.force ? "w" : "wx" });
50000
50255
  written.push({ path: target2, size: bytes.byteLength, mimeType });
50001
50256
  };
@@ -50008,8 +50263,8 @@ async function downloadBrand(client, id2, output2, flags = {}) {
50008
50263
  }
50009
50264
 
50010
50265
  // cli/src/review-check.ts
50011
- import { readFile as readFile17, readdir as readdir6, stat as stat9, writeFile as writeFile14 } from "node:fs/promises";
50012
- import { join as join17 } from "node:path";
50266
+ import { readFile as readFile18, readdir as readdir7, stat as stat10, writeFile as writeFile14 } from "node:fs/promises";
50267
+ import { join as join18 } from "node:path";
50013
50268
 
50014
50269
  // shared/frame-checks.ts
50015
50270
  var FRAME_CHECK_MAX_FRAMES = 12;
@@ -50053,17 +50308,17 @@ var frameCheckRequest = external_exports.object({
50053
50308
  // cli/src/review-check.ts
50054
50309
  async function framesToCheck(projectRoot, source, report2) {
50055
50310
  if (source === "frames") {
50056
- const folder2 = join17(projectRoot, "production/review/frames");
50057
- const names2 = await readdir6(folder2).catch(() => []);
50058
- const frames = names2.map((name2) => /^frame-([\d.]+)s\.jpg$/.exec(name2)).filter((match) => match !== null).map((match) => ({ file: join17(folder2, match[0]), time: Number(match[1]) })).sort((a, b) => a.time - b.time);
50311
+ const folder2 = join18(projectRoot, "production/review/frames");
50312
+ const names2 = await readdir7(folder2).catch(() => []);
50313
+ const frames = names2.map((name2) => /^frame-([\d.]+)s\.jpg$/.exec(name2)).filter((match) => match !== null).map((match) => ({ file: join18(folder2, match[0]), time: Number(match[1]) })).sort((a, b) => a.time - b.time);
50059
50314
  if (!frames.length) throw new Error("No full-size frames found. Run npm run frames -- --full (with --at TIMES for the moments to check) first.");
50060
50315
  return frames;
50061
50316
  }
50062
- const folder = join17(projectRoot, "production/review");
50317
+ const folder = join18(projectRoot, "production/review");
50063
50318
  const times = [...report2.seekMatches ?? []].map((match) => match.time).sort((a, b) => a - b);
50064
- const names = (await readdir6(folder)).filter((name2) => /^frame-\d{2}\.jpg$/.test(name2)).sort();
50319
+ const names = (await readdir7(folder)).filter((name2) => /^frame-\d{2}\.jpg$/.test(name2)).sort();
50065
50320
  if (!names.length || names.length !== times.length) throw new Error("The review frames do not match report.json. Run npm run review again.");
50066
- return names.map((name2, index2) => ({ file: join17(folder, name2), time: times[index2] }));
50321
+ return names.map((name2, index2) => ({ file: join18(folder, name2), time: times[index2] }));
50067
50322
  }
50068
50323
  function filmContext(report2, brief, extra) {
50069
50324
  const config2 = report2.config ?? {};
@@ -50078,29 +50333,29 @@ function compact(answers) {
50078
50333
  async function reviewCheck(client, options) {
50079
50334
  if (!options.yes && !options["dry-run"]) throw new Error("Pass --yes to authorize spending credits on the check, or --dry-run for its most.");
50080
50335
  const context = await localProject(options.directory, options.animation);
50081
- const reportFile = join17(context.projectRoot, "production/review/report.json");
50082
- const report2 = JSON.parse(await readFile17(reportFile, "utf8").catch(() => {
50336
+ const reportFile = join18(context.projectRoot, "production/review/report.json");
50337
+ const report2 = JSON.parse(await readFile18(reportFile, "utf8").catch(() => {
50083
50338
  throw new Error("Run npm run review first: production/review/report.json is missing.");
50084
50339
  }));
50085
50340
  const frames = await framesToCheck(context.projectRoot, options.source ?? "review", report2);
50086
50341
  if (frames.length > FRAME_CHECK_MAX_FRAMES) throw new Error(`Check at most ${FRAME_CHECK_MAX_FRAMES} frames at once; run npm run frames -- --full --at with fewer times.`);
50087
- const sizes = await Promise.all(frames.map(async (frame) => (await stat9(frame.file)).size));
50342
+ const sizes = await Promise.all(frames.map(async (frame) => (await stat10(frame.file)).size));
50088
50343
  if (sizes.reduce((sum, size) => sum + size, 0) > FRAME_CHECK_TOTAL_BYTES) throw new Error("These frames total over 8 MiB. Check fewer at once.");
50089
- const questions = options.questions ? frameCheckQuestions.parse(JSON.parse(await readFile17(options.questions, "utf8"))) : void 0;
50090
- const brief = await readFile17(join17(context.projectRoot, "production/brief.md"), "utf8").catch(() => "");
50344
+ const questions = options.questions ? frameCheckQuestions.parse(JSON.parse(await readFile18(options.questions, "utf8"))) : void 0;
50345
+ const brief = await readFile18(join18(context.projectRoot, "production/brief.md"), "utf8").catch(() => "");
50091
50346
  const link2 = await cloudLink(context.toolsRoot).catch(() => null);
50092
50347
  const body = {
50093
50348
  ...link2?.origin === client.origin ? { projectId: link2.projectId } : { workspace: await selectedWorkspace(client.origin) },
50094
50349
  context: filmContext(report2, brief, options.context),
50095
50350
  ...report2.config?.duration ? { duration: report2.config.duration } : {},
50096
- frames: await Promise.all(frames.map(async (frame) => ({ time: Math.round(frame.time * 1e3) / 1e3, image: `data:image/jpeg;base64,${(await readFile17(frame.file)).toString("base64")}` }))),
50351
+ frames: await Promise.all(frames.map(async (frame) => ({ time: Math.round(frame.time * 1e3) / 1e3, image: `data:image/jpeg;base64,${(await readFile18(frame.file)).toString("base64")}` }))),
50097
50352
  ...questions ? { questions } : {},
50098
50353
  ...options["dry-run"] ? { dryRun: true } : {}
50099
50354
  };
50100
50355
  if (options["dry-run"]) return client.request("/api/v1/frame-checks", { method: "POST", body, timeout: 12e4 });
50101
50356
  const result = await client.request("/api/v1/frame-checks", { method: "POST", body, timeout: 12e4 });
50102
50357
  const fileAt = (time3) => frames.find((frame) => Math.abs(frame.time - time3) < 1e-3)?.file.slice(context.projectRoot.length + 1);
50103
- const saved = join17(context.projectRoot, "production/review/check.json");
50358
+ const saved = join18(context.projectRoot, "production/review/check.json");
50104
50359
  await writeFile14(saved, `${JSON.stringify({ ...result, frames: result.frames.map((frame) => ({ file: fileAt(frame.time), ...frame })) }, null, 2)}
50105
50360
  `);
50106
50361
  return {
@@ -50114,8 +50369,8 @@ async function reviewCheck(client, options) {
50114
50369
 
50115
50370
  // cli/src/reference.ts
50116
50371
  import { openAsBlob } from "node:fs";
50117
- import { mkdir as mkdir14, stat as stat10, writeFile as writeFile15 } from "node:fs/promises";
50118
- import { basename as basename7, dirname as dirname13, extname as extname3, join as join18, relative as relative3, resolve as resolve15 } from "node:path";
50372
+ import { mkdir as mkdir15, stat as stat11, writeFile as writeFile15 } from "node:fs/promises";
50373
+ import { basename as basename7, dirname as dirname14, extname as extname3, join as join19, relative as relative3, resolve as resolve16 } from "node:path";
50119
50374
 
50120
50375
  // shared/video-analysis.ts
50121
50376
  var VIDEO_ANALYSIS_MAX_BYTES = 100 * 1024 * 1024;
@@ -50146,11 +50401,11 @@ var TYPES = { ".mp4": "video/mp4", ".m4v": "video/mp4", ".mov": "video/quicktime
50146
50401
  var POLL_MS = 1e4;
50147
50402
  var WAIT_LIMIT_MS = 70 * 6e4;
50148
50403
  async function briefFile(options, name2) {
50149
- if (options.out) return resolve15(options.out);
50404
+ if (options.out) return resolve16(options.out);
50150
50405
  const slug2 = name2.replace(/\.[a-z0-9]{2,4}$/i, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60) || "reference";
50151
50406
  const project = await localProject(options.directory ?? ".", options.animation).catch(() => null);
50152
- const film = project && await stat10(join18(project.projectRoot, "animaguru.project.json")).then(() => true, () => false);
50153
- return film ? join18(project.projectRoot, "production/references", `${slug2}.json`) : resolve15(options.directory ?? ".", `${slug2}.analysis.json`);
50407
+ const film = project && await stat11(join19(project.projectRoot, "animaguru.project.json")).then(() => true, () => false);
50408
+ return film ? join19(project.projectRoot, "production/references", `${slug2}.json`) : resolve16(options.directory ?? ".", `${slug2}.analysis.json`);
50154
50409
  }
50155
50410
  var pause = (ms, signal) => new Promise((done, fail) => {
50156
50411
  signal?.throwIfAborted();
@@ -50189,7 +50444,7 @@ async function report(analysis, options) {
50189
50444
  if (analysis.status === "failed") throw new Error(analysis.error ?? "The analysis failed.");
50190
50445
  if (analysis.status !== "completed") return { id: analysis.id, status: analysis.status, next: `animaguru reference get ${analysis.id} --wait` };
50191
50446
  const file2 = await briefFile(options, sourceName(analysis));
50192
- await mkdir14(dirname13(file2), { recursive: true });
50447
+ await mkdir15(dirname14(file2), { recursive: true });
50193
50448
  await writeFile15(file2, `${JSON.stringify(analysis, null, 2)}
50194
50449
  `);
50195
50450
  const brief = analysis.result?.brief;
@@ -50214,8 +50469,8 @@ async function referenceAnalyze(client, options) {
50214
50469
  if (!options.video.startsWith("https://")) throw new Error("Use an HTTPS link to the video file.");
50215
50470
  started = await client.request("/api/v1/video-analyses", { method: "POST", body: { ...settings2, url: options.video, ...dryRun ? { dryRun: true } : {} }, timeout: 12e4 });
50216
50471
  } else {
50217
- const file2 = resolve15(options.video);
50218
- const info = await stat10(file2).catch(() => null);
50472
+ const file2 = resolve16(options.video);
50473
+ const info = await stat11(file2).catch(() => null);
50219
50474
  if (!info?.isFile()) throw new Error(`No video file at ${options.video}. Give a file path, or an HTTPS link to the video file itself.`);
50220
50475
  const type = TYPES[extname3(file2).toLowerCase()];
50221
50476
  if (!type) throw new Error("Analyse an MP4, MOV or WebM video.");
@@ -50237,8 +50492,8 @@ async function referenceGet(client, id2, options) {
50237
50492
 
50238
50493
  // cli/src/curl.ts
50239
50494
  import { createWriteStream as createWriteStream2, openAsBlob as openAsBlob2 } from "node:fs";
50240
- import { readFile as readFile18, unlink as unlink3 } from "node:fs/promises";
50241
- import { extname as extname4, resolve as resolve16 } from "node:path";
50495
+ import { readFile as readFile19, unlink as unlink3 } from "node:fs/promises";
50496
+ import { extname as extname4, resolve as resolve17 } from "node:path";
50242
50497
  import { Readable as Readable2 } from "node:stream";
50243
50498
  import { pipeline as pipeline2 } from "node:stream/promises";
50244
50499
  function curlUrl(server, target2) {
@@ -50278,7 +50533,7 @@ var readStdin = async () => {
50278
50533
  };
50279
50534
  async function bodyValue(value, io) {
50280
50535
  if (value === "@-") return (io.stdin ?? readStdin)();
50281
- return value.startsWith("@") ? readFile18(resolve16(value.slice(1))) : value;
50536
+ return value.startsWith("@") ? readFile19(resolve17(value.slice(1))) : value;
50282
50537
  }
50283
50538
  var parsesAsJson = (body) => {
50284
50539
  try {
@@ -50294,7 +50549,7 @@ async function curl(options, io) {
50294
50549
  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.");
50295
50550
  let body;
50296
50551
  if (options.uploadFile !== void 0) {
50297
- const file2 = resolve16(options.uploadFile);
50552
+ const file2 = resolve17(options.uploadFile);
50298
50553
  body = await openAsBlob2(file2);
50299
50554
  if (!headers.has("content-type")) headers.set("Content-Type", UPLOAD_TYPES[extname4(file2).toLowerCase()] ?? "application/octet-stream");
50300
50555
  } else if (options.json !== void 0) {
@@ -50314,7 +50569,7 @@ async function curl(options, io) {
50314
50569
  const type = response.headers.get("content-type") ?? "";
50315
50570
  const failed = options.fail && response.status >= 400;
50316
50571
  if (options.output !== void 0 && !failed) {
50317
- const file2 = resolve16(options.output);
50572
+ const file2 = resolve17(options.output);
50318
50573
  let bytes = 0;
50319
50574
  try {
50320
50575
  await pipeline2(
@@ -50344,7 +50599,7 @@ async function curl(options, io) {
50344
50599
  }
50345
50600
 
50346
50601
  // cli/src/commands.ts
50347
- var assetModelIds = [...SPRITE_VIDEO_MODEL_IDS, ...MUSIC_MODELS.map((m) => m.id), ...SPEECH_MODELS.map((m) => m.id), ...SFX_MODELS.map((m) => m.id), ...VECTOR_MODELS.map((m) => m.id)];
50602
+ var assetModelIds = [...SPRITE_VIDEO_MODEL_IDS, ...MUSIC_MODELS.map((m) => m.id), ...SPEECH_MODELS.map((m) => m.id), ...SFX_MODELS.map((m) => m.id), ...VECTOR_MODELS.map((m) => m.id), ...MODEL_GENERATOR_IDS];
50348
50603
  var assetAuthorizationFlags = [
50349
50604
  { name: "yes", type: "boolean", description: "Authorize spending credits" },
50350
50605
  { name: "dry-run", type: "boolean", description: "Validate and estimate locally without submitting a paid job" }
@@ -50361,9 +50616,10 @@ var catalogSections = {
50361
50616
  music: ["assetTypes", "musicModels"],
50362
50617
  speech: ["assetTypes", "speechModels", "voices", "elevenLabsVoices"],
50363
50618
  sfx: ["assetTypes", "sfxModels"],
50619
+ model: ["assetTypes", "modelGenerators", "modelAnimations"],
50364
50620
  avatars: ["avatarTypes", "avatarLookTypes"]
50365
50621
  };
50366
- var catalog = () => ({ styles: SPRITE_STYLES, kinds: SPRITE_KINDS, actions: ALL_SPRITE_ACTIONS, videoModels: SPRITE_VIDEO_MODEL_IDS, assetTypes: ASSET_TYPES, musicModels: MUSIC_MODELS.map((m) => m.id), speechModels: SPEECH_MODELS.map((m) => m.id), sfxModels: SFX_MODELS.map((m) => m.id), vectorModels: VECTOR_MODELS.map((m) => m.id), vectorOptions: { workflows: VECTOR_WORKFLOWS, qualities: VECTOR_QUALITIES, aspectRatios: VECTOR_ASPECT_RATIOS, shapes: VECTOR_SHAPES, maxReferences: MAX_VECTOR_REFERENCES }, elevenLabsVoices: ELEVENLABS_VOICES, voices: SPEECH_VOICES, videoProfiles: SPRITE_VIDEO_MODELS, avatarTypes: AVATAR_CREATE_TYPES, avatarLookTypes: AVATAR_LOOK_TYPES });
50622
+ var catalog = () => ({ styles: SPRITE_STYLES, kinds: SPRITE_KINDS, actions: ALL_SPRITE_ACTIONS, videoModels: SPRITE_VIDEO_MODEL_IDS, assetTypes: ASSET_TYPES, musicModels: MUSIC_MODELS.map((m) => m.id), speechModels: SPEECH_MODELS.map((m) => m.id), sfxModels: SFX_MODELS.map((m) => m.id), modelGenerators: MODEL_GENERATORS.map(({ id: id2, label: label3, faces }) => ({ id: id2, label: label3, faces })), modelAnimations: MODEL_ANIMATIONS, vectorModels: VECTOR_MODELS.map((m) => m.id), vectorOptions: { workflows: VECTOR_WORKFLOWS, qualities: VECTOR_QUALITIES, aspectRatios: VECTOR_ASPECT_RATIOS, shapes: VECTOR_SHAPES, maxReferences: MAX_VECTOR_REFERENCES }, elevenLabsVoices: ELEVENLABS_VOICES, voices: SPEECH_VOICES, videoProfiles: SPRITE_VIDEO_MODELS, avatarTypes: AVATAR_CREATE_TYPES, avatarLookTypes: AVATAR_LOOK_TYPES });
50367
50623
  var modelFlags = [
50368
50624
  { name: "video-model", type: "string", choices: SPRITE_VIDEO_MODEL_IDS, description: "Video model override for this operation" }
50369
50625
  ];
@@ -50394,12 +50650,12 @@ var workspaceQuery = async (client) => {
50394
50650
  async function openBrowser(url3) {
50395
50651
  const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32" : "xdg-open";
50396
50652
  const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url3] : [url3];
50397
- await new Promise((resolve18) => {
50653
+ await new Promise((resolve19) => {
50398
50654
  const child = spawn3(command, args, { stdio: "ignore", detached: true });
50399
- child.once("error", () => resolve18());
50655
+ child.once("error", () => resolve19());
50400
50656
  child.once("spawn", () => {
50401
50657
  child.unref();
50402
- resolve18();
50658
+ resolve19();
50403
50659
  });
50404
50660
  });
50405
50661
  }
@@ -50425,7 +50681,10 @@ function createCli() {
50425
50681
  { name: "thumbnail", type: "string", default: "auto", description: "JPEG or PNG thumbnail up to 2 MiB to set after the upload; auto uses FILM.jpg beside FILM.mp4; none skips" },
50426
50682
  { name: "yes", type: "boolean", description: "Authorize sending this video, its subtitles and thumbnail to the selected channel and privacy; confirm upload rights" },
50427
50683
  { name: "dry-run", type: "boolean", description: "Inspect MP4 and metadata locally without Google authorization or network calls" }
50428
- ).action(async ({ args, flags, stdout, stderr, signal }) => out(stdout, await uploadYouTube({ ...flags, directory: args.directory, log: stderr, signal }))))).command("uploads", (uploads) => uploads.add(defineCommand("list", { description: "List local upload receipts without tokens or session URLs" }, (command) => command.action(async ({ stdout }) => out(stdout, await listYouTubeUploads())))).add(defineCommand("resume", { description: "Resume the same local MP4 and Google upload session after an interruption" }, (command) => command.args({ name: "id", type: "string", required: true, description: "Upload ID returned by youtube uploads list" }).flags({ name: "yes", type: "boolean", description: "Authorize resuming the saved upload and visibility" }).action(async ({ args, flags, stdout, stderr, signal }) => out(stdout, await resumeYouTube(args.id, { ...flags, log: stderr, signal }))))))).add(defineCommand("init", { description: "Install or refresh the AnimaGuru agent skill in the current folder" }, (command) => command.action(async ({ stdout }) => out(stdout, await initializeSkill())))).add(defineCommand("templates", { description: "List video categories and available starter templates" }, (command) => command.action(({ stdout }) => out(stdout, listTemplates())))).command("review", (review) => review.add(defineCommand("check", { description: "Check a film's review frames for visual faults with AnimaGuru's frame checker (Clef-omni): empty frames, cut-off or overlapping text, broken letters, hard-to-read text and a polish score. Run npm run review first; open the flagged frames first. Charges a fraction of a credit per frame" }, (command) => command.args({ name: "directory", type: "string", default: ".", description: "Video workspace or animation folder" }).flags(
50684
+ ).action(async ({ args, flags, stdout, stderr, signal }) => out(stdout, await uploadYouTube({ ...flags, directory: args.directory, log: stderr, signal }))))).command("uploads", (uploads) => uploads.add(defineCommand("list", { description: "List local upload receipts without tokens or session URLs" }, (command) => command.action(async ({ stdout }) => out(stdout, await listYouTubeUploads())))).add(defineCommand("resume", { description: "Resume the same local MP4 and Google upload session after an interruption" }, (command) => command.args({ name: "id", type: "string", required: true, description: "Upload ID returned by youtube uploads list" }).flags({ name: "yes", type: "boolean", description: "Authorize resuming the saved upload and visibility" }).action(async ({ args, flags, stdout, stderr, signal }) => out(stdout, await resumeYouTube(args.id, { ...flags, log: stderr, signal }))))))).add(defineCommand("init", { description: "Install or refresh the core AnimaGuru agent skill in the current folder; skills install adds every skill" }, (command) => command.action(async ({ stdout }) => out(stdout, await initializeSkill())))).command("skills", (skills) => skills.add(defineCommand("install", { description: "Install the latest AnimaGuru and PixiJS agent skills into a folder for Claude Code (.claude/skills) and Codex (.agents/skills), with npx skills; npx skills update refreshes them" }, (command) => command.flags(
50685
+ { name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" }, description: "Server origin whose skills to install" },
50686
+ { name: "dir", type: "string", default: ".", description: "Folder to install into (created if missing)" }
50687
+ ).action(async ({ flags, stdout, stderr, signal }) => out(stdout, await installSkills({ url: flags.url, dir: flags.dir, signal, log: stderr })))))).add(defineCommand("templates", { description: "List video categories and available starter templates" }, (command) => command.action(({ stdout }) => out(stdout, listTemplates())))).command("review", (review) => review.add(defineCommand("check", { description: "Check a film's review frames for visual faults with AnimaGuru's frame checker (Clef-omni): empty frames, cut-off or overlapping text, broken letters, hard-to-read text and a polish score. Run npm run review first; open the flagged frames first. Charges a fraction of a credit per frame" }, (command) => command.args({ name: "directory", type: "string", default: ".", description: "Video workspace or animation folder" }).flags(
50429
50688
  { name: "animation", type: "string", description: "Animation in a multi-animation workspace" },
50430
50689
  { name: "source", type: "string", choices: ["review", "frames"], default: "review", description: "review: npm run review's five frames; frames: the full-size frames of npm run frames -- --full" },
50431
50690
  { name: "context", type: "string", description: "What the film should show, added to its title, format and brief" },
@@ -50471,6 +50730,10 @@ function createCli() {
50471
50730
  { name: "poster", type: "string", description: "PNG, JPEG or WebP cover image" },
50472
50731
  { name: "subtitles", type: "string", default: "auto", description: "MP4 subtitle file (.vtt or .srt) the player shows over the picture; auto uses FILM.vtt or FILM.srt beside FILM.mp4; none skips" },
50473
50732
  { 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" },
50733
+ { name: "showcase", type: "string", choices: [...SHOWCASE_SECTORS], description: "Admins: add the video to the showcase under this sector; it shows on lead pages once public and approved" },
50734
+ { name: "showcase-tags", type: "string", description: `Admins: what the film is for, comma-separated: ${SHOWCASE_TAGS.join(", ")}` },
50735
+ { name: "showcase-language", type: "string", choices: ["en", "tr", "sv", "none"], description: "Admins: the language the film speaks or shows; none for music and pictures only (default)" },
50736
+ { name: "showcase-position", type: "number", description: "Admins: order in the showcase, lower first (default 0)" },
50474
50737
  { name: "yes", type: "boolean", description: "Authorize upload and chosen sharing" },
50475
50738
  { name: "dry-run", type: "boolean", description: "Inspect locally without uploading" }
50476
50739
  ).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(
@@ -50592,7 +50855,7 @@ Confirm code: ${device.user_code}`);
50592
50855
  })))).add(defineCommand("credits", { description: "Show the selected workspace's remaining credit balance" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" }, description: "Server origin" }).action(async ({ flags, stdout, signal }) => {
50593
50856
  const client = new Client(flags.url, signal);
50594
50857
  out(stdout, await client.request(`/api/v1/credits${await workspaceQuery(client)}`));
50595
- }))).add(defineCommand("catalog", { description: "List styles, kinds, actions, asset types, models, video settings, speech voices and avatar types" }, (command) => command.flags({ name: "type", type: "string", choices: Object.keys(catalogSections), description: "Only one part: sprites, image, vector, video, music, speech, sfx or avatars" }).action(({ flags, stdout }) => {
50858
+ }))).add(defineCommand("catalog", { description: "List styles, kinds, actions, asset types, models, video settings, speech voices and avatar types" }, (command) => command.flags({ name: "type", type: "string", choices: Object.keys(catalogSections), description: "Only one part: sprites, image, vector, video, music, speech, sfx, model or avatars" }).action(({ flags, stdout }) => {
50596
50859
  const full = catalog();
50597
50860
  const section = flags.type;
50598
50861
  out(stdout, section ? Object.fromEntries(catalogSections[section].map((key) => [key, full[key]])) : full);
@@ -50659,7 +50922,7 @@ Confirm code: ${device.user_code}`);
50659
50922
  ).action(async ({ args, flags, stdout, signal }) => out(stdout, await moveWork(new Client(flags.url, signal), args.id, flags))))).add(defineCommand("delete", { description: "Delete a project; requires --yes" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" }, description: "Server origin" }).args(id).flags({ name: "yes", type: "boolean", required: true }).action(async ({ args, flags, stdout, signal }) => {
50660
50923
  if (!flags.yes) throw new Error("Pass --yes to delete this project.");
50661
50924
  out(stdout, await new Client(flags.url, signal).request(`/api/v1/projects/${encodeURIComponent(args.id)}`, { method: "DELETE" }));
50662
- })))).command("assets", (assets) => assets.add(defineCommand("list", { description: "List saved assets, their blob hashes, and configured generation models" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } }).args(id).action(async ({ args, flags, stdout, signal }) => out(stdout, await new Client(flags.url, signal).request(`${projectPath(args.id)}/assets`))))).add(defineCommand("generate", { description: "Generate an image, vector (SVG), video, music, speech or sound-effect asset; --yes authorizes credits" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } }).args(id).flags(
50925
+ })))).command("assets", (assets) => assets.add(defineCommand("list", { description: "List saved assets, their blob hashes, and configured generation models" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } }).args(id).action(async ({ args, flags, stdout, signal }) => out(stdout, await new Client(flags.url, signal).request(`${projectPath(args.id)}/assets`))))).add(defineCommand("generate", { description: "Generate an image, vector (SVG), video, music, speech, sound-effect or 3D model asset; --yes authorizes credits" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } }).args(id).flags(
50663
50926
  { name: "type", type: "string", required: true, choices: ASSET_TYPES },
50664
50927
  { name: "name", type: "string", required: true },
50665
50928
  { name: "prompt", type: "string", description: "Description to generate, or verbatim transcript for speech; video lip-sync and relight and vector image-to-vector take none, and camera-controls, insert-video, recast and 3d-to-video may leave it out" },
@@ -50669,7 +50932,7 @@ Confirm code: ${device.user_code}`);
50669
50932
  { 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. Vector from text: up to 5, the first leading. 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)" },
50670
50933
  { 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" },
50671
50934
  { name: "workflow", type: "string", choices: [...VIDEO_WORKFLOWS, ...VECTOR_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. Vector: text-to-vector (default) or image-to-vector, which traces --source" },
50672
- { 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" },
50935
+ { 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. Model: the image or sprite art to build it from (image-to-3D); without one the prompt describes it" },
50673
50936
  { name: "end-reference", type: "string", description: "Video: ending image blob SHA; models with endFrameOnly accept it without --reference" },
50674
50937
  { name: "middle-reference", type: "string", description: "Reference-to-video: image blob SHA reached at --middle-seconds; needs --reference and --end-reference, 480p or 768p" },
50675
50938
  { name: "middle-seconds", type: "number", description: "Reference-to-video: when the middle image is reached, in seconds" },
@@ -50698,6 +50961,12 @@ Confirm code: ${device.user_code}`);
50698
50961
  { name: "voice", type: "string", description: "Gemini voice name or ElevenLabs voice ID (defaults: Kore / George)" },
50699
50962
  { name: "delivery", type: "string", description: "Gemini speech delivery instructions; ElevenLabs uses audio tags in the transcript" },
50700
50963
  { name: "loop", type: "boolean", description: "SFX: create a seamless loop" },
50964
+ { name: "texture", type: "boolean", description: "Model: textured (default on); --no-texture makes a plain white model" },
50965
+ { name: "pbr", type: "boolean", description: "Model: metallic, roughness and normal maps with the texture (default on); --no-pbr leaves them out" },
50966
+ { name: "faces", type: "number", description: "Model: target face count; Tripo H3.1 1,000\u20132,000,000, Hunyuan3D 40,000\u20131,500,000 (charged extra). Rigged models take at most 300,000" },
50967
+ { name: "rig", type: "boolean", description: "Model: auto-rig a humanoid character standing in a T- or A-pose, with animation clips" },
50968
+ { name: "animations", type: "string", description: "Model with --rig: up to 10 comma-separated clips, by name (catalog --type model) or Meshy library number 0\u2013696; default idle,walk" },
50969
+ { name: "height", type: "number", description: "Model with --rig: the character's height in metres (default 1.7)" },
50701
50970
  { name: "prompt-influence", type: "number", description: "SFX: prompt adherence, 0\u20131 (default 0.3)" },
50702
50971
  { name: "quality", type: "string", choices: VECTOR_QUALITIES, description: "Vector from text: low (fastest, 13 credits), medium (default, 16) or high (20, or 32 with --references)" },
50703
50972
  { name: "shapes", type: "string", choices: VECTOR_SHAPES, description: "Vector image-to-vector (10 credits): detailed (default) keeps fine detail, simple uses fewer, simpler shapes" },
@@ -50746,7 +51015,7 @@ Confirm code: ${device.user_code}`);
50746
51015
  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" }));
50747
51016
  })))).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 }) => {
50748
51017
  if (!flags.yes) throw new Error("Pass --yes to authorize spending credits.");
50749
- const body = spriteJobBody.parse({ ...JSON.parse(await readFile19(flags.file, "utf8")), ...selectedModels(flags) });
51018
+ const body = spriteJobBody.parse({ ...JSON.parse(await readFile20(flags.file, "utf8")), ...selectedModels(flags) });
50750
51019
  out(stdout, await new Client(flags.url, signal).request(`${projectPath(args.id)}/jobs`, { method: "POST", body }));
50751
51020
  }))).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 }) => {
50752
51021
  const body = selectedModels(flags);
@@ -50761,7 +51030,7 @@ Confirm code: ${device.user_code}`);
50761
51030
  const extension = flags.file.split(".").at(-1)?.toLowerCase();
50762
51031
  const type = extension === "png" ? "image/png" : extension === "jpg" || extension === "jpeg" ? "image/jpeg" : extension === "webp" ? "image/webp" : void 0;
50763
51032
  if (!type) throw new Error("Use a PNG, JPEG or WebP image.");
50764
- const bytes = await readFile19(flags.file);
51033
+ const bytes = await readFile20(flags.file);
50765
51034
  if (bytes.length > 10 * 1024 * 1024) throw new Error("Image must be at most 10 MB.");
50766
51035
  const form = new FormData();
50767
51036
  form.set("image", new Blob([bytes], { type }), `reference.${extension}`);
@@ -50777,8 +51046,8 @@ Confirm code: ${device.user_code}`);
50777
51046
  if (!file2) throw new Error(`No file at ${args.path} in this project. List them with animaguru files list.`);
50778
51047
  if (!/^[a-f0-9]{64}$/.test(file2.sha)) throw new Error("Invalid blob hash in manifest.");
50779
51048
  const response = await client.request(`/api/v1/blobs/${encodeURIComponent(manifest.ownerId)}/${file2.sha}`, { raw: true });
50780
- const path = resolve17(flags.output);
50781
- await mkdir15(dirname14(path), { recursive: true });
51049
+ const path = resolve18(flags.output);
51050
+ await mkdir16(dirname15(path), { recursive: true });
50782
51051
  const bytes = new Uint8Array(await response.arrayBuffer());
50783
51052
  await writeFile16(path, bytes, { flag: "wx" });
50784
51053
  out(stdout, { path, bytes: bytes.length });
@@ -50792,7 +51061,7 @@ Confirm code: ${device.user_code}`);
50792
51061
  const response = await client.request(`/api/v1/blobs/${encodeURIComponent(manifest.ownerId)}/${file2.sha}`, { raw: true });
50793
51062
  entries[path2] = new Uint8Array(await response.arrayBuffer());
50794
51063
  }
50795
- const path = resolve17(flags.output);
51064
+ const path = resolve18(flags.output);
50796
51065
  await writeFile16(path, zipSync(entries, { level: 0 }), { flag: "wx" });
50797
51066
  out(stdout, { path, files: manifest.files.length });
50798
51067
  })))).extend(version(package_default.version, { format: "plain" })).extend(help()).extend(completion());