@docyrus/animaguru 0.27.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +21 -2
  2. package/dist/animaguru.js +697 -460
  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 +1 -1
  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/install-example.mjs +10 -6
  20. package/templates/video/shared/locales/en/style-studies.json +35 -0
  21. package/templates/video/shared/locales/en/timeline.json +1 -0
  22. package/templates/video/src/kit/index.ts +1 -0
  23. package/templates/video/src/kit/three-stage.ts +262 -0
  24. package/templates/video/src/resource-timeline.ts +1 -0
  25. package/templates/video/src/studies/three/character-stage-3d.ts +172 -0
  26. package/templates/video/src/studies/three/exploded-teardown-3d.ts +214 -0
  27. package/templates/video/src/studies/three/index.ts +16 -0
  28. package/templates/video/src/studies/three/product-turntable-3d.ts +160 -0
  29. package/templates/video/src/studies/three/toy-diorama-3d.ts +245 -0
  30. package/templates/video/src/studies/three-film.ts +42 -0
  31. package/templates/video/src/style.css +2 -1
  32. 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.28.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
@@ -27487,8 +27487,8 @@ function emoji2() {
27487
27487
  }
27488
27488
  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
27489
  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 ?? ":");
27490
+ var mac = (delimiter3) => {
27491
+ const escapedDelim = escapeRegex(delimiter3 ?? ":");
27492
27492
  return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
27493
27493
  };
27494
27494
  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 +30516,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
30516
30516
  var NONE = 0;
30517
30517
  var ASSUMED = 1;
30518
30518
  var PROVEN = 2;
30519
- function isRecursive(inst, stack, resolve18) {
30519
+ function isRecursive(inst, stack, resolve19) {
30520
30520
  const cached2 = recursive.get(inst);
30521
30521
  if (cached2 !== void 0)
30522
30522
  return cached2 ? PROVEN : NONE;
@@ -30526,7 +30526,7 @@ function isRecursive(inst, stack, resolve18) {
30526
30526
  let result = NONE;
30527
30527
  const check2 = (child) => {
30528
30528
  if (result !== PROVEN && child?._zod) {
30529
- const answer = isRecursive(child, stack, resolve18);
30529
+ const answer = isRecursive(child, stack, resolve19);
30530
30530
  if (answer > result)
30531
30531
  result = answer;
30532
30532
  }
@@ -30537,7 +30537,7 @@ function isRecursive(inst, stack, resolve18) {
30537
30537
  const desc = Object.getOwnPropertyDescriptor(sh, key);
30538
30538
  if (spread && !desc.enumerable)
30539
30539
  continue;
30540
- const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve18) : NONE;
30540
+ const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve19) : NONE;
30541
30541
  if (child > answer)
30542
30542
  answer = child;
30543
30543
  }
@@ -30601,7 +30601,7 @@ function isRecursive(inst, stack, resolve18) {
30601
30601
  break;
30602
30602
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
30603
30603
  case "lazy": {
30604
- const inner = def._cachedInner ?? (resolve18 ? inst._zod.innerType : void 0);
30604
+ const inner = def._cachedInner ?? (resolve19 ? inst._zod.innerType : void 0);
30605
30605
  merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
30606
30606
  break;
30607
30607
  }
@@ -45293,10 +45293,62 @@ async function initializeSkill(directory = process.cwd()) {
45293
45293
  return { path, created };
45294
45294
  }
45295
45295
 
45296
+ // cli/src/skills.ts
45297
+ import { spawn as nodeSpawn } from "node:child_process";
45298
+ import { access, mkdir as mkdir6, readFile as readFile4, readdir as readdir2, stat } from "node:fs/promises";
45299
+ import { delimiter, dirname as dirname5, join as join6, resolve as resolve5 } from "node:path";
45300
+ var SKILLS_CLI = "skills@1.7.4";
45301
+ var SKILL_AGENTS = { "claude-code": ".claude/skills", codex: ".agents/skills" };
45302
+ var skillsSource = (server) => `${new URL(server).origin}/cli/skills`;
45303
+ async function skillsInstallCommand(source, platform = process.platform, execPath = process.execPath) {
45304
+ const args = ["-y", SKILLS_CLI, "add", source, "--skill", "*", ...Object.keys(SKILL_AGENTS).flatMap((agent) => ["--agent", agent]), "--copy", "--yes"];
45305
+ const adjacentNpx = resolve5(dirname5(execPath), "../lib/node_modules/npm/bin/npx-cli.js");
45306
+ try {
45307
+ await access(adjacentNpx);
45308
+ return { command: execPath, args: [adjacentNpx, ...args], shell: false };
45309
+ } catch {
45310
+ return { command: platform === "win32" ? "npx.cmd" : "npx", args, shell: platform === "win32" };
45311
+ }
45312
+ }
45313
+ async function lockedSkills(directory, source) {
45314
+ try {
45315
+ const lock = JSON.parse(await readFile4(join6(directory, "skills-lock.json"), "utf8"));
45316
+ return Object.entries(lock.skills ?? {}).filter(([, skill]) => skill.sourceUrl === source).map(([name2]) => name2).sort();
45317
+ } catch {
45318
+ return [];
45319
+ }
45320
+ }
45321
+ async function installSkills(options) {
45322
+ const directory = resolve5(options.dir ?? ".");
45323
+ await mkdir6(directory, { recursive: true });
45324
+ if (!(await stat(directory)).isDirectory()) throw new Error(`${directory} is not a folder.`);
45325
+ const source = skillsSource(options.url);
45326
+ const { command, args, shell } = await skillsInstallCommand(source);
45327
+ options.log?.(`Installing AnimaGuru skills from ${source} into ${directory}\u2026`);
45328
+ await new Promise((done, reject) => {
45329
+ const child = (options.spawn ?? nodeSpawn)(command, args, {
45330
+ cwd: directory,
45331
+ signal: options.signal,
45332
+ stdio: ["ignore", "pipe", "pipe"],
45333
+ shell,
45334
+ // The skills CLI reports installs to skills.sh unless told not to.
45335
+ env: { ...process.env, DISABLE_TELEMETRY: "1", DO_NOT_TRACK: "1", PATH: `${dirname5(process.execPath)}${delimiter}${process.env.PATH ?? ""}` }
45336
+ });
45337
+ child.stdout?.on("data", (chunk) => options.log?.(chunk.toString().trimEnd()));
45338
+ child.stderr?.on("data", (chunk) => options.log?.(chunk.toString().trimEnd()));
45339
+ 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));
45340
+ child.once("close", (code) => code === 0 ? done() : reject(new Error(`npx ${SKILLS_CLI} add exited with code ${code}; see its output above.`)));
45341
+ });
45342
+ const installed = await lockedSkills(directory, source);
45343
+ const folders = await Promise.all(Object.values(SKILL_AGENTS).map(async (folder) => (await readdir2(join6(directory, folder)).catch(() => [])).length));
45344
+ if (!installed.length || folders.some((count) => count === 0)) throw new Error("The skills CLI finished without installing AnimaGuru skills; see its output above.");
45345
+ return { directory, source, skills: installed.length, installed, agents: SKILL_AGENTS, lockfile: join6(directory, "skills-lock.json"), update: "npx skills update --project --yes" };
45346
+ }
45347
+
45296
45348
  // cli/src/create.ts
45297
45349
  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";
45350
+ import { cp, mkdir as mkdir7, readFile as readFile5, readdir as readdir3, access as access2, writeFile as writeFile6, rename as rename3 } from "node:fs/promises";
45351
+ import { basename as basename2, dirname as dirname6, delimiter as delimiter2, join as join7, resolve as resolve6 } from "node:path";
45300
45352
  import { fileURLToPath } from "node:url";
45301
45353
 
45302
45354
  // shared/locales/en/animation-styles.json
@@ -46275,6 +46327,74 @@ var animation_styles_default = {
46275
46327
  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
46328
  family: "graphic",
46277
46329
  example: true
46330
+ },
46331
+ "product-turntable-3d": {
46332
+ name: "3D Product Turntable",
46333
+ 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.",
46334
+ materials: [
46335
+ "Generated PBR product model",
46336
+ "Studio HDR reflections",
46337
+ "Shallow depth of field"
46338
+ ],
46339
+ motion: [
46340
+ "Turntable orbit and push-in",
46341
+ "Rack focus between details",
46342
+ "Callouts that track parts"
46343
+ ],
46344
+ 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.",
46345
+ family: "3d",
46346
+ example: true
46347
+ },
46348
+ "character-stage-3d": {
46349
+ name: "3D Character Stage",
46350
+ 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.",
46351
+ materials: [
46352
+ "Rigged character model",
46353
+ "Painted 2D backdrop",
46354
+ "Shadows cast onto the painting"
46355
+ ],
46356
+ motion: [
46357
+ "Walk cycles at true speed",
46358
+ "Crossfaded gestures",
46359
+ "A camera that follows the character"
46360
+ ],
46361
+ 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.",
46362
+ family: "3d",
46363
+ example: true
46364
+ },
46365
+ "toy-diorama-3d": {
46366
+ name: "3D Toy Diorama",
46367
+ 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.",
46368
+ materials: [
46369
+ "Toy-scale models",
46370
+ "Tilt-shift depth of field",
46371
+ "Warm tabletop light"
46372
+ ],
46373
+ motion: [
46374
+ "Free camera glides",
46375
+ "Little cars and figures on loops",
46376
+ "Focus pulled through the scene"
46377
+ ],
46378
+ 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.",
46379
+ family: "3d",
46380
+ example: true
46381
+ },
46382
+ "exploded-teardown-3d": {
46383
+ name: "3D Exploded Teardown",
46384
+ 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.",
46385
+ materials: [
46386
+ "Part-by-part 3D model",
46387
+ "Leader labels and callouts",
46388
+ "Clean studio grey"
46389
+ ],
46390
+ motion: [
46391
+ "Orbit while the parts separate",
46392
+ "Parts sliding along their axes",
46393
+ "Labels that follow each part"
46394
+ ],
46395
+ 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.",
46396
+ family: "3d",
46397
+ example: true
46278
46398
  }
46279
46399
  },
46280
46400
  browseStyles: "Browse animation styles",
@@ -46290,7 +46410,8 @@ var animation_styles_default = {
46290
46410
  material: "Materials & depth",
46291
46411
  cinematic: "Cinema & media",
46292
46412
  evidence: "Evidence & data",
46293
- documentary: "Documentary & broadcast"
46413
+ documentary: "Documentary & broadcast",
46414
+ "3d": "3D scenes"
46294
46415
  },
46295
46416
  searchLabel: "Search visual styles",
46296
46417
  searchPlaceholder: "Try paper, product, typography\u2026",
@@ -46423,7 +46544,11 @@ var VIDEO_STYLE_IDS = [
46423
46544
  "synthwave-y2k",
46424
46545
  "sticker-explainer",
46425
46546
  "variety-captions",
46426
- "one-line-art"
46547
+ "one-line-art",
46548
+ "product-turntable-3d",
46549
+ "character-stage-3d",
46550
+ "toy-diorama-3d",
46551
+ "exploded-teardown-3d"
46427
46552
  ];
46428
46553
  var VIDEO_WORKFLOW_IDS = [
46429
46554
  "product-proof",
@@ -46483,10 +46608,10 @@ function listTemplates() {
46483
46608
  return videoTemplateCatalog();
46484
46609
  }
46485
46610
  async function installDependencies(directory, options) {
46486
- const adjacentNpm = resolve5(dirname5(process.execPath), "../lib/node_modules/npm/bin/npm-cli.js");
46611
+ const adjacentNpm = resolve6(dirname6(process.execPath), "../lib/node_modules/npm/bin/npm-cli.js");
46487
46612
  let npmScript;
46488
46613
  try {
46489
- await access(adjacentNpm);
46614
+ await access2(adjacentNpm);
46490
46615
  npmScript = adjacentNpm;
46491
46616
  } catch {
46492
46617
  }
@@ -46497,7 +46622,7 @@ async function installDependencies(directory, options) {
46497
46622
  signal: options.signal,
46498
46623
  stdio: ["ignore", "pipe", "pipe"],
46499
46624
  shell: !npmScript && process.platform === "win32",
46500
- env: { ...process.env, PATH: `${dirname5(process.execPath)}${delimiter}${process.env.PATH ?? ""}` }
46625
+ env: { ...process.env, PATH: `${dirname6(process.execPath)}${delimiter2}${process.env.PATH ?? ""}` }
46501
46626
  });
46502
46627
  child.stdout.on("data", (chunk) => options.log?.(chunk.toString().trimEnd()));
46503
46628
  child.stderr.on("data", (chunk) => options.log?.(chunk.toString().trimEnd()));
@@ -46522,7 +46647,7 @@ var STARTER_STORY = {
46522
46647
  { id: "invitation", label: "The invitation", start: 16, end: 24, ending: "Your next story starts here" }
46523
46648
  ]
46524
46649
  };
