@genex-ai/cli-demo 1.19.0-dev.601 → 1.20.0-dev.602

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7164,9 +7164,20 @@ function applySkyboxGuard(prompt, raw) {
7164
7164
  if (guarded.length > SKYBOX_PROMPT_MAX) return { prompt, guarded: false, nouns };
7165
7165
  return { prompt: guarded, guarded: true, nouns };
7166
7166
  }
7167
+ var MODEL_TEXTURE_TIERS = ["standard", "detailed", "none"];
7167
7168
  function buildGenOptions(kind, opts) {
7168
7169
  const options = { ...opts.generationOptions };
7169
7170
  if (kind === "model" && opts.imageUrl) options.imageUrl = opts.imageUrl;
7171
+ if (kind === "model") {
7172
+ if (opts.texture === "none") options.texture = false;
7173
+ else if (opts.texture !== void 0) options.textureQuality = opts.texture;
7174
+ if (opts.geometry !== void 0) options.geometryQuality = opts.geometry;
7175
+ if (opts.quad) options.quad = true;
7176
+ if (opts.lowPoly) options.smartLowPoly = true;
7177
+ if (opts.parts) options.generateParts = true;
7178
+ if (opts.faceLimit !== void 0) options.faceLimit = opts.faceLimit;
7179
+ if (opts.autoSize) options.autoSize = true;
7180
+ }
7170
7181
  if (kind === "texture" && opts.terrain) options.terrain = true;
7171
7182
  if ((kind === "sfx" || kind === "music") && opts.duration) options.durationSeconds = opts.duration;
7172
7183
  if (kind === "voice") {
@@ -7217,6 +7228,11 @@ async function runGenerate(kind, opts) {
7217
7228
  const ref = opts.imageUrl.startsWith("data:") ? "local image" : opts.imageUrl;
7218
7229
  typedPrompt = `from image: ${path16.basename(ref).slice(0, 120)}`;
7219
7230
  }
7231
+ if (kind === "model" && opts.texture !== void 0 && !MODEL_TEXTURE_TIERS.includes(opts.texture)) {
7232
+ log.error(`--texture ${opts.texture} is a character texture size. \`genex model\` takes a texture TIER: ${MODEL_TEXTURE_TIERS.join("|")} (default detailed).`);
7233
+ process.exitCode = 1;
7234
+ return void 0;
7235
+ }
7220
7236
  if (!typedPrompt) {
7221
7237
  log.error(`Missing prompt. Usage: ${c.cyan(`genex ${kind} "<prompt>"`)}`);
7222
7238
  process.exitCode = 1;
@@ -7982,7 +7998,111 @@ function writeJson(value) {
7982
7998
  }
7983
7999
 
7984
8000
  // src/commands/model-sub.ts
7985
- var MODEL_SUBCOMMANDS = ["segment", "rig", "animate"];
8001
+ import fs18 from "fs/promises";
8002
+ import path17 from "path";
8003
+ var MODEL_SUBCOMMANDS = ["segment", "rig", "animate", "import"];
8004
+ function apiErrorMessage(data, fallback) {
8005
+ if (typeof data !== "object" || data === null) return fallback;
8006
+ const body = data;
8007
+ if (typeof body.message === "string" && body.message) return body.message;
8008
+ if (typeof body.error === "string" && body.error) return body.error;
8009
+ return fallback;
8010
+ }
8011
+ var MODEL_IMPORT_MAX_BYTES = 64 * 1024 * 1024;
8012
+ var GLB_MAGIC = 1179937895;
8013
+ async function importModelFile(args) {
8014
+ const { log } = args;
8015
+ const filePath = args.filePath.trim();
8016
+ if (!/\.glb$/i.test(filePath)) {
8017
+ log.error(`\`import\` takes a .glb file (binary glTF 2.0). Export ${path17.basename(filePath) || "the model"} as GLB first \u2014 Blender: File \u2192 Export \u2192 glTF 2.0, format "glTF Binary".`);
8018
+ return null;
8019
+ }
8020
+ let bytes;
8021
+ try {
8022
+ bytes = await fs18.readFile(filePath);
8023
+ } catch {
8024
+ log.error(`Couldn't read ${filePath}.`);
8025
+ return null;
8026
+ }
8027
+ if (bytes.byteLength > MODEL_IMPORT_MAX_BYTES) {
8028
+ log.error(`${path17.basename(filePath)} is ${(bytes.byteLength / 1e6).toFixed(1)} MB; imports are capped at ${MODEL_IMPORT_MAX_BYTES / 1024 / 1024} MB. Shrink its textures (they are usually the bulk) and try again.`);
8029
+ return null;
8030
+ }
8031
+ if (bytes.byteLength < 12 || bytes.readUInt32LE(0) !== GLB_MAGIC) {
8032
+ log.error(`${path17.basename(filePath)} is not a GLB (no glTF magic). A .gltf + .bin pair must be exported as one binary .glb.`);
8033
+ return null;
8034
+ }
8035
+ const headers = { "Content-Type": "application/json", Authorization: `Bearer ${args.token}` };
8036
+ const minted = await apiFetch(`${args.apiUrl}/api/generations/import`, {
8037
+ method: "POST",
8038
+ headers,
8039
+ body: JSON.stringify({ filename: path17.basename(filePath), bytes: bytes.byteLength, contentType: "model/gltf-binary" })
8040
+ });
8041
+ if (printedStructuredError(minted)) return null;
8042
+ if (!minted.ok) {
8043
+ const data = await minted.json().catch(() => ({}));
8044
+ log.error(apiErrorMessage(data, `Couldn't start the import (HTTP ${minted.status}).`));
8045
+ return null;
8046
+ }
8047
+ const { id, uploadUrl, url } = await minted.json();
8048
+ log.dim(` uploading ${path17.basename(filePath)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
8049
+ const put = await fetch(uploadUrl, {
8050
+ method: "PUT",
8051
+ headers: { "Content-Type": "model/gltf-binary", "Content-Length": String(bytes.byteLength) },
8052
+ body: new Uint8Array(bytes)
8053
+ });
8054
+ if (!put.ok) {
8055
+ log.error(`Upload failed (HTTP ${put.status}). Nothing was charged \u2014 run the import again.`);
8056
+ return null;
8057
+ }
8058
+ const done = await apiFetch(`${args.apiUrl}/api/generations/import/${encodeURIComponent(id)}/complete`, {
8059
+ method: "POST",
8060
+ headers
8061
+ });
8062
+ if (printedStructuredError(done)) return null;
8063
+ if (!done.ok) {
8064
+ const data = await done.json().catch(() => ({}));
8065
+ log.error(apiErrorMessage(data, `The import was refused (HTTP ${done.status}).`));
8066
+ return null;
8067
+ }
8068
+ const view = await done.json();
8069
+ return { id, url, inspection: view.inspection ?? null };
8070
+ }
8071
+ async function runModelImport(opts) {
8072
+ const log = createLogger({ quiet: opts.quiet || opts.json });
8073
+ const filePath = opts.sourceId?.trim();
8074
+ if (!filePath) {
8075
+ log.error(`Missing file. Usage: ${c.cyan("genex model import <file.glb>")}`);
8076
+ process.exitCode = 1;
8077
+ return;
8078
+ }
8079
+ const token = opts.token ?? await readUserToken(opts.envPath);
8080
+ if (!token) {
8081
+ log.error("Not authorized. Run `genex init` first to sign in.");
8082
+ process.exitCode = 1;
8083
+ return;
8084
+ }
8085
+ const project = await readProject();
8086
+ const apiUrl = getApiUrl(opts.apiUrl ?? project?.apiUrl);
8087
+ const imported = await importModelFile({ apiUrl, token, filePath, log });
8088
+ if (!imported) {
8089
+ process.exitCode = 1;
8090
+ return;
8091
+ }
8092
+ const next = {
8093
+ rig: `genex model rig ${imported.id}`,
8094
+ character: `genex character import ${filePath}`
8095
+ };
8096
+ if (opts.json) {
8097
+ writeJson({ kind: "model", id: imported.id, status: "completed", url: imported.url, inspection: imported.inspection, nextCommands: next });
8098
+ return;
8099
+ }
8100
+ const facts = imported.inspection ? ` \xB7 ${imported.inspection.faceCount.toLocaleString("en-US")} faces${imported.inspection.skinned ? " \xB7 already skinned" : ""}` : "";
8101
+ log.success(`Imported (${imported.id})${facts}`);
8102
+ log.plain(` ${imported.url}`);
8103
+ log.plain(` Rig it (7 body plans): ${c.cyan(next.rig)}`);
8104
+ log.plain(` Or make it a playable humanoid: ${c.cyan(next.character)}`);
8105
+ }
7986
8106
  var SEGMENT_GRANULARITIES = ["simple", "balanced", "detailed"];
7987
8107
  var MODEL_RIG_TYPES = [
7988
8108
  "biped",
@@ -8271,8 +8391,8 @@ async function toRow(e, v, cwd) {
8271
8391
  }
8272
8392
 
8273
8393
  // src/commands/controller.ts
8274
- import fs19 from "fs/promises";
8275
- import path18 from "path";
8394
+ import fs20 from "fs/promises";
8395
+ import path19 from "path";
8276
8396
 
8277
8397
  // ../../packages/meshy-animation-catalog/src/index.ts
8278
8398
  import { createHash } from "crypto";
@@ -17402,9 +17522,9 @@ function searchMeshyAnimations(query, options = {}) {
17402
17522
  }
17403
17523
 
17404
17524
  // src/lib/anims.ts
17405
- import fs18 from "fs/promises";
17406
- import path17 from "path";
17407
- var ANIMS_DEST = path17.join("public", "assets", "anims");
17525
+ import fs19 from "fs/promises";
17526
+ import path18 from "path";
17527
+ var ANIMS_DEST = path18.join("public", "assets", "anims");
17408
17528
  var HIDDEN_TAG = "reference";
17409
17529
  async function runAnims(opts) {
17410
17530
  const log = createLogger({ quiet: opts.quiet });
@@ -17420,7 +17540,7 @@ async function runAnims(opts) {
17420
17540
  printCatalog(log, manifest, selectors);
17421
17541
  return;
17422
17542
  }
17423
- const controllerMarker = path17.join(root, "src", "controllers", "character");
17543
+ const controllerMarker = path18.join(root, "src", "controllers", "character");
17424
17544
  if (!await exists2(controllerMarker)) {
17425
17545
  log.error(
17426
17546
  `No character controller in this game (missing ${c.cyan("src/controllers/character/")}).`
@@ -17429,11 +17549,11 @@ async function runAnims(opts) {
17429
17549
  process.exitCode = 1;
17430
17550
  return;
17431
17551
  }
17432
- const destDir = path17.join(root, ANIMS_DEST);
17433
- const gameManifestPath = path17.join(destDir, "manifest.json");
17552
+ const destDir = path18.join(root, ANIMS_DEST);
17553
+ const gameManifestPath = path18.join(destDir, "manifest.json");
17434
17554
  if (opts.reset) {
17435
- await fs18.rm(destDir, { recursive: true, force: true });
17436
- log.step(`Cleared ${c.cyan(ANIMS_DEST + path17.sep)} (--reset)`);
17555
+ await fs19.rm(destDir, { recursive: true, force: true });
17556
+ log.step(`Cleared ${c.cyan(ANIMS_DEST + path18.sep)} (--reset)`);
17437
17557
  }
17438
17558
  if (selectors.length === 0) {
17439
17559
  const installed = await readGameManifest(gameManifestPath);
@@ -17471,35 +17591,35 @@ async function runAnims(opts) {
17471
17591
  }
17472
17592
  }
17473
17593
  const wanted = [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
17474
- const cacheDir = path17.join(
17594
+ const cacheDir = path18.join(
17475
17595
  opts.cacheDir ?? getAnimsCacheDir(),
17476
17596
  `${manifest.library}-v${manifest.version}`
17477
17597
  );
17478
- await fs18.mkdir(cacheDir, { recursive: true });
17479
- await fs18.mkdir(destDir, { recursive: true });
17598
+ await fs19.mkdir(cacheDir, { recursive: true });
17599
+ await fs19.mkdir(destDir, { recursive: true });
17480
17600
  const base = getAnimsBase(opts.animsBase);
17481
17601
  let installedCount = 0;
17482
17602
  let presentCount = 0;
17483
17603
  let addedBytes = 0;
17484
17604
  const failures = [];
17485
17605
  for (const entry of wanted) {
17486
- const dest = path17.join(destDir, entry.file);
17606
+ const dest = path18.join(destDir, entry.file);
17487
17607
  if (await hasSize(dest, entry.bytes)) {
17488
17608
  presentCount++;
17489
17609
  continue;
17490
17610
  }
17491
17611
  try {
17492
- const cached = path17.join(cacheDir, entry.file);
17612
+ const cached = path18.join(cacheDir, entry.file);
17493
17613
  if (!await hasSize(cached, entry.bytes)) {
17494
17614
  const res = await fetch(base + entry.file);
17495
17615
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
17496
17616
  const buf = Buffer.from(await res.arrayBuffer());
17497
- await fs18.writeFile(cached, buf);
17617
+ await fs19.writeFile(cached, buf);
17498
17618
  }
17499
- await fs18.copyFile(cached, dest);
17619
+ await fs19.copyFile(cached, dest);
17500
17620
  installedCount++;
17501
17621
  addedBytes += entry.bytes;
17502
- log.dim(` ${path17.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
17622
+ log.dim(` ${path18.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
17503
17623
  } catch (err) {
17504
17624
  failures.push(`${entry.name} (${err instanceof Error ? err.message : String(err)})`);
17505
17625
  }
@@ -17515,13 +17635,13 @@ async function runAnims(opts) {
17515
17635
  version: manifest.version,
17516
17636
  clips: [...union].sort((a, b) => a.localeCompare(b))
17517
17637
  };
17518
- await fs18.writeFile(gameManifestPath, JSON.stringify(gameManifest, null, 2) + "\n");
17638
+ await fs19.writeFile(gameManifestPath, JSON.stringify(gameManifest, null, 2) + "\n");
17519
17639
  log.plain("");
17520
17640
  const parts = [`${installedCount} clip${installedCount === 1 ? "" : "s"} installed`];
17521
17641
  if (presentCount > 0) parts.push(`${presentCount} already present`);
17522
17642
  if (bundledSkips > 0) parts.push(`${bundledSkips} already bundled in animation-library.glb`);
17523
17643
  log.success(
17524
- `${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path17.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
17644
+ `${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path18.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
17525
17645
  );
17526
17646
  for (const [selector, entries] of resolved) {
17527
17647
  const names = entries.filter((e) => !coreNames.has(e.name)).map((e) => e.name);
@@ -17553,8 +17673,8 @@ async function loadManifest(baseOverride) {
17553
17673
  }
17554
17674
  } catch {
17555
17675
  }
17556
- const snapshotPath = path17.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
17557
- const manifest = JSON.parse(await fs18.readFile(snapshotPath, "utf8"));
17676
+ const snapshotPath = path18.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
17677
+ const manifest = JSON.parse(await fs19.readFile(snapshotPath, "utf8"));
17558
17678
  return { manifest, source: "snapshot" };
17559
17679
  }
17560
17680
  function resolveSelectors(manifest, selectors) {
@@ -17672,21 +17792,21 @@ function printCatalog(log, manifest, selectors) {
17672
17792
  }
17673
17793
  async function readGameManifest(file) {
17674
17794
  try {
17675
- return JSON.parse(await fs18.readFile(file, "utf8"));
17795
+ return JSON.parse(await fs19.readFile(file, "utf8"));
17676
17796
  } catch {
17677
17797
  return null;
17678
17798
  }
17679
17799
  }
17680
17800
  async function hasSize(file, bytes) {
17681
17801
  try {
17682
- return (await fs18.stat(file)).size === bytes;
17802
+ return (await fs19.stat(file)).size === bytes;
17683
17803
  } catch {
17684
17804
  return false;
17685
17805
  }
17686
17806
  }
17687
17807
  async function exists2(p) {
17688
17808
  try {
17689
- await fs18.access(p);
17809
+ await fs19.access(p);
17690
17810
  return true;
17691
17811
  } catch {
17692
17812
  return false;
@@ -17886,8 +18006,8 @@ var CONTROLLER_FILE_SETS = {
17886
18006
  ]
17887
18007
  }
17888
18008
  };
17889
- var CODE_DEST = path18.join("src", "controllers");
17890
- var ASSETS_DEST = path18.join("public", "assets");
18009
+ var CODE_DEST = path19.join("src", "controllers");
18010
+ var ASSETS_DEST = path19.join("public", "assets");
17891
18011
  async function runController(opts) {
17892
18012
  const log = createLogger({ quiet: opts.quiet });
17893
18013
  if (opts.kind?.trim() === "anims") {
@@ -17904,31 +18024,31 @@ async function runController(opts) {
17904
18024
  process.exitCode = 1;
17905
18025
  return;
17906
18026
  }
17907
- const srcDir = path18.join(getTemplatesDir(), "controllers");
18027
+ const srcDir = path19.join(getTemplatesDir(), "controllers");
17908
18028
  const root = opts.cwd ?? process.cwd();
17909
18029
  const set = CONTROLLER_FILE_SETS[kind];
17910
18030
  log.plain(c.bold(`genex controller ${kind}`));
17911
18031
  log.plain("");
17912
- log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path18.sep)}`);
18032
+ log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path19.sep)}`);
17913
18033
  const plan = [
17914
- ...set.code.map((rel) => ({ from: rel, rel: path18.join(CODE_DEST, rel) })),
18034
+ ...set.code.map((rel) => ({ from: rel, rel: path19.join(CODE_DEST, rel) })),
17915
18035
  ...set.assets.map((rel) => ({
17916
18036
  from: rel,
17917
- rel: path18.join(ASSETS_DEST, path18.basename(rel))
18037
+ rel: path19.join(ASSETS_DEST, path19.basename(rel))
17918
18038
  }))
17919
18039
  ];
17920
18040
  let copied = 0;
17921
18041
  let skipped = 0;
17922
18042
  try {
17923
18043
  for (const file of plan) {
17924
- const dest = path18.join(root, file.rel);
18044
+ const dest = path19.join(root, file.rel);
17925
18045
  if (!opts.force && await exists3(dest)) {
17926
18046
  skipped++;
17927
18047
  log.dim(` skipped ${file.rel} (exists \u2014 use --force to overwrite)`);
17928
18048
  continue;
17929
18049
  }
17930
- await fs19.mkdir(path18.dirname(dest), { recursive: true });
17931
- await fs19.copyFile(path18.join(srcDir, file.from), dest);
18050
+ await fs20.mkdir(path19.dirname(dest), { recursive: true });
18051
+ await fs20.copyFile(path19.join(srcDir, file.from), dest);
17932
18052
  copied++;
17933
18053
  log.dim(` ${file.rel}`);
17934
18054
  }
@@ -17981,7 +18101,7 @@ async function runController(opts) {
17981
18101
  for (const line of set.sketch) {
17982
18102
  log.dim(` ${line}`);
17983
18103
  }
17984
- if (kind === "character" && !await exists3(path18.join(root, ASSETS_DEST, "meshy-character.json"))) {
18104
+ if (kind === "character" && !await exists3(path19.join(root, ASSETS_DEST, "meshy-character.json"))) {
17985
18105
  log.plain("");
17986
18106
  log.plain(
17987
18107
  ` ${stepOffset + 3}. This game has no generated character yet \u2014 ${c.cyan(
@@ -18009,13 +18129,13 @@ async function installMeshyCharacterManifest(args) {
18009
18129
  }
18010
18130
  const body = await response.json();
18011
18131
  const manifest = body.manifest;
18012
- if (!manifest || manifest.schema !== 1 || manifest.rig !== "meshy-biped" || manifest.characterId !== args.characterId || typeof manifest.model?.url !== "string" || typeof manifest.model.skeletonSignature !== "string" || !Array.isArray(manifest.clips) || !manifest.locomotion?.slots) {
18132
+ if (!manifest || manifest.schema !== 1 || manifest.rig !== "meshy-biped" && manifest.rig !== "uthana-biped" || manifest.characterId !== args.characterId || typeof manifest.model?.url !== "string" || typeof manifest.model.skeletonSignature !== "string" || !Array.isArray(manifest.clips) || !manifest.locomotion?.slots) {
18013
18133
  throw new Error("The API returned an invalid Meshy character manifest.");
18014
18134
  }
18015
18135
  assertCompleteMeshyControllerPack(manifest);
18016
- const destination = path18.join(args.root, ASSETS_DEST, "meshy-character.json");
18017
- await fs19.mkdir(path18.dirname(destination), { recursive: true });
18018
- await fs19.writeFile(
18136
+ const destination = path19.join(args.root, ASSETS_DEST, "meshy-character.json");
18137
+ await fs20.mkdir(path19.dirname(destination), { recursive: true });
18138
+ await fs20.writeFile(
18019
18139
  destination,
18020
18140
  `${JSON.stringify(manifest, null, 2)}
18021
18141
  `
@@ -18025,6 +18145,13 @@ async function installMeshyCharacterManifest(args) {
18025
18145
  const pack2 = manifest.controllerPack;
18026
18146
  if (typeof pack2?.key === "string" && typeof pack2.version === "number") {
18027
18147
  args.log.success(`Meshy controller pack ${pack2.key} v${pack2.version}`);
18148
+ } else if (manifest.rig === "uthana-biped") {
18149
+ const slotCount = Object.keys(manifest.locomotion?.slots ?? {}).length;
18150
+ if (slotCount === 0) {
18151
+ args.log.warn(`Uthana-rigged import with no locomotion yet \u2014 run \`genex character animate ${args.characterId} --locomotion\` or the body will stand still.`);
18152
+ } else {
18153
+ args.log.success(`Uthana-rigged import \xB7 ${slotCount} locomotion slot${slotCount === 1 ? "" : "s"} from generated motion`);
18154
+ }
18028
18155
  } else {
18029
18156
  args.log.warn("Legacy Meshy manifest \u2014 regenerate the character for the immutable preview-reviewed neutral-v3 locomotion pack.");
18030
18157
  }
@@ -18153,14 +18280,14 @@ function assertCompleteMeshyControllerPack(manifest) {
18153
18280
  }
18154
18281
  async function installFallbackAvatar(args) {
18155
18282
  const { root, srcDir, log } = args;
18156
- const dest = path18.join(root, ASSETS_DEST, "avatar.vrm");
18157
- await fs19.mkdir(path18.dirname(dest), { recursive: true });
18158
- await fs19.copyFile(path18.join(srcDir, "assets", "default-avatar.vrm"), dest);
18283
+ const dest = path19.join(root, ASSETS_DEST, "avatar.vrm");
18284
+ await fs20.mkdir(path19.dirname(dest), { recursive: true });
18285
+ await fs20.copyFile(path19.join(srcDir, "assets", "default-avatar.vrm"), dest);
18159
18286
  log.dim(" public/assets/avatar.vrm (fallback avatar \u2014 bundled CC0 default)");
18160
18287
  }
18161
18288
  async function exists3(p) {
18162
18289
  try {
18163
- await fs19.access(p);
18290
+ await fs20.access(p);
18164
18291
  return true;
18165
18292
  } catch {
18166
18293
  return false;
@@ -18168,8 +18295,8 @@ async function exists3(p) {
18168
18295
  }
18169
18296
 
18170
18297
  // src/commands/character.ts
18171
- import fs20 from "fs/promises";
18172
- import path19 from "path";
18298
+ import fs21 from "fs/promises";
18299
+ import path20 from "path";
18173
18300
  function exactAnimation(selector) {
18174
18301
  const trimmed = selector.trim();
18175
18302
  if (/^-?\d+$/.test(trimmed)) return animationById(Number(trimmed));
@@ -18201,7 +18328,7 @@ async function context(opts) {
18201
18328
  return { token, apiUrl: getApiUrl(opts.apiUrl ?? project?.apiUrl) };
18202
18329
  }
18203
18330
  var CHARACTER_BRIEF_MAX_CHARS = 600;
18204
- function apiErrorMessage(data, fallback) {
18331
+ function apiErrorMessage2(data, fallback) {
18205
18332
  if (typeof data !== "object" || data === null) return fallback;
18206
18333
  const body = data;
18207
18334
  if (typeof body.message === "string" && body.message) return body.message;
@@ -18225,7 +18352,7 @@ async function quote(url, token, body) {
18225
18352
  if (printedStructuredError(response)) return null;
18226
18353
  if (!response.ok) {
18227
18354
  const data = await response.json().catch(() => ({}));
18228
- throw new Error(apiErrorMessage(data, `Quote failed (HTTP ${response.status}).`));
18355
+ throw new Error(apiErrorMessage2(data, `Quote failed (HTTP ${response.status}).`));
18229
18356
  }
18230
18357
  return (await response.json()).quote;
18231
18358
  }
@@ -18249,7 +18376,7 @@ function showAmbiguity(selector, candidates, log, json = false) {
18249
18376
  }
18250
18377
  process.exitCode = 1;
18251
18378
  }
18252
- var INSTALLED_MANIFEST = path19.join("public", "assets", "meshy-character.json");
18379
+ var INSTALLED_MANIFEST = path20.join("public", "assets", "meshy-character.json");
18253
18380
  async function resolveAdoptTarget(selector) {
18254
18381
  const trimmed = selector?.trim();
18255
18382
  if (trimmed && !trimmed.endsWith(".json")) {
@@ -18258,7 +18385,7 @@ async function resolveAdoptTarget(selector) {
18258
18385
  const file = trimmed ?? INSTALLED_MANIFEST;
18259
18386
  let raw;
18260
18387
  try {
18261
- raw = await fs20.readFile(file, "utf8");
18388
+ raw = await fs21.readFile(file, "utf8");
18262
18389
  } catch {
18263
18390
  return {
18264
18391
  ok: false,
@@ -18334,6 +18461,81 @@ async function runCharacterAdopt(opts) {
18334
18461
  }
18335
18462
  });
18336
18463
  }
18464
+ async function runCharacterImport(opts) {
18465
+ const log = createLogger({ quiet: opts.quiet || opts.json });
18466
+ const filePath = opts.importPath?.trim();
18467
+ if (!filePath) {
18468
+ fail2(opts, "Missing file. Usage: genex character import <file.glb> [--height 1.8] [--no-fingers]");
18469
+ return;
18470
+ }
18471
+ if (opts.texture !== void 0 || opts.ultra === false || opts.pose !== void 0 || opts.polycount !== void 0) {
18472
+ fail2(opts, "--texture / --no-ultra / --pose / --polycount are Meshy generation knobs; an import keeps your mesh exactly as it is. Uthana only adds the skeleton (--no-fingers skips finger joints).");
18473
+ return;
18474
+ }
18475
+ if (opts.controllerPack === false || (opts.actions?.length ?? 0) > 0) {
18476
+ fail2(opts, "An imported character has no Meshy controller pack and takes no catalog --animation: its motion comes from `genex character animate <id> --locomotion` and plain-word verbs.");
18477
+ return;
18478
+ }
18479
+ const ctx = await context(opts);
18480
+ if (!ctx) {
18481
+ fail2(opts, "Not authorized. Run `genex init` first to sign in.");
18482
+ return;
18483
+ }
18484
+ const imported = await importModelFile({ apiUrl: ctx.apiUrl, token: ctx.token, filePath, log });
18485
+ if (!imported) {
18486
+ process.exitCode = 1;
18487
+ return;
18488
+ }
18489
+ const body = {
18490
+ sourceGenerationId: imported.id,
18491
+ includeFingers: opts.fingers !== false,
18492
+ ...opts.height === void 0 ? {} : { heightMeters: opts.height }
18493
+ };
18494
+ const price = await quote(`${ctx.apiUrl}/api/characters/import/quote`, ctx.token, body);
18495
+ if (!price) {
18496
+ process.exitCode = 1;
18497
+ return;
18498
+ }
18499
+ if (!opts.json) {
18500
+ log.plain(c.bold("Character import quote"));
18501
+ log.plain(` ${price.credits} Genex credits \xB7 Uthana auto-rig${opts.fingers === false ? " (no finger joints)" : " with finger joints"} \xB7 the model upload was free`);
18502
+ log.plain("");
18503
+ }
18504
+ await runWorkflow({
18505
+ opts,
18506
+ ctx,
18507
+ kind: "character",
18508
+ prompt: `Import ${path20.basename(filePath)} as a rigged character`,
18509
+ createPath: "/api/characters/import",
18510
+ body,
18511
+ quote: price,
18512
+ completed: (view) => {
18513
+ const confidence = typeof view.metadata?.autoRigConfidence === "number" ? view.metadata.autoRigConfidence : null;
18514
+ const next = {
18515
+ locomotion: `genex character animate ${view.id} --locomotion`,
18516
+ install: `genex controller character --character ${view.id}`
18517
+ };
18518
+ if (opts.json) {
18519
+ writeJson({
18520
+ kind: "character",
18521
+ status: view.status,
18522
+ characterId: view.id,
18523
+ modelGenerationId: imported.id,
18524
+ rigProvider: "uthana",
18525
+ autoRigConfidence: confidence,
18526
+ nextCommand: next.locomotion,
18527
+ nextCommands: next
18528
+ });
18529
+ return;
18530
+ }
18531
+ log.success(`Rigged by Uthana${confidence === null ? "" : ` (confidence ${confidence.toFixed(2)})`}: ${c.cyan(view.id)}`);
18532
+ log.plain(" It has a skeleton and no clips yet. Give it the walk/run set, then point the game at it:");
18533
+ log.plain(` ${c.cyan(next.locomotion)}`);
18534
+ log.plain(` ${c.cyan(next.install)}`);
18535
+ log.dim(' Any move in plain words: genex character animate <id> "<what it should do>" \xB7 the Meshy catalog does not apply to this rig.');
18536
+ }
18537
+ });
18538
+ }
18337
18539
  async function runCharacter(opts) {
18338
18540
  if (opts.directText) {
18339
18541
  await runDirectTextCharacter(opts);
@@ -18362,7 +18564,7 @@ async function postWorkflow(url, token, body) {
18362
18564
  if (printedStructuredError(response)) return null;
18363
18565
  if (!response.ok) {
18364
18566
  const data2 = await response.json().catch(() => ({}));
18365
- throw new Error(apiErrorMessage(data2, `Character workflow request failed (HTTP ${response.status}).`));
18567
+ throw new Error(apiErrorMessage2(data2, `Character workflow request failed (HTTP ${response.status}).`));
18366
18568
  }
18367
18569
  const data = await response.json();
18368
18570
  if (typeof data.id !== "string" || data.id.length === 0) {
@@ -18432,7 +18634,11 @@ async function runCharacterConcept(opts) {
18432
18634
  return;
18433
18635
  }
18434
18636
  if (opts.polycount !== void 0) {
18435
- fail2(opts, "--polycount is only available with --direct-text. The approval flow remeshes only after finalize approval.", "character_concept");
18637
+ fail2(opts, "--polycount is only available with --direct-text. The approval flow takes its face budget on `character finalize --approve-remesh <faces>`.", "character_concept");
18638
+ return;
18639
+ }
18640
+ if (opts.texture !== void 0 || opts.ultra === false || opts.pose !== void 0) {
18641
+ fail2(opts, "--texture / --no-ultra / --pose apply to the paid mesh call: pass them to `character preview` (guided) or `character --direct-text` / `creature` (one shot). Concepts are images.", "character_concept");
18436
18642
  return;
18437
18643
  }
18438
18644
  if (opts.controllerPack === false) {
@@ -18528,10 +18734,26 @@ async function runDirectTextCharacter(opts) {
18528
18734
  actionIds: resolved.actionIds,
18529
18735
  controllerPack,
18530
18736
  ...opts.height === void 0 ? {} : { heightMeters: opts.height },
18531
- targetPolycount: opts.polycount ?? 1e4
18737
+ targetPolycount: opts.polycount ?? 1e4,
18738
+ ...meshyQualityOptions(opts),
18739
+ ...opts.pose === void 0 ? {} : { poseMode: opts.pose }
18532
18740
  }
18533
18741
  });
18534
18742
  }
18743
+ var CHARACTER_TEXTURE_SIZES = ["2k", "4k", "8k"];
18744
+ var REMESH_TARGET_MIN = 1e4;
18745
+ var REMESH_TARGET_MAX = 1e5;
18746
+ function meshyQualityOptions(opts) {
18747
+ return {
18748
+ ...opts.texture === void 0 ? {} : { textureResolution: opts.texture },
18749
+ ...opts.ultra === false ? { ultraMode: false } : {}
18750
+ };
18751
+ }
18752
+ function badCharacterTexture(opts) {
18753
+ if (opts.texture === void 0) return null;
18754
+ if (CHARACTER_TEXTURE_SIZES.includes(opts.texture)) return null;
18755
+ return `--texture ${opts.texture} is a \`genex model\` tier. A character takes a texture SIZE: ${CHARACTER_TEXTURE_SIZES.join("|")} (default 4k).`;
18756
+ }
18535
18757
  async function runCharacterPreview(opts) {
18536
18758
  const log = createLogger({ quiet: opts.quiet || opts.json });
18537
18759
  const conceptId = opts.conceptId?.trim();
@@ -18552,7 +18774,12 @@ async function runCharacterPreview(opts) {
18552
18774
  fail2(opts, "Not authorized. Run `genex init` first to sign in.", "character_preview");
18553
18775
  return;
18554
18776
  }
18555
- const body = { candidateIndex: opts.candidate, userApproved: true };
18777
+ const badTexture = badCharacterTexture(opts);
18778
+ if (badTexture) {
18779
+ fail2(opts, badTexture, "character_preview");
18780
+ return;
18781
+ }
18782
+ const body = { candidateIndex: opts.candidate, userApproved: true, ...meshyQualityOptions(opts) };
18556
18783
  const basePath = `/api/characters/concepts/${encodeURIComponent(conceptId)}/previews`;
18557
18784
  const price = await quote(`${ctx.apiUrl}${basePath}/quote`, ctx.token, body);
18558
18785
  if (!price) {
@@ -18590,12 +18817,20 @@ async function runCharacterFinalize(opts) {
18590
18817
  fail2(opts, "STOP: finalization requires explicit user approval of all four preview views. Re-run with --user-approved only after approval.");
18591
18818
  return;
18592
18819
  }
18593
- if (opts.approveRemesh !== 1e4) {
18594
- fail2(opts, "--approve-remesh must be exactly 10000. No other rigging-copy target is accepted.");
18820
+ const remeshTarget = opts.approveRemesh;
18821
+ if (remeshTarget === void 0 || !Number.isInteger(remeshTarget) || remeshTarget < REMESH_TARGET_MIN || remeshTarget > REMESH_TARGET_MAX) {
18822
+ fail2(
18823
+ opts,
18824
+ `--approve-remesh <faces> is required: the face budget of the rigging copy, ${REMESH_TARGET_MIN}-${REMESH_TARGET_MAX} (10000 for a crowd body, 30000 or more for a hero). The number is the approval.`
18825
+ );
18595
18826
  return;
18596
18827
  }
18597
18828
  if (opts.polycount !== void 0) {
18598
- fail2(opts, "Do not combine --polycount with finalize; --approve-remesh 10000 is the only accepted rigging-copy target.");
18829
+ fail2(opts, "Do not combine --polycount with finalize; --approve-remesh <faces> is the rigging-copy target.");
18830
+ return;
18831
+ }
18832
+ if (opts.texture !== void 0 || opts.ultra === false) {
18833
+ fail2(opts, "--texture / --no-ultra belong to `genex character preview` \u2014 the preview is the paid Meshy call; finalize remeshes and rigs what it made.");
18599
18834
  return;
18600
18835
  }
18601
18836
  if (opts.controllerPack === false) {
@@ -18615,7 +18850,7 @@ async function runCharacterFinalize(opts) {
18615
18850
  const body = {
18616
18851
  ...requestedBuild(opts, resolved.actionIds),
18617
18852
  userApproved: true,
18618
- approvedRemeshTarget: 1e4
18853
+ approvedRemeshTarget: remeshTarget
18619
18854
  };
18620
18855
  const basePath = `/api/characters/previews/${encodeURIComponent(previewId)}/finalize`;
18621
18856
  const price = await quote(`${ctx.apiUrl}${basePath}/quote`, ctx.token, body);
@@ -18626,7 +18861,7 @@ async function runCharacterFinalize(opts) {
18626
18861
  if (!opts.json) {
18627
18862
  log.plain(c.bold("Character finalize quote"));
18628
18863
  log.plain(` ${price.credits} Genex credits = base ${price.baseCredits} + actions ${price.animationCredits}`);
18629
- log.plain(" Approved rigging copy: 10,000 faces; the high-detail source remains preserved in R2.");
18864
+ log.plain(` Approved rigging copy: ${remeshTarget.toLocaleString("en-US")} faces; the high-detail source remains preserved in R2.`);
18630
18865
  log.plain("");
18631
18866
  }
18632
18867
  await runWorkflow({
@@ -18741,22 +18976,22 @@ async function context2(opts) {
18741
18976
  const project = await readProject();
18742
18977
  return { token, apiUrl: getApiUrl(opts.apiUrl ?? project?.apiUrl) };
18743
18978
  }
18744
- async function readVideo(path26, log) {
18979
+ async function readVideo(path27, log) {
18745
18980
  let bytes;
18746
18981
  try {
18747
- bytes = await readFile(path26);
18982
+ bytes = await readFile(path27);
18748
18983
  } catch {
18749
- log.error(`Can't read ${path26}.`);
18984
+ log.error(`Can't read ${path27}.`);
18750
18985
  return null;
18751
18986
  }
18752
18987
  if (bytes.byteLength > MAX_VIDEO_BYTES) {
18753
- log.error(`${basename(path26)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
18988
+ log.error(`${basename(path27)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
18754
18989
  return null;
18755
18990
  }
18756
18991
  return bytes;
18757
18992
  }
18758
- async function uploadVideo(apiUrl, token, characterId, path26, bytes, log) {
18759
- const contentType = /\.mov$/i.test(path26) ? "video/quicktime" : "video/mp4";
18993
+ async function uploadVideo(apiUrl, token, characterId, path27, bytes, log) {
18994
+ const contentType = /\.mov$/i.test(path27) ? "video/quicktime" : "video/mp4";
18760
18995
  const minted = await apiFetch(
18761
18996
  `${apiUrl}/api/characters/${encodeURIComponent(characterId)}/motions/video-url`,
18762
18997
  {
@@ -18771,7 +19006,7 @@ async function uploadVideo(apiUrl, token, characterId, path26, bytes, log) {
18771
19006
  return null;
18772
19007
  }
18773
19008
  const { uploadUrl, videoUrl } = await minted.json();
18774
- log.dim(` uploading ${basename(path26)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
19009
+ log.dim(` uploading ${basename(path27)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
18775
19010
  const put = await fetch(uploadUrl, {
18776
19011
  method: "PUT",
18777
19012
  headers: { "Content-Type": contentType, "Content-Length": String(bytes.byteLength) },
@@ -19087,8 +19322,8 @@ function rank(items, query) {
19087
19322
  }
19088
19323
 
19089
19324
  // src/commands/motion.ts
19090
- import fs21 from "fs/promises";
19091
- import path20 from "path";
19325
+ import fs22 from "fs/promises";
19326
+ import path21 from "path";
19092
19327
 
19093
19328
  // src/lib/motion/npz.ts
19094
19329
  import zlib from "zlib";
@@ -20339,7 +20574,7 @@ async function motionGen(opts, log) {
20339
20574
  }
20340
20575
  if (opts.constraintsPath !== void 0) {
20341
20576
  try {
20342
- const raw = await fs21.readFile(opts.constraintsPath, "utf8");
20577
+ const raw = await fs22.readFile(opts.constraintsPath, "utf8");
20343
20578
  generationOptions.constraints = JSON.parse(raw);
20344
20579
  } catch {
20345
20580
  log.error(`Couldn't read the --constraints JSON at ${opts.constraintsPath}.`);
@@ -20363,10 +20598,10 @@ async function motionGen(opts, log) {
20363
20598
  async function expandTakes(selectors) {
20364
20599
  const out = [];
20365
20600
  for (const sel of selectors) {
20366
- const st = await fs21.stat(sel).catch(() => null);
20601
+ const st = await fs22.stat(sel).catch(() => null);
20367
20602
  if (st?.isDirectory()) {
20368
- const names = await fs21.readdir(sel);
20369
- for (const n of names.sort()) if (n.endsWith(".npz")) out.push(path20.join(sel, n));
20603
+ const names = await fs22.readdir(sel);
20604
+ for (const n of names.sort()) if (n.endsWith(".npz")) out.push(path21.join(sel, n));
20370
20605
  } else if (st?.isFile()) {
20371
20606
  out.push(sel);
20372
20607
  } else {
@@ -20401,7 +20636,7 @@ async function motionVerify(opts, log) {
20401
20636
  let gates = DEFAULT_GATES;
20402
20637
  if (opts.gatesPath) {
20403
20638
  try {
20404
- gates = mergeGates(DEFAULT_GATES, JSON.parse(await fs21.readFile(opts.gatesPath, "utf8")));
20639
+ gates = mergeGates(DEFAULT_GATES, JSON.parse(await fs22.readFile(opts.gatesPath, "utf8")));
20405
20640
  } catch {
20406
20641
  log.error(`Couldn't read the --gates JSON at ${opts.gatesPath}.`);
20407
20642
  process.exitCode = 1;
@@ -20423,9 +20658,9 @@ async function motionVerify(opts, log) {
20423
20658
  }
20424
20659
  const reports = [];
20425
20660
  for (const file of files) {
20426
- const stem = path20.basename(file).replace(/\.npz$/, "");
20661
+ const stem = path21.basename(file).replace(/\.npz$/, "");
20427
20662
  try {
20428
- reports.push(analyzeTake(stem, await fs21.readFile(file), gates));
20663
+ reports.push(analyzeTake(stem, await fs22.readFile(file), gates));
20429
20664
  } catch (err) {
20430
20665
  reports.push({
20431
20666
  take: stem,
@@ -20463,7 +20698,7 @@ async function motionCompile(opts, log) {
20463
20698
  let cfg = DEFAULT_MOTION_CONFIG;
20464
20699
  if (opts.configPath) {
20465
20700
  try {
20466
- const patch = JSON.parse(await fs21.readFile(opts.configPath, "utf8"));
20701
+ const patch = JSON.parse(await fs22.readFile(opts.configPath, "utf8"));
20467
20702
  cfg = { ...DEFAULT_MOTION_CONFIG, ...patch, idleLoop: { ...DEFAULT_MOTION_CONFIG.idleLoop, ...patch.idleLoop } };
20468
20703
  } catch {
20469
20704
  log.error(`Couldn't read the --config JSON at ${opts.configPath}.`);
@@ -20481,16 +20716,16 @@ async function motionCompile(opts, log) {
20481
20716
  }
20482
20717
  const inputs = [];
20483
20718
  for (const file of files) {
20484
- const stem = path20.basename(file).replace(/\.npz$/, "");
20719
+ const stem = path21.basename(file).replace(/\.npz$/, "");
20485
20720
  try {
20486
- inputs.push({ stem, take: loadTake(await fs21.readFile(file)) });
20721
+ inputs.push({ stem, take: loadTake(await fs22.readFile(file)) });
20487
20722
  } catch (err) {
20488
20723
  log.error(`${stem}: ${err instanceof Error ? err.message : String(err)}`);
20489
20724
  process.exitCode = 1;
20490
20725
  return;
20491
20726
  }
20492
20727
  }
20493
- const setName = opts.set ?? path20.basename(opts.out).replace(/\.json$/, "");
20728
+ const setName = opts.set ?? path21.basename(opts.out).replace(/\.json$/, "");
20494
20729
  let result;
20495
20730
  try {
20496
20731
  result = compileSet(inputs, setName, cfg);
@@ -20505,9 +20740,9 @@ async function motionCompile(opts, log) {
20505
20740
  process.exitCode = 1;
20506
20741
  return;
20507
20742
  }
20508
- await fs21.mkdir(path20.dirname(path20.resolve(opts.out)), { recursive: true });
20743
+ await fs22.mkdir(path21.dirname(path21.resolve(opts.out)), { recursive: true });
20509
20744
  const json = JSON.stringify(result.data);
20510
- await fs21.writeFile(opts.out, json);
20745
+ await fs22.writeFile(opts.out, json);
20511
20746
  if (opts.json) {
20512
20747
  writeJson({ out: opts.out, set: setName, tracks: Object.keys(result.data.gaits), bytes: json.length });
20513
20748
  return;
@@ -20525,9 +20760,9 @@ var MOTION_RUNTIME_FILES = [
20525
20760
  var MOTION_PRESETS = {
20526
20761
  rifle: ["sets/rifle.json", "sets/jumps.json"]
20527
20762
  };
20528
- var MOTION_DEST = path20.join("src", "motion");
20763
+ var MOTION_DEST = path21.join("src", "motion");
20529
20764
  async function motionInstall(opts, log) {
20530
- const srcDir = path20.join(getTemplatesDir(), "motion");
20765
+ const srcDir = path21.join(getTemplatesDir(), "motion");
20531
20766
  const root = opts.cwd ?? process.cwd();
20532
20767
  const preset = opts.set;
20533
20768
  if (preset !== void 0 && !MOTION_PRESETS[preset]) {
@@ -20538,21 +20773,21 @@ async function motionInstall(opts, log) {
20538
20773
  const files = [...MOTION_RUNTIME_FILES, ...preset ? MOTION_PRESETS[preset] : []];
20539
20774
  log.plain(c.bold(`genex motion install${preset ? ` --set ${preset}` : ""}`));
20540
20775
  log.plain("");
20541
- log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST + path20.sep)}`);
20776
+ log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST + path21.sep)}`);
20542
20777
  let copied = 0, skipped = 0;
20543
20778
  try {
20544
20779
  for (const rel of files) {
20545
- const dest = path20.join(root, MOTION_DEST, rel);
20546
- const exists5 = await fs21.access(dest).then(() => true, () => false);
20780
+ const dest = path21.join(root, MOTION_DEST, rel);
20781
+ const exists5 = await fs22.access(dest).then(() => true, () => false);
20547
20782
  if (!opts.force && exists5) {
20548
20783
  skipped++;
20549
- log.dim(` skipped ${path20.join(MOTION_DEST, rel)} (exists \u2014 use --force to overwrite)`);
20784
+ log.dim(` skipped ${path21.join(MOTION_DEST, rel)} (exists \u2014 use --force to overwrite)`);
20550
20785
  continue;
20551
20786
  }
20552
- await fs21.mkdir(path20.dirname(dest), { recursive: true });
20553
- await fs21.copyFile(path20.join(srcDir, rel), dest);
20787
+ await fs22.mkdir(path21.dirname(dest), { recursive: true });
20788
+ await fs22.copyFile(path21.join(srcDir, rel), dest);
20554
20789
  copied++;
20555
- log.dim(` ${path20.join(MOTION_DEST, rel)}`);
20790
+ log.dim(` ${path21.join(MOTION_DEST, rel)}`);
20556
20791
  }
20557
20792
  } catch (err) {
20558
20793
  log.error(`Copy failed: ${String(err)}`);
@@ -20593,7 +20828,7 @@ async function motionConstraints(opts, log) {
20593
20828
  }
20594
20829
  const doc = directionConstraint(dir, speed, duration);
20595
20830
  const out = opts.out ?? "constraints.json";
20596
- await fs21.writeFile(out, JSON.stringify(doc));
20831
+ await fs22.writeFile(out, JSON.stringify(doc));
20597
20832
  if (opts.json) {
20598
20833
  writeJson({ out, dir: dirName, speed, duration, waypoints: doc[0].frame_indices.length });
20599
20834
  return;
@@ -20630,14 +20865,14 @@ async function runMotion(opts) {
20630
20865
  }
20631
20866
 
20632
20867
  // src/commands/blender.ts
20633
- import fs22 from "fs/promises";
20634
- import path21 from "path";
20868
+ import fs23 from "fs/promises";
20869
+ import path22 from "path";
20635
20870
  var SUBS2 = ["demo", "exec", "snap", "scene", "import", "export", "reset", "mcp", "serve", "seat", "release"];
20636
20871
  var DEFAULT_OUT_DIR = "assets/blender";
20637
20872
  async function writeB64(dir, name, b64) {
20638
- await fs22.mkdir(dir, { recursive: true });
20639
- const p = path21.join(dir, name);
20640
- await fs22.writeFile(p, Buffer.from(b64, "base64"));
20873
+ await fs23.mkdir(dir, { recursive: true });
20874
+ const p = path22.join(dir, name);
20875
+ await fs23.writeFile(p, Buffer.from(b64, "base64"));
20641
20876
  return p;
20642
20877
  }
20643
20878
  function reportScene(log, s) {
@@ -20721,7 +20956,7 @@ async function runBlender(opts) {
20721
20956
  log.plain(rest.join("\n"));
20722
20957
  return 1;
20723
20958
  }
20724
- const outDir = path21.resolve(opts.outDir ?? DEFAULT_OUT_DIR);
20959
+ const outDir = path22.resolve(opts.outDir ?? DEFAULT_OUT_DIR);
20725
20960
  const mode = opts.mode;
20726
20961
  if (mode !== void 0 && !isRenderMode(mode)) {
20727
20962
  log.error(`Unknown --mode ${mode}. Use one of: ${RENDER_MODES.join(", ")}.`);
@@ -20761,14 +20996,14 @@ async function runBlender(opts) {
20761
20996
  return 0;
20762
20997
  }
20763
20998
  case "export": {
20764
- const target = opts.out ?? path21.join(outDir, "scene.glb");
20999
+ const target = opts.out ?? path22.join(outDir, "scene.glb");
20765
21000
  const r = await blenderCall(base, "/export", { path: "/tmp/genex-export.glb" });
20766
21001
  if (!r.glbBase64) {
20767
21002
  log.error(`/export answered with no GLB bytes${r.uploaded ? " (it was uploaded, not inlined)" : ""}`);
20768
21003
  return 1;
20769
21004
  }
20770
- await fs22.mkdir(path21.dirname(target), { recursive: true });
20771
- await fs22.writeFile(target, Buffer.from(r.glbBase64, "base64"));
21005
+ await fs23.mkdir(path22.dirname(target), { recursive: true });
21006
+ await fs23.writeFile(target, Buffer.from(r.glbBase64, "base64"));
20772
21007
  log.success(`Exported ${r.bytes ?? 0} bytes`);
20773
21008
  log.plain(` ${c.cyan(target)}`);
20774
21009
  return 0;
@@ -20801,12 +21036,12 @@ async function runBlender(opts) {
20801
21036
  return 1;
20802
21037
  }
20803
21038
  try {
20804
- script = await fs22.readFile(opts.input, "utf8");
21039
+ script = await fs23.readFile(opts.input, "utf8");
20805
21040
  } catch {
20806
21041
  log.error(`Can't read ${opts.input}`);
20807
21042
  return 1;
20808
21043
  }
20809
- label = path21.basename(opts.input);
21044
+ label = path22.basename(opts.input);
20810
21045
  }
20811
21046
  const r = await blenderCall(base, "/exec", { script, ...mode ? { mode } : {}, sheet: { formats: SHEET_FORMATS } });
20812
21047
  if (r.stdout?.trim()) log.plain(r.stdout.trimEnd());
@@ -20925,9 +21160,9 @@ print(f"castle: {n} objects")
20925
21160
  `;
20926
21161
 
20927
21162
  // src/commands/asset-new.ts
20928
- import fs23 from "fs";
21163
+ import fs24 from "fs";
20929
21164
  import fsp from "fs/promises";
20930
- import path22 from "path";
21165
+ import path23 from "path";
20931
21166
  import { pathToFileURL } from "url";
20932
21167
  var EXTRA_FILES = [
20933
21168
  "genex-asset.example.json",
@@ -21021,7 +21256,7 @@ Every milestone ends with \`npm run verify\`, then \`npx genex preview --no-buil
21021
21256
  }
21022
21257
  async function runAssetNew(options) {
21023
21258
  const log = createLogger();
21024
- const cwd = options.dir ? path22.resolve(options.dir) : process.cwd();
21259
+ const cwd = options.dir ? path23.resolve(options.dir) : process.cwd();
21025
21260
  const slug = options.assetSlug;
21026
21261
  if (!slug) {
21027
21262
  log.error('Usage: genex asset new <slug> [--title "<Name>"] [--dims WxDxH] [--tri-band LOW-HIGH] [--holder "<your name>"] [--out <dir>]');
@@ -21031,14 +21266,14 @@ async function runAssetNew(options) {
21031
21266
  log.error(`The slug must be kebab-case (letters, digits, dashes), got: ${slug}`);
21032
21267
  return 1;
21033
21268
  }
21034
- const templateDir = path22.join(getTemplatesDir(), "asset-viewer");
21035
- if (!fs23.existsSync(templateDir)) {
21269
+ const templateDir = path23.join(getTemplatesDir(), "asset-viewer");
21270
+ if (!fs24.existsSync(templateDir)) {
21036
21271
  log.error(`Vendored asset-viewer template not found at ${templateDir} \u2014 reinstall the CLI.`);
21037
21272
  return 1;
21038
21273
  }
21039
- const manifestTools = await import(pathToFileURL(path22.join(templateDir, "tools", "emit-manifest.mjs")).href);
21274
+ const manifestTools = await import(pathToFileURL(path23.join(templateDir, "tools", "emit-manifest.mjs")).href);
21040
21275
  const { SHARED_FILES, PARITY_FILENAME, hashSharedFiles, pascalCase } = manifestTools;
21041
- const lockPath = path22.join(templateDir, "shared-files.sha256.json");
21276
+ const lockPath = path23.join(templateDir, "shared-files.sha256.json");
21042
21277
  const lock = JSON.parse(await fsp.readFile(lockPath, "utf8"));
21043
21278
  const actual = hashSharedFiles(templateDir);
21044
21279
  const drifted = SHARED_FILES.filter((rel) => lock.files[rel] !== actual[rel]);
@@ -21051,8 +21286,8 @@ async function runAssetNew(options) {
21051
21286
  const triBand = parseBand(options.triBand ?? "500-8000");
21052
21287
  const holder = options.holder?.trim() || HOLDER_PLACEHOLDER;
21053
21288
  const year = (/* @__PURE__ */ new Date()).getUTCFullYear();
21054
- const outDir = path22.resolve(cwd, options.out ?? slug);
21055
- if (fs23.existsSync(outDir) && fs23.readdirSync(outDir).length > 0 && !options.force) {
21289
+ const outDir = path23.resolve(cwd, options.out ?? slug);
21290
+ if (fs24.existsSync(outDir) && fs24.readdirSync(outDir).length > 0 && !options.force) {
21056
21291
  log.error(`${outDir} already holds files. Nothing was touched \u2014 pass --force to write into it anyway.`);
21057
21292
  return 1;
21058
21293
  }
@@ -21080,24 +21315,24 @@ async function runAssetNew(options) {
21080
21315
  };
21081
21316
  await fsp.mkdir(outDir, { recursive: true });
21082
21317
  for (const rel of [...SHARED_FILES, ...EXTRA_FILES]) {
21083
- const to = path22.join(outDir, rel);
21084
- await fsp.mkdir(path22.dirname(to), { recursive: true });
21085
- await fsp.copyFile(path22.join(templateDir, rel), to);
21318
+ const to = path23.join(outDir, rel);
21319
+ await fsp.mkdir(path23.dirname(to), { recursive: true });
21320
+ await fsp.copyFile(path23.join(templateDir, rel), to);
21086
21321
  }
21087
- const pkg = fillTemplate(await fsp.readFile(path22.join(templateDir, "package.json"), "utf8"), {
21322
+ const pkg = fillTemplate(await fsp.readFile(path23.join(templateDir, "package.json"), "utf8"), {
21088
21323
  slug,
21089
21324
  name,
21090
21325
  version
21091
21326
  });
21092
- await fsp.writeFile(path22.join(outDir, "package.json"), pkg, "utf8");
21093
- await fsp.writeFile(path22.join(outDir, "asset.config.json"), JSON.stringify(config, null, 2) + "\n", "utf8");
21094
- await fsp.writeFile(path22.join(outDir, ".gitignore"), GITIGNORE, "utf8");
21327
+ await fsp.writeFile(path23.join(outDir, "package.json"), pkg, "utf8");
21328
+ await fsp.writeFile(path23.join(outDir, "asset.config.json"), JSON.stringify(config, null, 2) + "\n", "utf8");
21329
+ await fsp.writeFile(path23.join(outDir, ".gitignore"), GITIGNORE, "utf8");
21095
21330
  await fsp.writeFile(
21096
- path22.join(outDir, "DESIGN.md"),
21331
+ path23.join(outDir, "DESIGN.md"),
21097
21332
  designDoc({ name, slug, sizeMeters, triBand, holder }),
21098
21333
  "utf8"
21099
21334
  );
21100
- const placeholder = await fsp.readFile(path22.join(templateDir, "src", "asset", "PLACEHOLDER.ts"), "utf8");
21335
+ const placeholder = await fsp.readFile(path23.join(templateDir, "src", "asset", "PLACEHOLDER.ts"), "utf8");
21101
21336
  const seeded = seedAssetSource(placeholder, {
21102
21337
  slug,
21103
21338
  name,
@@ -21108,8 +21343,8 @@ async function runAssetNew(options) {
21108
21343
  pascalCase
21109
21344
  });
21110
21345
  const assetFilename = `${slug.replace(/-([a-z0-9])/g, (_, ch) => ch.toUpperCase())}.ts`;
21111
- await fsp.mkdir(path22.join(outDir, "src", "asset"), { recursive: true });
21112
- await fsp.writeFile(path22.join(outDir, "src", "asset", assetFilename), seeded, "utf8");
21346
+ await fsp.mkdir(path23.join(outDir, "src", "asset"), { recursive: true });
21347
+ await fsp.writeFile(path23.join(outDir, "src", "asset", assetFilename), seeded, "utf8");
21113
21348
  const copied = hashSharedFiles(outDir);
21114
21349
  const mismatched = SHARED_FILES.filter((rel) => copied[rel] !== lock.files[rel]);
21115
21350
  if (mismatched.length) {
@@ -21117,7 +21352,7 @@ async function runAssetNew(options) {
21117
21352
  return 1;
21118
21353
  }
21119
21354
  await fsp.writeFile(
21120
- path22.join(outDir, PARITY_FILENAME),
21355
+ path23.join(outDir, PARITY_FILENAME),
21121
21356
  JSON.stringify(
21122
21357
  {
21123
21358
  note: "Recorded by `genex asset new`. Gate G13 recomputes these; do not hand-edit the viewer.",
@@ -21161,11 +21396,11 @@ async function runAssetNew(options) {
21161
21396
  }
21162
21397
 
21163
21398
  // src/commands/tools.ts
21164
- import path25 from "path";
21399
+ import path26 from "path";
21165
21400
 
21166
21401
  // src/lib/local-install.ts
21167
- import fs24 from "fs/promises";
21168
- import path23 from "path";
21402
+ import fs25 from "fs/promises";
21403
+ import path24 from "path";
21169
21404
  import { spawn as spawn4 } from "child_process";
21170
21405
  var CLI_PACKAGE = "@genex-ai/cli-demo";
21171
21406
  var FULL_NAME_FALLBACK = `npx ${CLI_PACKAGE}@${CLI_CHANNEL}`;
@@ -21178,17 +21413,17 @@ var LOCKFILES = [
21178
21413
  ];
21179
21414
  async function exists4(p) {
21180
21415
  try {
21181
- await fs24.access(p);
21416
+ await fs25.access(p);
21182
21417
  return true;
21183
21418
  } catch {
21184
21419
  return false;
21185
21420
  }
21186
21421
  }
21187
21422
  async function detectPackageManager(cwd) {
21188
- let dir = path23.resolve(cwd);
21423
+ let dir = path24.resolve(cwd);
21189
21424
  for (; ; ) {
21190
21425
  try {
21191
- const raw = await fs24.readFile(path23.join(dir, "package.json"), "utf8");
21426
+ const raw = await fs25.readFile(path24.join(dir, "package.json"), "utf8");
21192
21427
  const pm = JSON.parse(raw).packageManager;
21193
21428
  if (typeof pm === "string") {
21194
21429
  const name = pm.split("@")[0];
@@ -21197,18 +21432,18 @@ async function detectPackageManager(cwd) {
21197
21432
  } catch {
21198
21433
  }
21199
21434
  for (const [file, pm] of LOCKFILES) {
21200
- if (await exists4(path23.join(dir, file))) return pm;
21435
+ if (await exists4(path24.join(dir, file))) return pm;
21201
21436
  }
21202
- const parent = path23.dirname(dir);
21437
+ const parent = path24.dirname(dir);
21203
21438
  if (parent === dir) return "npm";
21204
21439
  dir = parent;
21205
21440
  }
21206
21441
  }
21207
21442
  async function findLocalCli(cwd) {
21208
- let dir = path23.resolve(cwd);
21443
+ let dir = path24.resolve(cwd);
21209
21444
  for (; ; ) {
21210
- if (await exists4(path23.join(dir, "node_modules", CLI_PACKAGE, "package.json"))) return dir;
21211
- const parent = path23.dirname(dir);
21445
+ if (await exists4(path24.join(dir, "node_modules", CLI_PACKAGE, "package.json"))) return dir;
21446
+ const parent = path24.dirname(dir);
21212
21447
  if (parent === dir) return null;
21213
21448
  dir = parent;
21214
21449
  }
@@ -21226,7 +21461,7 @@ function installArgs(pm, spec) {
21226
21461
  }
21227
21462
  }
21228
21463
  function manifestName(cwd) {
21229
- const slug = path23.basename(path23.resolve(cwd)).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 100);
21464
+ const slug = path24.basename(path24.resolve(cwd)).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 100);
21230
21465
  return slug || "genex-tools-workspace";
21231
21466
  }
21232
21467
  function isSourceRun(moduleUrl = import.meta.url) {
@@ -21284,10 +21519,10 @@ async function ensureLocalCli(log, cwd = process.cwd(), opts = {}) {
21284
21519
  return;
21285
21520
  }
21286
21521
  const pm = await detectPackageManager(cwd);
21287
- const hadManifest = await exists4(path23.join(cwd, "package.json"));
21522
+ const hadManifest = await exists4(path24.join(cwd, "package.json"));
21288
21523
  if (!hadManifest) {
21289
- await fs24.writeFile(
21290
- path23.join(cwd, "package.json"),
21524
+ await fs25.writeFile(
21525
+ path24.join(cwd, "package.json"),
21291
21526
  JSON.stringify({ name: manifestName(cwd), private: true }, null, 2) + "\n"
21292
21527
  );
21293
21528
  await ensureIgnored(cwd, "node_modules/");
@@ -21307,20 +21542,20 @@ async function ensureLocalCli(log, cwd = process.cwd(), opts = {}) {
21307
21542
  }
21308
21543
  }
21309
21544
  async function ensureIgnored(dir, entry) {
21310
- const file = path23.join(dir, ".gitignore");
21545
+ const file = path24.join(dir, ".gitignore");
21311
21546
  let content = "";
21312
21547
  try {
21313
- content = await fs24.readFile(file, "utf8");
21548
+ content = await fs25.readFile(file, "utf8");
21314
21549
  } catch {
21315
21550
  }
21316
21551
  if (content.split("\n").some((l) => l.trim() === entry)) return;
21317
21552
  let next = content;
21318
21553
  if (next.length > 0 && !next.endsWith("\n")) next += "\n";
21319
- await fs24.writeFile(file, next + entry + "\n");
21554
+ await fs25.writeFile(file, next + entry + "\n");
21320
21555
  }
21321
21556
 
21322
21557
  // src/commands/doctor.ts
21323
- import path24 from "path";
21558
+ import path25 from "path";
21324
21559
  var LANE_ORDER = [
21325
21560
  "model",
21326
21561
  "image",
@@ -21601,7 +21836,7 @@ async function fetchLegalStatus(apiUrl, token) {
21601
21836
  }
21602
21837
  async function firstSkillsMarker() {
21603
21838
  for (const target of resolveAgentTargets()) {
21604
- const marker = await readSkillsMarker(path24.join(target.baseDir, "skills"));
21839
+ const marker = await readSkillsMarker(path25.join(target.baseDir, "skills"));
21605
21840
  if (marker) return marker;
21606
21841
  }
21607
21842
  return null;
@@ -21640,8 +21875,8 @@ async function runTools(opts) {
21640
21875
  let totalNew = 0;
21641
21876
  let totalUpdated = 0;
21642
21877
  for (const t of targets) {
21643
- const dest = path25.join(t.baseDir, "skills");
21644
- const { copied, updated } = await copyTemplates(path25.join(templatesDir, "skills"), dest, {
21878
+ const dest = path26.join(t.baseDir, "skills");
21879
+ const { copied, updated } = await copyTemplates(path26.join(templatesDir, "skills"), dest, {
21645
21880
  filter: (rel) => skillFamilyFilter("tools")(`skills/${rel}`)
21646
21881
  });
21647
21882
  await pruneRemovedSkills(dest, log);
@@ -21702,7 +21937,7 @@ async function runTools(opts) {
21702
21937
  // src/lib/costs.ts
21703
21938
  var TYPICAL_CREDITS = {
21704
21939
  model: 35,
21705
- // 46 from a reference image
21940
+ // 46 from a reference image; --texture standard 23, --no-texture 12, --geometry detailed 58, --quad 41, --low-poly 46, --parts 58 quote off the estimate
21706
21941
  image: 4,
21707
21942
  // gpt-image-2 tiers (--transparent, --quality high, --edit, 4K) quote higher
21708
21943
  texture: 5,
@@ -21717,17 +21952,22 @@ var TYPICAL_CREDITS = {
21717
21952
  model_rig: 29,
21718
21953
  model_animation: 12,
21719
21954
  // PER CLIP — `--preset walk,run` quotes ceil(2 × 10¢ × 1.15)
21720
- character: 58,
21955
+ character: 64,
21956
+ // one-shot on Meshy 7 Ultra at 4k with the controller pack; `genex creature` (no pack) 46, --no-ultra 58, --texture 8k 69
21721
21957
  character_concept: 32,
21722
21958
  character_preview: 41,
21959
+ // Meshy 7 Ultra at 4k; --no-ultra 35, --texture 8k 46
21723
21960
  character_finalize: 29,
21724
21961
  character_animation: 6,
21725
21962
  character_motion: 46,
21726
21963
  // one text-route clip; other routes are priced off their own cost
21727
- character_rerig: 12
21964
+ character_rerig: 12,
21965
+ character_import: 18
21966
+ // Uthana auto-rig of your own GLB; the upload itself is free
21728
21967
  };
21729
21968
 
21730
21969
  // src/index.ts
21970
+ var TEXTURE_FLAG_VALUES = ["standard", "detailed", "none", "2k", "4k", "8k"];
21731
21971
  var MODEL_SUB_SET = new Set(MODEL_SUBCOMMANDS);
21732
21972
  var GEN_KINDS = /* @__PURE__ */ new Set(["model", "skybox", "sfx", "music", "voice", "texture", "image", "video"]);
21733
21973
  var HELP = `${c.bold("genex")} \u2014 set up your project's agent workspace, authorize, and publish 3D games.
@@ -21804,7 +22044,11 @@ ${c.bold("Usage")}
21804
22044
  default, and --no-resellable turns it back off.
21805
22045
  genex model "<prompt>" [options] Generate a 3D model (GLB); prints a public asset URL.
21806
22046
  --image <path|url> builds it FROM a reference
21807
- image instead (prompt optional).
22047
+ image instead (prompt optional). Quality knobs:
22048
+ --texture standard|detailed|none, --geometry
22049
+ detailed, --quad, --low-poly, --parts,
22050
+ --face-limit <n>, --auto-size (each is priced
22051
+ in the quote).
21808
22052
  genex model segment <id> Split a generated model into NAMED parts \u2014
21809
22053
  one GLB, parts addressable by name (doors,
21810
22054
  magazines, turrets, destructibles).
@@ -21814,6 +22058,10 @@ ${c.bold("Usage")}
21814
22058
  avian|serpentine|aquatic). Auto-detects the
21815
22059
  plan; --type picks it explicitly. Unriggable
21816
22060
  meshes are refused and auto-refunded.
22061
+ genex model import <file.glb> Bring in a mesh you already have (a Blender
22062
+ export, a bought asset) as a model of yours \u2014
22063
+ free, \u226464 MB \u2014 so rig/animate/segment, blender
22064
+ import and "Use in game" all work on it.
21817
22065
  genex model animate <rig-id> --preset walk[,run,\u2026]
21818
22066
  Retarget ready-made motion clips onto a
21819
22067
  'model rig' result (billed per clip).
@@ -21853,6 +22101,12 @@ ${c.bold("Usage")}
21853
22101
  can take new animations. With no id it adopts the
21854
22102
  body this game already wears. Free \u2014 it copies,
21855
22103
  it does not generate.
22104
+ genex character import <file.glb> Your own humanoid mesh, auto-rigged by Uthana
22105
+ (finger joints included; --no-fingers skips
22106
+ them; --height <m>). \u226430 MB, biped, T- or
22107
+ A-pose. Then 'character animate <id>
22108
+ --locomotion' \u2014 the Meshy catalog and pack do
22109
+ not apply to a Uthana rig.
21856
22110
  genex creature "<desc>" One-shot rigged enemy/creature (biped-shaped
21857
22111
  bodies only \u2014 Meshy rig limit): model \u2192 rig \u2192
21858
22112
  bind library clips via --animation. No approval
@@ -21914,6 +22168,14 @@ ${c.bold("Options for the generators (`model` `sfx` `music` `voice` `texture` `i
21914
22168
  --image <path|url> (model) build the model FROM this reference image \u2014 a
21915
22169
  local file (\u22644 MB, inlined) or a previous generation's
21916
22170
  asset URL. The prompt becomes optional.
22171
+ --texture <tier> (model) standard | detailed (default) | none \u2014 detailed is
22172
+ +10 credits over standard, none is geometry only.
22173
+ --geometry <tier> (model) standard (default) | detailed (+20 credits, hero pieces).
22174
+ --quad (model) quad-dominant mesh (+5; face limit \u2264150000).
22175
+ --low-poly (model) smart low-poly topology (+10) \u2014 game-ready meshes.
22176
+ --parts (model) separated, named parts at generation (+20).
22177
+ --face-limit <n> (model) cap on the raw mesh, 1000-2000000 (default 150000).
22178
+ --auto-size (model) scale to real-world metres by AI estimate.
21917
22179
  --granularity <g> (model segment) part granularity: simple | balanced |
21918
22180
  detailed (default balanced).
21919
22181
  --type <plan> (model rig) body plan: biped | quadruped | hexapod |
@@ -22060,13 +22322,23 @@ ${c.bold("Options for `character` / `animations search`")}
22060
22322
  ranked action IDs instead of choosing silently.
22061
22323
  --candidate <1|2|3> (character preview) selected concept candidate.
22062
22324
  --user-approved Confirm the user explicitly approved the candidate/preview.
22063
- --approve-remesh 10000
22064
- (character finalize) approve the separate 10,000-face rigging copy.
22065
- --direct-text Compatibility path: skip concept/preview approval and generate
22325
+ --approve-remesh <faces>
22326
+ (character finalize) approve the separate rigging copy at this
22327
+ face budget, 10000-100000 (10000 for a crowd body, 30000+ for a
22328
+ hero). The number IS the approval.
22329
+ --direct-text One-shot path: skip concept/preview approval and generate
22066
22330
  directly from text (defaults to a 10,000-face target).
22067
22331
  --no-controller-pack (character --direct-text only) skip the validated locomotion pack.
22068
22332
  --height <meters> (character) target character height, 0.5-3 meters.
22069
- --polycount <count> (character --direct-text only) target 10000-100000 polygons.
22333
+ --polycount <count> (character --direct-text / creature) target 10000-100000 polygons.
22334
+ --texture <res> (character/creature/preview) texture size 2k | 4k (default) | 8k
22335
+ (+5 credits). Meshy 7 on every lane.
22336
+ --no-ultra (character/creature/preview) skip Meshy Ultra (\u22125 credits, less
22337
+ surface detail).
22338
+ --pose <a-pose|t-pose>
22339
+ (character --direct-text / creature) preferred rest pose; the
22340
+ other stays the QA fallback.
22341
+ --no-fingers (character import) skip finger joints in the Uthana rig.
22070
22342
  --no-wait (character/preview/finalize/animate) enqueue and return a generation id.
22071
22343
  --json Emit one machine-readable workflow result object.
22072
22344
  --action <id|query> (character animate) action to add; repeatable.
@@ -22324,6 +22596,13 @@ function parseArgs(argv) {
22324
22596
  "--image",
22325
22597
  "--granularity",
22326
22598
  "--preset",
22599
+ // Quality knobs (2026-09-06): `--texture` is shared by `model`
22600
+ // (standard|detailed|none) and the character lanes (2k|4k|8k); each command
22601
+ // re-validates its own vocabulary client-side.
22602
+ "--texture",
22603
+ "--geometry",
22604
+ "--face-limit",
22605
+ "--pose",
22327
22606
  // `genex blender` (M1 spike): which shading the contact sheet uses.
22328
22607
  "--mode",
22329
22608
  // `genex ui` string flags (the numeric ones come from UI_NUMBER_FLAGS).
@@ -22407,6 +22686,24 @@ function parseArgs(argv) {
22407
22686
  case "--no-controller-pack":
22408
22687
  parsed.options.controllerPack = false;
22409
22688
  break;
22689
+ case "--no-fingers":
22690
+ parsed.options.fingers = false;
22691
+ break;
22692
+ case "--no-ultra":
22693
+ parsed.options.ultra = false;
22694
+ break;
22695
+ case "--quad":
22696
+ parsed.options.quad = true;
22697
+ break;
22698
+ case "--low-poly":
22699
+ parsed.options.lowPoly = true;
22700
+ break;
22701
+ case "--parts":
22702
+ parsed.options.parts = true;
22703
+ break;
22704
+ case "--auto-size":
22705
+ parsed.options.autoSize = true;
22706
+ break;
22410
22707
  case "--user-approved":
22411
22708
  parsed.options.userApproved = true;
22412
22709
  break;
@@ -22527,7 +22824,9 @@ function parseArgs(argv) {
22527
22824
  parsed.options.conceptId = arg;
22528
22825
  } else if (parsed.options.name === "finalize" && !parsed.options.previewId) {
22529
22826
  parsed.options.previewId = arg;
22530
- } else if (!["animate", "motions", "preview", "finalize", "adopt"].includes(parsed.options.name ?? "")) {
22827
+ } else if (parsed.options.name === "import" && !parsed.options.importPath) {
22828
+ parsed.options.importPath = arg;
22829
+ } else if (!["animate", "motions", "preview", "finalize", "adopt", "import"].includes(parsed.options.name ?? "")) {
22531
22830
  parsed.options.name = `${parsed.options.name} ${arg}`;
22532
22831
  } else {
22533
22832
  parsed.error = `Unexpected argument: ${arg}`;
@@ -22678,8 +22977,8 @@ function applyValueFlag(options, flag, value) {
22678
22977
  }
22679
22978
  case "--approve-remesh": {
22680
22979
  const n = Number(value);
22681
- if (!Number.isInteger(n) || n !== 1e4) {
22682
- throw new Error(`Invalid --approve-remesh value: ${value} (expected exactly 10000)`);
22980
+ if (!Number.isInteger(n) || n < REMESH_TARGET_MIN || n > REMESH_TARGET_MAX) {
22981
+ throw new Error(`Invalid --approve-remesh value: ${value} (expected ${REMESH_TARGET_MIN}-${REMESH_TARGET_MAX} faces)`);
22683
22982
  }
22684
22983
  options.approveRemesh = n;
22685
22984
  break;
@@ -22847,6 +23146,32 @@ function applyValueFlag(options, flag, value) {
22847
23146
  case "--image":
22848
23147
  options.imageUrl = value;
22849
23148
  break;
23149
+ case "--texture":
23150
+ if (!TEXTURE_FLAG_VALUES.includes(value)) {
23151
+ throw new Error(`Invalid --texture value: ${value} (model: standard|detailed|none; character: 2k|4k|8k)`);
23152
+ }
23153
+ options.texture = value;
23154
+ break;
23155
+ case "--geometry":
23156
+ if (value !== "standard" && value !== "detailed") {
23157
+ throw new Error(`Invalid --geometry value: ${value} (expected standard|detailed)`);
23158
+ }
23159
+ options.geometry = value;
23160
+ break;
23161
+ case "--face-limit": {
23162
+ const n = Number(value);
23163
+ if (!Number.isInteger(n) || n < 1e3 || n > 2e6) {
23164
+ throw new Error(`Invalid --face-limit value: ${value} (expected 1000-2000000)`);
23165
+ }
23166
+ options.faceLimit = n;
23167
+ break;
23168
+ }
23169
+ case "--pose":
23170
+ if (value !== "a-pose" && value !== "t-pose") {
23171
+ throw new Error(`Invalid --pose value: ${value} (expected a-pose|t-pose)`);
23172
+ }
23173
+ options.pose = value;
23174
+ break;
22850
23175
  case "--granularity":
22851
23176
  options.granularity = value;
22852
23177
  break;
@@ -22966,6 +23291,7 @@ async function main() {
22966
23291
  if (parsed.command === "model" && MODEL_SUB_SET.has(parsed.options.name ?? "")) {
22967
23292
  if (parsed.options.name === "segment") await runModelSegment(parsed.options);
22968
23293
  else if (parsed.options.name === "rig") await runModelRig(parsed.options);
23294
+ else if (parsed.options.name === "import") await runModelImport(parsed.options);
22969
23295
  else await runModelAnimate(parsed.options);
22970
23296
  return;
22971
23297
  }
@@ -23031,6 +23357,8 @@ async function main() {
23031
23357
  await runCharacterFinalize(parsed.options);
23032
23358
  } else if (parsed.options.name === "adopt") {
23033
23359
  await runCharacterAdopt(parsed.options);
23360
+ } else if (parsed.options.name === "import") {
23361
+ await runCharacterImport(parsed.options);
23034
23362
  } else {
23035
23363
  await runCharacter({ ...parsed.options, prompt: parsed.options.name });
23036
23364
  }