@genex-ai/cli-demo 0.11.0 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +1 -0
  2. package/dist/index.js +203 -4
  3. package/package.json +7 -2
  4. package/templates/controllers/NOTICE.md +65 -0
  5. package/templates/controllers/assets/animation-library.glb +0 -0
  6. package/templates/controllers/assets/character.glb +0 -0
  7. package/templates/controllers/assets/default-avatar.vrm +0 -0
  8. package/templates/controllers/character/character-animations.ts +682 -0
  9. package/templates/controllers/character/character-controller.ts +1636 -0
  10. package/templates/controllers/character/follow-camera.ts +644 -0
  11. package/templates/controllers/character/keyboard-input.ts +277 -0
  12. package/templates/controllers/character/presets.ts +176 -0
  13. package/templates/controllers/character/touch-joystick.ts +387 -0
  14. package/templates/controllers/character/vrm/capsule-fit.ts +52 -0
  15. package/templates/controllers/character/vrm/foot-ik.ts +341 -0
  16. package/templates/controllers/character/vrm/vrm-loader.ts +44 -0
  17. package/templates/controllers/character/vrm/vrm-retarget.ts +195 -0
  18. package/templates/controllers/drone/drone-controller.ts +1073 -0
  19. package/templates/controllers/drone/presets.ts +225 -0
  20. package/templates/controllers/interact/enter-exit.ts +502 -0
  21. package/templates/controllers/shared/colliders.ts +456 -0
  22. package/templates/controllers/shared/math.ts +230 -0
  23. package/templates/controllers/shared/physics-world.ts +622 -0
  24. package/templates/controllers/vehicle/presets.ts +297 -0
  25. package/templates/controllers/vehicle/vehicle-controller.ts +615 -0
  26. package/templates/controllers/vehicle/wheel.ts +1200 -0
  27. package/templates/skills/genex-getting-started/SKILL.md +5 -0
  28. package/templates/skills/genex-threejs-character-controller/SKILL.md +205 -0
  29. package/templates/skills/genex-threejs-character-controller/references/animations.md +235 -0
  30. package/templates/skills/genex-threejs-character-controller/references/tuning-and-presets.md +102 -0
  31. package/templates/skills/genex-threejs-character-controller/references/wiring.md +198 -0
  32. package/templates/skills/genex-threejs-embed-auth/SKILL.md +126 -54
  33. package/templates/skills/genex-threejs-multiplayer/SKILL.md +17 -11
  34. package/templates/skills/genex-threejs-physics-rapier/SKILL.md +128 -0
  35. package/templates/skills/genex-threejs-physics-rapier/references/colliders-from-assets.md +202 -0
  36. package/templates/skills/genex-threejs-physics-rapier/references/physics-setup.md +207 -0
  37. package/templates/skills/genex-threejs-skill-router/SKILL.md +3 -0
  38. package/templates/skills/genex-threejs-skill-router/references/routing-map.md +15 -7
  39. package/templates/skills/genex-threejs-vehicle-controllers/SKILL.md +110 -0
  40. package/templates/skills/genex-threejs-vehicle-controllers/references/car.md +162 -0
  41. package/templates/skills/genex-threejs-vehicle-controllers/references/drone.md +150 -0
  42. package/templates/skills/genex-threejs-vehicle-controllers/references/enter-exit.md +199 -0
package/README.md CHANGED
@@ -11,6 +11,7 @@ genex model "<prompt>" # generate a 3D model → assets/models/
11
11
  genex skybox "<prompt>" # generate a 360° sky → assets/skybox/
12
12
  genex sfx "<prompt>" # generate a sound fx → assets/sfx/
13
13
  genex texture "<prompt>" # generate a texture → assets/textures/