46525
- var writeStarterStory = (directory) => writeFile6(join6(directory, "story.json"), JSON.stringify(STARTER_STORY, null, 2) + "\n", { flag: "wx" });
46650
+ var writeStarterStory = (directory) => writeFile6(join7(directory, "story.json"), JSON.stringify(STARTER_STORY, null, 2) + "\n", { flag: "wx" });
46526
46651
  function starterQuiz(story = false) {
46527
46652
  return {
46528
46653
  version: 1,
@@ -46535,42 +46660,42 @@ function starterQuiz(story = false) {
46535
46660
  ] }
46536
46661
  };
46537
46662
  }
46538
- var writeStarterQuiz = (directory, story = false) => writeFile6(join6(directory, "quiz.json"), JSON.stringify(starterQuiz(story), null, 2) + "\n", { flag: "wx" });
46663
+ var writeStarterQuiz = (directory, story = false) => writeFile6(join7(directory, "quiz.json"), JSON.stringify(starterQuiz(story), null, 2) + "\n", { flag: "wx" });
46539
46664
  async function createVideoProject(options, installer = installDependencies) {
46540
46665
  if (!options.directory.trim()) throw new Error("Choose a new project folder, for example: animaguru create my-video");
46541
- const directory = resolve5(options.directory);
46666
+ const directory = resolve6(options.directory);
46542
46667
  options.signal?.throwIfAborted();
46543
46668
  try {
46544
- await mkdir6(directory);
46669
+ await mkdir7(directory);
46545
46670
  } catch (error62) {
46546
46671
  if (error62.code === "EEXIST") throw new Error(`Destination already exists: ${directory}. Choose a new folder; existing files were not changed.`);
46547
46672
  throw error62;
46548
46673
  }
46549
46674
  options.log?.(`Creating a video workspace in ${directory}\u2026`);
46550
- const templateRoot = join6(packageRoot, "templates/video");
46551
- for (const entry of await readdir2(templateRoot)) {
46675
+ const templateRoot = join7(packageRoot, "templates/video");
46676
+ for (const entry of await readdir3(templateRoot)) {
46552
46677
  if (entry === "WORKSPACE-AGENTS.md") continue;
46553
- await cp(join6(templateRoot, entry), join6(directory, entry), { recursive: true, errorOnExist: true, force: false });
46678
+ await cp(join7(templateRoot, entry), join7(directory, entry), { recursive: true, errorOnExist: true, force: false });
46554
46679
  }
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"));
46680
+ await rename3(join7(directory, "gitignore"), join7(directory, ".gitignore"));
46681
+ await mkdir7(join7(directory, "public/assets"), { recursive: true });
46682
+ await mkdir7(join7(directory, "production"), { recursive: true });
46683
+ const packagePath = join7(directory, "package.json");
46684
+ const manifest = JSON.parse(await readFile5(packagePath, "utf8"));
46560
46685
  manifest.name = basename2(directory).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 100) || "animaguru-video";
46561
46686
  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();
46687
+ 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)])];
46688
+ const pixiSkillsRoot = join7(packageRoot, "vendor/pixijs-skills/skills");
46689
+ const pixiSkills = (await readdir3(pixiSkillsRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
46565
46690
  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({
46691
+ const skillRoot = join7(directory, agent, "skills");
46692
+ await mkdir7(skillRoot, { recursive: true });
46693
+ for (const skill of skills) await cp(join7(packageRoot, "skills", skill), join7(skillRoot, skill), { recursive: true });
46694
+ for (const skill of pixiSkills) await cp(join7(pixiSkillsRoot, skill), join7(skillRoot, skill), { recursive: true });
46695
+ }
46696
+ await cp(join7(packageRoot, "vendor/pixijs-skills/LICENSE"), join7(directory, "PIXIJS-SKILLS-LICENSE.txt"));
46697
+ await cp(join7(packageRoot, "vendor/pixijs-skills/provenance.json"), join7(directory, "production/pixijs-skills-provenance.json"));
46698
+ await writeFile6(join7(directory, "animaguru.project.json"), JSON.stringify({
46574
46699
  schemaVersion: 3,
46575
46700
  cliVersion: package_default.version,
46576
46701
  category: null,
@@ -46582,7 +46707,7 @@ async function createVideoProject(options, installer = installDependencies) {
46582
46707
  interview: "pending",
46583
46708
  animaguruProjectId: null
46584
46709
  }, null, 2) + "\n");
46585
- await writeFile6(join6(directory, "animaguru.templates.json"), JSON.stringify(listTemplates(), null, 2) + "\n");
46710
+ await writeFile6(join7(directory, "animaguru.templates.json"), JSON.stringify(listTemplates(), null, 2) + "\n");
46586
46711
  if (options.story) await writeStarterStory(directory);
46587
46712
  if (options.quiz) await writeStarterQuiz(directory, options.story);
46588
46713
  if (options.install !== false) {
@@ -46609,28 +46734,28 @@ ${options.install === false ? "Run npm install, then " : "Run "}npm run dev insi
46609
46734
  }
46610
46735
  async function createWorkspace(options, installer = installDependencies) {
46611
46736
  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`);
46737
+ if (message.startsWith("Creating")) options.log?.(`Creating a shared-brand workspace in ${resolve6(options.directory)}\u2026`);
46613
46738
  } }, installer);
46614
46739
  const directory = result.path;
46615
46740
  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 });
46741
+ await rename3(join7(directory, "src"), join7(directory, "runtime"));
46742
+ await mkdir7(join7(directory, "templates/animation/src"), { recursive: true });
46743
+ await mkdir7(join7(directory, "templates/animation/shared/locales/en"), { recursive: true });
46619
46744
  for (const file2 of ["film.ts", "film-resources.ts"]) {
46620
- const original = await readFile4(join6(directory, "runtime", file2), "utf8");
46745
+ const original = await readFile5(join7(directory, "runtime", file2), "utf8");
46621
46746
  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));
46747
+ await writeFile6(join7(directory, "templates/animation/src", file2), rewritten);
46748
+ await rm3(join7(directory, "runtime", file2));
46624
46749
  }
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"));
46750
+ await rm3(join7(directory, "runtime/main.ts"));
46751
+ await rename3(join7(directory, "film.config.json"), join7(directory, "templates/animation/film.config.json"));
46752
+ await rename3(join7(directory, "shared/locales/en/film.json"), join7(directory, "templates/animation/shared/locales/en/film.json"));
46628
46753
  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({
46754
+ await writeFile6(join7(directory, "templates/animation/starter-film.sha256"), createHash7("sha256").update(await readFile5(join7(directory, "templates/animation/src/film.ts"))).digest("hex") + "\n");
46755
+ await rm3(join7(directory, "animaguru.project.json"));
46756
+ await mkdir7(join7(directory, "animations"));
46757
+ await mkdir7(join7(directory, "branding"));
46758
+ await writeFile6(join7(directory, "branding/brand.json"), JSON.stringify({
46634
46759
  name: "Your brand",
46635
46760
  tagline: "Your next story starts here",
46636
46761
  logo: null,
@@ -46638,18 +46763,18 @@ async function createWorkspace(options, installer = installDependencies) {
46638
46763
  fonts: { heading: { family: "Arial, sans-serif", file: null }, body: { family: "Arial, sans-serif", file: null } },
46639
46764
  assets: []
46640
46765
  }, 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"));
46766
+ 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");
46767
+ 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");
46768
+ const pkg = JSON.parse(await readFile5(join7(directory, "package.json"), "utf8"));
46644
46769
  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"));
46770
+ await writeFile6(join7(directory, "package.json"), JSON.stringify(pkg, null, 2) + "\n");
46771
+ const tsconfig = JSON.parse(await readFile5(join7(directory, "tsconfig.json"), "utf8"));
46647
46772
  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");
46773
+ await writeFile6(join7(directory, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
46774
+ await writeFile6(join7(directory, "AGENTS.md"), await readFile5(join7(packageRoot, "templates/video/WORKSPACE-AGENTS.md"), "utf8"));
46775
+ 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");
46776
+ await rm3(join7(directory, "WORKSPACE-AGENTS.md"), { force: true });
46777
+ 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
46778
  if (options.install !== false) {
46654
46779
  try {
46655
46780
  await installer(directory, options);
@@ -46665,23 +46790,23 @@ async function createWorkspace(options, installer = installDependencies) {
46665
46790
  skills: result.skills,
46666
46791
  agents: result.agents,
46667
46792
  installed: options.install !== false,
46668
- branding: join6(directory, "branding"),
46669
- animations: join6(directory, "animations"),
46793
+ branding: join7(directory, "branding"),
46794
+ animations: join7(directory, "animations"),
46670
46795
  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
46796
  };
46672
46797
  }
46673
46798
  async function findWorkspace(start = process.cwd()) {
46674
46799
  const { realpath } = await import("node:fs/promises");
46675
- let directory = await realpath(resolve5(start));
46800
+ let directory = await realpath(resolve6(start));
46676
46801
  while (true) {
46677
46802
  try {
46678
- const info = JSON.parse(await readFile4(join6(directory, "animaguru.workspace.json"), "utf8"));
46803
+ const info = JSON.parse(await readFile5(join7(directory, "animaguru.workspace.json"), "utf8"));
46679
46804
  if (info.schemaVersion !== 1 || info.animationsDirectory !== "animations" || info.branding !== "branding/brand.json") throw new Error("Unsupported AnimaGuru workspace.");
46680
46805
  return directory;
46681
46806
  } catch (error62) {
46682
46807
  if (error62.code !== "ENOENT") throw error62;
46683
46808
  }
46684
- const parent = dirname5(directory);
46809
+ const parent = dirname6(directory);
46685
46810
  if (parent === directory) throw new Error("Run this inside an AnimaGuru workspace, or pass --workspace PATH. Create one with animaguru create workspace NAME.");
46686
46811
  directory = parent;
46687
46812
  }
@@ -46690,23 +46815,23 @@ async function createAnimation(options) {
46690
46815
  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
46816
  options.signal?.throwIfAborted();
46692
46817
  const root = await findWorkspace(options.workspace);
46693
- const directory = join6(root, "animations", options.name);
46818
+ const directory = join7(root, "animations", options.name);
46694
46819
  try {
46695
- await mkdir6(directory);
46820
+ await mkdir7(directory);
46696
46821
  } catch (error62) {
46697
46822
  if (error62.code === "EEXIST") throw new Error(`Animation already exists: ${options.name}. Existing files were not changed.`);
46698
46823
  throw error62;
46699
46824
  }
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 });
46825
+ for (const file2 of await readdir3(join7(root, "templates/animation"))) {
46826
+ if (file2 !== "starter-film.sha256") await cp(join7(root, "templates/animation", file2), join7(directory, file2), { recursive: true, errorOnExist: true, force: false });
46702
46827
  }
46703
- const config2 = JSON.parse(await readFile4(join6(directory, "film.config.json"), "utf8"));
46828
+ const config2 = JSON.parse(await readFile5(join7(directory, "film.config.json"), "utf8"));
46704
46829
  config2.slug = options.name;
46705
46830
  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");
46831
+ await writeFile6(join7(directory, "film.config.json"), JSON.stringify(config2, null, 2) + "\n");
46832
+ await mkdir7(join7(directory, "public/assets"), { recursive: true });
46833
+ await mkdir7(join7(directory, "production"), { recursive: true });
46834
+ 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
46835
  if (options.story) await writeStarterStory(directory);
46711
46836
  if (options.quiz) await writeStarterQuiz(directory, options.story);
46712
46837
  const kind = options.story && options.quiz ? "Branching story with a quiz" : options.story ? "Branching story" : options.quiz ? "Quiz film" : "Animation";
@@ -46725,8 +46850,65 @@ async function createAnimation(options) {
46725
46850
  };
46726
46851
  }
46727
46852
 
46853
+ // shared/model-assets.ts
46854
+ var MODEL_GENERATORS = [
46855
+ {
46856
+ id: "tripo-h3.1",
46857
+ label: "Tripo H3.1",
46858
+ endpoints: { image: "tripo3d/h3.1/image-to-3d", text: "tripo3d/h3.1/text-to-3d" },
46859
+ faces: { min: 1e3, max: 2e6 },
46860
+ /** The direction the generated model faces; saved models are turned to +Z. */
46861
+ facing: "+x"
46862
+ },
46863
+ {
46864
+ id: "hunyuan-3d-3.1-pro",
46865
+ label: "Hunyuan3D 3.1 Pro",
46866
+ endpoints: { image: "fal-ai/hunyuan-3d/v3.1/pro/image-to-3d", text: "fal-ai/hunyuan-3d/v3.1/pro/text-to-3d" },
46867
+ faces: { min: 4e4, max: 15e5 },
46868
+ facing: "+z"
46869
+ }
46870
+ ];
46871
+ var MODEL_GENERATOR_IDS = MODEL_GENERATORS.map((m) => m.id);
46872
+ var DEFAULT_MODEL_GENERATOR = "tripo-h3.1";
46873
+ var modelGenerator = (id2) => MODEL_GENERATORS.find((m) => m.id === id2) ?? MODEL_GENERATORS[0];
46874
+ var MAX_MODEL_CLIPS = 10;
46875
+ var MAX_RIG_FACES = 3e5;
46876
+ var MAX_MESHY_ACTION = 696;
46877
+ var MAX_MODEL_PROMPT = 1e3;
46878
+ var MODEL_ANIMATIONS = [
46879
+ { id: "idle", action: 0, label: "Idle" },
46880
+ { id: "idle-2", action: 11, label: "Idle 2" },
46881
+ { id: "idle-3", action: 12, label: "Idle 3" },
46882
+ { id: "walk", action: 30, label: "Casual walk" },
46883
+ { id: "walk-woman", action: 1, label: "Walking (woman)" },
46884
+ { id: "stage-walk", action: 55, label: "Stage walk" },
46885
+ { id: "thoughtful-walk", action: 121, label: "Thoughtful walk" },
46886
+ { id: "run", action: 14, label: "Run" },
46887
+ { id: "run-fast", action: 16, label: "Fast run" },
46888
+ { id: "wave", action: 28, label: "Big wave hello" },
46889
+ { id: "wave-one-hand", action: 290, label: "One-hand wave" },
46890
+ { id: "cheer", action: 59, label: "Victory cheer" },
46891
+ { id: "cheer-hands-up", action: 298, label: "Cheer with both hands up" },
46892
+ { id: "fist-pump", action: 403, label: "Victory fist pump" },
46893
+ { id: "talk", action: 313, label: "Talk with open hands" },
46894
+ { id: "talk-passionately", action: 308, label: "Talk passionately" },
46895
+ { id: "phone-call", action: 312, label: "Phone conversation" },
46896
+ { id: "agree", action: 25, label: "Agree gesture" },
46897
+ { id: "jump", action: 466, label: "Jump" },
46898
+ { id: "jump-arms-open", action: 460, label: "Jump with arms open" },
46899
+ { id: "dance", action: 22, label: "Funny dance" },
46900
+ { id: "dance-2", action: 64, label: "All-night dance" },
46901
+ { id: "shuffle", action: 63, label: "Arm-circle shuffle" }
46902
+ ];
46903
+ var MODEL_ANIMATION_IDS = MODEL_ANIMATIONS.map((a) => a.id);
46904
+ var DEFAULT_MODEL_ANIMATIONS = ["idle", "walk"];
46905
+ var animationAction = (clip) => typeof clip === "number" ? clip : MODEL_ANIMATIONS.find((a) => a.id === clip).action;
46906
+ 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;
46907
+ var rigCostUsd = (clips2) => 0.2 + 0.12 * clips2;
46908
+ var modelCostUsd = (p) => generationCostUsd(p) + (p.rig ? rigCostUsd(p.animations.length) : 0);
46909
+
46728
46910
  // shared/assets.ts
46729
- var ASSET_TYPES = ["image", "vector", "video", "music", "speech", "sfx"];
46911
+ var ASSET_TYPES = ["image", "vector", "video", "music", "speech", "sfx", "model"];
46730
46912
  var SPEECH_MODELS = [
46731
46913
  { id: "gemini-3.8-flash-tts", label: "Gemini 3.8 Flash TTS" },
46732
46914
  { id: "gemini-3.8-flash-lite-tts", label: "Gemini 3.8 Flash-Lite TTS" },
@@ -47022,6 +47204,43 @@ function vectorChecks(p, ctx) {
47022
47204
  if (p.references.length) issue2("references", "image-to-vector traces its source alone and takes no references.");
47023
47205
  }
47024
47206
  var vectorDefaults = (p) => p.workflow === "text-to-vector" ? { ...p, quality: p.quality ?? "medium", aspectRatio: p.aspectRatio ?? "auto" } : { ...p, shapes: p.shapes ?? "detailed" };
47207
+ var animationClip = external_exports.union([external_exports.enum(MODEL_ANIMATION_IDS), external_exports.number().int().min(0).max(MAX_MESHY_ACTION)]);
47208
+ var modelAsset = external_exports.object({
47209
+ name: base.name,
47210
+ /** Describes the model for text-to-3D; with a reference image it is only a note. */
47211
+ prompt: external_exports.string().trim().max(4e3).default(""),
47212
+ type: external_exports.literal("model"),
47213
+ model: external_exports.enum(MODEL_GENERATOR_IDS).default(DEFAULT_MODEL_GENERATOR),
47214
+ /** A saved image or sprite art the model is built from. */
47215
+ reference: blob.optional(),
47216
+ /** Textured (default), or a plain white model. */
47217
+ texture: external_exports.boolean().default(true),
47218
+ /** Metallic, roughness and normal maps with the texture, so scene lights shape the surface. */
47219
+ pbr: external_exports.boolean().default(true),
47220
+ /** Target face count, within the generator's range; Hunyuan3D charges extra for it. */
47221
+ faces: external_exports.number().int().optional(),
47222
+ /** Auto-rig a humanoid character standing in a T- or A-pose. */
47223
+ rig: external_exports.boolean().default(false),
47224
+ /** Clips the rig gets (idle and walk when none are named). */
47225
+ animations: external_exports.array(animationClip).max(MAX_MODEL_CLIPS).default([]),
47226
+ /** The rigged character's height in metres (default 1.7). */
47227
+ height: external_exports.number().min(0.3).max(5).optional()
47228
+ });
47229
+ function modelChecks(p, ctx) {
47230
+ const issue2 = (path, message) => ctx.addIssue({ code: "custom", message, path: [path] });
47231
+ const generator = modelGenerator(p.model);
47232
+ 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.");
47233
+ if (!p.reference && p.prompt.length > MAX_MODEL_PROMPT) issue2("prompt", `Describe the model in at most ${MAX_MODEL_PROMPT.toLocaleString("en-US")} characters.`);
47234
+ 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.`);
47235
+ if (!p.rig && p.animations.length) issue2("animations", "Animation clips need rig: true.");
47236
+ if (!p.rig && p.height !== void 0) issue2("height", "height is the rigged character's height; it needs rig: true.");
47237
+ if (!p.rig) return;
47238
+ 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.`);
47239
+ else if (p.faces !== void 0 && p.faces > MAX_RIG_FACES) issue2("faces", `Rigging takes at most ${MAX_RIG_FACES.toLocaleString("en-US")} faces.`);
47240
+ const actions2 = p.animations.map(animationAction);
47241
+ if (new Set(actions2).size !== actions2.length) issue2("animations", "Choose each clip once.");
47242
+ }
47243
+ var modelDefaults = (p) => ({ ...p, animations: p.rig && !p.animations.length ? [...DEFAULT_MODEL_ANIMATIONS] : p.animations });
47025
47244
  var assetRequest = external_exports.discriminatedUnion("type", [
47026
47245
  imageAsset.superRefine(imageChecks),
47027
47246
  videoAsset.superRefine(videoChecks).transform(videoDefaults),
@@ -47031,7 +47250,8 @@ var assetRequest = external_exports.discriminatedUnion("type", [
47031
47250
  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
47251
  }).transform((p) => ({ ...p, voice: p.voice || (isElevenLabsSpeech(p.model) ? DEFAULT_ELEVENLABS_VOICE : "Kore") })),
47033
47252
  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)
47253
+ vectorAsset.superRefine(vectorChecks).transform(vectorDefaults),
47254
+ modelAsset.superRefine(modelChecks).transform(modelDefaults)
47035
47255
  ]);
47036
47256
  var assetGenerationRequest = external_exports.discriminatedUnion("type", [imageAsset.omit({ model: true }).superRefine(imageChecks), ...assetRequest.options.slice(1)]);
47037
47257
  var LIBRARY_AUDIO_MODEL = "library-audio";
@@ -47047,13 +47267,15 @@ function videoExtras(p, facts = {}) {
47047
47267
  if (w !== "reference-to-video" && w !== "insert-video" && w !== "3d-to-video") return {};
47048
47268
  const images = (p.references ?? []).reduce((sum, sha2) => sum + imageReferenceTokens(facts.size?.(sha2)), 0);
47049
47269
  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));
47270
+ const clips2 = Math.min(15, (p.referenceVideos ?? []).reduce((sum, sha2) => sum + Math.min(p.seconds, facts.seconds?.(sha2) ?? p.seconds), 0));
47051
47271
  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) };
47272
+ return { referenceTokens: images + clipReferenceTokens(clips2, p.resolution) + (w === "reference-to-video" ? audioReferenceTokens(audio) : 0) };
47053
47273
  }
47274
+ var modelEstimate = (p) => Math.max(1, Math.ceil(modelCostUsd(p) / 1e-3));
47054
47275
  function assetEstimate(p, now = Date.now()) {
47055
47276
  if (p.type === "image") return imageEstimate(p);
47056
47277
  if (p.type === "vector") return vectorEstimate(p);
47278
+ if (p.type === "model") return modelEstimate(p);
47057
47279
  if (p.type === "video") return spriteVideoEstimate(spriteVideoModel(p.model), p, videoExtras(p));
47058
47280
  if (p.type === "music") return 80;
47059
47281
  const units = (credits) => Math.max(1, Math.ceil(credits * UNITS_PER_CREDIT));
@@ -47070,8 +47292,8 @@ function assetEstimate(p, now = Date.now()) {
47070
47292
  }
47071
47293
 
47072
47294
  // 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";
47295
+ import { open as open4, readFile as readFile9, stat as stat4 } from "node:fs/promises";
47296
+ import { basename as basename4, join as join10, resolve as resolve10 } from "node:path";
47075
47297
  import { setTimeout as delay2 } from "node:timers/promises";
47076
47298
  import { spawn as spawn2 } from "node:child_process";
47077
47299
  import { createHash as createHash4 } from "node:crypto";
@@ -47167,12 +47389,12 @@ function parseSubtitles(input2, duration3) {
47167
47389
  }
47168
47390
 
47169
47391
  // 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";
47392
+ import { open as open2, readFile as readFile6, stat as stat2 } from "node:fs/promises";
47393
+ import { basename as basename3, resolve as resolve7 } from "node:path";
47172
47394
  import { createHash as createHash2 } from "node:crypto";
47173
47395
  async function exportReceipt(file2) {
47174
47396
  try {
47175
- const receipt = JSON.parse(await readFile5(file2.replace(/\.mp4$/i, ".json"), "utf8"));
47397
+ const receipt = JSON.parse(await readFile6(file2.replace(/\.mp4$/i, ".json"), "utf8"));
47176
47398
  if (receipt.file !== basename3(file2)) return {};
47177
47399
  return { title: typeof receipt.title === "string" ? receipt.title : void 0, language: typeof receipt.settings?.language === "string" ? receipt.settings.language : void 0 };
47178
47400
  } catch {
@@ -47182,22 +47404,22 @@ async function exportReceipt(file2) {
47182
47404
  async function readSubtitleFile(file2, maxBytes) {
47183
47405
  let info;
47184
47406
  try {
47185
- info = await stat(file2);
47407
+ info = await stat2(file2);
47186
47408
  } catch {
47187
47409
  throw new Error(`Cannot read the subtitle file ${file2}.`);
47188
47410
  }
47189
47411
  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);
47412
+ const content = await readFile6(file2);
47191
47413
  const text2 = content.toString("utf8");
47192
47414
  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
47415
  return { content, sha256: createHash2("sha256").update(content).digest("hex"), language: /^\uFEFF?WEBVTT[^\n]*\n(?:[^\n]+\n)*?Language:[ \t]*([^\s]+)/.exec(text2)?.[1] };
47194
47416
  }
47195
47417
  async function subtitleFile(root, mp4, choice, extensions) {
47196
47418
  if (choice === "none") return null;
47197
- if (choice !== "auto") return { file: resolve6(root, choice), explicit: true };
47419
+ if (choice !== "auto") return { file: resolve7(root, choice), explicit: true };
47198
47420
  for (const extension of extensions) {
47199
47421
  const file2 = mp4.replace(/\.mp4$/i, extension);
47200
- if (await stat(file2).then((info) => info.isFile(), () => false)) return { file: file2, explicit: false };
47422
+ if (await stat2(file2).then((info) => info.isFile(), () => false)) return { file: file2, explicit: false };
47201
47423
  }
47202
47424
  return null;
47203
47425
  }
@@ -47272,10 +47494,10 @@ async function mp4Info(file2) {
47272
47494
  }
47273
47495
 
47274
47496
  // 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";
47497
+ import { readFile as readFile7, readdir as readdir4 } from "node:fs/promises";
47498
+ import { join as join8, relative, resolve as resolve8, sep } from "node:path";
47277
47499
  async function localProject(directory, selected) {
47278
- const root = resolve7(directory);
47500
+ const root = resolve8(directory);
47279
47501
  let workspace3;
47280
47502
  try {
47281
47503
  workspace3 = await findWorkspace(root);
@@ -47287,10 +47509,10 @@ async function localProject(directory, selected) {
47287
47509
  let animation = selected ?? (location[0] === "animations" ? location[1] : void 0);
47288
47510
  if (!animation) {
47289
47511
  const names = [];
47290
- for (const entry of await readdir3(join7(workspace3, "animations"), { withFileTypes: true })) {
47512
+ for (const entry of await readdir4(join8(workspace3, "animations"), { withFileTypes: true })) {
47291
47513
  if (!entry.isDirectory()) continue;
47292
47514
  try {
47293
- await readFile6(join7(workspace3, "animations", entry.name, "animaguru.project.json"));
47515
+ await readFile7(join8(workspace3, "animations", entry.name, "animaguru.project.json"));
47294
47516
  names.push(entry.name);
47295
47517
  } catch (error62) {
47296
47518
  if (error62.code !== "ENOENT") throw error62;
@@ -47300,17 +47522,17 @@ async function localProject(directory, selected) {
47300
47522
  animation = names[0];
47301
47523
  }
47302
47524
  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"));
47525
+ const projectRoot = join8(workspace3, "animations", animation);
47526
+ await readFile7(join8(projectRoot, "animaguru.project.json"));
47305
47527
  return { toolsRoot: workspace3, projectRoot, animation };
47306
47528
  }
47307
47529
 
47308
47530
  // cli/src/cloud-sync.ts
47309
47531
  var import_isomorphic_git = __toESM(require_isomorphic_git(), 1);
47310
47532
  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";
47533
+ 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
47534
  import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
47313
- import { dirname as dirname6, join as join8, resolve as resolve8 } from "node:path";
47535
+ import { dirname as dirname7, join as join9, resolve as resolve9 } from "node:path";
47314
47536
  import { Readable } from "node:stream";
47315
47537
  import { pipeline } from "node:stream/promises";
47316
47538
  import { setTimeout as delay } from "node:timers/promises";
@@ -47529,37 +47751,37 @@ var list = (paths, limit2 = 200) => paths.length > limit2 ? [...paths.slice(0, l
47529
47751
  var short = (oid) => oid.slice(0, 12);
47530
47752
  var executable = (mode) => (mode & 73) !== 0;
47531
47753
  async function cloudRootFor(start) {
47532
- let directory = resolve8(start);
47754
+ let directory = resolve9(start);
47533
47755
  while (true) {
47534
- if (await exists(join8(directory, LINK_FILE)) || await exists(join8(directory, "animaguru.workspace.json"))) return directory;
47756
+ if (await exists(join9(directory, LINK_FILE)) || await exists(join9(directory, "animaguru.workspace.json"))) return directory;
47535
47757
  try {
47536
- const project = JSON.parse(await readFile7(join8(directory, "animaguru.project.json"), "utf8"));
47758
+ const project = JSON.parse(await readFile8(join9(directory, "animaguru.project.json"), "utf8"));
47537
47759
  if (!project.workspace) return directory;
47538
47760
  } catch (error62) {
47539
47761
  if (error62.code !== "ENOENT" && !(error62 instanceof SyntaxError)) throw error62;
47540
47762
  }
47541
- const parent = dirname6(directory);
47763
+ const parent = dirname7(directory);
47542
47764
  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
47765
  directory = parent;
47544
47766
  }
47545
47767
  }
47546
47768
  async function readJson2(path) {
47547
47769
  try {
47548
- return JSON.parse(await readFile7(path, "utf8"));
47770
+ return JSON.parse(await readFile8(path, "utf8"));
47549
47771
  } catch (error62) {
47550
47772
  if (error62.code === "ENOENT") return null;
47551
47773
  throw error62;
47552
47774
  }
47553
47775
  }
47554
47776
  async function writeJson2(path, value) {
47555
- await mkdir7(dirname6(path), { recursive: true });
47777
+ await mkdir8(dirname7(path), { recursive: true });
47556
47778
  const temp = `${path}.${randomUUID2()}.tmp`;
47557
47779
  await writeFile7(temp, JSON.stringify(value, null, 2) + "\n", { mode: 384 });
47558
47780
  await rename4(temp, path);
47559
47781
  }
47560
47782
  async function withLock(root, run) {
47561
- const path = join8(root, SYNC_DIR, "lock");
47562
- await mkdir7(dirname6(path), { recursive: true });
47783
+ const path = join9(root, SYNC_DIR, "lock");
47784
+ await mkdir8(dirname7(path), { recursive: true });
47563
47785
  for (let attempt = 0; ; attempt++) {
47564
47786
  try {
47565
47787
  await writeFile7(path, JSON.stringify({ pid: process.pid, at: Date.now() }), { flag: "wx" });
@@ -47587,27 +47809,27 @@ async function withLock(root, run) {
47587
47809
  }
47588
47810
  async function openContext(dir, options, url3) {
47589
47811
  const root = await cloudRootFor(dir);
47590
- const link2 = await readJson2(join8(root, LINK_FILE));
47812
+ const link2 = await readJson2(join9(root, LINK_FILE));
47591
47813
  if (!link2) throw new Error("This folder is not linked to a project. Run animaguru cloud link, or animaguru cloud clone PROJECT_ID.");
47592
47814
  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 };
47815
+ const state = await readJson2(join9(root, STATE_FILE)) ?? { schemaVersion: 1, base: null, files: {} };
47816
+ return { root, gitdir: join9(root, GIT_DIR), link: link2, state, client: new Client(url3 ?? link2.origin, options.signal), options };
47595
47817
  }
47596
47818
  async function cloudLink(dir) {
47597
- const link2 = await readJson2(join8(await cloudRootFor(dir), LINK_FILE));
47819
+ const link2 = await readJson2(join9(await cloudRootFor(dir), LINK_FILE));
47598
47820
  if (!link2) throw new Error("This folder is not linked to a project. Pass a project ID.");
47599
47821
  return link2;
47600
47822
  }
47601
- var saveState = (context) => writeJson2(join8(context.root, STATE_FILE), context.state);
47823
+ var saveState = (context) => writeJson2(join9(context.root, STATE_FILE), context.state);
47602
47824
  async function scanFiles(root) {
47603
47825
  const files = /* @__PURE__ */ new Map();
47604
47826
  const skipped = [];
47605
47827
  const tooLarge = [];
47606
47828
  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);
47829
+ const entries = (await readdir5(directory, { withFileTypes: true })).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
47608
47830
  const scoped = [...rules];
47609
47831
  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")) });
47832
+ 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
47833
  }
47612
47834
  for (const entry of entries) {
47613
47835
  const path = prefix ? `${prefix}/${entry.name}` : entry.name;
@@ -47618,7 +47840,7 @@ async function scanFiles(root) {
47618
47840
  continue;
47619
47841
  }
47620
47842
  if (entry.isDirectory()) {
47621
- await walk(join8(directory, entry.name), path, scoped);
47843
+ await walk(join9(directory, entry.name), path, scoped);
47622
47844
  continue;
47623
47845
  }
47624
47846
  if (!entry.isFile()) continue;
@@ -47629,7 +47851,7 @@ async function scanFiles(root) {
47629
47851
  skipped.push(`${path} (unsupported name)`);
47630
47852
  continue;
47631
47853
  }
47632
- const info = await stat2(join8(directory, entry.name));
47854
+ const info = await stat3(join9(directory, entry.name));
47633
47855
  if (info.size > CLOUD_OBJECT_MAX_BYTES) {
47634
47856
  skipped.push(`${path} (larger than ${CLOUD_OBJECT_MAX_BYTES / 1024 ** 3} GB)`);
47635
47857
  continue;
@@ -47638,7 +47860,7 @@ async function scanFiles(root) {
47638
47860
  tooLarge.push(safe);
47639
47861
  continue;
47640
47862
  }
47641
- files.set(safe, { path: safe, absolute: join8(directory, entry.name), size: info.size, mtimeMs: info.mtimeMs, mode: executable(info.mode) ? "100755" : "100644" });
47863
+ files.set(safe, { path: safe, absolute: join9(directory, entry.name), size: info.size, mtimeMs: info.mtimeMs, mode: executable(info.mode) ? "100755" : "100644" });
47642
47864
  if (files.size > CLOUD_MAX_FILES) throw new Error(`Cloud sync supports up to ${CLOUD_MAX_FILES} files. Add generated folders to .animaguruignore.`);
47643
47865
  }
47644
47866
  }
@@ -47670,7 +47892,7 @@ async function describeFiles(context, scanned) {
47670
47892
  const sha256 = await fileSha256(file2.absolute);
47671
47893
  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
47894
  } 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 });
47895
+ 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
47896
  }
47675
47897
  }
47676
47898
  return local;
@@ -47734,14 +47956,14 @@ ${entries.join(",\n")}
47734
47956
  }}
47735
47957
  `;
47736
47958
  }
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 } });
47959
+ async function token(context, access4) {
47960
+ const result = await context.client.request(`/api/v1/cloud/${context.link.projectId}/token`, { method: "POST", body: { access: access4 } });
47739
47961
  const remote = new URL(result.remote);
47740
47962
  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
47963
  return { ...result, onAuth: () => ({ username: "x", password: result.token.split("?expires=")[0] }) };
47742
47964
  }
