@genex-ai/cli-demo 1.5.2-dev.398 → 1.6.0-dev.403

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
@@ -1238,7 +1238,7 @@ async function syncSkillsForTarget(target, templatesDir = getTemplatesDir(), ver
1238
1238
  if (await readSkillsMarker(skillsDir) === version) return false;
1239
1239
  const src = target.full ? templatesDir : path7.join(templatesDir, "skills");
1240
1240
  const dest = target.full ? target.baseDir : skillsDir;
1241
- await copyTemplates(src, dest, { exclude: ["controllers", "motion"] });
1241
+ await copyTemplates(src, dest, { exclude: ["controllers", "motion", "asset-viewer"] });
1242
1242
  await pruneRemovedSkills(skillsDir, log);
1243
1243
  await writeSkillsMarker(skillsDir, version);
1244
1244
  return true;
@@ -1722,7 +1722,7 @@ async function runInit(opts) {
1722
1722
  const dest = t.full ? t.baseDir : path10.join(t.baseDir, "skills");
1723
1723
  const { copied, updated } = await copyTemplates(src, dest, {
1724
1724
  force: opts.force,
1725
- exclude: ["controllers", "motion"]
1725
+ exclude: ["controllers", "motion", "asset-viewer"]
1726
1726
  });
1727
1727
  await pruneRemovedSkills(path10.join(t.baseDir, "skills"), log);
1728
1728
  await writeSkillsMarker(path10.join(t.baseDir, "skills"));
@@ -17564,22 +17564,22 @@ async function context2(opts) {
17564
17564
  const project = await readProject();
17565
17565
  return { token, apiUrl: getApiUrl(opts.apiUrl ?? project?.apiUrl) };
17566
17566
  }
17567
- async function readVideo(path22, log) {
17567
+ async function readVideo(path23, log) {
17568
17568
  let bytes;
17569
17569
  try {
17570
- bytes = await readFile(path22);
17570
+ bytes = await readFile(path23);
17571
17571
  } catch {
17572
- log.error(`Can't read ${path22}.`);
17572
+ log.error(`Can't read ${path23}.`);
17573
17573
  return null;
17574
17574
  }
17575
17575
  if (bytes.byteLength > MAX_VIDEO_BYTES) {
17576
- log.error(`${basename(path22)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
17576
+ log.error(`${basename(path23)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
17577
17577
  return null;
17578
17578
  }
17579
17579
  return bytes;
17580
17580
  }
17581
- async function uploadVideo(apiUrl, token, characterId, path22, bytes, log) {
17582
- const contentType = /\.mov$/i.test(path22) ? "video/quicktime" : "video/mp4";
17581
+ async function uploadVideo(apiUrl, token, characterId, path23, bytes, log) {
17582
+ const contentType = /\.mov$/i.test(path23) ? "video/quicktime" : "video/mp4";
17583
17583
  const minted = await apiFetch(
17584
17584
  `${apiUrl}/api/characters/${encodeURIComponent(characterId)}/motions/video-url`,
17585
17585
  {
@@ -17594,7 +17594,7 @@ async function uploadVideo(apiUrl, token, characterId, path22, bytes, log) {
17594
17594
  return null;
17595
17595
  }
17596
17596
  const { uploadUrl, videoUrl } = await minted.json();
17597
- log.dim(` uploading ${basename(path22)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
17597
+ log.dim(` uploading ${basename(path23)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
17598
17598
  const put = await fetch(uploadUrl, {
17599
17599
  method: "PUT",
17600
17600
  headers: { "Content-Type": contentType, "Content-Length": String(bytes.byteLength) },
@@ -19452,6 +19452,241 @@ async function runMotion(opts) {
19452
19452
  }
19453
19453
  }
19454
19454
 
19455
+ // src/commands/asset-new.ts
19456
+ import fs23 from "fs";
19457
+ import fsp from "fs/promises";
19458
+ import path22 from "path";
19459
+ import { pathToFileURL } from "url";
19460
+ var EXTRA_FILES = [
19461
+ "genex-asset.example.json",
19462
+ "public/fonts/Geist-variable.woff2",
19463
+ "public/fonts/GeistMono-variable.woff2"
19464
+ ];
19465
+ var HOLDER_PLACEHOLDER = "Your Name";
19466
+ function parseDims(input) {
19467
+ const parts = String(input).toLowerCase().split("x").map((p) => Number(p.trim()));
19468
+ if (parts.length !== 3 || parts.some((n) => !Number.isFinite(n) || n <= 0)) {
19469
+ throw new Error(`--dims must look like 0.36x0.36x0.70 (width x depth x height), got: ${input}`);
19470
+ }
19471
+ const [width, depth, height] = parts;
19472
+ return [width, height, depth];
19473
+ }
19474
+ function parseBand(input) {
19475
+ const parts = String(input).split("-").map((p) => Number(p.replace(/[_,\s]/g, "")));
19476
+ const [lo, hi] = parts;
19477
+ if (parts.length !== 2 || lo === void 0 || hi === void 0 || !Number.isInteger(lo) || !Number.isInteger(hi) || lo <= 0 || lo >= hi) {
19478
+ throw new Error(`--tri-band must look like 800-2000, got: ${input}`);
19479
+ }
19480
+ return [lo, hi];
19481
+ }
19482
+ function titleCase(slug) {
19483
+ return slug.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
19484
+ }
19485
+ function seedAssetSource(placeholder, opts) {
19486
+ const pascal = opts.pascalCase(opts.slug);
19487
+ let out = placeholder.replaceAll("Placeholder", pascal);
19488
+ const line = (pattern, replacement) => {
19489
+ if (!pattern.test(out)) {
19490
+ throw new Error(`the template's PLACEHOLDER.ts no longer carries the scaffold line ${pattern}`);
19491
+ }
19492
+ out = out.replace(pattern, replacement);
19493
+ };
19494
+ line(/^\/\/ .* - procedural Three\.js asset\.$/m, `// ${opts.name} - procedural Three.js asset.`);
19495
+ line(
19496
+ /^\/\/ MIT License - Copyright \(c\) \d+ .*$/m,
19497
+ `// MIT License - Copyright (c) ${opts.year} ${opts.holder}. Free to use, modify and ship.`
19498
+ );
19499
+ line(/^const SLUG = .*$/m, `const SLUG = '${opts.slug}';`);
19500
+ line(/^const NAME = .*$/m, `const NAME = '${opts.name.replaceAll("'", "\\'")}';`);
19501
+ line(
19502
+ /^const SIZE_METERS: \[number, number, number\] = .*$/m,
19503
+ `const SIZE_METERS: [number, number, number] = [${opts.sizeMeters.join(", ")}];`
19504
+ );
19505
+ line(/^const TARGET_TRIANGLES = .*$/m, `const TARGET_TRIANGLES = ${opts.targetTriangles};`);
19506
+ return out;
19507
+ }
19508
+ function fillTemplate(text, values) {
19509
+ return text.replace(/\{\{(\w+)\}\}/g, (_, key) => {
19510
+ const value = values[key];
19511
+ if (value === void 0) throw new Error(`unfilled placeholder: {{${key}}}`);
19512
+ return value;
19513
+ });
19514
+ }
19515
+ var GITIGNORE = ["node_modules/", "dist/", ".gates/", ".genex/", ".env", ".env.*", "!.env.example", ""].join("\n");
19516
+ function designDoc(opts) {
19517
+ return `# ${opts.name}
19518
+
19519
+ A free, MIT-licensed procedural Three.js asset for the Genex asset library.
19520
+ One file, one factory, no dependency but \`three\`.
19521
+
19522
+ - **slug:** \`${opts.slug}\`
19523
+ - **size:** ${opts.sizeMeters.map((n) => n.toFixed(2)).join(" \xD7 ")} m (x \xD7 y \xD7 z, y = height)
19524
+ - **triangle band:** ${opts.triBand[0].toLocaleString("en-US")} - ${opts.triBand[1].toLocaleString("en-US")}
19525
+ - **origin:** base centre, y = 0. **Units:** metres. **Facing:** Y-up, +Z forward.
19526
+ - **licence:** MIT, copyright ${opts.holder}. Publishing this asset releases it MIT \u2014
19527
+ the dashboard's Copy code button makes it copyable by anyone.
19528
+
19529
+ ## Boundary note
19530
+
19531
+ This asset is **code**, not generated art: the lane is procedural geometry
19532
+ authored in TypeScript. No player character applies \u2014 the project is a viewer,
19533
+ not a game.
19534
+
19535
+ ## Build plan & status
19536
+
19537
+ 1. [ ] blockout - silhouette and real dimensions
19538
+ 2. [ ] form-refinement - the details that make it read as manufactured
19539
+ 3. [ ] material-pass - MeshStandardMaterial + DataTexture only
19540
+ 4. [ ] interaction-pass - \`assetRuntime.nodes\`, \`dispose()\`, options
19541
+
19542
+ **Now:** milestone 1.
19543
+
19544
+ Every milestone ends with \`npm run verify\`, then \`npx genex preview --no-build\`.
19545
+
19546
+ ## Decisions
19547
+ `;
19548
+ }
19549
+ async function runAssetNew(options) {
19550
+ const log = createLogger();
19551
+ const cwd = options.dir ? path22.resolve(options.dir) : process.cwd();
19552
+ const slug = options.assetSlug;
19553
+ if (!slug) {
19554
+ log.error('Usage: genex asset new <slug> [--title "<Name>"] [--dims WxDxH] [--tri-band LOW-HIGH] [--holder "<your name>"] [--out <dir>]');
19555
+ return 1;
19556
+ }
19557
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug)) {
19558
+ log.error(`The slug must be kebab-case (letters, digits, dashes), got: ${slug}`);
19559
+ return 1;
19560
+ }
19561
+ const templateDir = path22.join(getTemplatesDir(), "asset-viewer");
19562
+ if (!fs23.existsSync(templateDir)) {
19563
+ log.error(`Vendored asset-viewer template not found at ${templateDir} \u2014 reinstall the CLI.`);
19564
+ return 1;
19565
+ }
19566
+ const manifestTools = await import(pathToFileURL(path22.join(templateDir, "tools", "emit-manifest.mjs")).href);
19567
+ const { SHARED_FILES, PARITY_FILENAME, hashSharedFiles, pascalCase } = manifestTools;
19568
+ const lockPath = path22.join(templateDir, "shared-files.sha256.json");
19569
+ const lock = JSON.parse(await fsp.readFile(lockPath, "utf8"));
19570
+ const actual = hashSharedFiles(templateDir);
19571
+ const drifted = SHARED_FILES.filter((rel) => lock.files[rel] !== actual[rel]);
19572
+ if (drifted.length) {
19573
+ log.error(`The bundled viewer template is damaged (${drifted.join(", ")}) \u2014 reinstall the CLI.`);
19574
+ return 1;
19575
+ }
19576
+ const name = options.title ?? titleCase(slug);
19577
+ const sizeMeters = parseDims(options.dims ?? "1x1x1");
19578
+ const triBand = parseBand(options.triBand ?? "500-8000");
19579
+ const holder = options.holder?.trim() || HOLDER_PLACEHOLDER;
19580
+ const year = (/* @__PURE__ */ new Date()).getUTCFullYear();
19581
+ const outDir = path22.resolve(cwd, options.out ?? slug);
19582
+ if (fs23.existsSync(outDir) && fs23.readdirSync(outDir).length > 0 && !options.force) {
19583
+ log.error(`${outDir} already holds files. Nothing was touched \u2014 pass --force to write into it anyway.`);
19584
+ return 1;
19585
+ }
19586
+ const version = "1.0.0";
19587
+ const targetTriangles = Math.round((triBand[0] + triBand[1]) / 2);
19588
+ const config = {
19589
+ slug,
19590
+ name,
19591
+ summary: options.summary ?? `${name} - a free procedural Three.js asset, ${sizeMeters.map((n) => n.toFixed(2)).join(" \xD7 ")} m.`,
19592
+ tags: (options.tags ?? "prop").split(",").map((t) => t.trim()).filter(Boolean),
19593
+ materialClass: "unclassified",
19594
+ geometryClass: "unclassified",
19595
+ difficulty: "easy",
19596
+ version,
19597
+ minThreeRevision: 160,
19598
+ statedSizeMeters: sizeMeters,
19599
+ triBand,
19600
+ drawCallMax: 8,
19601
+ materialMax: 4,
19602
+ textureBytesMax: 4194304,
19603
+ maxTextureDimension: 1024,
19604
+ silhouetteCollapseRatio: 0.15,
19605
+ license: { holder, year },
19606
+ viewer: { cameraDistanceMul: 1 }
19607
+ };
19608
+ await fsp.mkdir(outDir, { recursive: true });
19609
+ for (const rel of [...SHARED_FILES, ...EXTRA_FILES]) {
19610
+ const to = path22.join(outDir, rel);
19611
+ await fsp.mkdir(path22.dirname(to), { recursive: true });
19612
+ await fsp.copyFile(path22.join(templateDir, rel), to);
19613
+ }
19614
+ const pkg = fillTemplate(await fsp.readFile(path22.join(templateDir, "package.json"), "utf8"), {
19615
+ slug,
19616
+ name,
19617
+ version
19618
+ });
19619
+ await fsp.writeFile(path22.join(outDir, "package.json"), pkg, "utf8");
19620
+ await fsp.writeFile(path22.join(outDir, "asset.config.json"), JSON.stringify(config, null, 2) + "\n", "utf8");
19621
+ await fsp.writeFile(path22.join(outDir, ".gitignore"), GITIGNORE, "utf8");
19622
+ await fsp.writeFile(
19623
+ path22.join(outDir, "DESIGN.md"),
19624
+ designDoc({ name, slug, sizeMeters, triBand, holder }),
19625
+ "utf8"
19626
+ );
19627
+ const placeholder = await fsp.readFile(path22.join(templateDir, "src", "asset", "PLACEHOLDER.ts"), "utf8");
19628
+ const seeded = seedAssetSource(placeholder, {
19629
+ slug,
19630
+ name,
19631
+ sizeMeters,
19632
+ targetTriangles,
19633
+ holder,
19634
+ year,
19635
+ pascalCase
19636
+ });
19637
+ const assetFilename = `${slug.replace(/-([a-z0-9])/g, (_, ch) => ch.toUpperCase())}.ts`;
19638
+ await fsp.mkdir(path22.join(outDir, "src", "asset"), { recursive: true });
19639
+ await fsp.writeFile(path22.join(outDir, "src", "asset", assetFilename), seeded, "utf8");
19640
+ const copied = hashSharedFiles(outDir);
19641
+ const mismatched = SHARED_FILES.filter((rel) => copied[rel] !== lock.files[rel]);
19642
+ if (mismatched.length) {
19643
+ log.error(`Internal error: copy was not verbatim for ${mismatched.join(", ")}`);
19644
+ return 1;
19645
+ }
19646
+ await fsp.writeFile(
19647
+ path22.join(outDir, PARITY_FILENAME),
19648
+ JSON.stringify(
19649
+ {
19650
+ note: "Recorded by `genex asset new`. Gate G13 recomputes these; do not hand-edit the viewer.",
19651
+ templateVersion: lock.templateVersion,
19652
+ scaffoldedAt: (/* @__PURE__ */ new Date()).toISOString(),
19653
+ files: copied
19654
+ },
19655
+ null,
19656
+ 2
19657
+ ) + "\n",
19658
+ "utf8"
19659
+ );
19660
+ const lines = [
19661
+ "",
19662
+ ` ${c.bold(name)} \u2192 ${outDir}`,
19663
+ ` ${sizeMeters.map((n) => n.toFixed(2)).join(" \xD7 ")} m \xB7 ${triBand[0].toLocaleString("en-US")} - ${triBand[1].toLocaleString("en-US")} tris \xB7 viewer ${lock.templateVersion}`,
19664
+ ""
19665
+ ];
19666
+ if (holder === HOLDER_PLACEHOLDER) {
19667
+ lines.push(
19668
+ ` ${c.yellow("\u26A0")} No --holder given: the MIT copyright line says "${HOLDER_PLACEHOLDER}".`,
19669
+ ` Set the real name in asset.config.json (license.holder) AND line 2 of src/asset/${assetFilename}`,
19670
+ ` before publishing \u2014 published assets are copyable by anyone under that name.`,
19671
+ ""
19672
+ );
19673
+ }
19674
+ lines.push(
19675
+ " next:",
19676
+ ` cd ${outDir}`,
19677
+ " npm install",
19678
+ " npx playwright-core install chromium # once per machine",
19679
+ ` # author src/asset/${assetFilename} \u2014 the genex-asset-author skill carries the contract`,
19680
+ " npm run verify # build + 17 gates + manifest stamp",
19681
+ " npx genex init # create the project (sign-in if needed)",
19682
+ " npx genex preview --no-build # draft turntable to show the user",
19683
+ " npx genex publish --no-build --categories assets",
19684
+ ""
19685
+ );
19686
+ log.plain(lines.join("\n"));
19687
+ return 0;
19688
+ }
19689
+
19455
19690
  // src/index.ts
19456
19691
  var GEN_KINDS = /* @__PURE__ */ new Set(["model", "skybox", "sfx", "music", "voice", "texture", "image", "video"]);
19457
19692
  var HELP = `${c.bold("genex")} \u2014 set up your project's agent workspace, authorize, and publish 3D games.
@@ -19564,6 +19799,13 @@ ${c.bold("Usage")}
19564
19799
  extract | masks | text-color | trim | audit.
19565
19800
  Pure local work (no API); the PNG tools read
19566
19801
  a file or URL, audit scans the project.
19802
+ genex asset new <slug> Scaffold ONE asset project for the free MIT
19803
+ asset library (its own project \u2014 one asset,
19804
+ one turntable page). Only when the user asks
19805
+ to make or publish an asset \u2014 never offered
19806
+ unprompted. Options: --title --dims WxDxH
19807
+ --tri-band LOW-HIGH --holder "<your name>"
19808
+ --summary --tags --out --force.
19567
19809
 
19568
19810
  ${c.bold("Options for the generators (`model` `sfx` `music` `voice` `texture` `image` `video`)")}
19569
19811
  --terrain (texture) seamless tiling surface for terrain/ground.
@@ -19844,7 +20086,13 @@ function parseArgs(argv) {
19844
20086
  "--config",
19845
20087
  "--set",
19846
20088
  "--speed",
19847
- "--video"
20089
+ "--video",
20090
+ // `genex asset new` value flags.
20091
+ "--dims",
20092
+ "--tri-band",
20093
+ "--holder",
20094
+ "--summary",
20095
+ "--tags"
19848
20096
  ]);
19849
20097
  let i = 0;
19850
20098
  while (i < argv.length) {
@@ -20013,6 +20261,13 @@ function parseArgs(argv) {
20013
20261
  }
20014
20262
  } else if (parsed.command === "animations") {
20015
20263
  parsed.options.query = parsed.options.query ? `${parsed.options.query} ${arg}` : arg;
20264
+ } else if (parsed.command === "asset") {
20265
+ if (parsed.options.name === "new" && parsed.options.assetSlug === void 0) {
20266
+ parsed.options.assetSlug = arg;
20267
+ } else {
20268
+ parsed.error = `Unexpected argument: ${arg}`;
20269
+ return parsed;
20270
+ }
20016
20271
  } else {
20017
20272
  parsed.error = `Unexpected argument: ${arg}`;
20018
20273
  return parsed;
@@ -20149,6 +20404,21 @@ function applyValueFlag(options, flag, value) {
20149
20404
  case "--set":
20150
20405
  options.set = value;
20151
20406
  break;
20407
+ case "--dims":
20408
+ options.dims = value;
20409
+ break;
20410
+ case "--tri-band":
20411
+ options.triBand = value;
20412
+ break;
20413
+ case "--holder":
20414
+ options.holder = value;
20415
+ break;
20416
+ case "--summary":
20417
+ options.summary = value;
20418
+ break;
20419
+ case "--tags":
20420
+ options.tags = value;
20421
+ break;
20152
20422
  case "--speed": {
20153
20423
  const n = Number(value);
20154
20424
  if (!Number.isFinite(n) || n <= 0) {
@@ -20435,6 +20705,14 @@ async function main() {
20435
20705
  await runAnimationsSearch(parsed.options);
20436
20706
  }
20437
20707
  break;
20708
+ case "asset":
20709
+ if (parsed.options.name !== "new") {
20710
+ log.error("Unknown asset command. Use `genex asset new <slug>`.");
20711
+ process.exitCode = 1;
20712
+ } else {
20713
+ process.exitCode = await runAssetNew(parsed.options);
20714
+ }
20715
+ break;
20438
20716
  case "preview":
20439
20717
  await runPreview(parsed.options);
20440
20718
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genex-ai/cli-demo",
3
- "version": "1.5.2-dev.398",
3
+ "version": "1.6.0-dev.403",
4
4
  "description": "Set up your project's agent workspace (.claude/.codex/.cursor in the game folder), authorize, create a game project, generate AI assets, and publish (genex CLI).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,25 @@
1
+ {
2
+ "slug": "placeholder",
3
+ "name": "Placeholder Asset",
4
+ "summary": "Scaffold stand-in: a revolved pedestal marker that already satisfies the asset contract.",
5
+ "tags": ["prop", "scaffold"],
6
+ "materialClass": "plastic",
7
+ "geometryClass": "revolved",
8
+ "difficulty": "easy",
9
+ "version": "1.0.0",
10
+ "minThreeRevision": 160,
11
+ "statedSizeMeters": [0.4, 0.6, 0.4],
12
+ "triBand": [200, 4000],
13
+ "drawCallMax": 8,
14
+ "materialMax": 4,
15
+ "textureBytesMax": 4194304,
16
+ "maxTextureDimension": 1024,
17
+ "silhouetteCollapseRatio": 0.15,
18
+ "license": {
19
+ "holder": "Genex",
20
+ "year": 2026
21
+ },
22
+ "viewer": {
23
+ "cameraDistanceMul": 1
24
+ }
25
+ }