14
+ genex controller <type> # install a tuned character|car|drone controller → src/controllers/
14
15
  ```
15
16
 
16
17
  > **Invoking it.** First-time setup runs via `npx @genex-ai/cli-demo@latest init`.
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { readFileSync } from "fs";
5
- import path12 from "path";
5
+ import path13 from "path";
6
6
  import { fileURLToPath as fileURLToPath2 } from "url";
7
7
 
8
8
  // src/commands/init.ts
@@ -91,6 +91,7 @@ async function walk(rootSrc, src, dest, opts, result) {
91
91
  const srcPath = path2.join(src, entry.name);
92
92
  const destPath = path2.join(dest, entry.name);
93
93
  const rel = path2.relative(rootSrc, srcPath);
94
+ if (opts.exclude?.includes(rel)) continue;
94
95
  if (entry.isDirectory()) {
95
96
  await fs2.mkdir(destPath, { recursive: true });
96
97
  await walk(rootSrc, srcPath, destPath, opts, result);
@@ -740,7 +741,10 @@ async function runInit(opts) {
740
741
  for (const t of targets) {
741
742
  const src = t.full ? templatesDir : path7.join(templatesDir, "skills");
742
743
  const dest = t.full ? t.baseDir : path7.join(t.baseDir, "skills");
743
- const { copied, updated } = await copyTemplates(src, dest, { force: opts.force });
744
+ const { copied, updated } = await copyTemplates(src, dest, {
745
+ force: opts.force,
746
+ exclude: ["controllers"]
747
+ });
744
748
  const added = copied.length - updated.length;
745
749
  totalNew += added;
746
750
  totalUpdated += updated.length;
@@ -1450,13 +1454,202 @@ function printHint(kind, written, log) {
1450
1454
  log.dim(` ${hint[kind]}`);
1451
1455
  }
1452
1456
 
1457
+ // src/commands/controller.ts
1458
+ import fs10 from "fs/promises";
1459
+ import path12 from "path";
1460
+ var CONTROLLER_KINDS = ["character", "car", "drone"];
1461
+ var SHARED = [
1462
+ "shared/math.ts",
1463
+ "shared/physics-world.ts",
1464
+ "shared/colliders.ts"
1465
+ ];
1466
+ var INPUT_AND_CAMERA = [
1467
+ "character/follow-camera.ts",
1468
+ "character/keyboard-input.ts",
1469
+ "character/touch-joystick.ts"
1470
+ ];
1471
+ var NOTICE = "NOTICE.md";
1472
+ var CONTROLLER_FILE_SETS = {
1473
+ character: {
1474
+ code: [
1475
+ ...SHARED,
1476
+ "character/character-controller.ts",
1477
+ "character/character-animations.ts",
1478
+ "character/presets.ts",
1479
+ // VRM avatar support (three-vrm): load + retarget the UAL clips + auto-fit
1480
+ // the capsule + optional foot IK. Owner's avatar replaces the old mannequin.
1481
+ "character/vrm/vrm-loader.ts",
1482
+ "character/vrm/vrm-retarget.ts",
1483
+ "character/vrm/capsule-fit.ts",
1484
+ "character/vrm/foot-ik.ts",
1485
+ ...INPUT_AND_CAMERA,
1486
+ NOTICE
1487
+ ],
1488
+ // The player's VRM is written to public/assets/avatar.vrm at install time by
1489
+ // installOwnerAvatar (owner's avatar, or the bundled default) — so it is NOT
1490
+ // a static manifest asset. animation-library.glb (46 clips) still is.
1491
+ assets: ["assets/animation-library.glb"],
1492
+ skill: "genex-threejs-character-controller",
1493
+ sketch: [
1494
+ `const physics = await PhysicsWorld.create();`,
1495
+ `const { scene, vrm } = await loadVrm("./assets/avatar.vrm");`,
1496
+ `const lib = await new GLTFLoader().loadAsync("./assets/animation-library.glb");`,
1497
+ `const character = new CharacterController(physics.world, camera, { ...characterPresets["default"].options, ...capsuleFromModel(scene), position: { x: 0, y: 2, z: 0 } });`,
1498
+ `character.root.add(scene); const anims = new CharacterAnimations(scene, retargetClips(vrm, lib.scene, lib.animations));`,
1499
+ `addEventListener("pointerdown", () => anims.playOneShot("Punch_Jab")); // per frame: anims.update(character, dt); vrm.update(dt);`
1500
+ ]
1501
+ },
1502
+ car: {
1503
+ code: [
1504
+ ...SHARED,
1505
+ "vehicle/vehicle-controller.ts",
1506
+ "vehicle/wheel.ts",
1507
+ "vehicle/presets.ts",
1508
+ "interact/enter-exit.ts",
1509
+ ...INPUT_AND_CAMERA,
1510
+ NOTICE
1511
+ ],
1512
+ assets: [],
1513
+ skill: "genex-threejs-vehicle-controllers",
1514
+ sketch: [
1515
+ `const physics = await PhysicsWorld.create(); // then physics.step(delta) every frame`,
1516
+ `const car = new VehicleController({ world: physics.world, position, carConfig: vehiclePresets["arcade-kart"].carConfig }); // + chassis colliders + car.addWheel(...) per preset slot`,
1517
+ `scene.add(car.chassisObject); physics.onBeforeStep(() => { car.setMovement(keyboard.getCarMovement()); car.update(); });`
1518
+ ]
1519
+ },
1520
+ drone: {
1521
+ code: [
1522
+ ...SHARED,
1523
+ "drone/drone-controller.ts",
1524
+ "drone/presets.ts",
1525
+ "interact/enter-exit.ts",
1526
+ ...INPUT_AND_CAMERA,
1527
+ NOTICE
1528
+ ],
1529
+ assets: [],
1530
+ skill: "genex-threejs-vehicle-controllers",
1531
+ sketch: [
1532
+ `const physics = await PhysicsWorld.create(); // then physics.step(delta) every frame`,
1533
+ `const drone = new DroneController({ world: physics.world, body, chassis, propellers, config: dronePresets["camera-drone"].config });`,
1534
+ `physics.onBeforeStep(() => { drone.setMovement(keyboard.getDroneMovement()); drone.update(); });`
1535
+ ]
1536
+ }
1537
+ };
1538
+ var CODE_DEST = path12.join("src", "controllers");
1539
+ var ASSETS_DEST = path12.join("public", "assets");
1540
+ async function runController(opts) {
1541
+ const log = createLogger({ quiet: opts.quiet });
1542
+ const kind = opts.kind?.trim();
1543
+ if (!kind || !CONTROLLER_KINDS.includes(kind)) {
1544
+ log.error(
1545
+ `Missing or unknown controller type${kind ? ` "${kind}"` : ""}. Usage: ${c.cyan(
1546
+ "genex controller <character|car|drone> [--force]"
1547
+ )}`
1548
+ );
1549
+ process.exitCode = 1;
1550
+ return;
1551
+ }
1552
+ const srcDir = path12.join(getTemplatesDir(), "controllers");
1553
+ const root = opts.cwd ?? process.cwd();
1554
+ const set = CONTROLLER_FILE_SETS[kind];
1555
+ log.plain(c.bold(`genex controller ${kind}`));
1556
+ log.plain("");
1557
+ log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path12.sep)}`);
1558
+ const plan = [
1559
+ ...set.code.map((rel) => ({ from: rel, rel: path12.join(CODE_DEST, rel) })),
1560
+ ...set.assets.map((rel) => ({
1561
+ from: rel,
1562
+ rel: path12.join(ASSETS_DEST, path12.basename(rel))
1563
+ }))
1564
+ ];
1565
+ let copied = 0;
1566
+ let skipped = 0;
1567
+ try {
1568
+ for (const file of plan) {
1569
+ const dest = path12.join(root, file.rel);
1570
+ if (!opts.force && await exists2(dest)) {
1571
+ skipped++;
1572
+ log.dim(` skipped ${file.rel} (exists \u2014 use --force to overwrite)`);
1573
+ continue;
1574
+ }
1575
+ await fs10.mkdir(path12.dirname(dest), { recursive: true });
1576
+ await fs10.copyFile(path12.join(srcDir, file.from), dest);
1577
+ copied++;
1578
+ log.dim(` ${file.rel}`);
1579
+ }
1580
+ } catch (err) {
1581
+ log.error(`Copy failed: ${String(err)}`);
1582
+ process.exitCode = 1;
1583
+ return;
1584
+ }
1585
+ log.success(
1586
+ `Controller files ready (${copied} copied${skipped > 0 ? `, ${skipped} skipped` : ""}).`
1587
+ );
1588
+ log.plain("");
1589
+ if (kind === "character") {
1590
+ const token = opts.token !== void 0 ? opts.token : await readUserToken();
1591
+ await installOwnerAvatar({ root, srcDir, apiUrl: getApiUrl(opts.apiUrl), token, log });
1592
+ log.plain("");
1593
+ }
1594
+ log.plain(c.bold("Next steps"));
1595
+ log.plain(
1596
+ ` 1. ${c.cyan(
1597
+ kind === "character" ? "npm i @dimforge/rapier3d-compat @pixiv/three-vrm" : "npm i @dimforge/rapier3d-compat"
1598
+ )} (three is already in the scaffold).`
1599
+ );
1600
+ log.plain(` 2. Load the ${c.cyan(set.skill)} skill for wiring, presets, and tuning.`);
1601
+ log.plain(" 3. Wiring sketch (controllers update BEFORE the physics step):");
1602
+ for (const line of set.sketch) {
1603
+ log.dim(` ${line}`);
1604
+ }
1605
+ }
1606
+ async function installOwnerAvatar(args) {
1607
+ const { root, srcDir, apiUrl, token, log } = args;
1608
+ const dest = path12.join(root, ASSETS_DEST, "avatar.vrm");
1609
+ await fs10.mkdir(path12.dirname(dest), { recursive: true });
1610
+ if (token) {
1611
+ try {
1612
+ const res = await fetch(`${apiUrl}/api/avatars/me`, {
1613
+ headers: { Authorization: `Bearer ${token}` }
1614
+ });
1615
+ if (res.ok) {
1616
+ const me = await res.json();
1617
+ if (me.vrmUrl) {
1618
+ const vrmRes = await fetch(me.vrmUrl);
1619
+ if (vrmRes.ok) {
1620
+ const buf = Buffer.from(await vrmRes.arrayBuffer());
1621
+ await fs10.writeFile(dest, buf);
1622
+ log.dim(` public/assets/avatar.vrm (your avatar \u2014 ${(buf.length / 1e6).toFixed(1)} MB)`);
1623
+ return;
1624
+ }
1625
+ }
1626
+ }
1627
+ log.dim(" couldn't fetch your avatar; using the bundled default.");
1628
+ } catch {
1629
+ log.dim(" avatar fetch failed (offline?); using the bundled default.");
1630
+ }
1631
+ }
1632
+ await fs10.copyFile(path12.join(srcDir, "assets", "default-avatar.vrm"), dest);
1633
+ log.dim(
1634
+ token ? " public/assets/avatar.vrm (bundled default)" : " public/assets/avatar.vrm (bundled default \u2014 sign in and re-run for your own)"
1635
+ );
1636
+ }
1637
+ async function exists2(p) {
1638
+ try {
1639
+ await fs10.access(p);
1640
+ return true;
1641
+ } catch {
1642
+ return false;
1643
+ }
1644
+ }
1645
+
1453
1646
  // src/index.ts
1454
1647
  var GEN_KINDS = /* @__PURE__ */ new Set(["model", "skybox", "sfx", "texture"]);
1455
1648
  function getVersion() {
1456
1649
  try {
1457
- const here = path12.dirname(fileURLToPath2(import.meta.url));
1650
+ const here = path13.dirname(fileURLToPath2(import.meta.url));
1458
1651
  const pkg = JSON.parse(
1459
- readFileSync(path12.resolve(here, "..", "package.json"), "utf8")
1652
+ readFileSync(path13.resolve(here, "..", "package.json"), "utf8")
1460
1653
  );
1461
1654
  return pkg.version ?? "0.0.0";
1462
1655
  } catch {
@@ -1473,6 +1666,8 @@ ${c.bold("Usage")}
1473
1666
  genex skybox "<prompt>" [options] Generate a skybox (equirect) into public/assets/skybox.
1474
1667
  genex sfx "<prompt>" [options] Generate a sound effect (mp3) into public/assets/sfx.
1475
1668
  genex texture "<prompt>" [options] Generate a PBR texture into public/assets/textures.
1669
+ genex controller <type> [--force] Install a physics controller (character|car|drone)
1670
+ into src/controllers (+ assets into public/assets).
1476
1671
 
1477
1672
  ${c.bold("Options for the generators (`model` `skybox` `sfx` `texture`)")}
1478
1673
  --terrain (texture) seamless tiling surface for terrain/ground.
@@ -1527,6 +1722,7 @@ ${c.bold("Examples")}
1527
1722
  genex skybox "golden hour over a misty mountain range"
1528
1723
  genex sfx "punchy laser zap" --duration 2
1529
1724
  genex texture "mossy cracked cobblestone" --terrain
1725
+ genex controller character
1530
1726
  `;