47743
47965
  async function ensureRepo(gitdir) {
47744
- if (await exists(join8(gitdir, "HEAD"))) return;
47966
+ if (await exists(join9(gitdir, "HEAD"))) return;
47745
47967
  await import_isomorphic_git.default.init({ fs, gitdir, bare: true, defaultBranch: CLOUD_BRANCH });
47746
47968
  await import_isomorphic_git.default.setConfig({ fs, gitdir, path: "remote.origin.fetch", value: `+refs/heads/${CLOUD_BRANCH}:refs/remotes/origin/${CLOUD_BRANCH}` });
47747
47969
  }
@@ -47807,12 +48029,12 @@ async function target(root, path) {
47807
48029
  if (builtIn.ignores(path)) throw new Error(`Refusing to write ${path}: cloud sync never writes ignored or secret files.`);
47808
48030
  let current = root;
47809
48031
  for (const part of parts.slice(0, -1)) {
47810
- current = join8(current, part);
48032
+ current = join9(current, part);
47811
48033
  const info2 = await lstat(current).catch(() => null);
47812
- if (!info2) await mkdir7(current);
48034
+ if (!info2) await mkdir8(current);
47813
48035
  else if (info2.isSymbolicLink() || !info2.isDirectory()) throw new Error(`Cannot write ${path}: ${part} is not a folder.`);
47814
48036
  }
47815
- const file2 = join8(current, parts.at(-1));
48037
+ const file2 = join9(current, parts.at(-1));
47816
48038
  const info = await lstat(file2).catch(() => null);
47817
48039
  if (info && !info.isFile()) throw new Error(`Cannot replace ${path}: it is a link or folder.`);
47818
48040
  return file2;
@@ -47838,7 +48060,7 @@ async function materialize(context, path, entry) {
47838
48060
  } else await writeFile7(temp, blob2, { mode: 420 });
47839
48061
  await chmod3(temp, entry.mode === "100755" ? 493 : 420);
47840
48062
  await rename4(temp, file2);
47841
- const info = await stat2(file2);
48063
+ const info = await stat3(file2);
47842
48064
  return { ...entry, size: info.size, mtimeMs: info.mtimeMs, ...pointer ? { sha256: pointer.sha256 } : {} };
47843
48065
  } catch (error62) {
47844
48066
  await rm2(temp, { force: true });
@@ -47846,10 +48068,10 @@ async function materialize(context, path, entry) {
47846
48068
  }
47847
48069
  }
