@genex-ai/cli-demo 0.37.0 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,6 +16,7 @@ genex texture "<prompt>" # generate a texture → prints an asset URL
16
16
  genex image "<prompt>" # generate an image → prints an asset URL
17
17
  genex video "<prompt>" # generate a video → prints an asset URL
18
18
  genex controller <type> # install a tuned character|car|drone controller → src/controllers/
19
+ genex controller anims <sel…> # download extra character animation clips (by tag or name) → public/assets/anims/
19
20
  ```
20
21
 
21
22
  > **Invoking it.** First-time setup runs via `npx @genex-ai/cli-demo@latest init`.
package/dist/index.js CHANGED
@@ -11,6 +11,15 @@ import { fileURLToPath } from "url";
11
11
  var DEFAULT_AUTH_URL = "https://demo-web.glotech.world";
12
12
  var DEFAULT_API_URL = "https://demo-api.glotech.world";
13
13
  var DEFAULT_COLYSEUS_URL = "wss://demo-colyseus.glotech.world";
14
+ var DEFAULT_ANIMS_BASE = "https://cdn.genex.technology/anims/ual1/v1/";
15
+ var ANIMS_BASE_ENV = "GENEX_ANIMS_BASE";
16
+ function getAnimsBase(override) {
17
+ const raw = override || process.env[ANIMS_BASE_ENV] || DEFAULT_ANIMS_BASE;
18
+ return raw.replace(/\/+$/, "") + "/";
19
+ }
20
+ function getAnimsCacheDir() {
21
+ return path.join(getGenexDir(), "cache", "anims");
22
+ }
14
23
  var ENV_TOKEN_KEY = "GENEX_TOKEN";
15
24
  var AUTH_URL_ENV = "GENEX_AUTH_URL";
16
25
  var API_URL_ENV = "GENEX_API_URL";
@@ -1956,8 +1965,305 @@ function printHint(kind, files, log) {
1956
1965
  }
1957
1966
 
1958
1967
  // src/commands/controller.ts
1968
+ import fs12 from "fs/promises";
1969
+ import path13 from "path";
1970
+
1971
+ // src/lib/anims.ts
1959
1972
  import fs11 from "fs/promises";
1960
1973
  import path12 from "path";
1974
+ var ANIMS_DEST = path12.join("public", "assets", "anims");
1975
+ var HIDDEN_TAG = "reference";
1976
+ async function runAnims(opts) {
1977
+ const log = createLogger({ quiet: opts.quiet });
1978
+ const root = opts.cwd ?? process.cwd();
1979
+ const selectors = opts.selectors ?? [];
1980
+ log.plain(c.bold("genex controller anims"));
1981
+ log.plain("");
1982
+ const { manifest, source } = await loadManifest(opts.animsBase);
1983
+ if (source === "snapshot") {
1984
+ log.dim(" (offline or CDN unreachable \u2014 using the bundled catalog snapshot)");
1985
+ }
1986
+ if (opts.list) {
1987
+ printCatalog(log, manifest, selectors);
1988
+ return;
1989
+ }
1990
+ const controllerMarker = path12.join(root, "src", "controllers", "character");
1991
+ if (!await exists2(controllerMarker)) {
1992
+ log.error(
1993
+ `No character controller in this game (missing ${c.cyan("src/controllers/character/")}).`
1994
+ );
1995
+ log.plain(` Run ${c.cyan("genex controller character")} first, then re-run this command.`);
1996
+ process.exitCode = 1;
1997
+ return;
1998
+ }
1999
+ const destDir = path12.join(root, ANIMS_DEST);
2000
+ const gameManifestPath = path12.join(destDir, "manifest.json");
2001
+ if (opts.reset) {
2002
+ await fs11.rm(destDir, { recursive: true, force: true });
2003
+ log.step(`Cleared ${c.cyan(ANIMS_DEST + path12.sep)} (--reset)`);
2004
+ }
2005
+ if (selectors.length === 0) {
2006
+ const installed = await readGameManifest(gameManifestPath);
2007
+ if (installed === null || installed.clips.length === 0) {
2008
+ log.plain(" No animation packs installed yet.");
2009
+ } else {
2010
+ log.plain(` Installed (${installed.clips.length} clips): ${installed.clips.join(", ")}`);
2011
+ }
2012
+ log.plain("");
2013
+ log.plain(
2014
+ ` Install with ${c.cyan("genex controller anims <tag|clip \u2026>")}; browse with ${c.cyan(
2015
+ "genex controller anims --list"
2016
+ )}.`
2017
+ );
2018
+ return;
2019
+ }
2020
+ let resolved;
2021
+ try {
2022
+ resolved = resolveSelectors(manifest, selectors);
2023
+ } catch (err) {
2024
+ log.error(err instanceof Error ? err.message : String(err));
2025
+ process.exitCode = 1;
2026
+ return;
2027
+ }
2028
+ const coreNames = new Set(manifest.core);
2029
+ const byName = /* @__PURE__ */ new Map();
2030
+ let bundledSkips = 0;
2031
+ for (const entries of resolved.values()) {
2032
+ for (const entry of entries) {
2033
+ if (coreNames.has(entry.name)) {
2034
+ bundledSkips++;
2035
+ continue;
2036
+ }
2037
+ byName.set(entry.name, entry);
2038
+ }
2039
+ }
2040
+ const wanted = [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
2041
+ const cacheDir = path12.join(
2042
+ opts.cacheDir ?? getAnimsCacheDir(),
2043
+ `${manifest.library}-v${manifest.version}`
2044
+ );
2045
+ await fs11.mkdir(cacheDir, { recursive: true });
2046
+ await fs11.mkdir(destDir, { recursive: true });
2047
+ const base = getAnimsBase(opts.animsBase);
2048
+ let installedCount = 0;
2049
+ let presentCount = 0;
2050
+ let addedBytes = 0;
2051
+ const failures = [];
2052
+ for (const entry of wanted) {
2053
+ const dest = path12.join(destDir, entry.file);
2054
+ if (await hasSize(dest, entry.bytes)) {
2055
+ presentCount++;
2056
+ continue;
2057
+ }
2058
+ try {
2059
+ const cached = path12.join(cacheDir, entry.file);
2060
+ if (!await hasSize(cached, entry.bytes)) {
2061
+ const res = await fetch(base + entry.file);
2062
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
2063
+ const buf = Buffer.from(await res.arrayBuffer());
2064
+ await fs11.writeFile(cached, buf);
2065
+ }
2066
+ await fs11.copyFile(cached, dest);
2067
+ installedCount++;
2068
+ addedBytes += entry.bytes;
2069
+ log.dim(` ${path12.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
2070
+ } catch (err) {
2071
+ failures.push(`${entry.name} (${err instanceof Error ? err.message : String(err)})`);
2072
+ }
2073
+ }
2074
+ const previous = await readGameManifest(gameManifestPath);
2075
+ const union = new Set(previous?.clips ?? []);
2076
+ for (const entry of wanted) {
2077
+ if (!failures.some((f) => f.startsWith(`${entry.name} (`))) union.add(entry.name);
2078
+ }
2079
+ const gameManifest = {
2080
+ schema: 1,
2081
+ library: manifest.library,
2082
+ version: manifest.version,
2083
+ clips: [...union].sort((a, b) => a.localeCompare(b))
2084
+ };
2085
+ await fs11.writeFile(gameManifestPath, JSON.stringify(gameManifest, null, 2) + "\n");
2086
+ log.plain("");
2087
+ const parts = [`${installedCount} clip${installedCount === 1 ? "" : "s"} installed`];
2088
+ if (presentCount > 0) parts.push(`${presentCount} already present`);
2089
+ if (bundledSkips > 0) parts.push(`${bundledSkips} already bundled in animation-library.glb`);
2090
+ log.success(
2091
+ `${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path12.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
2092
+ );
2093
+ for (const [selector, entries] of resolved) {
2094
+ const names = entries.filter((e) => !coreNames.has(e.name)).map((e) => e.name);
2095
+ if (names.length > 0) log.dim(` ${selector}: ${names.join(", ")}`);
2096
+ }
2097
+ log.info(
2098
+ `Wiring: load the ${c.cyan("genex-threejs-character-controller")} skill \u2192 "Animation packs" (loadCharacterClips picks these up automatically).`
2099
+ );
2100
+ if (failures.length > 0) {
2101
+ log.plain("");
2102
+ log.error(
2103
+ `${failures.length} clip${failures.length === 1 ? "" : "s"} failed to download: ${failures.join(", ")}`
2104
+ );
2105
+ log.plain(
2106
+ " Each clip needs the network once per machine \u2014 check your connection and re-run the same command (already-installed clips are skipped)."
2107
+ );
2108
+ process.exitCode = 1;
2109
+ }
2110
+ }
2111
+ async function loadManifest(baseOverride) {
2112
+ const base = getAnimsBase(baseOverride);
2113
+ try {
2114
+ const res = await fetch(base + "manifest.json", { signal: AbortSignal.timeout(5e3) });
2115
+ if (res.ok) {
2116
+ const manifest2 = await res.json();
2117
+ if (manifest2.schema === 1 && Array.isArray(manifest2.clips)) {
2118
+ return { manifest: manifest2, source: "cdn" };
2119
+ }
2120
+ }
2121
+ } catch {
2122
+ }
2123
+ const snapshotPath = path12.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
2124
+ const manifest = JSON.parse(await fs11.readFile(snapshotPath, "utf8"));
2125
+ return { manifest, source: "snapshot" };
2126
+ }
2127
+ function resolveSelectors(manifest, selectors) {
2128
+ const byName = new Map(manifest.clips.map((entry) => [entry.name, entry]));
2129
+ const byLowerName = new Map(manifest.clips.map((entry) => [entry.name.toLowerCase(), entry]));
2130
+ const tags = /* @__PURE__ */ new Map();
2131
+ for (const entry of manifest.clips) {
2132
+ for (const tag of entry.tags) {
2133
+ const list = tags.get(tag) ?? [];
2134
+ list.push(entry);
2135
+ tags.set(tag, list);
2136
+ }
2137
+ }
2138
+ const out = /* @__PURE__ */ new Map();
2139
+ for (const selector of selectors) {
2140
+ const exact = byName.get(selector) ?? byLowerName.get(selector.toLowerCase());
2141
+ if (exact) {
2142
+ out.set(selector, [exact]);
2143
+ continue;
2144
+ }
2145
+ const tagHit = tags.get(selector) ?? tags.get(selector.toLowerCase());
2146
+ if (tagHit) {
2147
+ out.set(selector, tagHit);
2148
+ continue;
2149
+ }
2150
+ const candidates = [...tags.keys(), ...byName.keys()];
2151
+ const close = suggest(selector, candidates);
2152
+ throw new Error(
2153
+ `unknown clip/tag "${selector}"${close.length > 0 ? ` \u2014 closest: ${close.join(", ")}` : ""}. Run ${c.cyan(
2154
+ "genex controller anims --list"
2155
+ )} for the catalog.`
2156
+ );
2157
+ }
2158
+ return out;
2159
+ }
2160
+ function suggest(input, candidates) {
2161
+ const lower = input.toLowerCase();
2162
+ const scored = [];
2163
+ for (const candidate of candidates) {
2164
+ const candidateLower = candidate.toLowerCase();
2165
+ if (candidateLower.includes(lower) || lower.includes(candidateLower)) {
2166
+ scored.push({ name: candidate, score: 0 });
2167
+ continue;
2168
+ }
2169
+ const distance = levenshtein(lower, candidateLower, 2);
2170
+ if (distance <= 2) scored.push({ name: candidate, score: distance });
2171
+ }
2172
+ scored.sort((a, b) => a.score - b.score || a.name.localeCompare(b.name));
2173
+ return scored.slice(0, 4).map((s) => s.name);
2174
+ }
2175
+ function levenshtein(a, b, max) {
2176
+ if (Math.abs(a.length - b.length) > max) return max + 1;
2177
+ let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
2178
+ for (let i = 1; i <= a.length; i++) {
2179
+ const curr = [i];
2180
+ let rowMin = i;
2181
+ for (let j = 1; j <= b.length; j++) {
2182
+ curr[j] = Math.min(
2183
+ prev[j] + 1,
2184
+ curr[j - 1] + 1,
2185
+ prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)
2186
+ );
2187
+ if (curr[j] < rowMin) rowMin = curr[j];
2188
+ }
2189
+ if (rowMin > max) return max + 1;
2190
+ prev = curr;
2191
+ }
2192
+ return prev[b.length];
2193
+ }
2194
+ function printCatalog(log, manifest, selectors) {
2195
+ const coreNames = new Set(manifest.core);
2196
+ if (selectors.length > 0) {
2197
+ let resolved;
2198
+ try {
2199
+ resolved = resolveSelectors(manifest, selectors);
2200
+ } catch (err) {
2201
+ log.error(err instanceof Error ? err.message : String(err));
2202
+ process.exitCode = 1;
2203
+ return;
2204
+ }
2205
+ for (const [selector, entries] of resolved) {
2206
+ log.plain(c.bold(selector));
2207
+ for (const entry of entries) {
2208
+ const bundled = coreNames.has(entry.name) ? " (bundled)" : "";
2209
+ log.plain(
2210
+ ` ${entry.name.padEnd(26)} ${entry.duration.toFixed(1)}s ${formatMb(entry.bytes)}${bundled} ${c.dim(entry.desc)}`
2211
+ );
2212
+ }
2213
+ }
2214
+ return;
2215
+ }
2216
+ const tags = /* @__PURE__ */ new Map();
2217
+ for (const entry of manifest.clips) {
2218
+ for (const tag of entry.tags) {
2219
+ if (tag === HIDDEN_TAG) continue;
2220
+ const list = tags.get(tag) ?? [];
2221
+ list.push(entry);
2222
+ tags.set(tag, list);
2223
+ }
2224
+ }
2225
+ log.plain(
2226
+ `${c.bold(`Animation packs`)} (${manifest.library} v${manifest.version}, ${manifest.clips.length} clips)`
2227
+ );
2228
+ log.plain(
2229
+ ` Install: ${c.cyan("genex controller anims <tag|clip \u2026>")} Details: ${c.cyan(
2230
+ "genex controller anims --list <tag>"
2231
+ )}`
2232
+ );
2233
+ log.plain("");
2234
+ for (const [tag, entries] of [...tags.entries()].sort(([a], [b]) => a.localeCompare(b))) {
2235
+ const bytes = entries.reduce((sum, entry) => sum + entry.bytes, 0);
2236
+ const suffix = tag === "core" ? " \u2014 bundled in animation-library.glb" : ` (${formatMb(bytes)})`;
2237
+ log.plain(` ${c.bold(tag.padEnd(18))} ${entries.map((e) => e.name).join(", ")}${suffix}`);
2238
+ }
2239
+ }
2240
+ async function readGameManifest(file) {
2241
+ try {
2242
+ return JSON.parse(await fs11.readFile(file, "utf8"));
2243
+ } catch {
2244
+ return null;
2245
+ }
2246
+ }
2247
+ async function hasSize(file, bytes) {
2248
+ try {
2249
+ return (await fs11.stat(file)).size === bytes;
2250
+ } catch {
2251
+ return false;
2252
+ }
2253
+ }
2254
+ async function exists2(p) {
2255
+ try {
2256
+ await fs11.access(p);
2257
+ return true;
2258
+ } catch {
2259
+ return false;
2260
+ }
2261
+ }
2262
+ function formatMb(bytes) {
2263
+ return bytes >= 1e6 ? `${(bytes / 1e6).toFixed(1)} MB` : `${Math.round(bytes / 1e3)} KB`;
2264
+ }
2265
+
2266
+ // src/commands/controller.ts
1961
2267
  var CONTROLLER_KINDS = ["character", "car", "drone"];
1962
2268
  var SHARED = [
1963
2269
  "shared/math.ts",
@@ -1976,6 +2282,7 @@ var CONTROLLER_FILE_SETS = {
1976
2282
  ...SHARED,
1977
2283
  "character/character-controller.ts",
1978
2284
  "character/character-animations.ts",
2285
+ "character/animation-packs.ts",
1979
2286
  "character/presets.ts",
1980
2287
  // VRM avatar support (three-vrm): load + retarget the UAL clips + auto-fit
1981
2288
  // the capsule + optional foot IK. Owner's avatar replaces the old mannequin.
@@ -1988,15 +2295,16 @@ var CONTROLLER_FILE_SETS = {
1988
2295
  ],
1989
2296
  // The player's VRM is written to public/assets/avatar.vrm at install time by
1990
2297
  // installOwnerAvatar (owner's avatar, or the bundled default) — so it is NOT
1991
- // a static manifest asset. animation-library.glb (46 clips) still is.
2298
+ // a static manifest asset. animation-library.glb (the 12-clip core) still is;
2299
+ // extra clips arrive via `genex controller anims` into public/assets/anims/.
1992
2300
  assets: ["assets/animation-library.glb"],
1993
2301
  skill: "genex-threejs-character-controller",
1994
2302
  sketch: [
1995
2303
  `const physics = await PhysicsWorld.create();`,
1996
2304
  `const { scene, vrm } = await loadVrm("./assets/avatar.vrm");`,
1997
- `const lib = await new GLTFLoader().loadAsync("./assets/animation-library.glb");`,
2305
+ `const clips = await loadCharacterClips(vrm); // core library + every genex-controller-anims pack`,
1998
2306
  `const character = new CharacterController(physics.world, camera, { ...characterPresets["default"].options, ...capsuleFromModel(scene), position: { x: 0, y: 2, z: 0 } });`,
1999
- `character.root.add(scene); const anims = new CharacterAnimations(scene, retargetClips(vrm, lib.scene, lib.animations));`,
2307
+ `character.root.add(scene); const anims = new CharacterAnimations(scene, clips);`,
2000
2308
  `addEventListener("pointerdown", () => anims.playOneShot("Punch_Jab")); // per frame: anims.update(character, dt); vrm.update(dt);`
2001
2309
  ]