1531
1727
  function parseArgs(argv) {
1532
1728
  const parsed = {
@@ -1696,6 +1892,9 @@ async function main() {
1696
1892
  case "init":
1697
1893
  await runInit(parsed.options);
1698
1894
  break;
1895
+ case "controller":
1896
+ await runController({ ...parsed.options, kind: parsed.options.name });
1897
+ break;
1699
1898
  case "preview":
1700
1899
  await runPreview(parsed.options);
1701
1900
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genex-ai/cli-demo",
3
- "version": "0.11.0",
3
+ "version": "0.14.2",
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": {
@@ -36,7 +36,12 @@
36
36
  "access": "public"
37
37
  },
38
38
  "devDependencies": {
39
- "tsup": "^8.0.0"
39
+ "@dimforge/rapier3d-compat": "^0.19.3",
40
+ "@pixiv/three-vrm": "^3.5.4",
41
+ "@types/three": "^0.185.0",
42
+ "three": "^0.185.1",
43
+ "tsup": "^8.0.0",
44
+ "typescript": "^5.8.0"
40
45
  },
41
46
  "repository": {
42
47
  "type": "git",
@@ -0,0 +1,65 @@
1
+ # Third-party notices
2
+
3
+ The controller code and assets in this directory are vendored into your game
4
+ by `npx genex controller`. They bundle the following third-party work:
5
+
6
+ ## ecctrl — MIT License
7
+
8
+ The character, vehicle, and drone controllers are a vanilla-TypeScript port of
9
+ the **ecctrl** character/vehicle controller library (a pmndrs project),
10
+ pinned at upstream commit `e2f4eb8`.
11
+
12
+ Copyright (c) 2023-2026 Erdong Chen
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+
32
+ Every ported source file carries its own SPDX header
33
+ (`SPDX-FileCopyrightText: 2023-2026 Erdong Chen`,
34
+ `SPDX-License-Identifier: MIT`).
35
+
36
+ ## Quaternius Universal Animation Library — CC0 1.0 Universal
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.
41
+
42
+ Dedicated to the public domain under the Creative Commons CC0 1.0 Universal
43
+ license (SPDX: `CC0-1.0`). No attribution is required by the license; this
44
+ notice is provided as a courtesy.
45
+
46
+ ## Default avatar — CC0 1.0 Universal
47
+
48
+ The character controller plays as your VRM avatar. When you have not chosen one
49
+ (or run offline), the bundled default `assets/default-avatar.vrm` is copied to
50
+ `public/assets/avatar.vrm`. It is **Wizzir** from the **100 Avatars** project by
51
+ **Polygonal Mind** (polygonalmind.com), dedicated to the public domain under
52
+ CC0 1.0 Universal (SPDX: `CC0-1.0`). Attribution is a courtesy, not required.
53
+
54
+ ## @pixiv/three-vrm — MIT License
55
+
56
+ The VRM support in `character/vrm/` (loader, animation retargeter, foot IK) is
57
+ built on the **@pixiv/three-vrm** npm package, MIT-licensed by pixiv Inc.
58
+ `vrm-retarget.ts` adapts that package's official humanoid-retarget example (MIT)
59
+ to the Quaternius rig. Install it into your game with `npm i @pixiv/three-vrm`.
60
+
61
+ Copyright (c) 2019-2026 pixiv Inc. — Permission is hereby granted, free of
62
+ charge, to any person obtaining a copy of this software and associated
63
+ documentation files, to deal in the Software without restriction. THE SOFTWARE
64
+ IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. (Full MIT text ships with the
65
+ `@pixiv/three-vrm` package's LICENSE file.)