47848
48070
  async function backup(context, folder, path) {
47849
- const source = join8(context.root, path);
48071
+ const source = join9(context.root, path);
47850
48072
  if (!await exists(source)) return;
47851
- const destination = join8(context.root, SYNC_DIR, "backups", folder, path);
47852
- await mkdir7(dirname6(destination), { recursive: true });
48073
+ const destination = join9(context.root, SYNC_DIR, "backups", folder, path);
48074
+ await mkdir8(dirname7(destination), { recursive: true });
47853
48075
  await fs.promises.copyFile(source, destination);
47854
48076
  }
47855
48077
  async function remove(context, path) {
@@ -47869,24 +48091,24 @@ async function apply(context, local, writes, deletes) {
47869
48091
  }
47870
48092
  async function linkCloud(client, options) {
47871
48093
  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.`);
48094
+ 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
48095
  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
48096
  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 });
48097
+ await writeJson2(join9(root, LINK_FILE), { schemaVersion: 1, origin: client.origin, projectId: project.id, name: project.name, workspace: project.workspaceId });
47876
48098
  await keepOutOfGit(root);
47877
48099
  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
48100
  }
47879
48101
  async function keepOutOfGit(root) {
47880
- const path = join8(root, ".gitignore");
47881
- const text2 = await readFile7(path, "utf8").catch(() => null);
48102
+ const path = join9(root, ".gitignore");
48103
+ const text2 = await readFile8(path, "utf8").catch(() => null);
47882
48104
  if (text2 !== null && !/^\/?\.animaguru\/(?:sync\/?)?$/m.test(text2)) await writeFile7(path, `${text2}${text2.endsWith("\n") || !text2 ? "" : "\n"}.animaguru/sync/
47883
48105
  `);
47884
48106
  }
47885
48107
  async function unlinkCloud(options) {
47886
48108
  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.");
48109
+ if (!await exists(join9(root, LINK_FILE))) throw new Error("This folder is not linked to a project.");
47888
48110
  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 });
48111
+ for (const name2 of ["link.json", "state.json", "repo.git"]) await rm2(join9(root, SYNC_DIR, name2), { recursive: true, force: true });
47890
48112
  });
47891
48113
  return { unlinked: true, path: root, note: "The project's animations and versions were not changed." };
47892
48114
  }
@@ -47936,13 +48158,13 @@ async function pushCloud(options) {
47936
48158
  if (!local.size) throw new Error("There are no files to save.");
47937
48159
  for (const [path, entry] of local) {
47938
48160
  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));
48161
+ const bytes = entry.sha256 ? new TextEncoder().encode(cloudPointer(entry.sha256, entry.size)) : await readFile8(join9(context.root, path));
47940
48162
  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
48163
  }
47942
- const uploaded = await uploadObjects(context, local, (path) => join8(context.root, path));
48164
+ const uploaded = await uploadObjects(context, local, (path) => join9(context.root, path));
47943
48165
  for (const [path, entry] of local) {
47944
48166
  if (!entry.sha256 || base2.get(path)?.oid === entry.oid) continue;
47945
- const info = await stat2(join8(context.root, path));
48167
+ const info = await stat3(join9(context.root, path));
47946
48168
  if (info.size !== entry.size || info.mtimeMs !== entry.mtimeMs) throw new Error(`${path} changed while saving. Run the push again.`);
47947
48169
  }
47948
48170
  const tree = await writeTree(context.gitdir, local, manifestText(local));
@@ -48017,10 +48239,10 @@ async function pullCloud(options) {
48017
48239
  async function cloneCloud(client, options) {
48018
48240
  const { project } = await client.request(`/api/v1/cloud/${encodeURIComponent(options.id)}`);
48019
48241
  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 });
48242
+ const root = resolve9(options.dir ?? (project.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || project.id));
48243
+ 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.`);
48244
+ await mkdir8(root, { recursive: true });
48245
+ await writeJson2(join9(root, LINK_FILE), { schemaVersion: 1, origin: client.origin, projectId: project.id, name: project.name, workspace: project.workspaceId });
48024
48246
  const pulled = await pullCloud({ ...options, dir: root });
48025
48247
  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
48248
  }