2002
2310
  },
@@ -2036,45 +2344,49 @@ var CONTROLLER_FILE_SETS = {
2036
2344
  ]
2037
2345
  }
2038
2346
  };
2039
- var CODE_DEST = path12.join("src", "controllers");
2040
- var ASSETS_DEST = path12.join("public", "assets");
2347
+ var CODE_DEST = path13.join("src", "controllers");
2348
+ var ASSETS_DEST = path13.join("public", "assets");
2041
2349
  async function runController(opts) {
2042
2350
  const log = createLogger({ quiet: opts.quiet });
2351
+ if (opts.kind?.trim() === "anims") {
2352
+ await runAnims(opts);
2353
+ return;
2354
+ }
2043
2355
  const kind = opts.kind?.trim();
2044
2356
  if (!kind || !CONTROLLER_KINDS.includes(kind)) {
2045
2357
  log.error(
2046
2358
  `Missing or unknown controller type${kind ? ` "${kind}"` : ""}. Usage: ${c.cyan(
2047
2359
  "genex controller <character|car|drone> [--force]"
2048
- )}`
2360
+ )} or ${c.cyan("genex controller anims <tag|clip \u2026>")}`
2049
2361
  );
2050
2362
  process.exitCode = 1;
2051
2363
  return;
2052
2364
  }
2053
- const srcDir = path12.join(getTemplatesDir(), "controllers");
2365
+ const srcDir = path13.join(getTemplatesDir(), "controllers");
2054
2366
  const root = opts.cwd ?? process.cwd();
2055
2367
  const set = CONTROLLER_FILE_SETS[kind];
2056
2368
  log.plain(c.bold(`genex controller ${kind}`));
2057
2369
  log.plain("");
2058
- log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path12.sep)}`);
2370
+ log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path13.sep)}`);
2059
2371
  const plan = [
2060
- ...set.code.map((rel) => ({ from: rel, rel: path12.join(CODE_DEST, rel) })),
2372
+ ...set.code.map((rel) => ({ from: rel, rel: path13.join(CODE_DEST, rel) })),
2061
2373
  ...set.assets.map((rel) => ({
2062
2374
  from: rel,
2063
- rel: path12.join(ASSETS_DEST, path12.basename(rel))
2375
+ rel: path13.join(ASSETS_DEST, path13.basename(rel))
2064
2376
  }))
2065
2377
  ];
2066
2378
  let copied = 0;
2067
2379
  let skipped = 0;
2068
2380
  try {
2069
2381
  for (const file of plan) {
2070
- const dest = path12.join(root, file.rel);
2071
- if (!opts.force && await exists2(dest)) {
2382
+ const dest = path13.join(root, file.rel);
2383
+ if (!opts.force && await exists3(dest)) {
2072
2384
  skipped++;
2073
2385
  log.dim(` skipped ${file.rel} (exists \u2014 use --force to overwrite)`);
2074
2386
  continue;
2075
2387
  }
2076
- await fs11.mkdir(path12.dirname(dest), { recursive: true });
2077
- await fs11.copyFile(path12.join(srcDir, file.from), dest);
2388
+ await fs12.mkdir(path13.dirname(dest), { recursive: true });
2389
+ await fs12.copyFile(path13.join(srcDir, file.from), dest);
2078
2390
  copied++;
2079
2391
  log.dim(` ${file.rel}`);
2080
2392
  }
@@ -2106,8 +2418,8 @@ async function runController(opts) {
2106
2418
  }
2107
2419
  async function installOwnerAvatar(args) {
2108
2420
  const { root, srcDir, apiUrl, token, log } = args;
2109
- const dest = path12.join(root, ASSETS_DEST, "avatar.vrm");
2110
- await fs11.mkdir(path12.dirname(dest), { recursive: true });
2421
+ const dest = path13.join(root, ASSETS_DEST, "avatar.vrm");
2422
+ await fs12.mkdir(path13.dirname(dest), { recursive: true });
2111
2423
  if (token) {
2112
2424
  try {
2113
2425
  const res = await apiFetch(`${apiUrl}/api/avatars/me`, {
@@ -2119,7 +2431,7 @@ async function installOwnerAvatar(args) {
2119
2431
  const vrmRes = await fetch(me.vrmUrl);
2120
2432
  if (vrmRes.ok) {
2121
2433
  const buf = Buffer.from(await vrmRes.arrayBuffer());
2122
- await fs11.writeFile(dest, buf);
2434
+ await fs12.writeFile(dest, buf);
2123
2435
  log.dim(` public/assets/avatar.vrm (your avatar \u2014 ${(buf.length / 1e6).toFixed(1)} MB)`);
2124
2436
  return;
2125
2437
  }
@@ -2130,14 +2442,14 @@ async function installOwnerAvatar(args) {
2130
2442
  log.dim(" avatar fetch failed (offline?); using the bundled default.");
2131
2443
  }
2132
2444
  }
2133
- await fs11.copyFile(path12.join(srcDir, "assets", "default-avatar.vrm"), dest);
2445
+ await fs12.copyFile(path13.join(srcDir, "assets", "default-avatar.vrm"), dest);
2134
2446
  log.dim(
2135
2447
  token ? " public/assets/avatar.vrm (bundled default)" : " public/assets/avatar.vrm (bundled default \u2014 sign in and re-run for your own)"
2136
2448
  );
2137
2449
  }
2138
- async function exists2(p) {
2450
+ async function exists3(p) {
2139
2451
  try {
2140
- await fs11.access(p);
2452
+ await fs12.access(p);
2141
2453
  return true;
2142
2454
  } catch {
2143
2455
  return false;
@@ -2230,6 +2542,10 @@ ${c.bold("Usage")}
2230
2542
  genex video "<prompt>" [options] Generate a video (mp4); prints a public asset URL.
2231
2543
  genex controller <type> [--force] Install a physics controller (character|car|drone)
2232
2544
  into src/controllers (+ assets into public/assets).
2545
+ genex controller anims <sel \u2026> Download extra character animation clips by tag or
2546
+ exact name (sword, stealth, Celebration, \u2026) into
2547
+ public/assets/anims/. --list shows the catalog;
2548
+ --reset forgets the previous selection first.
2233
2549
  genex explore ["<query>"] [options] Search the curated community gallery \u2014 proven
2234
2550
  Three.js systems you can clone or borrow parts
2235
2551
  from. No query lists the whole catalog.
@@ -2390,6 +2706,12 @@ function parseArgs(argv) {
2390
2706
  case "--force":
2391
2707
  parsed.options.force = true;
2392
2708
  break;
2709
+ case "--list":
2710
+ parsed.options.list = true;
2711
+ break;
2712
+ case "--reset":
2713
+ parsed.options.reset = true;
2714
+ break;
2393
2715
  case "--regenerate-cover":
2394
2716
  parsed.options.regenerateCover = true;
2395
2717
  break;
@@ -2419,6 +2741,8 @@ function parseArgs(argv) {
2419
2741
  parsed.options.name = arg;
2420
2742
  } else if (parsed.command === "explore") {
2421
2743
  parsed.options.name = `${parsed.options.name} ${arg}`;
2744
+ } else if (parsed.command === "controller") {
2745
+ (parsed.options.selectors ??= []).push(arg);
2422
2746
  } else {
2423
2747
  parsed.error = `Unexpected argument: ${arg}`;
2424
2748
  return parsed;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genex-ai/cli-demo",
3
- "version": "0.37.0",
3
+ "version": "0.38.0",
4
4
  "description": "Set up your ~/.claude workspace, authorize, create a game project, generate AI assets, and publish (genex CLI).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,15 +33,18 @@ Every ported source file carries its own SPDX header
33
33
  (`SPDX-FileCopyrightText: 2023-2026 Erdong Chen`,
34
34
  `SPDX-License-Identifier: MIT`).
35
35
 
36
- ## Quaternius Universal Animation Library — CC0 1.0 Universal
36
+ ## Quaternius Universal Animation Library [Pro] — CC0 1.0 Universal
37
37
 
38
- - `assets/animation-library.glb` — the Universal Animation Library by
39
- **Quaternius** (quaternius.com): 46 humanoid animation clips on a
40
- Blender-Rigify-style skeleton, as bundled by upstream ecctrl.
38
+ - `assets/animation-library.glb` — the bundled 12-clip core, cut from the
39
+ **Universal Animation Library [Pro]** by **Quaternius** (quaternius.com):
40
+ 120 humanoid animation clips on a UE-mannequin-style skeleton.
41
+ - `public/assets/anims/*.glb` — additional per-clip cuts from the same
42
+ library, installed on demand by `npx genex controller anims`.
41
43
 
42
44
  Dedicated to the public domain under the Creative Commons CC0 1.0 Universal
43
45
  license (SPDX: `CC0-1.0`). No attribution is required by the license; this
44
- notice is provided as a courtesy.
46
+ notice is provided as a courtesy — supporting Quaternius on Patreon is a nice
47
+ way to say thanks.
45
48
 
46
49
  ## Default avatar — CC0 1.0 Universal
47
50