@@ -48070,8 +48292,8 @@ function sharingEmails(value) {
48070
48292
  return value ? value.split(",").map((email3) => email3.trim()).filter(Boolean) : [];
48071
48293
  }
48072
48294
  async function exportHtml(root, animation, signal, log) {
48073
- const script = join9(root, "scripts/export-html.mjs");
48074
- await stat3(script).catch(() => {
48295
+ const script = join10(root, "scripts/export-html.mjs");
48296
+ await stat4(script).catch(() => {
48075
48297
  throw new Error("Choose an AnimaGuru video project, or pass --file for an exported HTML5 ZIP or MP4.");
48076
48298
  });
48077
48299
  log?.("Building the portable HTML5 animation\u2026");
@@ -48104,23 +48326,23 @@ async function exportHtml(root, animation, signal, log) {
48104
48326
  async function submitVideo(client, options) {
48105
48327
  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
48328
  const workspace3 = options.workspace === void 0 ? void 0 : libraryWorkspace(options.workspace);
48107
- const root = resolve9(options.directory ?? ".");
48329
+ const root = resolve10(options.directory ?? ".");
48108
48330
  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);
48331
+ const file2 = options.file ? resolve10(root, options.file) : await exportHtml(context.toolsRoot, context.animation, options.signal, options.log);
48332
+ const info = await stat4(file2);
48111
48333
  const format = file2.toLowerCase().endsWith(".zip") ? "html5" : file2.toLowerCase().endsWith(".mp4") ? "mp4" : null;
48112
48334
  if (!info.isFile() || !format) throw new Error("Submit an AnimaGuru HTML5 .zip export or an .mp4 file.");
48113
48335
  videoSubmission.parse({ title: options.title ?? "Video", format, bytes: info.size });
48114
48336
  let metadata = {};
48115
48337
  let receipt = {};
48116
48338
  if (format === "html5") {
48117
- const files = unzipSync(new Uint8Array(await readFile8(file2)), { filter: (entry) => entry.name === "manifest.json" && entry.originalSize <= 256 * 1024 });
48339
+ const files = unzipSync(new Uint8Array(await readFile9(file2)), { filter: (entry) => entry.name === "manifest.json" && entry.originalSize <= 256 * 1024 });
48118
48340
  if (!files["manifest.json"]) throw new Error("Use an AnimaGuru HTML5 export with manifest.json.");
48119
48341
  metadata = JSON.parse(strFromU8(files["manifest.json"]));
48120
48342
  if (metadata.format !== "spriteguru-html5" || metadata.version !== 1) throw new Error("Use an AnimaGuru HTML5 export.");
48121
48343
  } else {
48122
48344
  try {
48123
- metadata = JSON.parse(await readFile8(join9(context.projectRoot, "film.config.json"), "utf8"));
48345
+ metadata = JSON.parse(await readFile9(join10(context.projectRoot, "film.config.json"), "utf8"));
48124
48346
  } catch {
48125
48347
  }
48126
48348
  const handle2 = await open4(file2, "r");
@@ -48163,10 +48385,10 @@ async function submitVideo(client, options) {
48163
48385
  }
48164
48386
  let poster;
48165
48387
  if (options.poster) {
48166
- const path = resolve9(root, options.poster);
48167
- const info2 = await stat3(path);
48388
+ const path = resolve10(root, options.poster);
48389
+ const info2 = await stat4(path);
48168
48390
  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);
48391
+ poster = await readFile9(path);
48170
48392
  videoPosterType(poster);
48171
48393
  }
48172
48394
  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 +48438,7 @@ async function changeVideoSubtitles(client, id2, options) {
48216
48438
  return client.request(path + encodeURIComponent(options.remove), { method: "DELETE" });
48217
48439
  }
48218
48440
  if (!options.file) throw new Error("Pass --file with a .srt or .vtt subtitle file, or --remove LANGUAGE.");
48219
- const file2 = resolve9(options.file);
48441
+ const file2 = resolve10(options.file);
48220
48442
  const { content, language: header } = await readSubtitleFile(file2, SUBTITLE_MAX_BYTES);
48221
48443
  const language = subtitleLanguage.safeParse(options.language ?? header ?? (await exportReceipt(file2.replace(/\.(?:srt|vtt)$/i, ".mp4"))).language);
48222
48444
  if (!language.success) throw new Error("Pass --language with the subtitles' language code, such as en or tr.");
@@ -48250,8 +48472,8 @@ async function moveVideos(client, ids, options) {
48250
48472
  }
48251
48473
 
48252
48474
  // 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";
48475
+ import { mkdir as mkdir9, readFile as readFile10, stat as stat5, writeFile as writeFile8 } from "node:fs/promises";
48476
+ import { dirname as dirname8, join as join11, resolve as resolve11 } from "node:path";
48255
48477
  import { setTimeout as delay3 } from "node:timers/promises";
48256
48478
  var extras = {
48257
48479
  image: ["aspect-ratio", "transparent", "references", "mask"],
@@ -48289,6 +48511,7 @@ var extras = {
48289
48511
  music: [],
48290
48512
  speech: ["voice", "delivery"],
48291
48513
  sfx: ["seconds", "loop", "prompt-influence"],
48514
+ model: ["reference", "texture", "pbr", "faces", "rig", "animations", "height"],
48292
48515
  vector: ["workflow", "references", "source", "aspect-ratio", "quality", "shapes", "seed"]
48293
48516
  };
48294
48517
  var list2 = (items) => items.length < 2 ? items.join("") : `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
@@ -48310,6 +48533,7 @@ var regions = (value) => value?.split(";").filter((region) => region.trim()).map
48310
48533
  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
48534
  return { x: x2, y, width, height };
48312
48535
  });
48536
+ var clips = (value) => value?.split(",").map((clip) => clip.trim()).filter(Boolean).map((clip) => /^\d+$/.test(clip) ? Number(clip) : clip);
48313
48537
  var cameraKeyframes = (value) => value?.split(";").filter((frame) => frame.trim()).map((frame) => {
48314
48538
  const [time3, azimuth, elevation, distance, ...rest] = frame.split(",").map((n) => n.trim() ? Number(n) : NaN);
48315
48539
  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 +48548,10 @@ function assetFromFlags(flags) {
48324
48548
  if (flags.type === "image" && flags.model) throw new Error("Image models are managed by staff. Omit --model.");
48325
48549
  for (const name2 of Object.values(extras).flat()) {
48326
48550
  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());
48551
+ const given2 = value !== void 0 && (value !== false || (name2 === "texture" || name2 === "pbr") && flags.type !== "model");
48552
+ if (given2 && !extras[flags.type]?.includes(name2)) throw new Error(`--${name2} is not an option for ${flags.type} assets. ${accepted(flags.type)}`.trim());
48328
48553
  }
48329
- if (flags.type !== "video" && flags.type !== "vector" && flags.prompt === void 0) throw new Error(`--prompt is required for ${flags.type} assets.`);
48554
+ 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
48555
  const base2 = { type: flags.type, name: flags.name, prompt: flags.prompt, model: flags.model };
48331
48556
  if (flags.type === "image") return parseAsset({ ...base2, aspectRatio: flags["aspect-ratio"], transparent: flags.transparent, references: shas(flags.references), mask: regions(flags.mask) });
48332
48557
  if (flags.type === "video") {
@@ -48364,11 +48589,12 @@ function assetFromFlags(flags) {
48364
48589
  });
48365
48590
  }
48366
48591
  if (flags.type === "sfx") return parseAsset({ ...base2, seconds: flags.seconds, loop: flags.loop, promptInfluence: flags["prompt-influence"] });
48592
+ 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
48593
  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
48594
  return parseAsset({ ...base2, ...flags.type === "speech" ? { voice: flags.voice, delivery: flags.delivery } : {} });
48369
48595
  }
48370
48596
  async function assetFromFile(file2, model) {
48371
- const input2 = JSON.parse(await readFile9(file2, "utf8"));
48597
+ const input2 = JSON.parse(await readFile10(file2, "utf8"));
48372
48598
  if (!input2 || typeof input2 !== "object" || Array.isArray(input2)) throw new Error("The asset file must contain a JSON object.");
48373
48599
  return parseAsset({ ...input2, ...model ? { model } : {} });
48374
48600
  }
@@ -48376,7 +48602,7 @@ var MAX_BATCH = 50;
48376
48602
  var isObject2 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
48377
48603
  var fileName = (name2) => name2.normalize("NFKD").replace(/[^\w.-]+/g, "-").replace(/-{2,}/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, 80);
48378
48604
  async function assetsFromBatchFile(file2, model) {
48379
- const input2 = JSON.parse(await readFile9(file2, "utf8"));
48605
+ const input2 = JSON.parse(await readFile10(file2, "utf8"));
48380
48606
  let defaults = {};
48381
48607
  let items;
48382
48608
  if (Array.isArray(input2)) items = input2;
@@ -48462,18 +48688,19 @@ var MIME_EXTENSIONS = {
48462
48688
  "image/webp": "webp",
48463
48689
  "image/svg+xml": "svg",
48464
48690
  "video/mp4": "mp4",
48465
- "video/webm": "webm"
48691
+ "video/webm": "webm",
48692
+ "model/gltf-binary": "glb"
48466
48693
  };
48467
48694
  var extensionFor = (asset) => MIME_EXTENSIONS[asset.mimeType?.split(";")[0].trim().toLowerCase()] ?? /\.([a-z0-9]{1,5})$/i.exec(asset.file ?? "")?.[1]?.toLowerCase() ?? "bin";
48468
48695
  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);
48696
+ const path = resolve11(output2);
48697
+ const isFolder = folder || /[\\/]$/.test(output2) || await stat5(path).then((info) => info.isDirectory(), () => false);
48471
48698
  if (!isFolder) {
48472
- await mkdir8(dirname7(path), { recursive: true });
48699
+ await mkdir9(dirname8(path), { recursive: true });
48473
48700
  return path;
48474
48701
  }
48475
- await mkdir8(path, { recursive: true });
48476
- return join10(path, `${fileName(asset.name) || asset.id}.${extensionFor(asset)}`);
48702
+ await mkdir9(path, { recursive: true });
48703
+ return join11(path, `${fileName(asset.name) || asset.id}.${extensionFor(asset)}`);
48477
48704
  }
48478
48705
  async function saveAsset(client, library, asset, path) {
48479
48706
  if (!library.ownerId || !/^[a-f0-9]{64}$/.test(asset.file)) throw new Error("Invalid asset download metadata.");
@@ -48497,7 +48724,7 @@ async function downloadAsset(client, project, assetId, output2) {
48497
48724
  const library = await client.request(assetsPath(project));
48498
48725
  const asset = library.assets.find((a) => a.id === assetId);
48499
48726
  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));
48727
+ return saveAsset(client, library, asset, resolve11(output2));
48501
48728
  }
48502
48729
  async function generateAsset(client, project, body, options, signal) {
48503
48730
  const waiting = !options["dry-run"] && Boolean(options.wait || options.download);
@@ -48574,7 +48801,7 @@ async function submitAssetBatch(client, project, requests, options, signal) {
48574
48801
  }
48575
48802
 
48576
48803
  // cli/src/avatars.ts
48577
- import { readFile as readFile10, stat as stat5 } from "node:fs/promises";
48804
+ import { readFile as readFile11, stat as stat6 } from "node:fs/promises";
48578
48805
  import { extname } from "node:path";
48579
48806
  import { setTimeout as delay4 } from "node:timers/promises";
48580
48807
 
@@ -48680,8 +48907,8 @@ async function avatarFile2(value) {
48680
48907
  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
48908
  const mediaType = MEDIA_TYPES[extname(value).toLowerCase()];
48682
48909
  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") };
48910
+ 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.");
48911
+ return { type: "base64", mediaType, data: (await readFile11(value)).toString("base64") };
48685
48912
  }
48686
48913
  async function avatarFromFlags(flags) {
48687
48914
  const prompt = flags.type === "prompt";
@@ -48729,11 +48956,11 @@ async function waitForLook(client, project, lookId, timeout, signal, interval =
48729
48956
  // cli/src/youtube-auth.ts
48730
48957
  import { createServer } from "node:http";
48731
48958
  import { randomBytes, createHash as createHash5, timingSafeEqual } from "node:crypto";
48732
- import { readFile as readFile12, stat as stat6 } from "node:fs/promises";
48959
+ import { readFile as readFile13, stat as stat7 } from "node:fs/promises";
48733
48960
 
48734
48961
  // 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";
48962
+ 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";
48963
+ import { dirname as dirname9, join as join12 } from "node:path";
48737
48964
  import { randomUUID as randomUUID3 } from "node:crypto";
48738
48965
  function refuseInAnimator() {
48739
48966
  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 +49018,14 @@ var youtubeJob = external_exports.object({
48791
49018
  captions: youtubeCaptions.optional(),
48792
49019
  thumbnail: youtubeThumbnail.optional()
48793
49020
  }).strict();
48794
- var youtubeDirectory = () => join11(dirname8(credentialsPath()), "youtube");
48795
- var credentialFile = () => join11(youtubeDirectory(), "credentials.json");
49021
+ var youtubeDirectory = () => join12(dirname9(credentialsPath()), "youtube");
49022
+ var credentialFile = () => join12(youtubeDirectory(), "credentials.json");
48796
49023
  function jobFile(id2) {
48797
49024
  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`);
49025
+ return join12(youtubeDirectory(), "uploads", `${id2}.json`);
48799
49026
  }
48800
49027
  async function privateDirectory(directory) {
48801
- await mkdir9(directory, { recursive: true, mode: 448 });
49028
+ await mkdir10(directory, { recursive: true, mode: 448 });
48802
49029
  const info = await lstat2(directory);
48803
49030
  if (info.isSymbolicLink() || !info.isDirectory()) throw new Error("YouTube configuration must use a real directory.");
48804
49031
  await chmod4(directory, 448);
@@ -48815,11 +49042,11 @@ async function existingDirectory(directory) {
48815
49042
  }
48816
49043
  async function readPrivate(file2) {
48817
49044
  try {
48818
- if (!await existingDirectory(youtubeDirectory()) || !await existingDirectory(dirname8(file2))) return null;
49045
+ if (!await existingDirectory(youtubeDirectory()) || !await existingDirectory(dirname9(file2))) return null;
48819
49046
  const info = await lstat2(file2);
48820
49047
  if (info.isSymbolicLink() || !info.isFile() || info.size > 65536) throw new Error("Invalid local YouTube state file.");
48821
49048
  await chmod4(file2, 384);
48822
- return JSON.parse(await readFile11(file2, "utf8"));
49049
+ return JSON.parse(await readFile12(file2, "utf8"));
48823
49050
  } catch (error62) {
48824
49051
  if (error62.code === "ENOENT") return null;
48825
49052
  throw new Error("Cannot read local YouTube state. Check your AnimaGuru configuration directory.");
@@ -48827,7 +49054,7 @@ async function readPrivate(file2) {
48827
49054
  }
48828
49055
  async function writePrivate(file2, value) {
48829
49056
  await privateDirectory(youtubeDirectory());
48830
- await privateDirectory(dirname8(file2));
49057
+ await privateDirectory(dirname9(file2));
48831
49058
  const temporary = `${file2}.${randomUUID3()}.tmp`;
48832
49059
  try {
48833
49060
  await writeFile9(temporary, JSON.stringify(value) + "\n", { flag: "wx", mode: 384 });
@@ -48859,11 +49086,11 @@ function youtubeJobSummary(job) {
48859
49086
  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
49087
  }
48861
49088
  async function localYouTubeJobs() {
48862
- const directory = join11(youtubeDirectory(), "uploads");
49089
+ const directory = join12(youtubeDirectory(), "uploads");
48863
49090
  if (!await existingDirectory(directory)) return [];
48864
49091
  let files;
48865
49092
  try {
48866
- files = await readdir5(directory);
49093
+ files = await readdir6(directory);
48867
49094
  } catch (error62) {
48868
49095
  if (error62.code === "ENOENT") return [];
48869
49096
  throw error62;
@@ -48880,10 +49107,10 @@ async function clearYouTubeData() {
48880
49107
  await unlink(credentialFile()).catch((error62) => {
48881
49108
  if (error62.code !== "ENOENT") throw error62;
48882
49109
  });
48883
- if (!await existingDirectory(join11(youtubeDirectory(), "uploads"))) return;
49110
+ if (!await existingDirectory(join12(youtubeDirectory(), "uploads"))) return;
48884
49111
  let files;
48885
49112
  try {
48886
- files = await readdir5(join11(youtubeDirectory(), "uploads"));
49113
+ files = await readdir6(join12(youtubeDirectory(), "uploads"));
48887
49114
  } catch (error62) {
48888
49115
  if (error62.code === "ENOENT") return;
48889
49116
  throw error62;
@@ -48893,7 +49120,7 @@ async function clearYouTubeData() {
48893
49120
  async function withYouTubeLock(operation) {
48894
49121
  refuseInAnimator();
48895
49122
  await privateDirectory(youtubeDirectory());
48896
- const file2 = join11(youtubeDirectory(), "operation.lock");
49123
+ const file2 = join12(youtubeDirectory(), "operation.lock");
48897
49124
  const lock = await open5(file2, "wx", 384).catch((error62) => {
48898
49125
  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
49126
  throw error62;
@@ -49041,11 +49268,11 @@ async function youtubeLogin(options) {
49041
49268
  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
49269
  if (existing) throw new Error("A YouTube channel is already connected. Run youtube auth logout --yes before switching channels.");
49043
49270
  const file2 = options["client-file"];
49044
- const info = await stat6(file2);
49271
+ const info = await stat7(file2);
49045
49272
  if (!info.isFile() || info.size > 65536) throw new Error("Use Google's downloaded Desktop app OAuth client JSON.");
49046
49273
  let installed;
49047
49274
  try {
49048
- installed = JSON.parse(await readFile12(file2, "utf8")).installed;
49275
+ installed = JSON.parse(await readFile13(file2, "utf8")).installed;
49049
49276
  } catch {
49050
49277
  }
49051
49278
  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 +49282,8 @@ async function youtubeLogin(options) {
49055
49282
  let receive;
49056
49283
  let fail;
49057
49284
  let used = false;
49058
- const callback = new Promise((resolve18, reject) => {
49059
- receive = resolve18;
49285
+ const callback = new Promise((resolve19, reject) => {
49286
+ receive = resolve19;
49060
49287
  fail = reject;
49061
49288
  });
49062
49289
  void callback.catch(() => {
@@ -49094,9 +49321,9 @@ async function youtubeLogin(options) {
49094
49321
  response.end("Authorization received. Return to your AnimaGuru terminal.");
49095
49322
  receive(code);
49096
49323
  });
49097
- await new Promise((resolve18, reject) => {
49324
+ await new Promise((resolve19, reject) => {
49098
49325
  server.once("error", reject);
49099
- server.listen(0, "127.0.0.1", resolve18);
49326
+ server.listen(0, "127.0.0.1", resolve19);
49100
49327
  });
49101
49328
  const address = server.address();
49102
49329
  if (!address || typeof address === "string") throw new Error("Cannot open the local YouTube authorization listener.");
@@ -49131,7 +49358,7 @@ async function youtubeLogin(options) {
49131
49358
  clearTimeout(timer);
49132
49359
  options.signal?.removeEventListener("abort", abort);
49133
49360
  server.closeAllConnections();
49134
- await new Promise((resolve18) => server.close(() => resolve18()));
49361
+ await new Promise((resolve19) => server.close(() => resolve19()));
49135
49362
  }
49136
49363
  });
49137
49364
  }
@@ -49162,8 +49389,8 @@ async function youtubeLogout(options) {
49162
49389
 
49163
49390
  // cli/src/youtube-upload.ts
49164
49391
  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";
49392
+ import { open as open6, readFile as readFile14, stat as stat8 } from "node:fs/promises";
49393
+ import { basename as basename5, join as join13, resolve as resolve12 } from "node:path";
49167
49394
  import { setTimeout as delay5 } from "node:timers/promises";
49168
49395
  var YOUTUBE_CHUNK_BYTES = 8 * 1024 * 1024;
49169
49396
  function safeYouTubeSession(value) {
@@ -49214,23 +49441,23 @@ async function planCaptions(root, mp4, options, fallbackLanguage) {
49214
49441
  async function readThumbnail(file2) {
49215
49442
  let info;
49216
49443
  try {
49217
- info = await stat7(file2);
49444
+ info = await stat8(file2);
49218
49445
  } catch {
49219
49446
  throw new Error(`Cannot read the thumbnail ${file2}.`);
49220
49447
  }
49221
49448
  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);
49449
+ const content = await readFile14(file2);
49223
49450
  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
49451
  if (!type) throw new Error("Use a JPEG or PNG thumbnail.");
49225
49452
  return { content, type, sha256: createHash6("sha256").update(content).digest("hex") };
49226
49453
  }
49227
49454
  async function planThumbnail(root, mp4, choice = "auto") {
49228
49455
  if (choice === "none") return null;
49229
- let file2 = choice === "auto" ? void 0 : resolve11(root, choice);
49456
+ let file2 = choice === "auto" ? void 0 : resolve12(root, choice);
49230
49457
  if (!file2) {
49231
49458
  for (const extension of [".jpg", ".jpeg", ".png"]) {
49232
49459
  const candidate = mp4.replace(/\.mp4$/i, extension);
49233
- if (await stat7(candidate).then((info) => info.isFile(), () => false)) {
49460
+ if (await stat8(candidate).then((info) => info.isFile(), () => false)) {
49234
49461
  file2 = candidate;
49235
49462
  break;
49236
49463
  }
@@ -49415,12 +49642,12 @@ async function uploadYouTube(options) {
49415
49642
  refuseInAnimator();
49416
49643
  if (!options.yes && !options["dry-run"]) throw new Error("Pass --yes to authorize the YouTube upload, or --dry-run to inspect it locally.");
49417
49644
  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);
49645
+ const root = resolve12(options.directory ?? ".");
49646
+ const file2 = resolve12(root, options.file);
49420
49647
  const context = await localProject(root, options.animation);
49421
49648
  let config2 = {};
49422
49649
  try {
49423
- config2 = JSON.parse(await readFile13(join12(context.projectRoot, "film.config.json"), "utf8"));
49650
+ config2 = JSON.parse(await readFile14(join13(context.projectRoot, "film.config.json"), "utf8"));
49424
49651
  } catch {
49425
49652
  }
49426
49653
  const receipt = await exportReceipt(file2);
@@ -49497,8 +49724,8 @@ async function resumeYouTube(id2, options) {
49497
49724
  var listYouTubeUploads = () => withYouTubeLock(async () => ({ uploads: (await localYouTubeJobs()).map(youtubeJobSummary) }));
49498
49725
 
49499
49726
  // 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";
49727
+ import { access as access3, mkdir as mkdir11, writeFile as writeFile10 } from "node:fs/promises";
49728
+ import { dirname as dirname10, join as join14, relative as relative2, resolve as resolve13, sep as sep2 } from "node:path";
49502
49729
 
49503
49730
  // shared/motion-library.ts
49504
49731
  var MOTION_CATEGORIES = [
@@ -49547,18 +49774,18 @@ async function searchMotion(client, query, flags) {
49547
49774
  if (flags.code) params.set("include_code", "true");
49548
49775
  return client.request(`/api/v1/motion-library/search?${params}`);
49549
49776
  }
49550
- var exists2 = (path) => access2(path).then(() => true, () => false);
49777
+ var exists2 = (path) => access3(path).then(() => true, () => false);
49551
49778
  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;
49779
+ for (let directory = start; ; directory = dirname10(directory)) {
49780
+ if (await exists2(join14(directory, "animaguru.workspace.json"))) return directory;
49781
+ if (dirname10(directory) === directory) return null;
49555
49782
  }
49556
49783
  }
49557
49784
  async function writeMotionExample(example, directory, { force = false } = {}) {
49558
- const target2 = resolve12(directory);
49785
+ const target2 = resolve13(directory);
49559
49786
  const workspace3 = await workspaceRoot(target2);
49560
49787
  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 }));
49788
+ const files = example.files.map((file2) => ({ path: join14(target2, safeProjectPath(file2.path)), code: inAnimation ? motionCodeForWorkspace(file2.code) : file2.code }));
49562
49789
  if (!force) {
49563
49790
  const clashes = [];
49564
49791
  for (const [index2, file2] of files.entries()) if (index2 === 0 && await exists2(file2.path)) clashes.push(file2.path);
@@ -49570,7 +49797,7 @@ async function writeMotionExample(example, directory, { force = false } = {}) {
49570
49797
  kept.push(file2.path);
49571
49798
  continue;
49572
49799
  }
49573
- await mkdir10(dirname9(file2.path), { recursive: true });
49800
+ await mkdir11(dirname10(file2.path), { recursive: true });
49574
49801
  await writeFile10(file2.path, file2.code);
49575
49802
  written.push(file2.path);
49576
49803
  }
@@ -49578,8 +49805,8 @@ async function writeMotionExample(example, directory, { force = false } = {}) {
49578
49805
  }
49579
49806
 
49580
49807
  // 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";
49808
+ import { mkdir as mkdir12, readFile as readFile15, writeFile as writeFile11 } from "node:fs/promises";
49809
+ import { dirname as dirname11, join as join15, resolve as resolve14 } from "node:path";
49583
49810
 
49584
49811
  // shared/asset-library.ts
49585
49812
  var LIBRARY_ASSET_TYPES = ["sprite", "image", "video", "music", "speech", "sfx"];
@@ -49666,23 +49893,23 @@ function searchLibrary(client, query, flags) {
49666
49893
  var getLibraryAsset = (client, id2) => client.request(libraryPath(id2));
49667
49894
  async function downloadLibraryAsset(client, id2, output2, { force = false } = {}) {
49668
49895
  const asset = await getLibraryAsset(client, id2);
49669
- const root = resolve13(output2, asset.type === "sprite" ? safeProjectPath(asset.id) : ".");
49896
+ const root = resolve14(output2, asset.type === "sprite" ? safeProjectPath(asset.id) : ".");
49670
49897
  const written = [];
49671
49898
  for (const file2 of asset.files) {
49672
- const path = join14(root, safeProjectPath(file2.path));
49899
+ const path = join15(root, safeProjectPath(file2.path));
49673
49900
  const response = await client.request(`${libraryPath(asset.id)}/files/${file2.path.split("/").map(encodeURIComponent).join("/")}?download=1`, { raw: true, timeout: 3e5 });
49674
49901
  const bytes = new Uint8Array(await response.arrayBuffer());
49675
- await mkdir11(dirname10(path), { recursive: true });
49902
+ await mkdir12(dirname11(path), { recursive: true });
49676
49903
  await writeFile11(path, bytes, { flag: force ? "w" : "wx" }).catch((error62) => {
49677
49904
  throw error62.code === "EEXIST" ? new Error(`Already exists: ${path}. Pass --force to replace it.`) : error62;
49678
49905
  });
49679
49906
  written.push({ path, size: bytes.byteLength, mimeType: file2.mimeType });
49680
49907
  }
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 })) } : {} };
49908
+ 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
49909
  }
49683
49910
  var importLibraryAsset = (client, id2, project, name2) => client.request(`${libraryPath(id2)}/import`, { method: "POST", body: { projectId: project, ...name2 ? { name: name2 } : {} } });
49684
49911
  async function words(flags) {
49685
- const input2 = flags.file ? JSON.parse(await readFile14(flags.file, "utf8")) : {};
49912
+ const input2 = flags.file ? JSON.parse(await readFile15(flags.file, "utf8")) : {};
49686
49913
  if (!input2 || typeof input2 !== "object" || Array.isArray(input2)) throw new Error("The file must contain a JSON object.");
49687
49914
  return {
49688
49915
  ...input2,
@@ -49727,8 +49954,8 @@ async function moveWork(client, project, flags) {
49727
49954
  }
49728
49955
 
49729
49956
  // 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";
49957
+ import { mkdir as mkdir13, readFile as readFile16, rename as rename6, unlink as unlink2, writeFile as writeFile12 } from "node:fs/promises";
49958
+ import { dirname as dirname12, join as join16 } from "node:path";
49732
49959
  import { randomUUID as randomUUID5 } from "node:crypto";
49733
49960
  var releaseUrl = "https://animaguru.com/cli/release.json";
49734
49961
  function versionParts(version3) {
@@ -49749,10 +49976,10 @@ function parseRelease(value) {
49749
49976
  versionParts(value.version);
49750
49977
  return { name: value.name, version: value.version };
49751
49978
  }
49752
- var cachePath = () => join15(dirname11(credentialsPath()), "cli-release.json");
49979
+ var cachePath = () => join16(dirname12(credentialsPath()), "cli-release.json");
49753
49980
  async function cachedRelease() {
49754
49981
  try {
49755
- return parseRelease(JSON.parse(await readFile15(cachePath(), "utf8")));
49982
+ return parseRelease(JSON.parse(await readFile16(cachePath(), "utf8")));
49756
49983
  } catch {
49757
49984
  return void 0;
49758
49985
  }
@@ -49768,7 +49995,7 @@ async function latestRelease(signal) {
49768
49995
  const path = cachePath();
49769
49996
  const temp = `${path}.${randomUUID5()}.tmp`;
49770
49997
  try {
49771
- await mkdir12(dirname11(path), { recursive: true, mode: 448 });
49998
+ await mkdir13(dirname12(path), { recursive: true, mode: 448 });
49772
49999
  await writeFile12(temp, JSON.stringify(release) + "\n", { flag: "wx", mode: 384 });
49773
50000
  await rename6(temp, path);
49774
50001
  } catch {
@@ -49810,8 +50037,8 @@ async function requireCurrentRelease(argv, stderr) {
49810
50037
  }
49811
50038
 
49812
50039
  // 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";
50040
+ import { lstat as lstat3, mkdir as mkdir14, readFile as readFile17, stat as stat9, writeFile as writeFile13 } from "node:fs/promises";
50041
+ import { basename as basename6, dirname as dirname13, extname as extname2, join as join17, resolve as resolve15 } from "node:path";
49815
50042
 
49816
50043
  // shared/brands.ts
49817
50044
  var brandId = external_exports.string().regex(/^br_[a-f0-9]{24}$/);
@@ -49930,7 +50157,7 @@ async function listBrands(client, selected) {
49930
50157
  }
49931
50158
  var getBrand = (client, id2) => client.request(brandPath(id2));
49932
50159
  async function jsonObject(file2) {
49933
- const value = JSON.parse(await readFile16(file2, "utf8"));
50160
+ const value = JSON.parse(await readFile17(file2, "utf8"));
49934
50161
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("The file must contain a JSON object.");
49935
50162
  return value;
49936
50163
  }
@@ -49957,12 +50184,12 @@ async function uploadBrandFile(client, id2, file2, flags) {
49957
50184
  const path = brandPath(id2);
49958
50185
  if (!flags.yes) throw new Error("Pass --yes to authorize uploading this file to the brand.");
49959
50186
  if (flags.kind && !BRAND_FILE_KINDS.includes(flags.kind)) throw new Error("Unknown brand file kind.");
49960
- const info = await stat8(file2);
50187
+ const info = await stat9(file2);
49961
50188
  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
50189
  const extension = extname2(file2).slice(1).toLowerCase().replace(/^jpeg$/, "jpg");
49963
50190
  const mimeType = flags["mime-type"] ?? Object.entries(BRAND_FILE_TYPES).find(([, ext]) => ext === extension)?.[0];
49964
50191
  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));
50192
+ const bytes = new Uint8Array(await readFile17(file2));
49966
50193
  if (!bytes.byteLength || bytes.byteLength > BRAND_FILE_BYTES) throw new Error("A brand file must be between 1 byte and 32 MiB.");
49967
50194
  const form = new FormData();
49968
50195
  form.set("file", new File([bytes], basename6(file2), { type: mimeType }));
@@ -49980,22 +50207,22 @@ async function downloadBrand(client, id2, output2, flags = {}) {
49980
50207
  `, mimeType: "application/json" },
49981
50208
  { path: "branding/brand-guide.md", content: brandGuide(brand), mimeType: "text/markdown" }
49982
50209
  ];
49983
- const root = resolve14(output2);
49984
- const targets = [...textFiles.map((file2) => file2.path), ...files.map(brandFilePath)].map((path) => join16(root, safeProjectPath(path)));
50210
+ const root = resolve15(output2);
50211
+ const targets = [...textFiles.map((file2) => file2.path), ...files.map(brandFilePath)].map((path) => join17(root, safeProjectPath(path)));
49985
50212
  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]) {
50213
+ for (const path of [join17(root, "branding"), ...targets]) {
49987
50214
  const info = await lstat3(path).catch((error62) => {
49988
50215
  if (error62.code !== "ENOENT") throw error62;
49989
50216
  return null;
49990
50217
  });
49991
50218
  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.`);
50219
+ if (path !== join17(root, "branding") && info && (!flags.force || !info.isFile())) throw new Error(`Already exists: ${path}. Pass --force to replace files.`);
49993
50220
  }
49994
- await mkdir13(join16(root, "branding"), { recursive: true });
50221
+ await mkdir14(join17(root, "branding"), { recursive: true });
49995
50222
  const written = [];
49996
50223
  const save = async (path, bytes, mimeType) => {
49997
- const target2 = join16(root, safeProjectPath(path));
49998
- await mkdir13(dirname12(target2), { recursive: true });
50224
+ const target2 = join17(root, safeProjectPath(path));
50225
+ await mkdir14(dirname13(target2), { recursive: true });
49999
50226
  await writeFile13(target2, bytes, { flag: flags.force ? "w" : "wx" });
50000
50227
  written.push({ path: target2, size: bytes.byteLength, mimeType });
50001
50228
  };
@@ -50008,8 +50235,8 @@ async function downloadBrand(client, id2, output2, flags = {}) {
50008
50235
  }
50009
50236
 
50010
50237
  // 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";
50238
+ import { readFile as readFile18, readdir as readdir7, stat as stat10, writeFile as writeFile14 } from "node:fs/promises";
50239
+ import { join as join18 } from "node:path";
50013
50240
 
50014
50241
  // shared/frame-checks.ts
50015
50242
  var FRAME_CHECK_MAX_FRAMES = 12;
@@ -50053,17 +50280,17 @@ var frameCheckRequest = external_exports.object({
50053
50280
  // cli/src/review-check.ts
50054
50281
  async function framesToCheck(projectRoot, source, report2) {
50055
50282
  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);
50283
+ const folder2 = join18(projectRoot, "production/review/frames");
50284
+ const names2 = await readdir7(folder2).catch(() => []);
50285
+ 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
50286
  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
50287
  return frames;
50061
50288
  }
50062
- const folder = join17(projectRoot, "production/review");
50289
+ const folder = join18(projectRoot, "production/review");
50063
50290
  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();
50291
+ const names = (await readdir7(folder)).filter((name2) => /^frame-\d{2}\.jpg$/.test(name2)).sort();
50065
50292
  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] }));
50293
+ return names.map((name2, index2) => ({ file: join18(folder, name2), time: times[index2] }));
50067
50294
  }
50068
50295
  function filmContext(report2, brief, extra) {
50069
50296
  const config2 = report2.config ?? {};
@@ -50078,29 +50305,29 @@ function compact(answers) {
50078
50305
  async function reviewCheck(client, options) {
50079
50306
  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
50307
  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(() => {
50308
+ const reportFile = join18(context.projectRoot, "production/review/report.json");
50309
+ const report2 = JSON.parse(await readFile18(reportFile, "utf8").catch(() => {
50083
50310
  throw new Error("Run npm run review first: production/review/report.json is missing.");
50084
50311
  }));
50085
50312
  const frames = await framesToCheck(context.projectRoot, options.source ?? "review", report2);
50086
50313
  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));
50314
+ const sizes = await Promise.all(frames.map(async (frame) => (await stat10(frame.file)).size));
50088
50315
  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(() => "");
50316
+ const questions = options.questions ? frameCheckQuestions.parse(JSON.parse(await readFile18(options.questions, "utf8"))) : void 0;
50317
+ const brief = await readFile18(join18(context.projectRoot, "production/brief.md"), "utf8").catch(() => "");
50091
50318
  const link2 = await cloudLink(context.toolsRoot).catch(() => null);
50092
50319
  const body = {
50093
50320
  ...link2?.origin === client.origin ? { projectId: link2.projectId } : { workspace: await selectedWorkspace(client.origin) },
50094
50321
  context: filmContext(report2, brief, options.context),
50095
50322
  ...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")}` }))),
50323
+ 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
50324
  ...questions ? { questions } : {},
50098
50325
  ...options["dry-run"] ? { dryRun: true } : {}
50099
50326
  };
50100
50327
  if (options["dry-run"]) return client.request("/api/v1/frame-checks", { method: "POST", body, timeout: 12e4 });
50101
50328
  const result = await client.request("/api/v1/frame-checks", { method: "POST", body, timeout: 12e4 });
50102
50329
  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");
50330
+ const saved = join18(context.projectRoot, "production/review/check.json");
50104
50331
  await writeFile14(saved, `${JSON.stringify({ ...result, frames: result.frames.map((frame) => ({ file: fileAt(frame.time), ...frame })) }, null, 2)}
50105
50332
  `);
50106
50333
  return {
@@ -50114,8 +50341,8 @@ async function reviewCheck(client, options) {
50114
50341
 
50115
50342
  // cli/src/reference.ts
50116
50343
  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";
50344
+ import { mkdir as mkdir15, stat as stat11, writeFile as writeFile15 } from "node:fs/promises";
50345
+ import { basename as basename7, dirname as dirname14, extname as extname3, join as join19, relative as relative3, resolve as resolve16 } from "node:path";
50119
50346
 
50120
50347
  // shared/video-analysis.ts
50121
50348
  var VIDEO_ANALYSIS_MAX_BYTES = 100 * 1024 * 1024;
@@ -50146,11 +50373,11 @@ var TYPES = { ".mp4": "video/mp4", ".m4v": "video/mp4", ".mov": "video/quicktime
50146
50373
  var POLL_MS = 1e4;
50147
50374
  var WAIT_LIMIT_MS = 70 * 6e4;
50148
50375
  async function briefFile(options, name2) {
50149
- if (options.out) return resolve15(options.out);
50376
+ if (options.out) return resolve16(options.out);
50150
50377
  const slug2 = name2.replace(/\.[a-z0-9]{2,4}$/i, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60) || "reference";
50151
50378
  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`);
50379
+ const film = project && await stat11(join19(project.projectRoot, "animaguru.project.json")).then(() => true, () => false);
50380
+ return film ? join19(project.projectRoot, "production/references", `${slug2}.json`) : resolve16(options.directory ?? ".", `${slug2}.analysis.json`);
50154
50381
  }
50155
50382
  var pause = (ms, signal) => new Promise((done, fail) => {
50156
50383
  signal?.throwIfAborted();
@@ -50189,7 +50416,7 @@ async function report(analysis, options) {
50189
50416
  if (analysis.status === "failed") throw new Error(analysis.error ?? "The analysis failed.");
50190
50417
  if (analysis.status !== "completed") return { id: analysis.id, status: analysis.status, next: `animaguru reference get ${analysis.id} --wait` };
50191
50418
  const file2 = await briefFile(options, sourceName(analysis));
50192
- await mkdir14(dirname13(file2), { recursive: true });
50419
+ await mkdir15(dirname14(file2), { recursive: true });
50193
50420
  await writeFile15(file2, `${JSON.stringify(analysis, null, 2)}
50194
50421
  `);
50195
50422
  const brief = analysis.result?.brief;
@@ -50214,8 +50441,8 @@ async function referenceAnalyze(client, options) {
50214
50441
  if (!options.video.startsWith("https://")) throw new Error("Use an HTTPS link to the video file.");
50215
50442
  started = await client.request("/api/v1/video-analyses", { method: "POST", body: { ...settings2, url: options.video, ...dryRun ? { dryRun: true } : {} }, timeout: 12e4 });
50216
50443
  } else {
50217
- const file2 = resolve15(options.video);
50218
- const info = await stat10(file2).catch(() => null);
50444
+ const file2 = resolve16(options.video);
50445
+ const info = await stat11(file2).catch(() => null);
50219
50446
  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
50447
  const type = TYPES[extname3(file2).toLowerCase()];
50221
50448
  if (!type) throw new Error("Analyse an MP4, MOV or WebM video.");
@@ -50237,8 +50464,8 @@ async function referenceGet(client, id2, options) {
50237
50464
 
50238
50465
  // cli/src/curl.ts
50239
50466
  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";
50467
+ import { readFile as readFile19, unlink as unlink3 } from "node:fs/promises";
50468
+ import { extname as extname4, resolve as resolve17 } from "node:path";
50242
50469
  import { Readable as Readable2 } from "node:stream";
50243
50470
  import { pipeline as pipeline2 } from "node:stream/promises";
50244
50471
  function curlUrl(server, target2) {
@@ -50278,7 +50505,7 @@ var readStdin = async () => {
50278
50505
  };
50279
50506
  async function bodyValue(value, io) {
50280
50507
  if (value === "@-") return (io.stdin ?? readStdin)();
50281
- return value.startsWith("@") ? readFile18(resolve16(value.slice(1))) : value;
50508
+ return value.startsWith("@") ? readFile19(resolve17(value.slice(1))) : value;
50282
50509
  }
50283
50510
  var parsesAsJson = (body) => {
50284
50511
  try {
@@ -50294,7 +50521,7 @@ async function curl(options, io) {
50294
50521
  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
50522
  let body;
50296
50523
  if (options.uploadFile !== void 0) {
50297
- const file2 = resolve16(options.uploadFile);
50524
+ const file2 = resolve17(options.uploadFile);
50298
50525
  body = await openAsBlob2(file2);
50299
50526
  if (!headers.has("content-type")) headers.set("Content-Type", UPLOAD_TYPES[extname4(file2).toLowerCase()] ?? "application/octet-stream");
50300
50527
  } else if (options.json !== void 0) {
@@ -50314,7 +50541,7 @@ async function curl(options, io) {
50314
50541
  const type = response.headers.get("content-type") ?? "";
50315
50542
  const failed = options.fail && response.status >= 400;
50316
50543
  if (options.output !== void 0 && !failed) {
50317
- const file2 = resolve16(options.output);
50544
+ const file2 = resolve17(options.output);
50318
50545
  let bytes = 0;
50319
50546
  try {
50320
50547
  await pipeline2(
@@ -50344,7 +50571,7 @@ async function curl(options, io) {
50344
50571
  }
50345
50572
 
50346
50573
  // 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)];
50574
+ 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
50575
  var assetAuthorizationFlags = [
50349
50576
  { name: "yes", type: "boolean", description: "Authorize spending credits" },
50350
50577
  { name: "dry-run", type: "boolean", description: "Validate and estimate locally without submitting a paid job" }
@@ -50361,9 +50588,10 @@ var catalogSections = {
50361
50588
  music: ["assetTypes", "musicModels"],
50362
50589
  speech: ["assetTypes", "speechModels", "voices", "elevenLabsVoices"],
50363
50590
  sfx: ["assetTypes", "sfxModels"],
50591
+ model: ["assetTypes", "modelGenerators", "modelAnimations"],
50364
50592
  avatars: ["avatarTypes", "avatarLookTypes"]
50365
50593
  };
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 });
50594
+ 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
50595
  var modelFlags = [
50368
50596
  { name: "video-model", type: "string", choices: SPRITE_VIDEO_MODEL_IDS, description: "Video model override for this operation" }
50369
50597
  ];
@@ -50394,12 +50622,12 @@ var workspaceQuery = async (client) => {
50394
50622
  async function openBrowser(url3) {
50395
50623
  const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32" : "xdg-open";
50396
50624
  const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url3] : [url3];
50397
- await new Promise((resolve18) => {
50625
+ await new Promise((resolve19) => {
50398
50626
  const child = spawn3(command, args, { stdio: "ignore", detached: true });
50399
- child.once("error", () => resolve18());
50627
+ child.once("error", () => resolve19());
50400
50628
  child.once("spawn", () => {
50401
50629
  child.unref();
50402
- resolve18();
50630
+ resolve19();
50403
50631
  });
50404
50632
  });
50405
50633
  }
@@ -50425,7 +50653,10 @@ function createCli() {
50425
50653
  { 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
50654
  { name: "yes", type: "boolean", description: "Authorize sending this video, its subtitles and thumbnail to the selected channel and privacy; confirm upload rights" },
50427
50655
  { 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(
50656
+ ).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(
50657
+ { name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" }, description: "Server origin whose skills to install" },
50658
+ { name: "dir", type: "string", default: ".", description: "Folder to install into (created if missing)" }
50659
+ ).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
50660
  { name: "animation", type: "string", description: "Animation in a multi-animation workspace" },
50430
50661
  { 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
50662
  { name: "context", type: "string", description: "What the film should show, added to its title, format and brief" },
@@ -50592,7 +50823,7 @@ Confirm code: ${device.user_code}`);
50592
50823
  })))).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
50824
  const client = new Client(flags.url, signal);
50594
50825
  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 }) => {
50826
+ }))).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
50827
  const full = catalog();
50597
50828
  const section = flags.type;
50598
50829
  out(stdout, section ? Object.fromEntries(catalogSections[section].map((key) => [key, full[key]])) : full);
@@ -50659,7 +50890,7 @@ Confirm code: ${device.user_code}`);
50659
50890
  ).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
50891
  if (!flags.yes) throw new Error("Pass --yes to delete this project.");
50661
50892
  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(
50893
+ })))).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
50894
  { name: "type", type: "string", required: true, choices: ASSET_TYPES },
50664
50895
  { name: "name", type: "string", required: true },
50665
50896
  { 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 +50900,7 @@ Confirm code: ${device.user_code}`);
50669
50900
  { 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
50901
  { 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
50902
  { 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" },
50903
+ { 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
50904
  { name: "end-reference", type: "string", description: "Video: ending image blob SHA; models with endFrameOnly accept it without --reference" },
50674
50905
  { 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
50906
  { name: "middle-seconds", type: "number", description: "Reference-to-video: when the middle image is reached, in seconds" },
@@ -50698,6 +50929,12 @@ Confirm code: ${device.user_code}`);
50698
50929
  { name: "voice", type: "string", description: "Gemini voice name or ElevenLabs voice ID (defaults: Kore / George)" },
50699
50930
  { name: "delivery", type: "string", description: "Gemini speech delivery instructions; ElevenLabs uses audio tags in the transcript" },
50700
50931
  { name: "loop", type: "boolean", description: "SFX: create a seamless loop" },
50932
+ { name: "texture", type: "boolean", description: "Model: textured (default on); --no-texture makes a plain white model" },
50933
+ { name: "pbr", type: "boolean", description: "Model: metallic, roughness and normal maps with the texture (default on); --no-pbr leaves them out" },
50934
+ { 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" },
50935
+ { name: "rig", type: "boolean", description: "Model: auto-rig a humanoid character standing in a T- or A-pose, with animation clips" },
50936
+ { 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" },
50937
+ { name: "height", type: "number", description: "Model with --rig: the character's height in metres (default 1.7)" },
50701
50938
  { name: "prompt-influence", type: "number", description: "SFX: prompt adherence, 0\u20131 (default 0.3)" },
50702
50939
  { 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
50940
  { 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 +50983,7 @@ Confirm code: ${device.user_code}`);
50746
50983
  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
50984
  })))).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
50985
  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) });
50986
+ const body = spriteJobBody.parse({ ...JSON.parse(await readFile20(flags.file, "utf8")), ...selectedModels(flags) });
50750
50987
  out(stdout, await new Client(flags.url, signal).request(`${projectPath(args.id)}/jobs`, { method: "POST", body }));
50751
50988
  }))).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
50989
  const body = selectedModels(flags);
@@ -50761,7 +50998,7 @@ Confirm code: ${device.user_code}`);
50761
50998
  const extension = flags.file.split(".").at(-1)?.toLowerCase();
50762
50999
  const type = extension === "png" ? "image/png" : extension === "jpg" || extension === "jpeg" ? "image/jpeg" : extension === "webp" ? "image/webp" : void 0;
50763
51000
  if (!type) throw new Error("Use a PNG, JPEG or WebP image.");
50764
- const bytes = await readFile19(flags.file);
51001
+ const bytes = await readFile20(flags.file);
50765
51002
  if (bytes.length > 10 * 1024 * 1024) throw new Error("Image must be at most 10 MB.");
50766
51003
  const form = new FormData();
50767
51004
  form.set("image", new Blob([bytes], { type }), `reference.${extension}`);
@@ -50777,8 +51014,8 @@ Confirm code: ${device.user_code}`);
50777
51014
  if (!file2) throw new Error(`No file at ${args.path} in this project. List them with animaguru files list.`);
50778
51015
  if (!/^[a-f0-9]{64}$/.test(file2.sha)) throw new Error("Invalid blob hash in manifest.");
50779
51016
  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 });
51017
+ const path = resolve18(flags.output);
51018
+ await mkdir16(dirname15(path), { recursive: true });
50782
51019
  const bytes = new Uint8Array(await response.arrayBuffer());
50783
51020
  await writeFile16(path, bytes, { flag: "wx" });
50784
51021
  out(stdout, { path, bytes: bytes.length });
@@ -50792,7 +51029,7 @@ Confirm code: ${device.user_code}`);
50792
51029
  const response = await client.request(`/api/v1/blobs/${encodeURIComponent(manifest.ownerId)}/${file2.sha}`, { raw: true });
50793
51030
  entries[path2] = new Uint8Array(await response.arrayBuffer());
50794
51031
  }
50795
- const path = resolve17(flags.output);
51032
+ const path = resolve18(flags.output);
50796
51033
  await writeFile16(path, zipSync(entries, { level: 0 }), { flag: "wx" });
50797
51034
  out(stdout, { path, files: manifest.files.length });
50798
51035
  })))).extend(version(package_default.version, { format: "plain" })).extend(help()).extend(completion());