@genex-ai/cli-demo 1.38.0-dev.1096 → 1.38.1-dev.1097

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
@@ -810,9 +810,9 @@ async function runAuth(opts) {
810
810
  }
811
811
 
812
812
  // src/commands/remix.ts
813
- import fs19 from "fs/promises";
814
- import path19 from "path";
815
- import crypto5 from "crypto";
813
+ import fs20 from "fs/promises";
814
+ import path20 from "path";
815
+ import crypto6 from "crypto";
816
816
  import { spawn as spawn4 } from "child_process";
817
817
 
818
818
  // src/lib/game-config.ts
@@ -2852,6 +2852,98 @@ async function detectMatchmaking(log, cwd = process.cwd()) {
2852
2852
  }
2853
2853
  return null;
2854
2854
  }
2855
+ var AUTHORITY_TICK_RATES = [20, 30, 60, 120];
2856
+ var AUTHORITY_PHYSICS_ONLY_TICK_HZ = 120;
2857
+ var authorityTickHzOf = (value) => AUTHORITY_TICK_RATES.find((hz) => hz === value);
2858
+ var AUTHORITY_TICK_RATES_TEXT = `${AUTHORITY_TICK_RATES.slice(0, -1).join(", ")} or ${AUTHORITY_TICK_RATES.at(-1)}`;
2859
+ var AUTHORITY_MAX_PLAYERS = 64;
2860
+ var MAX_AUTHORITY_MANIFEST_CHARS = 1024;
2861
+ var AUTHORITY_SERVER_DIR = "server";
2862
+ var ROOM_MODULE_FILE = /\.(js|ts)$/;
2863
+ var PRESET_NAME = /^[a-z][a-z0-9-]{0,31}$/;
2864
+ var TEMPLATE_GENEX_SETTINGS = "genex.json";
2865
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2866
+ function modulePathProblem(module) {
2867
+ if (typeof module !== "string" || module === "") return `needs "module", the room module's path (e.g. "server/room.ts")`;
2868
+ if (!ROOM_MODULE_FILE.test(module)) return `module "${module.slice(0, 80)}" is not a .js or .ts file`;
2869
+ const unsafe = module.includes("\\") || /[\u0000-\u001f]/.test(module) || module.startsWith("/") || /^[A-Za-z]:/.test(module);
2870
+ const segments = module.split("/");
2871
+ if (unsafe || segments.some((s) => s === "" || s === "." || s === "..")) {
2872
+ return `module "${module.slice(0, 80)}" must be a plain path inside the game folder, like "server/room.ts"`;
2873
+ }
2874
+ if (segments[0] !== AUTHORITY_SERVER_DIR || segments.length < 2) {
2875
+ return `module "${module.slice(0, 80)}" must be in the game's ${AUTHORITY_SERVER_DIR}/ folder, like "server/room.ts"`;
2876
+ }
2877
+ return null;
2878
+ }
2879
+ function knobProblem(block) {
2880
+ const { tickHz, maxPlayers, preset } = block;
2881
+ if (tickHz !== void 0 && authorityTickHzOf(tickHz) === void 0) {
2882
+ return `tickHz must be ${AUTHORITY_TICK_RATES_TEXT}`;
2883
+ }
2884
+ const players = typeof maxPlayers === "number" && Number.isSafeInteger(maxPlayers) && maxPlayers >= 1 && maxPlayers <= AUTHORITY_MAX_PLAYERS;
2885
+ if (maxPlayers !== void 0 && !players) return `maxPlayers must be a whole number from 1 to ${AUTHORITY_MAX_PLAYERS}`;
2886
+ if (preset !== void 0 && !(typeof preset === "string" && PRESET_NAME.test(preset))) {
2887
+ return 'preset must be a preset name, like "topdown"';
2888
+ }
2889
+ return null;
2890
+ }
2891
+ function parseAuthorityManifest(value) {
2892
+ if (!isRecord(value)) return { problem: 'must be an object like { "module": "server/room.ts" }' };
2893
+ if (JSON.stringify(value).length > MAX_AUTHORITY_MANIFEST_CHARS) {
2894
+ return { problem: `is over ${MAX_AUTHORITY_MANIFEST_CHARS} characters` };
2895
+ }
2896
+ const problem = modulePathProblem(value.module) ?? knobProblem(value);
2897
+ if (problem) return { problem };
2898
+ const manifest = { module: value.module };
2899
+ const tickHz = authorityTickHzOf(value.tickHz);
2900
+ if (tickHz !== void 0) manifest.tickHz = tickHz;
2901
+ if (value.maxPlayers !== void 0) manifest.maxPlayers = value.maxPlayers;
2902
+ if (value.preset !== void 0) manifest.preset = value.preset;
2903
+ return { manifest };
2904
+ }
2905
+ function within(child, parent) {
2906
+ const rel = path14.relative(parent, child);
2907
+ return rel === "" || !rel.startsWith("..") && !path14.isAbsolute(rel);
2908
+ }
2909
+ async function moduleFileProblem(cwd, module) {
2910
+ try {
2911
+ const root = await fs13.realpath(cwd);
2912
+ const real = await fs13.realpath(path14.join(root, module));
2913
+ if (!within(real, root)) return `module "${module}" leads outside the game folder`;
2914
+ if (!(await fs13.stat(real)).isFile()) return `module "${module}" is not a file`;
2915
+ return null;
2916
+ } catch {
2917
+ return `module "${module}" does not exist`;
2918
+ }
2919
+ }
2920
+ async function readJsonFile(cwd, rel) {
2921
+ try {
2922
+ return JSON.parse(await fs13.readFile(path14.join(cwd, rel), "utf8"));
2923
+ } catch {
2924
+ return void 0;
2925
+ }
2926
+ }
2927
+ async function detectAuthority(cwd = process.cwd()) {
2928
+ const pkg = await readJsonFile(cwd, "package.json");
2929
+ const fromPkg = isRecord(pkg) && isRecord(pkg.genex) && Object.hasOwn(pkg.genex, "authority");
2930
+ let block;
2931
+ let source;
2932
+ if (fromPkg) {
2933
+ block = pkg.genex.authority;
2934
+ source = "package.json";
2935
+ } else {
2936
+ const settings = await readJsonFile(cwd, TEMPLATE_GENEX_SETTINGS);
2937
+ if (!isRecord(settings) || !Object.hasOwn(settings, "authority")) return { state: "none" };
2938
+ block = settings.authority;
2939
+ source = "genex.json";
2940
+ }
2941
+ const parsed = parseAuthorityManifest(block);
2942
+ if ("problem" in parsed) return { state: "invalid", source, problem: parsed.problem };
2943
+ const onDisk = await moduleFileProblem(cwd, parsed.manifest.module);
2944
+ if (onDisk) return { state: "invalid", source, problem: onDisk };
2945
+ return { state: "declared", source, manifest: parsed.manifest };
2946
+ }
2855
2947
  var TOUCH_KIT_MARKERS = /controllers\/touch\/|controllers\/character\/touch-joystick|TouchJoystick|VirtualButton|DragZone|RotateOverlay/;
2856
2948
  async function detectMobileControls(cwd = process.cwd()) {
2857
2949
  try {
@@ -3459,7 +3551,8 @@ async function detectFeatures(log, cwd = process.cwd()) {
3459
3551
  generations: await detectGenerationAudit(cwd),
3460
3552
  embedBoot: await detectEmbedBoot(cwd),
3461
3553
  capabilities: await detectCapabilities(cwd),
3462
- shellApis: await detectShellApis(cwd)
3554
+ shellApis: await detectShellApis(cwd),
3555
+ authority: await detectAuthority(cwd)
3463
3556
  };
3464
3557
  }
3465
3558
  function advisoryNudges(log, d) {
@@ -3478,6 +3571,16 @@ function advisoryNudges(log, d) {
3478
3571
  "package.json declares genex.matchmaking but @genex-ai/multiplayer is not installed \u2014 matchmaking cannot work. Install the SDK and use matchmake() (see genex-threejs-multiplayer)."
3479
3572
  );
3480
3573
  }
3574
+ if (d.authority.state === "invalid") {
3575
+ log.warn(
3576
+ `${d.authority.source} declares genex.authority but ${d.authority.problem} \u2014 the deploy will refuse it until the room module is named correctly (see genex-authoritative-multiplayer).`
3577
+ );
3578
+ }
3579
+ if (d.authority.state === "declared" && !d.multiplayer) {
3580
+ log.warn(
3581
+ "genex.authority names a room module but @genex-ai/multiplayer is not installed \u2014 the game cannot join its server. Install the SDK and connect with connectAuthority (see genex-authoritative-multiplayer)."
3582
+ );
3583
+ }
3481
3584
  embedBootNudges(log, d);
3482
3585
  shellNudges(log, d);
3483
3586
  surfaceNudges(log, d.surfaces);
@@ -3685,6 +3788,223 @@ async function borrowEvidence(meta, cwd) {
3685
3788
  return readme.includes(host) || /\b(upstream|originally by|ported from|borrowed from)\b/i.test(readme);
3686
3789
  }
3687
3790
 
3791
+ // src/lib/authority-publish.ts
3792
+ import crypto2 from "crypto";
3793
+ import fs14 from "fs/promises";
3794
+ import { createRequire } from "module";
3795
+ import path15 from "path";
3796
+ import { pathToFileURL } from "url";
3797
+ var AUTHORITY_DEV_PACKAGE = "@genex-ai/authority-dev";
3798
+ var MODULE_CONTRACT = 1;
3799
+ var MODULE_MAX_BYTES = 1048576;
3800
+ var REPORT_MAX_BYTES = 64 * 1024;
3801
+ var REPORT_COPY = path15.join(".genex", "authority", "report.json");
3802
+ var MAX_FAILURES_SHOWN = 6;
3803
+ var AuthorityStop = {
3804
+ /** The manifest's `genex.authority` is unusable (detect-features said why). */
3805
+ ManifestInvalid: "manifest_invalid",
3806
+ /** The game has no @genex-ai/authority-dev to bundle and validate with. */
3807
+ ToolchainMissing: "toolchain_missing",
3808
+ /** It has one, but it does not export the library calls this CLI uses. */
3809
+ ToolchainUnusable: "toolchain_unusable",
3810
+ /** The module did not bundle (the bundler's reason is printed beside it). */
3811
+ BundleFailed: "bundle_failed",
3812
+ /** The module failed validation (each typed reason is printed). */
3813
+ ValidateFailed: "validate_failed",
3814
+ /** The report passed a different module than the one bundled for upload. */
3815
+ ShaDisagrees: "sha_disagrees",
3816
+ /** The module or its report is larger than the API stores. */
3817
+ TooLarge: "too_large",
3818
+ /** The resolved tick rate, player count or preset is one the API refuses. */
3819
+ ConfigOutOfRange: "config_out_of_range",
3820
+ /** The owner is not on a plan with Genex servers (403 authority_requires_pro). */
3821
+ RequiresPro: "requires_pro",
3822
+ /** The presign or a PUT failed. */
3823
+ UploadFailed: "upload_failed"
3824
+ };
3825
+ var sha256 = (bytes) => crypto2.createHash("sha256").update(bytes).digest("hex");
3826
+ var localTestCommand = (module) => `npx genex-authority-dev start --module ${module} --bots 3`;
3827
+ async function loadAuthorityToolchain(gameDir) {
3828
+ let manifestPath;
3829
+ try {
3830
+ manifestPath = createRequire(path15.join(gameDir, "package.json")).resolve(`${AUTHORITY_DEV_PACKAGE}/package.json`);
3831
+ } catch {
3832
+ return { ok: false, stop: AuthorityStop.ToolchainMissing };
3833
+ }
3834
+ try {
3835
+ const pkg = JSON.parse(await fs14.readFile(manifestPath, "utf8"));
3836
+ const root = pkg.exports?.["."];
3837
+ const entry = typeof root === "string" ? root : root?.import;
3838
+ if (typeof entry !== "string") return { ok: false, stop: AuthorityStop.ToolchainUnusable };
3839
+ const lib = await import(pathToFileURL(path15.resolve(path15.dirname(manifestPath), entry)).href);
3840
+ if (typeof lib.bundleModule !== "function" || typeof lib.validateModule !== "function") {
3841
+ return { ok: false, stop: AuthorityStop.ToolchainUnusable };
3842
+ }
3843
+ return { ok: true, toolchain: lib, version: typeof pkg.version === "string" ? pkg.version : "unknown" };
3844
+ } catch {
3845
+ return { ok: false, stop: AuthorityStop.ToolchainUnusable };
3846
+ }
3847
+ }
3848
+ function stopped(log, stop2, sentence, ...details) {
3849
+ log.error(`Room module refused (${stop2}): ${sentence} Nothing was deployed.`);
3850
+ for (const line of details) log.dim(` ${line}`);
3851
+ return null;
3852
+ }
3853
+ var failureLine = (f) => `${f.code ? `${f.reason}:${f.code}` : f.reason} \u2014 ${f.detail}${f.tick === void 0 ? "" : ` (tick ${f.tick})`}`;
3854
+ async function keepReport(gameDir, bytes) {
3855
+ const file = path15.join(gameDir, REPORT_COPY);
3856
+ await fs14.mkdir(path15.dirname(file), { recursive: true }).catch(() => {
3857
+ });
3858
+ await fs14.writeFile(file, bytes).catch(() => {
3859
+ });
3860
+ return REPORT_COPY.split(path15.sep).join("/");
3861
+ }
3862
+ function goLiveBlock(manifest, report, code, reportBytes) {
3863
+ const preset = manifest.preset ?? report.config?.preset?.name;
3864
+ return {
3865
+ contract: MODULE_CONTRACT,
3866
+ moduleSha256: sha256(code),
3867
+ bytes: code.length,
3868
+ tickHz: manifest.tickHz ?? report.config?.tickHz ?? 0,
3869
+ maxPlayers: manifest.maxPlayers ?? report.config?.maxPlayers ?? 0,
3870
+ ...preset ? { preset } : {},
3871
+ reportSha256: sha256(reportBytes)
3872
+ };
3873
+ }
3874
+ var asksPhysics = (report) => typeof report.config?.physics === "object" && report.config.physics !== null;
3875
+ function blockProblem(block, report) {
3876
+ if (authorityTickHzOf(block.tickHz) === void 0) return `tick rate ${block.tickHz} is not ${AUTHORITY_TICK_RATES_TEXT}`;
3877
+ if (block.tickHz === AUTHORITY_PHYSICS_ONLY_TICK_HZ && !asksPhysics(report)) {
3878
+ return `tick rate ${block.tickHz} is only for a module that asks for physics`;
3879
+ }
3880
+ if (!Number.isSafeInteger(block.maxPlayers) || block.maxPlayers < 1 || block.maxPlayers > AUTHORITY_MAX_PLAYERS) {
3881
+ return `${block.maxPlayers} players is outside 1\u2013${AUTHORITY_MAX_PLAYERS}`;
3882
+ }
3883
+ return null;
3884
+ }
3885
+ async function prepareAuthorityModule(gameDir, detection, log, injected) {
3886
+ if (detection === void 0 || detection.state === "none") return null;
3887
+ if (detection.state === "invalid") {
3888
+ stopped(log, AuthorityStop.ManifestInvalid, `${detection.source} genex.authority ${detection.problem}.`);
3889
+ return false;
3890
+ }
3891
+ const { manifest } = detection;
3892
+ let toolchain = injected;
3893
+ if (!toolchain) {
3894
+ const loaded = await loadAuthorityToolchain(gameDir);
3895
+ if (!loaded.ok) {
3896
+ const verb = loaded.stop === AuthorityStop.ToolchainMissing ? "is not installed in this game" : "in this game is too old";
3897
+ stopped(log, loaded.stop, `${AUTHORITY_DEV_PACKAGE} ${verb}; it bundles and checks the room module.`, `Run \`npm i -D ${AUTHORITY_DEV_PACKAGE}@latest\`, then deploy again.`);
3898
+ return false;
3899
+ }
3900
+ toolchain = loaded.toolchain;
3901
+ }
3902
+ const entry = path15.join(gameDir, manifest.module);
3903
+ log.step(`Checking the room module (${manifest.module})\u2026`);
3904
+ const bundled = await toolchain.bundleModule(entry, gameDir);
3905
+ if (!bundled.ok) {
3906
+ stopped(log, AuthorityStop.BundleFailed, `${manifest.module} does not bundle.`, failureLine(bundled.failure));
3907
+ return false;
3908
+ }
3909
+ const report = await toolchain.validateModule({ module: entry, root: gameDir });
3910
+ const reportBytes = Buffer.from(`${JSON.stringify(report, null, 2)}
3911
+ `, "utf8");
3912
+ const kept = await keepReport(gameDir, reportBytes);
3913
+ if (!report.passed) {
3914
+ const shown = report.failures.slice(0, MAX_FAILURES_SHOWN).map(failureLine);
3915
+ const more = report.failures.length > MAX_FAILURES_SHOWN ? [`\u2026and ${report.failures.length - MAX_FAILURES_SHOWN} more`] : [];
3916
+ stopped(log, AuthorityStop.ValidateFailed, `${manifest.module} did not pass validation.`, ...shown, ...more, `Full report: ${kept}`, `Reproduce: npx genex-authority-dev validate --module ${manifest.module}`);
3917
+ return false;
3918
+ }
3919
+ const code = Buffer.from(bundled.value.code, "utf8");
3920
+ if (report.moduleSha256 !== sha256(code) || bundled.value.sha256 !== sha256(code)) {
3921
+ stopped(log, AuthorityStop.ShaDisagrees, "the report passed a different bundle than the one about to be uploaded.", `Update ${AUTHORITY_DEV_PACKAGE} and deploy again.`);
3922
+ return false;
3923
+ }
3924
+ if (code.length > MODULE_MAX_BYTES || reportBytes.length > REPORT_MAX_BYTES) {
3925
+ stopped(log, AuthorityStop.TooLarge, `the module (${code.length} bytes) or its report (${reportBytes.length} bytes) is over what Genex stores.`);
3926
+ return false;
3927
+ }
3928
+ const goLive = goLiveBlock(manifest, report, code, reportBytes);
3929
+ const problem = blockProblem(goLive, report);
3930
+ if (problem) {
3931
+ stopped(log, AuthorityStop.ConfigOutOfRange, `${problem}.`, "Set genex.authority's tickHz / maxPlayers, or the module's config, to a value Genex runs.");
3932
+ return false;
3933
+ }
3934
+ log.success(`Room module passed (${goLive.moduleSha256.slice(0, 12)}, ${code.length} bytes).`);
3935
+ return { manifest, code, report: reportBytes, goLive };
3936
+ }
3937
+ function isServerFile(relPath) {
3938
+ const [top, ...rest] = relPath.split("/");
3939
+ return rest.length > 0 && top?.toLowerCase() === AUTHORITY_SERVER_DIR;
3940
+ }
3941
+ var isPresigned = (value) => {
3942
+ const v = value;
3943
+ return typeof v?.url === "string" && typeof v.headers === "object" && v.headers !== null;
3944
+ };
3945
+ async function putSigned(target, body, what, log) {
3946
+ if (target.headers["Content-Length"] !== String(body.length)) {
3947
+ log.error(`The server signed the ${what} for ${target.headers["Content-Length"]} bytes, not ${body.length}. Nothing was deployed.`);
3948
+ return false;
3949
+ }
3950
+ try {
3951
+ const res = await fetch(target.url, { method: "PUT", headers: target.headers, body });
3952
+ if (res.ok) return true;
3953
+ log.error(`Couldn't upload the ${what} (HTTP ${res.status}). Nothing was deployed.`);
3954
+ } catch (err) {
3955
+ log.error(`Couldn't upload the ${what}: ${String(err)}. Nothing was deployed.`);
3956
+ }
3957
+ return false;
3958
+ }
3959
+ async function reportPresignRefusal(res, module, log) {
3960
+ const body = await res.clone().json().catch(() => null);
3961
+ if (res.status === 403 && body?.error === "authority_requires_pro") {
3962
+ log.error(`Room module refused (${AuthorityStop.RequiresPro}): server-authoritative multiplayer on Genex servers needs Genex Pro. Nothing was deployed.`);
3963
+ log.dim(` Test it on this machine without an account: ${localTestCommand(module)}`);
3964
+ return;
3965
+ }
3966
+ if (res.status === 401) {
3967
+ log.error("Not authorized (HTTP 401) \u2014 your sign-in expired or was revoked. Run `genex link <your-game>` and sign in again.");
3968
+ return;
3969
+ }
3970
+ if (res.status === 404) {
3971
+ log.error("This account doesn't own this game (HTTP 404). Run `genex link <your-game>` as the owner.");
3972
+ return;
3973
+ }
3974
+ const said = typeof body?.message === "string" ? body.message : typeof body?.error === "string" ? body.error : "";
3975
+ log.error(`Couldn't store the room module (HTTP ${res.status})${said ? `: ${said}` : ""}. Nothing was deployed.`);
3976
+ }
3977
+ async function uploadAuthorityModule(api, prepared, log) {
3978
+ log.step("Uploading the room module\u2026");
3979
+ let res;
3980
+ try {
3981
+ res = await apiFetch(`${api.apiUrl}/api/games/${api.projectId}/authority-module`, {
3982
+ method: "POST",
3983
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${api.token}` },
3984
+ body: JSON.stringify({
3985
+ sha256: prepared.goLive.moduleSha256,
3986
+ bytes: prepared.code.length,
3987
+ reportSha256: prepared.goLive.reportSha256,
3988
+ reportBytes: prepared.report.length
3989
+ })
3990
+ });
3991
+ } catch (err) {
3992
+ log.error(`Couldn't reach the API at ${api.apiUrl}: ${String(err)}`);
3993
+ return false;
3994
+ }
3995
+ if (!res.ok) {
3996
+ await reportPresignRefusal(res, prepared.manifest.module, log);
3997
+ return false;
3998
+ }
3999
+ const grant = await res.json().catch(() => null);
4000
+ if (!isPresigned(grant?.module) || !isPresigned(grant?.report)) {
4001
+ log.error("The API answered the room module upload without upload addresses. Nothing was deployed.");
4002
+ return false;
4003
+ }
4004
+ if (!await putSigned(grant.module, prepared.code, "room module", log)) return false;
4005
+ return putSigned(grant.report, prepared.report, "validation report", log);
4006
+ }
4007
+
3688
4008
  // src/lib/generation-plan.ts
3689
4009
  var LANE_OF_KIND = {
3690
4010
  image: "image",
@@ -3863,14 +4183,14 @@ function auditGenerationPlan(input) {
3863
4183
  }
3864
4184
 
3865
4185
  // src/lib/deploy.ts
3866
- import crypto2 from "crypto";
3867
- import fs17 from "fs/promises";
4186
+ import crypto3 from "crypto";
4187
+ import fs18 from "fs/promises";
3868
4188
  import os5 from "os";
3869
- import path17 from "path";
4189
+ import path18 from "path";
3870
4190
 
3871
4191
  // src/lib/source-size.ts
3872
- import fs14 from "fs/promises";
3873
- import path15 from "path";
4192
+ import fs15 from "fs/promises";
4193
+ import path16 from "path";
3874
4194
  var FALLBACK_SOURCE_LIMITS = {
3875
4195
  maxTotalBytes: 5 * 1024 * 1024 * 1024,
3876
4196
  maxFiles: 5e3
@@ -3912,7 +4232,7 @@ async function measurePayload(cwd, staged) {
3912
4232
  for (const file of staged) {
3913
4233
  let size = 0;
3914
4234
  try {
3915
- size = (await fs14.stat(path15.join(cwd, file))).size;
4235
+ size = (await fs15.stat(path16.join(cwd, file))).size;
3916
4236
  } catch {
3917
4237
  size = 0;
3918
4238
  }
@@ -3923,12 +4243,12 @@ async function measurePayload(cwd, staged) {
3923
4243
  }
3924
4244
 
3925
4245
  // src/commands/ui.ts
3926
- import fs16 from "fs/promises";
3927
- import path16 from "path";
4246
+ import fs17 from "fs/promises";
4247
+ import path17 from "path";
3928
4248
  import { PNG as PNG2 } from "pngjs";
3929
4249
 
3930
4250
  // src/lib/png-tools.ts
3931
- import fs15 from "fs/promises";
4251
+ import fs16 from "fs/promises";
3932
4252
  import { PNG } from "pngjs";
3933
4253
  var ALPHA_TRANSPARENT_MAX = 16;
3934
4254
  var isHttpUrl = (s) => /^https?:\/\//i.test(s);
@@ -3939,12 +4259,12 @@ async function loadPng(input) {
3939
4259
  if (!res.ok) throw new Error(`Couldn't fetch ${input} (HTTP ${res.status}).`);
3940
4260
  buf = Buffer.from(await res.arrayBuffer());
3941
4261
  } else {
3942
- buf = await fs15.readFile(input);
4262
+ buf = await fs16.readFile(input);
3943
4263
  }
3944
4264
  return PNG.sync.read(buf);
3945
4265
  }
3946
4266
  async function writePng(file, png) {
3947
- await fs15.writeFile(file, PNG.sync.write(png));
4267
+ await fs16.writeFile(file, PNG.sync.write(png));
3948
4268
  }
3949
4269
  function cropPng(image, box) {
3950
4270
  const out = new PNG({ width: box.w, height: box.h });
@@ -4244,7 +4564,7 @@ async function uiExtract(opts, log) {
4244
4564
  const dilatePx = opts.dilate ?? 0;
4245
4565
  const sheet = await loadPng(input);
4246
4566
  const { width: W, height: H, data } = sheet;
4247
- await fs16.mkdir(outDir, { recursive: true });
4567
+ await fs17.mkdir(outDir, { recursive: true });
4248
4568
  log.plain(c.bold("genex ui extract"));
4249
4569
  log.dim(` ${input} (${W}x${H}) \u2192 ${outDir}, ${names2.length} names`);
4250
4570
  let hasTransparency = false;
@@ -4473,7 +4793,7 @@ async function uiExtract(opts, log) {
4473
4793
  rimPixels: speckle.sampled
4474
4794
  });
4475
4795
  }
4476
- const outPath = path16.join(outDir, `${name}.png`);
4796
+ const outPath = path17.join(outDir, `${name}.png`);
4477
4797
  await writePng(outPath, out);
4478
4798
  const sidecar = {
4479
4799
  name,
@@ -4492,7 +4812,7 @@ async function uiExtract(opts, log) {
4492
4812
  defringed
4493
4813
  };
4494
4814
  const { name: _n, out: _o, ...sidecarBody } = sidecar;
4495
- await fs16.writeFile(
4815
+ await fs17.writeFile(
4496
4816
  outPath.replace(/\.png$/i, "") + ".bbox.json",
4497
4817
  JSON.stringify(sidecarBody, null, 2)
4498
4818
  );
@@ -4501,8 +4821,8 @@ async function uiExtract(opts, log) {
4501
4821
  `${name}.png ${cropW}x${cropH} (ar ${sidecar.aspectRatio}) at sheet ${padX0},${padY0}`
4502
4822
  );
4503
4823
  }
4504
- const debugPath = path16.join(outDir, "extract-debug.json");
4505
- await fs16.writeFile(
4824
+ const debugPath = path17.join(outDir, "extract-debug.json");
4825
+ await fs17.writeFile(
4506
4826
  debugPath,
4507
4827
  JSON.stringify(
4508
4828
  {
@@ -4964,7 +5284,7 @@ async function uiMasks(opts, log) {
4964
5284
  const registrationTolerance = opts.registrationTolerance ?? 0.04;
4965
5285
  const edgeFlushMax = opts.edgeFlushMax ?? 0.04;
4966
5286
  const loosened = registrationTolerance > 0.04 || edgeFlushMax > 0.04 || minCoverage < 0.01 || maxCoverage > 0.85;
4967
- await fs16.mkdir(outDir, { recursive: true });
5287
+ await fs17.mkdir(outDir, { recursive: true });
4968
5288
  log.plain(c.bold("genex ui masks"));
4969
5289
  log.dim(` ${input} (${image.width}x${image.height}), ${pairs.length} pair(s) \u2192 ${outDir}`);
4970
5290
  const sheetComponents = detectSheetComponents(image, opts.minPixels ?? 2e3);
@@ -5017,11 +5337,11 @@ async function uiMasks(opts, log) {
5017
5337
  });
5018
5338
  }
5019
5339
  const overlay = makeOverlay(clean2, converted.png);
5020
- const framePath = path16.join(outDir, `${pair.name}-frame.png`);
5021
- const maskPath = path16.join(outDir, `${pair.name}-mask.png`);
5022
- const annotatedPath = path16.join(outDir, `${pair.name}-annotated-source.png`);
5023
- const overlayPath = path16.join(outDir, `${pair.name}-overlay.png`);
5024
- const metaPath = path16.join(outDir, `${pair.name}.annotated-progress.json`);
5340
+ const framePath = path17.join(outDir, `${pair.name}-frame.png`);
5341
+ const maskPath = path17.join(outDir, `${pair.name}-mask.png`);
5342
+ const annotatedPath = path17.join(outDir, `${pair.name}-annotated-source.png`);
5343
+ const overlayPath = path17.join(outDir, `${pair.name}-overlay.png`);
5344
+ const metaPath = path17.join(outDir, `${pair.name}.annotated-progress.json`);
5025
5345
  await writePng(framePath, clean2);
5026
5346
  await writePng(maskPath, converted.png);
5027
5347
  await writePng(annotatedPath, annotated);
@@ -5068,7 +5388,7 @@ async function uiMasks(opts, log) {
5068
5388
  },
5069
5389
  overlay: overlayPath
5070
5390
  };
5071
- await fs16.writeFile(metaPath, `${JSON.stringify(meta, null, 2)}
5391
+ await fs17.writeFile(metaPath, `${JSON.stringify(meta, null, 2)}
5072
5392
  `);
5073
5393
  results.push(meta);
5074
5394
  const fb = converted.bbox;
@@ -5084,8 +5404,8 @@ async function uiMasks(opts, log) {
5084
5404
  );
5085
5405
  }
5086
5406
  }
5087
- const indexPath = path16.join(outDir, "annotated-progress.json");
5088
- await fs16.writeFile(indexPath, `${JSON.stringify({ input, loosened, pairs: results }, null, 2)}
5407
+ const indexPath = path17.join(outDir, "annotated-progress.json");
5408
+ await fs17.writeFile(indexPath, `${JSON.stringify({ input, loosened, pairs: results }, null, 2)}
5089
5409
  `);
5090
5410
  log.plain("");
5091
5411
  log.success(`Wrote ${pairs.length} frame+mask pair(s) \u2192 ${outDir} (index: ${indexPath}).`);
@@ -5211,7 +5531,7 @@ async function uiTextColor(opts, log) {
5211
5531
  };
5212
5532
  process.stdout.write(`${JSON.stringify(result, null, 2)}
5213
5533
  `);
5214
- if (opts.out) await fs16.writeFile(opts.out, `${JSON.stringify(result, null, 2)}
5534
+ if (opts.out) await fs17.writeFile(opts.out, `${JSON.stringify(result, null, 2)}
5215
5535
  `);
5216
5536
  if (opts.cropPath) log.dim(` crop \u2192 ${opts.cropPath}`);
5217
5537
  }
@@ -5243,7 +5563,7 @@ async function uiTrim(opts, log) {
5243
5563
  const sidecar = computeBBoxes(trimmed);
5244
5564
  const speckleAllowed = !!(speckle && speckle.ratio > SPECKLE_MAX_RATIO);
5245
5565
  const sidecarPath = outPath.replace(/\.png$/i, "") + ".bbox.json";
5246
- await fs16.writeFile(
5566
+ await fs17.writeFile(
5247
5567
  sidecarPath,
5248
5568
  JSON.stringify(speckleAllowed ? { ...sidecar, speckleAllowed: true } : sidecar, null, 2)
5249
5569
  );
@@ -5350,7 +5670,7 @@ async function uiPlate(opts, log) {
5350
5670
  fail("No interior found \u2014 the image is fully transparent (or erode ate everything). Check --in / lower --erode.");
5351
5671
  }
5352
5672
  await writePng(outPath, out);
5353
- const name = path16.basename(outPath);
5673
+ const name = path17.basename(outPath);
5354
5674
  log.plain(c.bold("genex ui plate"));
5355
5675
  log.success(`${outPath} ${W}x${H}, interior ${(count / (W * H) * 100).toFixed(1)}% (erode ${erode}px)`);
5356
5676
  log.dim(" Wire it as the plate's silhouette (same box as the frame <img>, plate UNDER the art):");
@@ -5515,13 +5835,13 @@ async function walkFiles(dir) {
5515
5835
  const out = [];
5516
5836
  let entries;
5517
5837
  try {
5518
- entries = await fs16.readdir(dir, { withFileTypes: true });
5838
+ entries = await fs17.readdir(dir, { withFileTypes: true });
5519
5839
  } catch {
5520
5840
  return out;
5521
5841
  }
5522
5842
  for (const entry of entries) {
5523
5843
  if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
5524
- const p = path16.join(dir, entry.name);
5844
+ const p = path17.join(dir, entry.name);
5525
5845
  if (entry.isDirectory()) out.push(...await walkFiles(p));
5526
5846
  else out.push(p);
5527
5847
  }
@@ -5530,7 +5850,7 @@ async function walkFiles(dir) {
5530
5850
  async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
5531
5851
  const viewportFindings = [];
5532
5852
  try {
5533
- const indexHtml = await fs16.readFile(path16.join(cwd, "index.html"), "utf8");
5853
+ const indexHtml = await fs17.readFile(path17.join(cwd, "index.html"), "utf8");
5534
5854
  if (!/<meta[^>]+name=["']viewport["']/i.test(indexHtml)) {
5535
5855
  viewportFindings.push({
5536
5856
  kind: "viewport-meta",
@@ -5544,28 +5864,28 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
5544
5864
  }
5545
5865
  } catch {
5546
5866
  }
5547
- const absAssets = path16.resolve(cwd, assetDir);
5867
+ const absAssets = path17.resolve(cwd, assetDir);
5548
5868
  try {
5549
- if (!(await fs16.stat(absAssets)).isDirectory()) {
5869
+ if (!(await fs17.stat(absAssets)).isDirectory()) {
5550
5870
  return viewportFindings.length > 0 ? viewportFindings : null;
5551
5871
  }
5552
5872
  } catch {
5553
5873
  return viewportFindings.length > 0 ? viewportFindings : null;
5554
5874
  }
5555
- const srcFiles = (await walkFiles(path16.resolve(cwd, srcDir))).filter(
5556
- (p) => AUDIT_SRC_EXTS.has(path16.extname(p).toLowerCase())
5875
+ const srcFiles = (await walkFiles(path17.resolve(cwd, srcDir))).filter(
5876
+ (p) => AUDIT_SRC_EXTS.has(path17.extname(p).toLowerCase())
5557
5877
  );
5558
5878
  try {
5559
- for (const name of await fs16.readdir(cwd)) {
5560
- const ext = path16.extname(name).toLowerCase();
5561
- if (ext === ".html" || ext === ".css") srcFiles.push(path16.join(cwd, name));
5879
+ for (const name of await fs17.readdir(cwd)) {
5880
+ const ext = path17.extname(name).toLowerCase();
5881
+ if (ext === ".html" || ext === ".css") srcFiles.push(path17.join(cwd, name));
5562
5882
  }
5563
5883
  } catch {
5564
5884
  }
5565
5885
  const sources = [];
5566
5886
  for (const p of srcFiles) {
5567
5887
  try {
5568
- sources.push({ rel: path16.relative(cwd, p), text: await fs16.readFile(p, "utf8") });
5888
+ sources.push({ rel: path17.relative(cwd, p), text: await fs17.readFile(p, "utf8") });
5569
5889
  } catch {
5570
5890
  }
5571
5891
  }
@@ -5575,11 +5895,11 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
5575
5895
  const metaByName = /* @__PURE__ */ new Map();
5576
5896
  const bboxByPng = /* @__PURE__ */ new Map();
5577
5897
  for (const p of assetFiles) {
5578
- const base = path16.basename(p);
5898
+ const base = path17.basename(p);
5579
5899
  const metaMatch = /^(.+)\.annotated-progress\.json$/.exec(base);
5580
5900
  if (metaMatch) {
5581
5901
  try {
5582
- const meta = JSON.parse(await fs16.readFile(p, "utf8"));
5902
+ const meta = JSON.parse(await fs17.readFile(p, "utf8"));
5583
5903
  metaByName.set(metaMatch[1], {
5584
5904
  cleanCrop: meta.clean?.crop ?? null,
5585
5905
  loosened: meta.loosened === true
@@ -5590,7 +5910,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
5590
5910
  }
5591
5911
  if (base.endsWith(".bbox.json")) {
5592
5912
  try {
5593
- const sidecar = JSON.parse(await fs16.readFile(p, "utf8"));
5913
+ const sidecar = JSON.parse(await fs17.readFile(p, "utf8"));
5594
5914
  if (sidecar.sheetBBox) bboxByPng.set(base.replace(/\.bbox\.json$/, ".png"), sidecar.sheetBBox);
5595
5915
  } catch {
5596
5916
  }
@@ -5599,7 +5919,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
5599
5919
  for (const [name, meta] of metaByName) {
5600
5920
  if (!meta.cleanCrop || !referenced(`${name}-mask.png`)) continue;
5601
5921
  for (const p of assetFiles) {
5602
- const base = path16.basename(p);
5922
+ const base = path17.basename(p);
5603
5923
  if (!base.toLowerCase().endsWith(".png")) continue;
5604
5924
  if (base !== `${name}.png` && !base.startsWith(`${name}-`)) continue;
5605
5925
  if (/-mask\.png$|-frame\.png$|-overlay\.png$|-annotated-source\.png$/.test(base)) continue;
@@ -5623,7 +5943,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
5623
5943
  }
5624
5944
  const pngByBase = /* @__PURE__ */ new Map();
5625
5945
  for (const p of assetFiles) {
5626
- const base = path16.basename(p);
5946
+ const base = path17.basename(p);
5627
5947
  if (base.toLowerCase().endsWith(".png")) pngByBase.set(base, p);
5628
5948
  }
5629
5949
  for (const [maskBase, maskPath] of pngByBase) {
@@ -5636,8 +5956,8 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
5636
5956
  let frame;
5637
5957
  let mask;
5638
5958
  try {
5639
- frame = PNG2.sync.read(await fs16.readFile(framePath));
5640
- mask = PNG2.sync.read(await fs16.readFile(maskPath));
5959
+ frame = PNG2.sync.read(await fs17.readFile(framePath));
5960
+ mask = PNG2.sync.read(await fs17.readFile(maskPath));
5641
5961
  } catch {
5642
5962
  continue;
5643
5963
  }
@@ -5656,7 +5976,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
5656
5976
  if (!referenced(base)) continue;
5657
5977
  let png;
5658
5978
  try {
5659
- png = PNG2.sync.read(await fs16.readFile(p));
5979
+ png = PNG2.sync.read(await fs17.readFile(p));
5660
5980
  } catch {
5661
5981
  continue;
5662
5982
  }
@@ -5676,7 +5996,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
5676
5996
  }
5677
5997
  const maskReported = /* @__PURE__ */ new Set();
5678
5998
  for (const p of assetFiles) {
5679
- const m = /^(.+)\.annotated-progress\.json$/.exec(path16.basename(p));
5999
+ const m = /^(.+)\.annotated-progress\.json$/.exec(path17.basename(p));
5680
6000
  if (!m) continue;
5681
6001
  const maskBase = `${m[1]}-mask.png`;
5682
6002
  if (!referenced(maskBase)) {
@@ -5688,14 +6008,14 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
5688
6008
  }
5689
6009
  }
5690
6010
  for (const p of assetFiles) {
5691
- const base = path16.basename(p);
6011
+ const base = path17.basename(p);
5692
6012
  if (!base.toLowerCase().endsWith(".png")) continue;
5693
6013
  if (/-annotated-source\.png$|-overlay\.png$/.test(base)) continue;
5694
6014
  if (maskReported.has(base)) continue;
5695
6015
  if (!referenced(base)) {
5696
6016
  findings.push({
5697
6017
  kind: "unwired-sprite",
5698
- message: `${path16.relative(cwd, p)} is on disk but never referenced in ${srcDir}/index.html/CSS \u2014 wire it, or record the one-line reason it was cut. (Computed-string references are invisible here \u2014 check.)`
6018
+ message: `${path17.relative(cwd, p)} is on disk but never referenced in ${srcDir}/index.html/CSS \u2014 wire it, or record the one-line reason it was cut. (Computed-string references are invisible here \u2014 check.)`
5699
6019
  });
5700
6020
  }
5701
6021
  }
@@ -5827,7 +6147,7 @@ async function printUiAuditPreflight(log) {
5827
6147
  }
5828
6148
  async function printPipelineStatePreflight(log, cwd = process.cwd()) {
5829
6149
  try {
5830
- const design = await fs17.readFile(path17.join(cwd, "DESIGN.md"), "utf8").catch(() => "");
6150
+ const design = await fs18.readFile(path18.join(cwd, "DESIGN.md"), "utf8").catch(() => "");
5831
6151
  const warnings = [];
5832
6152
  if (design.length > 0 && !/##\s*build plan & status/i.test(design)) {
5833
6153
  warnings.push(
@@ -5835,7 +6155,7 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
5835
6155
  );
5836
6156
  }
5837
6157
  if (!/player character:/i.test(design)) {
5838
- const hasCharacter = await fs17.access(path17.join(cwd, "public", "assets", "meshy-character.json")).then(() => true, () => false);
6158
+ const hasCharacter = await fs18.access(path18.join(cwd, "public", "assets", "meshy-character.json")).then(() => true, () => false);
5839
6159
  if (!hasCharacter && await loadsPlayerBody(cwd)) {
5840
6160
  warnings.push(
5841
6161
  `Player character is the stock avatar \u2014 no generated character is wired. The game's own generated character is the player's body wherever a human body appears on screen, first-person included (genex-ai-character). Generate it, or record "Player character: VRM \u2014 <reason>" in DESIGN.md (no human body in this game / out of credits / player declined).`
@@ -5852,12 +6172,12 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
5852
6172
  async function loadsPlayerBody(cwd) {
5853
6173
  const BODY_LOADERS = /\bloadPlayerCharacter\s*\(|\bloadVrm(?:Clone)?\s*\(/;
5854
6174
  try {
5855
- const entries = await fs17.readdir(path17.join(cwd, "src"), { recursive: true });
6175
+ const entries = await fs18.readdir(path18.join(cwd, "src"), { recursive: true });
5856
6176
  for (const rel of entries) {
5857
6177
  if (rel.includes("node_modules")) continue;
5858
- if (rel.split(path17.sep)[0] === "controllers" || rel.startsWith("controllers/")) continue;
6178
+ if (rel.split(path18.sep)[0] === "controllers" || rel.startsWith("controllers/")) continue;
5859
6179
  if (!/\.(ts|tsx|js|jsx)$/.test(rel)) continue;
5860
- const text = await fs17.readFile(path17.join(cwd, "src", rel), "utf8").catch(() => "");
6180
+ const text = await fs18.readFile(path18.join(cwd, "src", rel), "utf8").catch(() => "");
5861
6181
  if (BODY_LOADERS.test(text)) return true;
5862
6182
  }
5863
6183
  } catch {
@@ -5900,6 +6220,8 @@ async function deployGame(ctx, opts, log) {
5900
6220
  );
5901
6221
  return false;
5902
6222
  }
6223
+ const authority = await prepareAuthorityModule(cwd, opts.authority, log, opts.authorityToolchain);
6224
+ if (authority === false) return false;
5903
6225
  if (!opts.noBuild && await hasBuildScript(cwd)) {
5904
6226
  log.step("Building the production bundle\u2026");
5905
6227
  const built = await run("npm", ["run", "build"]);
@@ -5911,11 +6233,15 @@ async function deployGame(ctx, opts, log) {
5911
6233
  }
5912
6234
  log.success("Built.");
5913
6235
  }
5914
- const distDir = path17.join(cwd, "dist");
6236
+ const distDir = path18.join(cwd, "dist");
5915
6237
  const siteDir = await isDir(distDir) ? distDir : cwd;
5916
- const rel = path17.relative(cwd, siteDir) || ".";
6238
+ const rel = path18.relative(cwd, siteDir) || ".";
5917
6239
  if (siteDir === cwd) await writeGitignore(cwd, log);
5918
- const files = await collectFiles(siteDir);
6240
+ const collected = await collectFiles(siteDir);
6241
+ const files = authority ? collected.filter((f) => !isServerFile(f.relPath)) : collected;
6242
+ if (authority && files.length < collected.length) {
6243
+ log.dim(` Kept ${collected.length - files.length} server file(s) out of the game's public build.`);
6244
+ }
5919
6245
  if (files.length === 0) {
5920
6246
  log.warn("Nothing to deploy \u2014 the build produced no files.");
5921
6247
  return false;
@@ -5966,6 +6292,7 @@ async function deployGame(ctx, opts, log) {
5966
6292
  log.error(`Build is ${mb2(totalBytes)} MB \u2014 over the ${mb2(grant.limits.maxTotalBytes)} MB limit.`);
5967
6293
  return false;
5968
6294
  }
6295
+ if (authority && !await uploadAuthorityModule(ctx, authority, log)) return false;
5969
6296
  log.step(`Uploading ${files.length} files\u2026`);
5970
6297
  if (!await uploadAll(grant, files, log)) {
5971
6298
  log.error("Couldn't upload your game \u2014 please try again.");
@@ -5985,7 +6312,7 @@ async function deployGame(ctx, opts, log) {
5985
6312
  );
5986
6313
  if (pushed !== true) return false;
5987
6314
  log.step("Publishing\u2026");
5988
- const published = await callPublish(ctx, commit, opts, log, sourceCommitSha, capabilities);
6315
+ const published = await callPublish(ctx, commit, opts, log, sourceCommitSha, capabilities, authority?.goLive);
5989
6316
  if (!published) return false;
5990
6317
  if (opts.channel === "staging") {
5991
6318
  const pushedHere = opts.sourceDir === void 0;
@@ -6010,7 +6337,7 @@ async function deployGame(ctx, opts, log) {
6010
6337
  }
6011
6338
  async function hasBuildScript(cwd) {
6012
6339
  try {
6013
- const pkg = JSON.parse(await fs17.readFile(path17.join(cwd, "package.json"), "utf8"));
6340
+ const pkg = JSON.parse(await fs18.readFile(path18.join(cwd, "package.json"), "utf8"));
6014
6341
  return Boolean(pkg.scripts?.build);
6015
6342
  } catch {
6016
6343
  return false;
@@ -6019,12 +6346,12 @@ async function hasBuildScript(cwd) {
6019
6346
  async function collectFiles(root) {
6020
6347
  const out = [];
6021
6348
  const walk2 = async (dir, prefix) => {
6022
- for (const e of await fs17.readdir(dir, { withFileTypes: true })) {
6349
+ for (const e of await fs18.readdir(dir, { withFileTypes: true })) {
6023
6350
  const relPath = prefix ? `${prefix}/${e.name}` : e.name;
6024
6351
  if (e.isDirectory()) {
6025
- if (!EXCLUDE_DIRS.has(e.name)) await walk2(path17.join(dir, e.name), relPath);
6352
+ if (!EXCLUDE_DIRS.has(e.name)) await walk2(path18.join(dir, e.name), relPath);
6026
6353
  } else if (e.isFile() && !isSecretEnvFile(e.name)) {
6027
- out.push({ relPath, bytes: await fs17.readFile(path17.join(dir, e.name)) });
6354
+ out.push({ relPath, bytes: await fs18.readFile(path18.join(dir, e.name)) });
6028
6355
  }
6029
6356
  }
6030
6357
  };
@@ -6032,7 +6359,7 @@ async function collectFiles(root) {
6032
6359
  return out;
6033
6360
  }
6034
6361
  function contentCommit(files) {
6035
- const h = crypto2.createHash("sha1");
6362
+ const h = crypto3.createHash("sha1");
6036
6363
  for (const f of [...files].sort((a, b) => a.relPath < b.relPath ? -1 : a.relPath > b.relPath ? 1 : 0)) {
6037
6364
  h.update(f.relPath, "utf8");
6038
6365
  h.update("\0");
@@ -6210,7 +6537,7 @@ async function uploadPartWithRetry(base, encoded, auth, uploadId, partNumber, by
6210
6537
  await new Promise((resolve) => setTimeout(resolve, attempt === 0 ? 500 : 2e3));
6211
6538
  }
6212
6539
  }
6213
- async function callPublish(ctx, commit, opts, log, sourceCommitSha, capabilities) {
6540
+ async function callPublish(ctx, commit, opts, log, sourceCommitSha, capabilities, authority) {
6214
6541
  let res;
6215
6542
  try {
6216
6543
  res = await apiFetch(`${ctx.apiUrl}/api/games/${ctx.projectId}/publish`, {
@@ -6234,7 +6561,10 @@ async function callPublish(ctx, commit, opts, log, sourceCommitSha, capabilities
6234
6561
  // Recorded into the release history and acted on by nothing. Absent when
6235
6562
  // the source push produced no commit (a user's own repo, or a failure
6236
6563
  // that already stopped the deploy), which the server accepts as null.
6237
- ...sourceCommitSha ? { sourceCommitSha } : {}
6564
+ ...sourceCommitSha ? { sourceCommitSha } : {},
6565
+ // Last, and only when there is one: a game without a room module sends the body it always
6566
+ // sent, so the channel keeps whatever module it had (absent = keep, on the API).
6567
+ ...authority ? { authority } : {}
6238
6568
  })
6239
6569
  });
6240
6570
  } catch (err) {
@@ -6259,6 +6589,16 @@ async function callPublish(ctx, commit, opts, log, sourceCommitSha, capabilities
6259
6589
  log.dim(" Re-run once the API finishes deploying; the build you meant to keep is still in R2.");
6260
6590
  return null;
6261
6591
  }
6592
+ if (authority && body?.authority?.moduleSha256 !== authority.moduleSha256) {
6593
+ log.error("The server did not record this build's room module, so its multiplayer rooms would not start.");
6594
+ log.dim(` Your ${asked} build may already be serving${body?.url ? ` at ${body.url}` : ""}. Deploy again once the API is updated.`);
6595
+ return null;
6596
+ }
6597
+ const kept = authority ? void 0 : body?.authority?.moduleSha256;
6598
+ if (typeof kept === "string") {
6599
+ log.warn(`This ${asked} channel still runs the room module from an earlier build (${kept.slice(0, 12)}); this build declares none.`);
6600
+ log.dim(" Its rooms keep that module until a build declares another in genex.authority.");
6601
+ }
6262
6602
  return { url: body?.url ?? "" };
6263
6603
  }
6264
6604
  async function pushSource(sourceDir, ctx, log, branch = "main", expectStagingCommit, onCommit) {
@@ -6296,17 +6636,17 @@ async function pushSource(sourceDir, ctx, log, branch = "main", expectStagingCom
6296
6636
  ) === true;
6297
6637
  }
6298
6638
  async function resolveSourceDir(dir, cwd = process.cwd()) {
6299
- const abs = path17.resolve(cwd, dir);
6300
- const stat = await fs17.lstat(abs).catch(() => null);
6639
+ const abs = path18.resolve(cwd, dir);
6640
+ const stat = await fs18.lstat(abs).catch(() => null);
6301
6641
  if (!stat) return { refused: `--source ${abs} does not exist.` };
6302
6642
  if (stat.isSymbolicLink()) {
6303
6643
  return { refused: `--source ${abs} is a symbolic link. Pass the folder itself.` };
6304
6644
  }
6305
6645
  if (!stat.isDirectory()) return { refused: `--source ${abs} is not a folder.` };
6306
- const real = await fs17.realpath(abs);
6307
- const project = await fs17.realpath(cwd).catch(() => path17.resolve(cwd));
6308
- const genex = path17.join(project, ".genex");
6309
- const genexTarget = await fs17.realpath(genex).catch(() => genex);
6646
+ const real = await fs18.realpath(abs);
6647
+ const project = await fs18.realpath(cwd).catch(() => path18.resolve(cwd));
6648
+ const genex = path18.join(project, ".genex");
6649
+ const genexTarget = await fs18.realpath(genex).catch(() => genex);
6310
6650
  for (const meta of /* @__PURE__ */ new Set([genex, genexTarget])) {
6311
6651
  if (real === meta) {
6312
6652
  return { refused: `--source ${abs} is this project's .genex folder, not the game's source.` };
@@ -6320,8 +6660,8 @@ async function resolveSourceDir(dir, cwd = process.cwd()) {
6320
6660
  return { dir: real };
6321
6661
  }
6322
6662
  function isInside(child, parent) {
6323
- const rel = path17.relative(parent, child);
6324
- return rel !== "" && rel !== ".." && !rel.startsWith(`..${path17.sep}`) && !path17.isAbsolute(rel);
6663
+ const rel = path18.relative(parent, child);
6664
+ return rel !== "" && rel !== ".." && !rel.startsWith(`..${path18.sep}`) && !path18.isAbsolute(rel);
6325
6665
  }
6326
6666
  var WORKSPACE_SOURCE_REF = "workspace";
6327
6667
  function gitLfsInstallCommand(platform) {
@@ -6340,7 +6680,7 @@ async function pushWorktree(workTree, pushUrl, managed, log, branch = "main", on
6340
6680
  log.error("Couldn't save your game's source \u2014 please try again.");
6341
6681
  return "failed";
6342
6682
  };
6343
- const gitDir = await fs17.mkdtemp(path17.join(os5.tmpdir(), "genex-source-"));
6683
+ const gitDir = await fs18.mkdtemp(path18.join(os5.tmpdir(), "genex-source-"));
6344
6684
  const base = { GIT_DIR: gitDir };
6345
6685
  if (urlHasEmbeddedCredentials(pushUrl)) {
6346
6686
  base.GIT_CONFIG_COUNT = "1";
@@ -6355,8 +6695,8 @@ async function pushWorktree(workTree, pushUrl, managed, log, branch = "main", on
6355
6695
  };
6356
6696
  try {
6357
6697
  if ((await run("git", ["init", "-q"], base)).code !== 0) return failed();
6358
- await fs17.writeFile(path17.join(gitDir, "info", "exclude"), excludeFile());
6359
- const env = { ...base, GIT_WORK_TREE: workTree, GIT_INDEX_FILE: path17.join(gitDir, "index-source") };
6698
+ await fs18.writeFile(path18.join(gitDir, "info", "exclude"), excludeFile());
6699
+ const env = { ...base, GIT_WORK_TREE: workTree, GIT_INDEX_FILE: path18.join(gitDir, "index-source") };
6360
6700
  let lfs = (await run("git", ["lfs", "version"], base)).code !== 0 ? false : true;
6361
6701
  if (!lfs) {
6362
6702
  log.step("Installing git-lfs (keeps large binary assets out of the source push)\u2026");
@@ -6422,7 +6762,7 @@ async function pushWorktree(workTree, pushUrl, managed, log, branch = "main", on
6422
6762
  } catch {
6423
6763
  return failed();
6424
6764
  } finally {
6425
- await fs17.rm(gitDir, { recursive: true, force: true }).catch(() => {
6765
+ await fs18.rm(gitDir, { recursive: true, force: true }).catch(() => {
6426
6766
  });
6427
6767
  }
6428
6768
  }
@@ -6483,7 +6823,7 @@ async function fetchPushUrl(ctx, log, expectStagingCommit) {
6483
6823
  }
6484
6824
  async function isDir(p) {
6485
6825
  try {
6486
- return (await fs17.stat(p)).isDirectory();
6826
+ return (await fs18.stat(p)).isDirectory();
6487
6827
  } catch {
6488
6828
  return false;
6489
6829
  }
@@ -6516,7 +6856,7 @@ async function waitUntilLive(playUrl, fingerprint, timeoutMs, log) {
6516
6856
  }
6517
6857
 
6518
6858
  // src/lib/project.ts
6519
- import crypto3 from "crypto";
6859
+ import crypto4 from "crypto";
6520
6860
  async function createDraftProject(opts) {
6521
6861
  const { apiUrl, token, dashboardUrl, log } = opts;
6522
6862
  log.step("Creating your project\u2026");
@@ -6582,7 +6922,7 @@ async function createDraftProject(opts) {
6582
6922
  return { meta: null };
6583
6923
  }
6584
6924
  function randomSuffix() {
6585
- return crypto3.randomBytes(3).toString("hex");
6925
+ return crypto4.randomBytes(3).toString("hex");
6586
6926
  }
6587
6927
  async function fetchOwnerProject(apiUrl, token, slug) {
6588
6928
  try {
@@ -6606,9 +6946,9 @@ async function fetchOwnerProject(apiUrl, token, slug) {
6606
6946
  }
6607
6947
 
6608
6948
  // src/lib/cover.ts
6609
- import crypto4 from "crypto";
6610
- import fs18 from "fs/promises";
6611
- import path18 from "path";
6949
+ import crypto5 from "crypto";
6950
+ import fs19 from "fs/promises";
6951
+ import path19 from "path";
6612
6952
  var COVER_MAX_BYTES = 8 * 1024 * 1024;
6613
6953
  var COVER_MIN_WIDTH = 320;
6614
6954
  var COVER_MIN_HEIGHT = 180;
@@ -6936,12 +7276,12 @@ function reportCoverOutcome(log, outcome2, opts) {
6936
7276
  return;
6937
7277
  }
6938
7278
  }
6939
- function sha256(bytes) {
6940
- return crypto4.createHash("sha256").update(bytes).digest("hex");
7279
+ function sha2562(bytes) {
7280
+ return crypto5.createHash("sha256").update(bytes).digest("hex");
6941
7281
  }
6942
7282
  async function readCoverMemo(cwd = process.cwd()) {
6943
7283
  try {
6944
- const memo = JSON.parse(await fs18.readFile(path18.join(cwd, COVER_MEMO), "utf8"));
7284
+ const memo = JSON.parse(await fs19.readFile(path19.join(cwd, COVER_MEMO), "utf8"));
6945
7285
  return typeof memo.sha256 === "string" ? memo : null;
6946
7286
  } catch {
6947
7287
  return null;
@@ -6949,10 +7289,10 @@ async function readCoverMemo(cwd = process.cwd()) {
6949
7289
  }
6950
7290
  async function rememberCoverUpload(bytes, file, outcome2, cwd = process.cwd()) {
6951
7291
  if (!isFinalAnswer(outcome2)) return;
6952
- const memo = { sha256: sha256(bytes), file, outcome: outcome2.kind, at: (/* @__PURE__ */ new Date()).toISOString() };
7292
+ const memo = { sha256: sha2562(bytes), file, outcome: outcome2.kind, at: (/* @__PURE__ */ new Date()).toISOString() };
6953
7293
  try {
6954
- await fs18.mkdir(path18.join(cwd, ".genex"), { recursive: true });
6955
- await fs18.writeFile(path18.join(cwd, COVER_MEMO), JSON.stringify(memo, null, 2) + "\n");
7294
+ await fs19.mkdir(path19.join(cwd, ".genex"), { recursive: true });
7295
+ await fs19.writeFile(path19.join(cwd, COVER_MEMO), JSON.stringify(memo, null, 2) + "\n");
6956
7296
  } catch {
6957
7297
  }
6958
7298
  }
@@ -6961,7 +7301,7 @@ async function findScratchCover(cwd = process.cwd()) {
6961
7301
  for (const name of SCRATCH_COVER_NAMES) {
6962
7302
  const rel = `${SCRATCH_DIR}/${name}`;
6963
7303
  try {
6964
- const stat = await fs18.stat(path18.join(cwd, rel));
7304
+ const stat = await fs19.stat(path19.join(cwd, rel));
6965
7305
  if (stat.isFile() && (!best || stat.mtimeMs > best.mtime)) best = { rel, mtime: stat.mtimeMs };
6966
7306
  } catch {
6967
7307
  }
@@ -6973,9 +7313,9 @@ async function pickUpScratchCover(args) {
6973
7313
  try {
6974
7314
  const rel = await findScratchCover(cwd);
6975
7315
  if (!rel) return null;
6976
- const bytes = await fs18.readFile(path18.join(cwd, rel));
7316
+ const bytes = await fs19.readFile(path19.join(cwd, rel));
6977
7317
  const memo = await readCoverMemo(cwd);
6978
- if (memo?.sha256 === sha256(bytes)) return null;
7318
+ if (memo?.sha256 === sha2562(bytes)) return null;
6979
7319
  const outcome2 = await uploadCover({ apiUrl: args.apiUrl, token: args.token, projectId: args.projectId, bytes, label: rel });
6980
7320
  reportCoverOutcome(args.log, outcome2, {
6981
7321
  label: rel,
@@ -7036,7 +7376,10 @@ async function runPreview(opts) {
7036
7376
  embedSdkVersion: detections.embedSdkVersion,
7037
7377
  multiplayer: detections.multiplayer,
7038
7378
  mobileControls: detections.mobileControls,
7039
- capabilities: detections.capabilities
7379
+ capabilities: detections.capabilities,
7380
+ // The room module, when the game declares one: bundled, validated and uploaded with this
7381
+ // staging build (promote carries it to production with the build).
7382
+ authority: detections.authority
7040
7383
  },
7041
7384
  log
7042
7385
  );
@@ -7109,28 +7452,28 @@ function loggerFor(opts) {
7109
7452
  }
7110
7453
  async function readState(directory) {
7111
7454
  try {
7112
- const marker = path19.join(directory, ".genex", "remix-operation.json");
7113
- if ((await fs19.lstat(marker)).isSymbolicLink()) return null;
7114
- const value = JSON.parse(await fs19.readFile(marker, "utf8"));
7455
+ const marker = path20.join(directory, ".genex", "remix-operation.json");
7456
+ if ((await fs20.lstat(marker)).isSymbolicLink()) return null;
7457
+ const value = JSON.parse(await fs20.readFile(marker, "utf8"));
7115
7458
  return value.schema === 1 && value.directory === directory && typeof value.operationId === "string" && /^[a-zA-Z0-9_-]{8,128}$/.test(value.operationId) && value.source && /^[a-f0-9]{40}$/.test(value.source.sourceCommitSha) ? value : null;
7116
7459
  } catch {
7117
7460
  return null;
7118
7461
  }
7119
7462
  }
7120
7463
  async function saveState(state) {
7121
- const dir = path19.join(state.directory, ".genex");
7122
- await fs19.mkdir(dir, { recursive: true, mode: 448 });
7123
- const temp = path19.join(dir, "remix-operation.next.json");
7124
- await fs19.writeFile(temp, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
7125
- await fs19.rename(temp, path19.join(dir, "remix-operation.json"));
7464
+ const dir = path20.join(state.directory, ".genex");
7465
+ await fs20.mkdir(dir, { recursive: true, mode: 448 });
7466
+ const temp = path20.join(dir, "remix-operation.next.json");
7467
+ await fs20.writeFile(temp, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
7468
+ await fs20.rename(temp, path20.join(dir, "remix-operation.json"));
7126
7469
  }
7127
7470
  async function checkDestination(directory) {
7128
7471
  try {
7129
- const stat = await fs19.lstat(directory);
7472
+ const stat = await fs20.lstat(directory);
7130
7473
  if (!stat.isDirectory() || stat.isSymbolicLink()) throw new RemixError("destination_occupied", "The destination is not a fresh directory. Choose a new folder.");
7131
- const entries = await fs19.readdir(directory);
7474
+ const entries = await fs20.readdir(directory);
7132
7475
  if (!entries.length) return null;
7133
- const genex = await fs19.lstat(path19.join(directory, ".genex")).catch(() => null);
7476
+ const genex = await fs20.lstat(path20.join(directory, ".genex")).catch(() => null);
7134
7477
  const state = genex?.isDirectory() && !genex.isSymbolicLink() ? await readState(directory) : null;
7135
7478
  if (state) return state;
7136
7479
  throw new RemixError("destination_occupied", "The destination already contains files. Choose a fresh folder; remix never clears an existing project.");
@@ -7141,7 +7484,7 @@ async function checkDestination(directory) {
7141
7484
  }
7142
7485
  async function assertReferenceParents(cwd) {
7143
7486
  for (const rel of [".genex", ".genex/refs"]) {
7144
- const stat = await fs19.lstat(path19.join(cwd, rel)).catch((err) => {
7487
+ const stat = await fs20.lstat(path20.join(cwd, rel)).catch((err) => {
7145
7488
  if (err.code === "ENOENT") return null;
7146
7489
  throw err;
7147
7490
  });
@@ -7149,41 +7492,41 @@ async function assertReferenceParents(cwd) {
7149
7492
  }
7150
7493
  }
7151
7494
  async function archiveSourceInstructions(directory) {
7152
- const archive = path19.join(directory, ".genex", "source-instructions");
7495
+ const archive = path20.join(directory, ".genex", "source-instructions");
7153
7496
  const rootArtifacts = /* @__PURE__ */ new Set([".claude", ".codex", ".cursor", ".agents", ".mcp.json", ".cursorrules", "DESIGN.md"]);
7154
7497
  const instructionNames = /* @__PURE__ */ new Set(["AGENTS.md", "CLAUDE.md", "GEMINI.md"]);
7155
7498
  const visit = async (dir) => {
7156
- for (const entry of await fs19.readdir(dir, { withFileTypes: true })) {
7499
+ for (const entry of await fs20.readdir(dir, { withFileTypes: true })) {
7157
7500
  if (entry.name === ".genex" || entry.name === ".git" || entry.name === "node_modules") continue;
7158
- const from = path19.join(dir, entry.name), rel = path19.relative(directory, from);
7501
+ const from = path20.join(dir, entry.name), rel = path20.relative(directory, from);
7159
7502
  if (instructionNames.has(entry.name) || dir === directory && rootArtifacts.has(entry.name)) {
7160
- const to = path19.join(archive, rel);
7161
- await fs19.mkdir(path19.dirname(to), { recursive: true });
7162
- if (await fs19.lstat(to).catch(() => null)) throw new RemixError("destination_changed", `Source instructions at ${rel} changed during preparation; both copies were retained.`);
7163
- await fs19.rename(from, to);
7503
+ const to = path20.join(archive, rel);
7504
+ await fs20.mkdir(path20.dirname(to), { recursive: true });
7505
+ if (await fs20.lstat(to).catch(() => null)) throw new RemixError("destination_changed", `Source instructions at ${rel} changed during preparation; both copies were retained.`);
7506
+ await fs20.rename(from, to);
7164
7507
  } else if (entry.isDirectory()) await visit(from);
7165
7508
  }
7166
7509
  };
7167
7510
  await visit(directory);
7168
7511
  }
7169
7512
  async function acquireLock(directory) {
7170
- const lock = path19.join(directory, ".genex", "remix.lock");
7513
+ const lock = path20.join(directory, ".genex", "remix.lock");
7171
7514
  for (let attempt = 0; attempt < 2; attempt++) {
7172
7515
  try {
7173
- const handle = await fs19.open(lock, "wx", 384);
7516
+ const handle = await fs20.open(lock, "wx", 384);
7174
7517
  await handle.writeFile(String(process.pid));
7175
7518
  await handle.close();
7176
- return () => fs19.unlink(lock).catch(() => {
7519
+ return () => fs20.unlink(lock).catch(() => {
7177
7520
  });
7178
7521
  } catch (err) {
7179
7522
  if (err.code !== "EEXIST") throw err;
7180
- const pid = Number(await fs19.readFile(lock, "utf8").catch(() => ""));
7523
+ const pid = Number(await fs20.readFile(lock, "utf8").catch(() => ""));
7181
7524
  if (!Number.isInteger(pid) || pid <= 0) throw new RemixError("remix_in_progress", "Another remix owns this destination. Retry after it finishes.");
7182
7525
  try {
7183
7526
  process.kill(pid, 0);
7184
7527
  } catch (probe) {
7185
7528
  if (probe.code === "ESRCH") {
7186
- await fs19.unlink(lock);
7529
+ await fs20.unlink(lock);
7187
7530
  continue;
7188
7531
  }
7189
7532
  }
@@ -7269,13 +7612,13 @@ function pinRemixDependencies(pkg, version) {
7269
7612
  return warnings;
7270
7613
  }
7271
7614
  async function installRemixDependencies(cwd, pm, version, log, runner = childRun) {
7272
- const pkgPath = path19.join(cwd, "package.json");
7273
- const pkg = JSON.parse(await fs19.readFile(pkgPath, "utf8"));
7615
+ const pkgPath = path20.join(cwd, "package.json");
7616
+ const pkg = JSON.parse(await fs20.readFile(pkgPath, "utf8"));
7274
7617
  const warnings = pinRemixDependencies(pkg, version);
7275
- await fs19.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
7618
+ await fs20.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
7276
7619
  log.step(`Restoring dependencies with ${pm}; pinning ${CLI_PACKAGE}@${version} (${CLI_CHANNEL}).`);
7277
7620
  const args = pm === "npm" ? ["install", "--workspaces=false", "--no-audit", "--no-fund"] : pm === "pnpm" ? ["install", "--ignore-workspace", "--no-frozen-lockfile"] : ["install"];
7278
- if (pm === "yarn") await fs19.writeFile(path19.join(cwd, "yarn.lock"), "", { flag: "wx" }).catch((err) => {
7621
+ if (pm === "yarn") await fs20.writeFile(path20.join(cwd, "yarn.lock"), "", { flag: "wx" }).catch((err) => {
7279
7622
  if (err.code !== "EEXIST") throw err;
7280
7623
  });
7281
7624
  await runner(cwd, pm, args);
@@ -7309,27 +7652,27 @@ function rebindRemixConfig(content, slug) {
7309
7652
  async function configureIdentity(state, log) {
7310
7653
  const cwd = state.directory, meta = state.project;
7311
7654
  await writeProject(meta, cwd);
7312
- const configFile = path19.join(cwd, "src", "genex.config.ts");
7313
- const config = await fs19.readFile(configFile, "utf8").catch((err) => {
7655
+ const configFile = path20.join(cwd, "src", "genex.config.ts");
7656
+ const config = await fs20.readFile(configFile, "utf8").catch((err) => {
7314
7657
  if (err.code === "ENOENT") return null;
7315
7658
  throw err;
7316
7659
  });
7317
- if (config !== null) await fs19.writeFile(configFile, rebindRemixConfig(config, meta.slug));
7660
+ if (config !== null) await fs20.writeFile(configFile, rebindRemixConfig(config, meta.slug));
7318
7661
  await writeGameConfigFiles(meta, log, cwd);
7319
7662
  await writeGitignore(cwd, log);
7320
7663
  await installRemixProfile(cwd, state.apiUrl, log);
7321
- const manifestFile = path19.join(cwd, "package.json");
7322
- const manifest = JSON.parse(await fs19.readFile(manifestFile, "utf8"));
7664
+ const manifestFile = path20.join(cwd, "package.json");
7665
+ const manifest = JSON.parse(await fs20.readFile(manifestFile, "utf8"));
7323
7666
  manifest.genex.remixSource = { projectId: state.source.projectId, slug: state.source.slug, sourceCommitSha: state.source.sourceCommitSha, version: state.source.version, versionCertainty: state.source.versionCertainty };
7324
- await fs19.writeFile(manifestFile, JSON.stringify(manifest, null, 2) + "\n");
7667
+ await fs20.writeFile(manifestFile, JSON.stringify(manifest, null, 2) + "\n");
7325
7668
  const possible = [];
7326
7669
  const quoted = new RegExp(`(["'\`])(?:${state.source.slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}|${state.source.projectId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})\\1`);
7327
7670
  const scan = async (dir) => {
7328
- for (const entry of await fs19.readdir(dir, { withFileTypes: true })) {
7671
+ for (const entry of await fs20.readdir(dir, { withFileTypes: true })) {
7329
7672
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
7330
- const file = path19.join(dir, entry.name);
7673
+ const file = path20.join(dir, entry.name);
7331
7674
  if (entry.isDirectory()) await scan(file);
7332
- else if (entry.isFile() && /\.(?:[cm]?[jt]sx?|html)$/.test(entry.name) && (await fs19.stat(file)).size < 1024 * 1024 && quoted.test(await fs19.readFile(file, "utf8"))) possible.push(path19.relative(cwd, file));
7675
+ else if (entry.isFile() && /\.(?:[cm]?[jt]sx?|html)$/.test(entry.name) && (await fs20.stat(file)).size < 1024 * 1024 && quoted.test(await fs20.readFile(file, "utf8"))) possible.push(path20.relative(cwd, file));
7333
7676
  }
7334
7677
  };
7335
7678
  await scan(cwd);
@@ -7354,7 +7697,7 @@ async function runRemix(opts, deps = {}) {
7354
7697
  const apiUrl = getApiUrl(opts.apiUrl), dashboardUrl = new URL(getAuthUrl(opts.authUrl)).origin;
7355
7698
  if (opts.sourceOnly && !opts.destination) await assertReferenceParents(originalCwd);
7356
7699
  const selected = parseRemixSource(opts.remixSource, apiUrl, dashboardUrl);
7357
- let directory = opts.destination ? path19.resolve(originalCwd, opts.destination) : !opts.sourceOnly ? path19.resolve(originalCwd, `${selected.slug}-remix`) : void 0;
7700
+ let directory = opts.destination ? path20.resolve(originalCwd, opts.destination) : !opts.sourceOnly ? path20.resolve(originalCwd, `${selected.slug}-remix`) : void 0;
7358
7701
  if (directory) state = await checkDestination(directory);
7359
7702
  resumed = state !== null;
7360
7703
  if (state && (state.apiUrl !== apiUrl || state.sourceOnly !== Boolean(opts.sourceOnly) || ![state.requestedSlug, state.source.slug].includes(selected.slug) || opts.operationId && state.operationId !== opts.operationId || opts.name && state.name !== opts.name || selected.version && selected.version !== state.source.version)) throw new RemixError("operation_mismatch", "This folder belongs to a different remix operation. Resume with its original source/options or choose a fresh folder.");
@@ -7372,29 +7715,29 @@ async function runRemix(opts, deps = {}) {
7372
7715
  log.step(`Resolving source for ${selected.slug}\u2026`);
7373
7716
  const body = await responseJson(await request(`${apiUrl}/api/projects/${encodeURIComponent(selected.slug)}/source?${query}`, { headers, redirect: "error" }));
7374
7717
  validateSource(body, Boolean(opts.sourceOnly));
7375
- directory ??= path19.resolve(originalCwd, ".genex", "refs", `${body.source.slug}-${body.source.sourceCommitSha.slice(0, 12)}`);
7718
+ directory ??= path20.resolve(originalCwd, ".genex", "refs", `${body.source.slug}-${body.source.sourceCommitSha.slice(0, 12)}`);
7376
7719
  const existing = await checkDestination(directory);
7377
7720
  if (existing) {
7378
7721
  if (!existing.sourceOnly || !opts.sourceOnly || existing.apiUrl !== apiUrl || existing.source.sourceCommitSha !== body.source.sourceCommitSha || existing.source.projectId !== body.source.projectId) throw new RemixError("destination_occupied", "This reference destination already belongs to another operation. Choose a fresh folder.");
7379
7722
  state = existing;
7380
7723
  resumed = true;
7381
7724
  } else {
7382
- state = { schema: 1, operationId: opts.operationId ?? crypto5.randomUUID(), directory, apiUrl, dashboardUrl, requestedSlug: selected.slug, source: body.source, sourceToken: body.sourceToken, sourceOnly: Boolean(opts.sourceOnly), cliVersion: getCliVersion(), name: opts.name?.trim() || `${body.source.title.slice(0, 74)} remix`, stage: "download", warnings: [...body.source.warnings] };
7383
- await fs19.mkdir(directory, { recursive: true });
7384
- if ((await fs19.readdir(directory)).length) throw new RemixError("destination_occupied", "The destination changed during setup. Choose a fresh folder.");
7385
- await fs19.mkdir(path19.join(directory, ".genex"), { mode: 448 });
7725
+ state = { schema: 1, operationId: opts.operationId ?? crypto6.randomUUID(), directory, apiUrl, dashboardUrl, requestedSlug: selected.slug, source: body.source, sourceToken: body.sourceToken, sourceOnly: Boolean(opts.sourceOnly), cliVersion: getCliVersion(), name: opts.name?.trim() || `${body.source.title.slice(0, 74)} remix`, stage: "download", warnings: [...body.source.warnings] };
7726
+ await fs20.mkdir(directory, { recursive: true });
7727
+ if ((await fs20.readdir(directory)).length) throw new RemixError("destination_occupied", "The destination changed during setup. Choose a fresh folder.");
7728
+ await fs20.mkdir(path20.join(directory, ".genex"), { mode: 448 });
7386
7729
  await saveState(state);
7387
7730
  }
7388
7731
  }
7389
7732
  unlock = await acquireLock(state.directory);
7390
7733
  state = await readState(state.directory) ?? state;
7391
7734
  if (resumed && state.stage === "download") await renewSource(state, request, headers);
7392
- const staging = path19.join(state.directory, ".genex", "remix-stage");
7735
+ const staging = path20.join(state.directory, ".genex", "remix-stage");
7393
7736
  if (state.stage === "download") {
7394
- await fs19.rm(staging, { recursive: true, force: true });
7395
- await fs19.mkdir(staging, { recursive: true });
7396
- const archive = path19.join(staging, "source.zip"), sourceDir = path19.join(staging, "source");
7397
- await fs19.mkdir(sourceDir);
7737
+ await fs20.rm(staging, { recursive: true, force: true });
7738
+ await fs20.mkdir(staging, { recursive: true });
7739
+ const archive = path20.join(staging, "source.zip"), sourceDir = path20.join(staging, "source");
7740
+ await fs20.mkdir(sourceDir);
7398
7741
  const archiveUrl = new URL(state.source.archiveUrl, state.apiUrl);
7399
7742
  if (archiveUrl.origin !== new URL(state.apiUrl).origin || !archiveUrl.pathname.startsWith("/api/projects/")) throw new RemixError("invalid_source_response", "Source archive must be served by the selected Genex API.");
7400
7743
  log.step(`Downloading ${state.source.slug} at ${state.source.sourceCommitSha.slice(0, 12)}\u2026`);
@@ -7409,26 +7752,26 @@ async function runRemix(opts, deps = {}) {
7409
7752
  if (!state.sourceOnly) {
7410
7753
  let pkg;
7411
7754
  try {
7412
- pkg = JSON.parse(await fs19.readFile(path19.join(sourceDir, "package.json"), "utf8"));
7755
+ pkg = JSON.parse(await fs20.readFile(path20.join(sourceDir, "package.json"), "utf8"));
7413
7756
  } catch (error) {
7414
- const staticEntry = await fs19.stat(path19.join(sourceDir, "index.html")).catch(() => null);
7757
+ const staticEntry = await fs20.stat(path20.join(sourceDir, "index.html")).catch(() => null);
7415
7758
  if (error.code !== "ENOENT" || !staticEntry?.isFile()) throw new RemixError("source_unavailable", "Source has neither a readable package.json nor a static index.html. Use --source-only to inspect it.");
7416
7759
  pkg = { name: manifestName(state.directory), private: true };
7417
- await fs19.writeFile(path19.join(sourceDir, "package.json"), JSON.stringify(pkg, null, 2) + "\n");
7760
+ await fs20.writeFile(path20.join(sourceDir, "package.json"), JSON.stringify(pkg, null, 2) + "\n");
7418
7761
  state.warnings.push("This static game had no package.json; added a minimal private manifest for the local CLI. Its source and index.html remain unchanged.");
7419
7762
  }
7420
7763
  if (!pkg || typeof pkg !== "object" || Array.isArray(pkg)) throw new RemixError("source_unavailable", "Source package.json is not a project manifest.");
7421
7764
  }
7422
- state.transferEntries = await fs19.readdir(sourceDir);
7765
+ state.transferEntries = await fs20.readdir(sourceDir);
7423
7766
  state.stage = "transfer";
7424
7767
  await saveState(state);
7425
7768
  }
7426
7769
  if (state.stage === "transfer") {
7427
7770
  for (const entry of state.transferEntries ?? []) {
7428
- const from = path19.join(staging, "source", entry), to = path19.join(state.directory, entry);
7429
- if (await fs19.lstat(from).catch(() => null)) {
7430
- if (await fs19.lstat(to).catch(() => null)) throw new RemixError("destination_changed", `A file appeared at ${entry} during source preparation. It was left untouched.`);
7431
- await fs19.rename(from, to);
7771
+ const from = path20.join(staging, "source", entry), to = path20.join(state.directory, entry);
7772
+ if (await fs20.lstat(from).catch(() => null)) {
7773
+ if (await fs20.lstat(to).catch(() => null)) throw new RemixError("destination_changed", `A file appeared at ${entry} during source preparation. It was left untouched.`);
7774
+ await fs20.rename(from, to);
7432
7775
  }
7433
7776
  }
7434
7777
  if (!state.sourceOnly) {
@@ -7437,7 +7780,7 @@ async function runRemix(opts, deps = {}) {
7437
7780
  }
7438
7781
  state.stage = state.sourceOnly ? "prepared" : "configure";
7439
7782
  await saveState(state);
7440
- await fs19.rm(staging, { recursive: true, force: true });
7783
+ await fs20.rm(staging, { recursive: true, force: true });
7441
7784
  }
7442
7785
  process.chdir(state.directory);
7443
7786
  if (state.stage === "configure") {
@@ -7508,18 +7851,18 @@ async function runRemix(opts, deps = {}) {
7508
7851
  }
7509
7852
 
7510
7853
  // src/commands/init.ts
7511
- import fs23 from "fs/promises";
7512
- import path22 from "path";
7854
+ import fs24 from "fs/promises";
7855
+ import path23 from "path";
7513
7856
 
7514
7857
  // src/lib/item-drafts.ts
7515
7858
  import { createHash as createHash2, randomBytes } from "crypto";
7516
7859
  import { constants as fsConstants2 } from "fs";
7517
- import fs22 from "fs/promises";
7860
+ import fs23 from "fs/promises";
7518
7861
  import os6 from "os";
7519
- import path21 from "path";
7862
+ import path22 from "path";
7520
7863
 
7521
7864
  // src/lib/item-media.ts
7522
- import fs20 from "fs/promises";
7865
+ import fs21 from "fs/promises";
7523
7866
  var MIB = 1024 * 1024;
7524
7867
  var MediaKind = { Picture: "picture", Model: "model" };
7525
7868
  var MediaContentType = {
@@ -7553,7 +7896,7 @@ async function readItemMedia(kind, file) {
7553
7896
  }
7554
7897
  let size;
7555
7898
  try {
7556
- const stat = await fs20.stat(file);
7899
+ const stat = await fs21.stat(file);
7557
7900
  if (!stat.isFile()) throw new Error("not a file");
7558
7901
  size = stat.size;
7559
7902
  } catch {
@@ -7563,7 +7906,7 @@ async function readItemMedia(kind, file) {
7563
7906
  if (size > max) {
7564
7907
  throw new ItemMediaError(`${file} is ${Math.ceil(size / MIB * 10) / 10} MB; ${kind === MediaKind.Picture ? "a picture" : "a model"} may be at most ${max / MIB} MB.`);
7565
7908
  }
7566
- const bytes = await fs20.readFile(file);
7909
+ const bytes = await fs21.readFile(file);
7567
7910
  const contentType = mediaContentType(kind, bytes);
7568
7911
  if (!contentType) {
7569
7912
  throw new ItemMediaError(
@@ -7576,8 +7919,8 @@ async function readItemMedia(kind, file) {
7576
7919
  // src/lib/item-package.ts
7577
7920
  import { createHash } from "crypto";
7578
7921
  import { constants as fsConstants } from "fs";
7579
- import fs21 from "fs/promises";
7580
- import path20 from "path";
7922
+ import fs22 from "fs/promises";
7923
+ import path21 from "path";
7581
7924
  import zlib from "zlib";
7582
7925
  var MIB2 = 1024 * 1024;
7583
7926
  var ITEM_PACKAGE_BYTES_MAX = 50 * MIB2;
@@ -7656,16 +7999,16 @@ async function packItemDirectory(dir) {
7656
7999
  }
7657
8000
  async function realDirectory(dir) {
7658
8001
  try {
7659
- const root = await fs21.realpath(dir);
7660
- if ((await fs21.stat(root)).isDirectory()) return root;
8002
+ const root = await fs22.realpath(dir);
8003
+ if ((await fs22.stat(root)).isDirectory()) return root;
7661
8004
  } catch {
7662
8005
  }
7663
8006
  throw new ItemPackageError(PackageErrorCode.DirMissing, `${dir} is not a folder.`);
7664
8007
  }
7665
8008
  async function insideRoot(root, candidate) {
7666
8009
  try {
7667
- const real = await fs21.realpath(candidate);
7668
- return real === root || real.startsWith(root + path20.sep);
8010
+ const real = await fs22.realpath(candidate);
8011
+ return real === root || real.startsWith(root + path21.sep);
7669
8012
  } catch {
7670
8013
  return false;
7671
8014
  }
@@ -7681,10 +8024,10 @@ function portableName(relPath) {
7681
8024
  }
7682
8025
  var byName = (a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
7683
8026
  async function collect(root, abs, rel, out) {
7684
- const entries = (await fs21.readdir(abs, { withFileTypes: true })).sort(byName);
8027
+ const entries = (await fs22.readdir(abs, { withFileTypes: true })).sort(byName);
7685
8028
  for (const entry of entries) {
7686
8029
  const relPath = rel ? `${rel}/${entry.name}` : entry.name;
7687
- const full = path20.join(abs, entry.name);
8030
+ const full = path21.join(abs, entry.name);
7688
8031
  const excluded = packExclusion(relPath, entry.name);
7689
8032
  if (excluded) {
7690
8033
  out.skipped.push({ path: relPath, reason: excluded });
@@ -7722,10 +8065,10 @@ async function collect(root, abs, rel, out) {
7722
8065
  }
7723
8066
  }
7724
8067
  async function readRegularFile(root, full) {
7725
- if (!await insideRoot(root, path20.dirname(full))) return PackSkip.Symlink;
8068
+ if (!await insideRoot(root, path21.dirname(full))) return PackSkip.Symlink;
7726
8069
  let handle;
7727
8070
  try {
7728
- handle = await fs21.open(full, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0));
8071
+ handle = await fs22.open(full, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0));
7729
8072
  } catch {
7730
8073
  return PackSkip.Symlink;
7731
8074
  }
@@ -7933,14 +8276,14 @@ function entryBytes(zip, entry) {
7933
8276
  if (sourceCrc32(data) !== entry.crc) invalid(`${entry.path} fails its checksum.`, entry.path);
7934
8277
  return data;
7935
8278
  }
7936
- var sha2562 = (data) => createHash("sha256").update(data).digest("hex");
8279
+ var sha2563 = (data) => createHash("sha256").update(data).digest("hex");
7937
8280
  function checkAgainstManifest(zip, files, manifest) {
7938
8281
  const expected = new Map(manifest.files.map((f) => [f.path, f]));
7939
8282
  if (expected.size !== manifest.files.length) throw new ItemPackageError(PackageErrorCode.Mismatch, "The package's manifest lists a file twice.");
7940
8283
  for (const file of files) {
7941
8284
  const want = expected.get(file.path);
7942
8285
  if (!want) throw new ItemPackageError(PackageErrorCode.Mismatch, `${file.path} is in the package but not in its manifest.`, file.path);
7943
- if (want.bytes !== file.size || want.sha256.toLowerCase() !== sha2562(entryBytes(zip, file))) {
8286
+ if (want.bytes !== file.size || want.sha256.toLowerCase() !== sha2563(entryBytes(zip, file))) {
7944
8287
  throw new ItemPackageError(PackageErrorCode.Mismatch, `${file.path} is not the file the manifest recorded.`, file.path);
7945
8288
  }
7946
8289
  expected.delete(file.path);
@@ -7959,7 +8302,7 @@ async function extractItemPackage(zip, dest, manifest) {
7959
8302
  for (const file of files) entryBytes(zip, file);
7960
8303
  if (manifest) checkAgainstManifest(zip, files, manifest);
7961
8304
  try {
7962
- await fs21.mkdir(dest);
8305
+ await fs22.mkdir(dest);
7963
8306
  } catch (err) {
7964
8307
  if (err.code === "EEXIST") {
7965
8308
  throw new ItemPackageError(PackageErrorCode.DestinationExists, `${dest} already exists.`);
@@ -7967,13 +8310,13 @@ async function extractItemPackage(zip, dest, manifest) {
7967
8310
  throw err;
7968
8311
  }
7969
8312
  for (const entry of entries) {
7970
- const target = path20.join(dest, ...entry.path.split("/"));
8313
+ const target = path21.join(dest, ...entry.path.split("/"));
7971
8314
  if (entry.dir) {
7972
- await fs21.mkdir(target, { recursive: true });
8315
+ await fs22.mkdir(target, { recursive: true });
7973
8316
  continue;
7974
8317
  }
7975
- await fs21.mkdir(path20.dirname(target), { recursive: true });
7976
- await fs21.writeFile(target, entryBytes(zip, entry), { flag: "wx", mode: FILE_MODE });
8318
+ await fs22.mkdir(path21.dirname(target), { recursive: true });
8319
+ await fs22.writeFile(target, entryBytes(zip, entry), { flag: "wx", mode: FILE_MODE });
7977
8320
  }
7978
8321
  return files.map((f) => f.path);
7979
8322
  }
@@ -8200,7 +8543,7 @@ function isLocalId(value) {
8200
8543
  function newLocalId() {
8201
8544
  return `${LOCAL_ID_PREFIX}${randomBytes(6).toString("hex")}`;
8202
8545
  }
8203
- var sha2563 = (bytes) => createHash2("sha256").update(bytes).digest("hex");
8546
+ var sha2564 = (bytes) => createHash2("sha256").update(bytes).digest("hex");
8204
8547
  function contentHash(fields, files) {
8205
8548
  const canonical = JSON.stringify({
8206
8549
  description: fields.description ?? null,
@@ -8213,9 +8556,9 @@ function contentHash(fields, files) {
8213
8556
  const parts = [canonical];
8214
8557
  for (const kind of FILE_ORDER) {
8215
8558
  const bytes = files[kind];
8216
- if (bytes) parts.push(`${kind}:${sha2563(bytes)}`);
8559
+ if (bytes) parts.push(`${kind}:${sha2564(bytes)}`);
8217
8560
  }
8218
- return sha2563(parts.join("\n"));
8561
+ return sha2564(parts.join("\n"));
8219
8562
  }
8220
8563
  function draftLine(draft) {
8221
8564
  const { record } = draft;
@@ -8245,13 +8588,13 @@ var machineFolder = () => new LocalDraftError(
8245
8588
  "This is the home folder: its .genex is this machine's Genex folder (sign-in and caches), not a game's. Run it in the game's folder; nothing was read or written."
8246
8589
  );
8247
8590
  async function isHomeFolder(cwd) {
8248
- const [here, home] = await Promise.all([fs22.realpath(cwd), fs22.realpath(os6.homedir()).catch(() => path21.resolve(os6.homedir()))]);
8591
+ const [here, home] = await Promise.all([fs23.realpath(cwd), fs23.realpath(os6.homedir()).catch(() => path22.resolve(os6.homedir()))]);
8249
8592
  return here === home;
8250
8593
  }
8251
- var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
8594
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
8252
8595
  async function lstatOrNull(full) {
8253
8596
  try {
8254
- return await fs22.lstat(full);
8597
+ return await fs23.lstat(full);
8255
8598
  } catch (err) {
8256
8599
  if (err.code === "ENOENT") return null;
8257
8600
  throw err;
@@ -8259,8 +8602,8 @@ async function lstatOrNull(full) {
8259
8602
  }
8260
8603
  var isRealFolder = (stat) => stat !== null && stat.isDirectory() && !stat.isSymbolicLink();
8261
8604
  async function itemsFolder(cwd, create) {
8262
- const genex = path21.join(cwd, ".genex");
8263
- const items = path21.join(genex, "items");
8605
+ const genex = path22.join(cwd, ".genex");
8606
+ const items = path22.join(genex, "items");
8264
8607
  if (await isHomeFolder(cwd)) {
8265
8608
  if (!create && !await lstatOrNull(items)) return null;
8266
8609
  throw machineFolder();
@@ -8269,22 +8612,22 @@ async function itemsFolder(cwd, create) {
8269
8612
  let stat = await lstatOrNull(dir);
8270
8613
  if (!stat && !create) return null;
8271
8614
  if (!stat) {
8272
- await fs22.mkdir(dir).catch((err) => {
8615
+ await fs23.mkdir(dir).catch((err) => {
8273
8616
  if (err.code !== "EEXIST") throw err;
8274
8617
  });
8275
8618
  stat = await lstatOrNull(dir);
8276
8619
  }
8277
8620
  if (!isRealFolder(stat)) throw folderRefused(rel);
8278
8621
  }
8279
- const [root, real] = await Promise.all([fs22.realpath(cwd), fs22.realpath(items)]);
8280
- if (real !== path21.join(root, ".genex", "items")) throw folderRefused(LOCAL_ITEMS_DIR);
8622
+ const [root, real] = await Promise.all([fs23.realpath(cwd), fs23.realpath(items)]);
8623
+ if (real !== path22.join(root, ".genex", "items")) throw folderRefused(LOCAL_ITEMS_DIR);
8281
8624
  return items;
8282
8625
  }
8283
8626
  async function draftFolder(items, localId, create) {
8284
- const dir = path21.join(items, localId);
8627
+ const dir = path22.join(items, localId);
8285
8628
  let stat = await lstatOrNull(dir);
8286
8629
  if (!stat && create) {
8287
- await fs22.mkdir(dir).catch((err) => {
8630
+ await fs23.mkdir(dir).catch((err) => {
8288
8631
  if (err.code !== "EEXIST") throw err;
8289
8632
  });
8290
8633
  stat = await lstatOrNull(dir);
@@ -8307,7 +8650,7 @@ async function readStored(full, max) {
8307
8650
  if (stat.isSymbolicLink()) throw new StoredFileError(ReadRefusal.Link);
8308
8651
  let handle;
8309
8652
  try {
8310
- handle = await fs22.open(full, fsConstants2.O_RDONLY | (fsConstants2.O_NOFOLLOW ?? 0));
8653
+ handle = await fs23.open(full, fsConstants2.O_RDONLY | (fsConstants2.O_NOFOLLOW ?? 0));
8311
8654
  } catch (err) {
8312
8655
  if (err.code === "ENOENT") throw new StoredFileError(ReadRefusal.Missing);
8313
8656
  throw new StoredFileError(ReadRefusal.Link);
@@ -8362,7 +8705,7 @@ function checkFields(raw, label) {
8362
8705
  }
8363
8706
  var RECORD_KEYS = /* @__PURE__ */ new Set(["localId", "name", "description", "priceCoins", "type", "tags", "shareable", "files", "createdAt", "updatedAt"]);
8364
8707
  function checkFileNames(files, label) {
8365
- if (!isRecord(files)) throw invalid2(`${label} is not a draft this CLI wrote: its files are not a list of names.`);
8708
+ if (!isRecord2(files)) throw invalid2(`${label} is not a draft this CLI wrote: its files are not a list of names.`);
8366
8709
  const out = {};
8367
8710
  for (const [kind, name] of Object.entries(files)) {
8368
8711
  if (!FILE_ORDER.includes(kind)) throw invalid2(`${label} names a file kind no draft has (${JSON.stringify(kind.slice(0, 40))}).`);
@@ -8376,7 +8719,7 @@ function checkFileNames(files, label) {
8376
8719
  }
8377
8720
  function parseDraftRecord(raw, fileId) {
8378
8721
  const label = `${LOCAL_ITEMS_DIR}/${fileId}.json`;
8379
- if (!isRecord(raw)) throw invalid2(`${label} is not a draft this CLI wrote: it is not an object.`);
8722
+ if (!isRecord2(raw)) throw invalid2(`${label} is not a draft this CLI wrote: it is not an object.`);
8380
8723
  const extra = Object.keys(raw).find((key) => !RECORD_KEYS.has(key));
8381
8724
  if (extra !== void 0) throw invalid2(`${label} is not a draft this CLI wrote: it has a field no draft has (${JSON.stringify(extra.slice(0, 40))}).`);
8382
8725
  if (raw.localId !== fileId) throw invalid2(`${label} is not a draft this CLI wrote: its localId is not its file name.`);
@@ -8409,7 +8752,7 @@ async function readRecord2(items, localId) {
8409
8752
  const rel = `${LOCAL_ITEMS_DIR}/${localId}.json`;
8410
8753
  let text;
8411
8754
  try {
8412
- text = await readStored(path21.join(items, `${localId}.json`), DRAFT_FILE_BYTES_MAX);
8755
+ text = await readStored(path22.join(items, `${localId}.json`), DRAFT_FILE_BYTES_MAX);
8413
8756
  } catch (err) {
8414
8757
  if (!(err instanceof StoredFileError)) throw err;
8415
8758
  if (err.reason === ReadRefusal.Missing) throw notFound(localId);
@@ -8434,7 +8777,7 @@ async function readDraft(items, localId) {
8434
8777
  const rel = `${LOCAL_ITEMS_DIR}/${localId}/${name}`;
8435
8778
  let bytes;
8436
8779
  try {
8437
- bytes = await readStored(path21.join(dir, name), FILE_BYTES_MAX[kind]);
8780
+ bytes = await readStored(path22.join(dir, name), FILE_BYTES_MAX[kind]);
8438
8781
  } catch (err) {
8439
8782
  if (err instanceof StoredFileError) throw unreadable(rel, err.reason, FILE_BYTES_MAX[kind]);
8440
8783
  throw err;
@@ -8453,7 +8796,7 @@ async function loadLocalDraft(cwd, localId) {
8453
8796
  }
8454
8797
  var isDraftFileName = (name) => name.endsWith(".json") && name !== LAST_SYNC_FILE;
8455
8798
  async function draftFileNames(items) {
8456
- return (await fs22.readdir(items)).filter(isDraftFileName).sort();
8799
+ return (await fs23.readdir(items)).filter(isDraftFileName).sort();
8457
8800
  }
8458
8801
  async function holdsDraftFiles(items) {
8459
8802
  return (await draftFileNames(items)).length > 0;
@@ -8489,12 +8832,12 @@ async function listLocalDrafts(cwd) {
8489
8832
  return { drafts, refused };
8490
8833
  }
8491
8834
  async function writeReplacing(target, bytes) {
8492
- const temporary = path21.join(path21.dirname(target), `.${path21.basename(target)}.${randomBytes(4).toString("hex")}.tmp`);
8493
- await fs22.writeFile(temporary, bytes, { flag: "wx", mode: 420 });
8835
+ const temporary = path22.join(path22.dirname(target), `.${path22.basename(target)}.${randomBytes(4).toString("hex")}.tmp`);
8836
+ await fs23.writeFile(temporary, bytes, { flag: "wx", mode: 420 });
8494
8837
  try {
8495
- await fs22.rename(temporary, target);
8838
+ await fs23.rename(temporary, target);
8496
8839
  } catch (err) {
8497
- await fs22.rm(temporary, { force: true });
8840
+ await fs23.rm(temporary, { force: true });
8498
8841
  throw err;
8499
8842
  }
8500
8843
  }
@@ -8507,13 +8850,13 @@ async function storeDraft(items, record, newFiles) {
8507
8850
  if (newFiles.package) incoming.push([DraftFileKind.Package, STORED_NAMES.package[0] ?? "", newFiles.package]);
8508
8851
  for (const [kind, name, bytes] of incoming) {
8509
8852
  if (!STORED_NAMES[kind].includes(name)) throw invalid2(`A ${kind} of that type cannot be stored with a draft.`);
8510
- await writeReplacing(path21.join(dir, name), bytes);
8853
+ await writeReplacing(path22.join(dir, name), bytes);
8511
8854
  const previous = files[kind];
8512
- if (previous && previous !== name) await fs22.rm(path21.join(dir, previous), { force: true });
8855
+ if (previous && previous !== name) await fs23.rm(path22.join(dir, previous), { force: true });
8513
8856
  files[kind] = name;
8514
8857
  }
8515
8858
  const stored = { ...record, files };
8516
- await writeReplacing(path21.join(items, `${record.localId}.json`), `${JSON.stringify(orderedRecord(stored), null, 2)}
8859
+ await writeReplacing(path22.join(items, `${record.localId}.json`), `${JSON.stringify(orderedRecord(stored), null, 2)}
8517
8860
  `);
8518
8861
  return readDraft(items, record.localId);
8519
8862
  }
@@ -8536,7 +8879,7 @@ async function createLocalDraft(cwd, fields, files, now = /* @__PURE__ */ new Da
8536
8879
  if (!items) throw folderRefused(LOCAL_ITEMS_DIR);
8537
8880
  for (let attempt = 0; attempt < NEW_ID_ATTEMPTS; attempt++) {
8538
8881
  const localId = newLocalId();
8539
- const taken = await lstatOrNull(path21.join(items, `${localId}.json`)) ?? await lstatOrNull(path21.join(items, localId));
8882
+ const taken = await lstatOrNull(path22.join(items, `${localId}.json`)) ?? await lstatOrNull(path22.join(items, localId));
8540
8883
  if (taken) continue;
8541
8884
  const stamp = now.toISOString();
8542
8885
  return storeDraft(items, { localId, ...fields, files: {}, createdAt: stamp, updatedAt: stamp }, files);
@@ -8558,26 +8901,26 @@ async function removeLocalDraft(cwd, localId) {
8558
8901
  }
8559
8902
  async function removeFrom(items, localId) {
8560
8903
  const record = await readRecord2(items, localId);
8561
- const dir = path21.join(items, localId);
8904
+ const dir = path22.join(items, localId);
8562
8905
  const stat = await lstatOrNull(dir);
8563
8906
  if (stat && !isRealFolder(stat)) throw folderRefused(`${LOCAL_ITEMS_DIR}/${localId}`);
8564
- await fs22.rm(path21.join(items, `${localId}.json`), { force: true });
8565
- if (stat) await fs22.rm(dir, { recursive: true, force: true });
8907
+ await fs23.rm(path22.join(items, `${localId}.json`), { force: true });
8908
+ if (stat) await fs23.rm(dir, { recursive: true, force: true });
8566
8909
  return record;
8567
8910
  }
8568
8911
  async function removeSyncedDraft(items, localId) {
8569
8912
  await removeFrom(items, localId);
8570
8913
  }
8571
8914
  async function writeLastSync(items, document) {
8572
- await writeReplacing(path21.join(items, LAST_SYNC_FILE), `${JSON.stringify(document)}
8915
+ await writeReplacing(path22.join(items, LAST_SYNC_FILE), `${JSON.stringify(document)}
8573
8916
  `);
8574
8917
  }
8575
8918
  async function takeSyncLock(items, now = Date.now()) {
8576
- const lock = path21.join(items, SYNC_LOCK_FILE);
8919
+ const lock = path22.join(items, SYNC_LOCK_FILE);
8577
8920
  const body = JSON.stringify({ pid: process.pid, at: new Date(now).toISOString() });
8578
8921
  const create = async () => {
8579
8922
  try {
8580
- await fs22.writeFile(lock, body, { flag: "wx", mode: 420 });
8923
+ await fs23.writeFile(lock, body, { flag: "wx", mode: 420 });
8581
8924
  return true;
8582
8925
  } catch (err) {
8583
8926
  if (err.code === "EEXIST") return false;
@@ -8588,11 +8931,11 @@ async function takeSyncLock(items, now = Date.now()) {
8588
8931
  const held = await lstatOrNull(lock);
8589
8932
  const fresh = held !== null && held.isFile() && !held.isSymbolicLink() && now - held.mtimeMs < SYNC_LOCK_STALE_MS;
8590
8933
  if (fresh) return null;
8591
- await fs22.rm(lock, { force: true });
8934
+ await fs23.rm(lock, { force: true });
8592
8935
  if (!await create()) return null;
8593
8936
  }
8594
8937
  return async () => {
8595
- await fs22.rm(lock, { force: true });
8938
+ await fs23.rm(lock, { force: true });
8596
8939
  };
8597
8940
  }
8598
8941
 
@@ -8611,7 +8954,7 @@ function isTransferUrl(url) {
8611
8954
  if (parsed.protocol === "https:") return true;
8612
8955
  return parsed.protocol === "http:" && LOOPBACK_HOSTS.has(parsed.hostname);
8613
8956
  }
8614
- async function putSigned(uploadUrl, bytes, contentType) {
8957
+ async function putSigned2(uploadUrl, bytes, contentType) {
8615
8958
  if (!isTransferUrl(uploadUrl)) throw new Error("The upload link Genex returned isn't an https URL.");
8616
8959
  const res = await fetch(uploadUrl, {
8617
8960
  method: "PUT",
@@ -8666,7 +9009,7 @@ async function uploadItemPart(call3, skuUrl2, part) {
8666
9009
  if (!urlRes.ok) return { ok: false, refused: urlRes, what: `upload the ${part.kind}` };
8667
9010
  const { uploadUrl, key } = await urlRes.json();
8668
9011
  try {
8669
- await putSigned(uploadUrl, part.bytes, part.contentType);
9012
+ await putSigned2(uploadUrl, part.bytes, part.contentType);
8670
9013
  } catch (err) {
8671
9014
  return { ok: false, putFailed: err instanceof Error ? err.message : String(err) };
8672
9015
  }
@@ -8970,7 +9313,7 @@ var TOOLING_ENTRIES = /* @__PURE__ */ new Set([
8970
9313
  ]);
8971
9314
  async function listPreexistingEntries(dir) {
8972
9315
  try {
8973
- return (await fs23.readdir(dir)).filter((name) => !TOOLING_ENTRIES.has(name)).sort();
9316
+ return (await fs24.readdir(dir)).filter((name) => !TOOLING_ENTRIES.has(name)).sort();
8974
9317
  } catch {
8975
9318
  return [];
8976
9319
  }
@@ -9038,8 +9381,8 @@ async function runInit(opts) {
9038
9381
  let totalUpdated = 0;
9039
9382
  const refused = [];
9040
9383
  for (const t of targets) {
9041
- const src = t.full ? templatesDir : path22.join(templatesDir, "skills");
9042
- const dest = t.full ? t.baseDir : path22.join(t.baseDir, "skills");
9384
+ const src = t.full ? templatesDir : path23.join(templatesDir, "skills");
9385
+ const dest = t.full ? t.baseDir : path23.join(t.baseDir, "skills");
9043
9386
  const family = remixing ? skillFamilyFilter("remix") : converting ? skillFamilyFilter("tools", { hosted: true }) : skillFamilyFilter("game");
9044
9387
  let copied;
9045
9388
  let updated;
@@ -9050,8 +9393,8 @@ async function runInit(opts) {
9050
9393
  exclude: ["controllers", "motion", "asset-viewer", "blender-service"],
9051
9394
  filter: t.full ? family : (rel) => family(`skills/${rel}`)
9052
9395
  }));
9053
- await pruneRemovedSkills(path22.join(t.baseDir, "skills"), log);
9054
- await writeSkillsMarker(path22.join(t.baseDir, "skills"));
9396
+ await pruneRemovedSkills(path23.join(t.baseDir, "skills"), log);
9397
+ await writeSkillsMarker(path23.join(t.baseDir, "skills"));
9055
9398
  } catch (err) {
9056
9399
  const refusal2 = explainFsPermission(err, dest);
9057
9400
  if (!refusal2) throw err;
@@ -9121,7 +9464,7 @@ async function runInit(opts) {
9121
9464
  if (email) log.plain(` signed in as ${c.cyan(email)}`);
9122
9465
  };
9123
9466
  await echoIdentity();
9124
- const projectName = opts.name?.trim() || path22.basename(process.cwd());
9467
+ const projectName = opts.name?.trim() || path23.basename(process.cwd());
9125
9468
  const create = (bearer) => createDraftProject({
9126
9469
  apiUrl,
9127
9470
  token: bearer,
@@ -9173,7 +9516,7 @@ async function runInit(opts) {
9173
9516
  if (await writeToolsContract(process.cwd(), { hosted: true })) {
9174
9517
  log.dim(" wrote the Genex Tools publishing rules into AGENTS.md (managed block)");
9175
9518
  }
9176
- await ensureEnvVar(path22.join(process.cwd(), ".env"), "VITE_GENEX_SLUG", meta.slug, log);
9519
+ await ensureEnvVar(path23.join(process.cwd(), ".env"), "VITE_GENEX_SLUG", meta.slug, log);
9177
9520
  await reportCliEvent(apiUrl, token, "tools_converted");
9178
9521
  }
9179
9522
  if (!remixing) warnPreexisting(log, preexisting);
@@ -9201,9 +9544,9 @@ async function runInit(opts) {
9201
9544
  }
9202
9545
 
9203
9546
  // src/commands/link.ts
9204
- import fs24 from "fs/promises";
9547
+ import fs25 from "fs/promises";
9205
9548
  import os7 from "os";
9206
- import path23 from "path";
9549
+ import path24 from "path";
9207
9550
  async function runLink(opts) {
9208
9551
  const log = createLogger({ quiet: opts.quiet });
9209
9552
  log.plain(c.bold("genex link"));
@@ -9286,7 +9629,7 @@ async function runLink(opts) {
9286
9629
  }
9287
9630
  async function isEmptyDir(cwd) {
9288
9631
  try {
9289
- const entries = await fs24.readdir(cwd);
9632
+ const entries = await fs25.readdir(cwd);
9290
9633
  return entries.every((e) => e === ".git" || e === ".genex" || e === ".DS_Store");
9291
9634
  } catch {
9292
9635
  return false;
@@ -9300,13 +9643,13 @@ async function downloadSource(apiUrl, token, project, log) {
9300
9643
  return false;
9301
9644
  }
9302
9645
  log.step(`Downloading the ${grant.sourceRef === "preview" ? "draft" : "published"} source\u2026`);
9303
- const staging = await fs24.mkdtemp(path23.join(os7.tmpdir(), "genex-link-"));
9304
- const fresh = path23.join(staging, "source");
9646
+ const staging = await fs25.mkdtemp(path24.join(os7.tmpdir(), "genex-link-"));
9647
+ const fresh = path24.join(staging, "source");
9305
9648
  try {
9306
9649
  if (!await cloneSource(grant, fresh, log)) return false;
9307
- await fs24.rm(path23.join(fresh, ".git"), { recursive: true, force: true });
9308
- for (const entry of await fs24.readdir(fresh)) {
9309
- await fs24.cp(path23.join(fresh, entry), path23.join(process.cwd(), entry), {
9650
+ await fs25.rm(path24.join(fresh, ".git"), { recursive: true, force: true });
9651
+ for (const entry of await fs25.readdir(fresh)) {
9652
+ await fs25.cp(path24.join(fresh, entry), path24.join(process.cwd(), entry), {
9310
9653
  recursive: true,
9311
9654
  force: true
9312
9655
  });
@@ -9314,28 +9657,28 @@ async function downloadSource(apiUrl, token, project, log) {
9314
9657
  log.success("Downloaded.");
9315
9658
  return true;
9316
9659
  } finally {
9317
- await fs24.rm(staging, { recursive: true, force: true }).catch(() => {
9660
+ await fs25.rm(staging, { recursive: true, force: true }).catch(() => {
9318
9661
  });
9319
9662
  }
9320
9663
  }
9321
9664
  async function ensureSlugEnv(slug, log, cwd = process.cwd()) {
9322
- const file = path23.join(cwd, ".env");
9665
+ const file = path24.join(cwd, ".env");
9323
9666
  let content;
9324
9667
  try {
9325
- content = await fs24.readFile(file, "utf8");
9668
+ content = await fs25.readFile(file, "utf8");
9326
9669
  } catch {
9327
9670
  return;
9328
9671
  }
9329
9672
  const re = /^(\s*VITE_GENEX_SLUG=)(.*)$/m;
9330
9673
  const m = content.match(re);
9331
9674
  if (!m) {
9332
- await fs24.appendFile(file, `${content.endsWith("\n") ? "" : "\n"}VITE_GENEX_SLUG=${slug}
9675
+ await fs25.appendFile(file, `${content.endsWith("\n") ? "" : "\n"}VITE_GENEX_SLUG=${slug}
9333
9676
  `);
9334
9677
  log.dim(` added VITE_GENEX_SLUG=${slug} to .env`);
9335
9678
  return;
9336
9679
  }
9337
9680
  if (m[2].trim() === slug) return;
9338
- await fs24.writeFile(file, content.replace(re, `$1${slug}`));
9681
+ await fs25.writeFile(file, content.replace(re, `$1${slug}`));
9339
9682
  log.dim(` updated .env: VITE_GENEX_SLUG=${slug} (was ${m[2].trim()})`);
9340
9683
  }
9341
9684
  async function fetchOwnProject(apiUrl, token, slug, log) {
@@ -9389,8 +9732,8 @@ async function listOwnSlugs(apiUrl, token, log) {
9389
9732
  }
9390
9733
 
9391
9734
  // src/commands/pull.ts
9392
- import fs25 from "fs/promises";
9393
- import path24 from "path";
9735
+ import fs26 from "fs/promises";
9736
+ import path25 from "path";
9394
9737
  import os8 from "os";
9395
9738
  function isMachineLocal(entry) {
9396
9739
  if (entry === ".genex" || entry === "node_modules" || entry === ".git") return true;
@@ -9450,23 +9793,23 @@ async function runPull(opts) {
9450
9793
  process.exitCode = 1;
9451
9794
  return;
9452
9795
  }
9453
- const staging = await fs25.mkdtemp(path24.join(os8.tmpdir(), "genex-pull-"));
9454
- const fresh = path24.join(staging, "source");
9796
+ const staging = await fs26.mkdtemp(path25.join(os8.tmpdir(), "genex-pull-"));
9797
+ const fresh = path25.join(staging, "source");
9455
9798
  try {
9456
9799
  if (!await cloneSource(grant, fresh, log)) {
9457
9800
  process.exitCode = 1;
9458
9801
  return;
9459
9802
  }
9460
- await fs25.rm(path24.join(fresh, ".git"), { recursive: true, force: true });
9803
+ await fs26.rm(path25.join(fresh, ".git"), { recursive: true, force: true });
9461
9804
  const kept = await keepReplaced(cwd, log);
9462
9805
  await replaceTree(cwd, fresh);
9463
9806
  if (kept) {
9464
9807
  log.plain("");
9465
- log.info(`What was here is kept at ${c.cyan(path24.relative(cwd, kept) || kept)}`);
9808
+ log.info(`What was here is kept at ${c.cyan(path25.relative(cwd, kept) || kept)}`);
9466
9809
  log.dim(" Nothing was thrown away \u2014 re-apply from there, or delete it when you are done.");
9467
9810
  }
9468
9811
  } finally {
9469
- await fs25.rm(staging, { recursive: true, force: true }).catch(() => {
9812
+ await fs26.rm(staging, { recursive: true, force: true }).catch(() => {
9470
9813
  });
9471
9814
  }
9472
9815
  if (await hasRemixProfile(cwd)) await installRemixProfile(cwd, apiUrl, log);
@@ -9486,13 +9829,13 @@ async function runPull(opts) {
9486
9829
  }
9487
9830
  async function keepReplaced(cwd, log) {
9488
9831
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
9489
- const dest = path24.join(cwd, ".genex", `replaced-${stamp}`);
9832
+ const dest = path25.join(cwd, ".genex", `replaced-${stamp}`);
9490
9833
  try {
9491
- const entries = (await fs25.readdir(cwd)).filter((e) => !isMachineLocal(e));
9834
+ const entries = (await fs26.readdir(cwd)).filter((e) => !isMachineLocal(e));
9492
9835
  if (entries.length === 0) return null;
9493
- await fs25.mkdir(dest, { recursive: true });
9836
+ await fs26.mkdir(dest, { recursive: true });
9494
9837
  for (const entry of entries) {
9495
- await fs25.cp(path24.join(cwd, entry), path24.join(dest, entry), { recursive: true });
9838
+ await fs26.cp(path25.join(cwd, entry), path25.join(dest, entry), { recursive: true });
9496
9839
  }
9497
9840
  return dest;
9498
9841
  } catch (err) {
@@ -9502,19 +9845,19 @@ async function keepReplaced(cwd, log) {
9502
9845
  }
9503
9846
  }
9504
9847
  async function replaceTree(dest, src) {
9505
- for (const entry of await fs25.readdir(dest)) {
9848
+ for (const entry of await fs26.readdir(dest)) {
9506
9849
  if (isMachineLocal(entry)) continue;
9507
- await fs25.rm(path24.join(dest, entry), { recursive: true, force: true });
9850
+ await fs26.rm(path25.join(dest, entry), { recursive: true, force: true });
9508
9851
  }
9509
- for (const entry of await fs25.readdir(src)) {
9852
+ for (const entry of await fs26.readdir(src)) {
9510
9853
  if (isMachineLocal(entry)) continue;
9511
- await fs25.cp(path24.join(src, entry), path24.join(dest, entry), { recursive: true });
9854
+ await fs26.cp(path25.join(src, entry), path25.join(dest, entry), { recursive: true });
9512
9855
  }
9513
9856
  }
9514
9857
 
9515
9858
  // src/commands/rename.ts
9516
- import fs26 from "fs/promises";
9517
- import path25 from "path";
9859
+ import fs27 from "fs/promises";
9860
+ import path26 from "path";
9518
9861
  async function runRename(opts) {
9519
9862
  const log = createLogger({ quiet: opts.quiet });
9520
9863
  log.plain(c.bold("genex rename"));
@@ -9602,23 +9945,23 @@ async function runRename(opts) {
9602
9945
  log.info("Run `genex preview` (or `publish`) to rebuild \u2014 the new slug is baked into the bundle.");
9603
9946
  }
9604
9947
  async function rewriteSlugEnv(from, to, log, cwd = process.cwd()) {
9605
- const file = path25.join(cwd, ".env");
9948
+ const file = path26.join(cwd, ".env");
9606
9949
  let content;
9607
9950
  try {
9608
- content = await fs26.readFile(file, "utf8");
9951
+ content = await fs27.readFile(file, "utf8");
9609
9952
  } catch {
9610
9953
  return;
9611
9954
  }
9612
9955
  const re = /^(\s*VITE_GENEX_SLUG=)(.*)$/m;
9613
9956
  if (!re.test(content)) return;
9614
- await fs26.writeFile(file, content.replace(re, `$1${to}`));
9957
+ await fs27.writeFile(file, content.replace(re, `$1${to}`));
9615
9958
  log.dim(` .env: VITE_GENEX_SLUG=${to} (was ${from})`);
9616
9959
  }
9617
9960
  async function rewriteBakedSlug(from, to, log, cwd = process.cwd()) {
9618
- const file = path25.join(cwd, "src", "genex.config.ts");
9961
+ const file = path26.join(cwd, "src", "genex.config.ts");
9619
9962
  let content;
9620
9963
  try {
9621
- content = await fs26.readFile(file, "utf8");
9964
+ content = await fs27.readFile(file, "utf8");
9622
9965
  } catch {
9623
9966
  return;
9624
9967
  }
@@ -9628,7 +9971,7 @@ async function rewriteBakedSlug(from, to, log, cwd = process.cwd()) {
9628
9971
  log.warn(` src/genex.config.ts has no "${from}" literal \u2014 check its slug by hand.`);
9629
9972
  return;
9630
9973
  }
9631
- await fs26.writeFile(file, content.replace(quoted, `"${to}"`));
9974
+ await fs27.writeFile(file, content.replace(quoted, `"${to}"`));
9632
9975
  log.dim(` src/genex.config.ts: baked slug -> ${to}`);
9633
9976
  }
9634
9977
 
@@ -10558,9 +10901,9 @@ var ShopSession = class {
10558
10901
  };
10559
10902
 
10560
10903
  // src/commands/items.ts
10561
- import crypto6 from "crypto";
10562
- import fs27 from "fs/promises";
10563
- import path26 from "path";
10904
+ import crypto7 from "crypto";
10905
+ import fs28 from "fs/promises";
10906
+ import path27 from "path";
10564
10907
  var SUBS2 = ["search", "add", "remove", "list"];
10565
10908
  var ITEMS_DIR = SHARED_ITEMS_DIR;
10566
10909
  var REPLACED_DIR = ".genex/replaced-items";
@@ -10756,26 +11099,26 @@ var ItemsSession = class _ItemsSession {
10756
11099
  */
10757
11100
  async writeItemFolder(skuId, itemFile, pkg) {
10758
11101
  const root = process.cwd();
10759
- const itemsDir = path26.join(root, ITEMS_DIR);
11102
+ const itemsDir = path27.join(root, ITEMS_DIR);
10760
11103
  await ensureRealDir(itemsDir, ITEMS_DIR);
10761
- const final = path26.join(itemsDir, skuId);
11104
+ const final = path27.join(itemsDir, skuId);
10762
11105
  const existing = await lstatOrNull2(final);
10763
11106
  if (existing && !existing.isDirectory()) {
10764
11107
  throw new FolderRefused(`${ITEMS_DIR}/${skuId} is a link or a file, not a folder. Move it aside first; nothing was written through it.`);
10765
11108
  }
10766
11109
  const keep = existing ? await replacedItemsDir(root) : null;
10767
11110
  const zip = pkg ? await downloadItemPackage(pkg.url) : null;
10768
- const staging = path26.join(itemsDir, `.${skuId}.incoming-${crypto6.randomBytes(4).toString("hex")}`);
11111
+ const staging = path27.join(itemsDir, `.${skuId}.incoming-${crypto7.randomBytes(4).toString("hex")}`);
10769
11112
  try {
10770
11113
  const files = zip && pkg ? await extractItemPackage(zip, staging, pkg.manifest) : [];
10771
- if (!zip) await fs27.mkdir(staging);
10772
- await fs27.writeFile(path26.join(staging, ITEM_FILE), `${JSON.stringify(itemFile, null, 2)}
11114
+ if (!zip) await fs28.mkdir(staging);
11115
+ await fs28.writeFile(path27.join(staging, ITEM_FILE), `${JSON.stringify(itemFile, null, 2)}
10773
11116
  `, { flag: "wx" });
10774
- if (keep) await fs27.rename(final, path26.join(keep, `${skuId}-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`));
10775
- await fs27.rename(staging, final);
11117
+ if (keep) await fs28.rename(final, path27.join(keep, `${skuId}-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`));
11118
+ await fs28.rename(staging, final);
10776
11119
  return files;
10777
11120
  } finally {
10778
- await fs27.rm(staging, { recursive: true, force: true });
11121
+ await fs28.rm(staging, { recursive: true, force: true });
10779
11122
  }
10780
11123
  }
10781
11124
  async remove(skuId) {
@@ -10830,22 +11173,22 @@ function folderRefusal(skuId, name, err) {
10830
11173
  }
10831
11174
  async function lstatOrNull2(p) {
10832
11175
  try {
10833
- return await fs27.lstat(p);
11176
+ return await fs28.lstat(p);
10834
11177
  } catch (err) {
10835
11178
  if (err.code === "ENOENT") return null;
10836
11179
  throw err;
10837
11180
  }
10838
11181
  }
10839
11182
  async function replacedItemsDir(root) {
10840
- await fs27.mkdir(path26.join(root, ".genex"), { recursive: true });
10841
- const keep = path26.join(root, ...REPLACED_DIR.split("/"));
11183
+ await fs28.mkdir(path27.join(root, ".genex"), { recursive: true });
11184
+ const keep = path27.join(root, ...REPLACED_DIR.split("/"));
10842
11185
  await ensureRealDir(keep, REPLACED_DIR);
10843
11186
  return keep;
10844
11187
  }
10845
11188
  async function ensureRealDir(dir, label) {
10846
11189
  const stat = await lstatOrNull2(dir);
10847
11190
  if (!stat) {
10848
- await fs27.mkdir(dir);
11191
+ await fs28.mkdir(dir);
10849
11192
  return;
10850
11193
  }
10851
11194
  if (!stat.isDirectory()) {
@@ -11059,7 +11402,10 @@ async function runPublish(opts) {
11059
11402
  mobileControls: detections.mobileControls,
11060
11403
  // Declared on the staging go-live; the promotion below copies that
11061
11404
  // build's own list to production with it.
11062
- capabilities: detections.capabilities
11405
+ capabilities: detections.capabilities,
11406
+ // The room module, when the game declares one: bundled, validated and uploaded with this
11407
+ // staging build (the promotion below carries it to production).
11408
+ authority: detections.authority
11063
11409
  },
11064
11410
  log
11065
11411
  );
@@ -11295,23 +11641,36 @@ async function readPreviousVersion(apiUrl, projectId, token, log) {
11295
11641
  log.dim(" Nothing was changed. Try again shortly, or update the server.");
11296
11642
  return null;
11297
11643
  }
11298
- if (!body.available || !body.commit || !body.currentCommit) {
11644
+ const available = typeof body.releaseAvailable === "boolean" ? body.releaseAvailable : body.available;
11645
+ if (!available || !body.commit || !body.currentCommit) {
11299
11646
  log.error("This game has no previous version to go back to.");
11300
11647
  log.dim(" Only versions published from this account since the feature shipped are recorded.");
11301
11648
  return null;
11302
11649
  }
11650
+ const release = typeof body.release === "string" ? body.release : null;
11651
+ const currentRelease = typeof body.currentRelease === "string" ? body.currentRelease : null;
11303
11652
  return {
11304
11653
  commit: body.commit,
11305
11654
  deployedAt: body.deployedAt ?? null,
11306
- currentCommit: body.currentCommit
11655
+ currentCommit: body.currentCommit,
11656
+ release,
11657
+ currentRelease,
11658
+ expectLive: currentRelease ?? body.currentCommit
11307
11659
  };
11308
11660
  }
11309
- async function restorePreviousVersion(apiUrl, projectId, token, expectLiveCommit, log) {
11661
+ function releaseModule(tag) {
11662
+ if (tag === null) return void 0;
11663
+ const at = tag.indexOf("@");
11664
+ if (at < 0) return void 0;
11665
+ const module = tag.slice(at + 1);
11666
+ return module === "none" ? null : module;
11667
+ }
11668
+ async function restorePreviousVersion(apiUrl, projectId, token, expectLive, log) {
11310
11669
  let res;
11311
11670
  try {
11312
11671
  res = await apiFetch(`${apiUrl}/api/games/${projectId}/rollback`, {
11313
11672
  method: "POST",
11314
- headers: { Authorization: `Bearer ${token}`, "If-Match": expectLiveCommit }
11673
+ headers: { Authorization: `Bearer ${token}`, "If-Match": expectLive }
11315
11674
  });
11316
11675
  } catch (err) {
11317
11676
  log.error(`Couldn't reach the API at ${apiUrl}: ${String(err)}`);
@@ -11335,6 +11694,12 @@ async function restorePreviousVersion(apiUrl, projectId, token, expectLiveCommit
11335
11694
  return null;
11336
11695
  }
11337
11696
  if (res.status === 428) {
11697
+ const body2 = await res.json().catch(() => null);
11698
+ if (body2?.error === "release_match_required") {
11699
+ log.error("The previous version differs from the live one only in its room module, and the server needs that named exactly \u2014 nothing was changed.");
11700
+ log.dim(" Run the command again; if it repeats, update the CLI.");
11701
+ return null;
11702
+ }
11338
11703
  log.error("This CLI is out of date for this command \u2014 upgrade and try again.");
11339
11704
  return null;
11340
11705
  }
@@ -11368,6 +11733,26 @@ function when(iso) {
11368
11733
  if (hours < 48) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
11369
11734
  return `${Math.round(hours / 24)} days ago`;
11370
11735
  }
11736
+ var moduleLabel = (module) => module === null ? "no room module" : `room module ${module.slice(0, 7)}`;
11737
+ function restoreLines(previous) {
11738
+ const now = releaseModule(previous.currentRelease);
11739
+ const back = releaseModule(previous.release);
11740
+ const hasModule = now !== void 0 && now !== null || back !== void 0 && back !== null;
11741
+ if (!hasModule || now === void 0 || back === void 0) {
11742
+ return [`live now: ${previous.currentCommit.slice(0, 7)}`, `goes back to: ${previous.commit.slice(0, 7)}`];
11743
+ }
11744
+ if (previous.commit === previous.currentCommit) {
11745
+ return [
11746
+ `same game build: ${previous.commit.slice(0, 7)}`,
11747
+ `live now: ${moduleLabel(now)}`,
11748
+ `goes back to: ${moduleLabel(back)}`
11749
+ ];
11750
+ }
11751
+ return [
11752
+ `live now: ${previous.currentCommit.slice(0, 7)} with ${moduleLabel(now)}`,
11753
+ `goes back to: ${previous.commit.slice(0, 7)} with ${moduleLabel(back)}`
11754
+ ];
11755
+ }
11371
11756
  async function runRollback(opts) {
11372
11757
  const log = createLogger({ quiet: opts.quiet });
11373
11758
  log.plain(c.bold("genex rollback"));
@@ -11401,15 +11786,14 @@ async function runRollback(opts) {
11401
11786
  }
11402
11787
  if (!opts.yes) {
11403
11788
  log.warn(`This puts players back on the version from ${when(previous.deployedAt)}.`);
11404
- log.dim(` live now: ${previous.currentCommit.slice(0, 7)}`);
11405
- log.dim(` goes back to: ${previous.commit.slice(0, 7)}`);
11789
+ for (const line of restoreLines(previous)) log.dim(` ${line}`);
11406
11790
  log.dim(" Your code is NOT changed \u2014 this folder, and what a Remix copies, stay as they are.");
11407
11791
  log.dim(" Re-run with --yes to do it.");
11408
11792
  process.exitCode = 1;
11409
11793
  return;
11410
11794
  }
11411
11795
  log.step("Putting the previous version back\u2026");
11412
- const result = await restorePreviousVersion(apiUrl, meta.id, token, previous.currentCommit, log);
11796
+ const result = await restorePreviousVersion(apiUrl, meta.id, token, previous.expectLive, log);
11413
11797
  if (!result) {
11414
11798
  process.exitCode = 1;
11415
11799
  return;
@@ -11423,8 +11807,8 @@ async function runRollback(opts) {
11423
11807
  }
11424
11808
 
11425
11809
  // src/commands/cover.ts
11426
- import fs28 from "fs/promises";
11427
- import path27 from "path";
11810
+ import fs29 from "fs/promises";
11811
+ import path28 from "path";
11428
11812
  var WHAT_A_COVER_IS = "one real frame of the game: its signature moment, real gameplay, well lit, no menus, popups or busy HUD, landscape 16:9, no text";
11429
11813
  async function runCover(opts) {
11430
11814
  const log = createLogger({ quiet: opts.quiet || opts.json });
@@ -11449,7 +11833,7 @@ async function runCover(opts) {
11449
11833
  }
11450
11834
  let bytes;
11451
11835
  try {
11452
- const stat = await fs28.stat(file);
11836
+ const stat = await fs29.stat(file);
11453
11837
  if (!stat.isFile()) throw new Error("not a file");
11454
11838
  if (stat.size > COVER_MAX_BYTES) {
11455
11839
  return finish(
@@ -11459,23 +11843,23 @@ async function runCover(opts) {
11459
11843
  log
11460
11844
  );
11461
11845
  }
11462
- bytes = await fs28.readFile(file);
11846
+ bytes = await fs29.readFile(file);
11463
11847
  } catch {
11464
11848
  return finish({ kind: "invalid", message: `Can't read ${file} \u2014 pass the path of a PNG, JPEG or WebP image.` }, file, opts, log);
11465
11849
  }
11466
- log.step(`Uploading ${path27.basename(file)} as the cover\u2026`);
11850
+ log.step(`Uploading ${path28.basename(file)} as the cover\u2026`);
11467
11851
  const outcome2 = await uploadCover({ apiUrl, token, projectId: meta.id, bytes, label: file });
11468
11852
  await rememberNomination(bytes, file, outcome2);
11469
11853
  finish(outcome2, file, opts, log);
11470
11854
  }
11471
11855
  async function rememberNomination(bytes, file, outcome2) {
11472
11856
  const scratch = await findScratchCover();
11473
- if (!scratch || path27.resolve(scratch) === path27.resolve(file)) {
11857
+ if (!scratch || path28.resolve(scratch) === path28.resolve(file)) {
11474
11858
  await rememberCoverUpload(bytes, file, outcome2);
11475
11859
  return;
11476
11860
  }
11477
11861
  if (outcome2.kind !== "applied" && outcome2.kind !== "outranked") return;
11478
- const stale = await fs28.readFile(scratch).catch(() => null);
11862
+ const stale = await fs29.readFile(scratch).catch(() => null);
11479
11863
  if (stale) await rememberCoverUpload(stale, scratch, outcome2);
11480
11864
  }
11481
11865
  async function sendClip(apiUrl, token, projectId, clip, opts, log) {
@@ -11486,16 +11870,16 @@ async function sendClip(apiUrl, token, projectId, clip, opts, log) {
11486
11870
  if (!opts.commit?.trim()) return invalid3("--clip needs --commit <sha>: the git commit of the build the recording shows.");
11487
11871
  let bytes;
11488
11872
  try {
11489
- const stat = await fs28.stat(clip);
11873
+ const stat = await fs29.stat(clip);
11490
11874
  if (!stat.isFile()) throw new Error("not a file");
11491
11875
  if (stat.size > CLIP_MAX_BYTES) {
11492
11876
  return invalid3(`${clip} is ${(stat.size / 1024 / 1024).toFixed(1)} MB; a cover clip recording can be at most ${CLIP_MAX_BYTES / 1024 / 1024} MB.`);
11493
11877
  }
11494
- bytes = await fs28.readFile(clip);
11878
+ bytes = await fs29.readFile(clip);
11495
11879
  } catch {
11496
11880
  return invalid3(`Can't read ${clip} \u2014 pass the path of an MP4 or WebM recording.`);
11497
11881
  }
11498
- log.step(`Uploading ${path27.basename(clip)} as the cover clip\u2026`);
11882
+ log.step(`Uploading ${path28.basename(clip)} as the cover clip\u2026`);
11499
11883
  const outcome2 = await uploadClip({ apiUrl, token, projectId, bytes, label: clip, buildCommit: opts.commit });
11500
11884
  finishClip(outcome2, clip, opts, log);
11501
11885
  }
@@ -11553,8 +11937,8 @@ async function showCover(apiUrl, token, slug, opts, log) {
11553
11937
  }
11554
11938
 
11555
11939
  // src/commands/generate.ts
11556
- import fs31 from "fs/promises";
11557
- import path30 from "path";
11940
+ import fs32 from "fs/promises";
11941
+ import path31 from "path";
11558
11942
  import { PNG as PNG4 } from "pngjs";
11559
11943
 
11560
11944
  // src/lib/glass.ts
@@ -11742,13 +12126,13 @@ function ceilingVerdict(input) {
11742
12126
  }
11743
12127
 
11744
12128
  // src/lib/generation-admission.ts
11745
- import fs30 from "fs/promises";
11746
- import path29 from "path";
12129
+ import fs31 from "fs/promises";
12130
+ import path30 from "path";
11747
12131
  import { createHash as createHash3, randomUUID } from "crypto";
11748
12132
 
11749
12133
  // src/lib/asset-budget.ts
11750
- import fs29 from "fs/promises";
11751
- import path28 from "path";
12134
+ import fs30 from "fs/promises";
12135
+ import path29 from "path";
11752
12136
  var DEFAULT_ASSET_BUDGET_SHARE = 0.5;
11753
12137
  var DEFAULT_ASSET_BUDGET_CAP = 500;
11754
12138
  function envShare(raw, fallback) {
@@ -11775,10 +12159,10 @@ function allowanceFor(input) {
11775
12159
  if (!Number.isFinite(spendable) || spendable <= 0) return 0;
11776
12160
  return Math.max(0, Math.min(Math.floor(spendable * config.share), config.cap));
11777
12161
  }
11778
- var storePath = (cwd) => path28.join(cwd, ".genex", "asset-budget.json");
12162
+ var storePath = (cwd) => path29.join(cwd, ".genex", "asset-budget.json");
11779
12163
  async function readBudgetStore(cwd = process.cwd()) {
11780
12164
  try {
11781
- const raw = await fs29.readFile(storePath(cwd), "utf8");
12165
+ const raw = await fs30.readFile(storePath(cwd), "utf8");
11782
12166
  const parsed = JSON.parse(raw);
11783
12167
  if (!parsed || parsed.v !== 1 || typeof parsed.allowanceCredits !== "number" || !Number.isFinite(parsed.allowanceCredits) || parsed.setBy !== "default" && parsed.setBy !== "user") {
11784
12168
  return null;
@@ -11797,8 +12181,8 @@ async function readBudgetStore(cwd = process.cwd()) {
11797
12181
  }
11798
12182
  async function writeBudgetStore(cwd, store) {
11799
12183
  try {
11800
- await fs29.access(path28.join(cwd, ".genex"));
11801
- await fs29.writeFile(storePath(cwd), `${JSON.stringify(store, null, 2)}
12184
+ await fs30.access(path29.join(cwd, ".genex"));
12185
+ await fs30.writeFile(storePath(cwd), `${JSON.stringify(store, null, 2)}
11802
12186
  `, "utf8");
11803
12187
  return true;
11804
12188
  } catch {
@@ -11809,7 +12193,7 @@ async function ensureBudgetStore(cwd, input) {
11809
12193
  const existing = await readBudgetStore(cwd);
11810
12194
  if (existing) return existing;
11811
12195
  try {
11812
- await fs29.stat(storePath(cwd));
12196
+ await fs30.stat(storePath(cwd));
11813
12197
  throw new Error("Stored asset allowance is unreadable or invalid; reconcile it before paid generation.");
11814
12198
  } catch (error) {
11815
12199
  if (error.code !== "ENOENT") throw error;
@@ -11913,13 +12297,13 @@ async function assetBudgetGate(input) {
11913
12297
 
11914
12298
  // src/lib/generation-admission.ts
11915
12299
  async function withAdmissionLock(cwd, work, waitMs = 3e4) {
11916
- const lock = path29.join(cwd, ".genex", "generation-admission.lock");
11917
- await fs30.mkdir(path29.dirname(lock), { recursive: true });
12300
+ const lock = path30.join(cwd, ".genex", "generation-admission.lock");
12301
+ await fs31.mkdir(path30.dirname(lock), { recursive: true });
11918
12302
  const deadline = Date.now() + waitMs;
11919
12303
  let file;
11920
12304
  while (!file) {
11921
12305
  try {
11922
- file = await fs30.open(lock, "wx");
12306
+ file = await fs31.open(lock, "wx");
11923
12307
  } catch (error) {
11924
12308
  if (error.code !== "EEXIST") throw error;
11925
12309
  if (Date.now() >= deadline) throw new Error("Another generation admission holds .genex/generation-admission.lock. If its process died, reconcile pending reservations before removing that lock.");
@@ -11931,7 +12315,7 @@ async function withAdmissionLock(cwd, work, waitMs = 3e4) {
11931
12315
  return await work();
11932
12316
  } finally {
11933
12317
  await file.close();
11934
- await fs30.unlink(lock);
12318
+ await fs31.unlink(lock);
11935
12319
  }
11936
12320
  }
11937
12321
  async function enqueueWithAllowance(input) {
@@ -12180,7 +12564,7 @@ var isRemoteRef = (value) => /^(https?:\/\/|data:)/i.test(value);
12180
12564
  async function inlineLocalImage(filePath, flag) {
12181
12565
  let bytes;
12182
12566
  try {
12183
- bytes = await fs31.readFile(filePath);
12567
+ bytes = await fs32.readFile(filePath);
12184
12568
  } catch {
12185
12569
  return { ok: false, error: `Couldn't read the ${flag} file at ${filePath}.` };
12186
12570
  }
@@ -12190,7 +12574,7 @@ async function inlineLocalImage(filePath, flag) {
12190
12574
  error: `${flag} file is ${(bytes.length / 1048576).toFixed(1)} MB \u2014 over the ~4 MB inline limit. Downscale/compress it first, or pass an asset URL instead.`
12191
12575
  };
12192
12576
  }
12193
- const mime = IMAGE_MIME_BY_EXT[path30.extname(filePath).toLowerCase()] ?? "image/png";
12577
+ const mime = IMAGE_MIME_BY_EXT[path31.extname(filePath).toLowerCase()] ?? "image/png";
12194
12578
  return { ok: true, dataUri: `data:${mime};base64,${bytes.toString("base64")}` };
12195
12579
  }
12196
12580
  var SKYBOX_ENVIRONMENT_SUFFIX = ". The image contains ONLY sky: cloud, atmosphere, light, weather and distant haze at the horizon. Every structure, object, plant and ground surface is outside the frame.";
@@ -12385,7 +12769,7 @@ async function runGenerate(kind, opts) {
12385
12769
  let typedPrompt = opts.prompt?.trim();
12386
12770
  if (!typedPrompt && kind === "model" && opts.imageUrl) {
12387
12771
  const ref = opts.imageUrl.startsWith("data:") ? "local image" : opts.imageUrl;
12388
- typedPrompt = `from image: ${path30.basename(ref).slice(0, 120)}`;
12772
+ typedPrompt = `from image: ${path31.basename(ref).slice(0, 120)}`;
12389
12773
  }
12390
12774
  if (kind === "model" && opts.texture !== void 0 && !MODEL_TEXTURE_TIERS.includes(opts.texture)) {
12391
12775
  log.error(`--texture ${opts.texture} is a character texture size. \`genex model\` takes a texture TIER: ${MODEL_TEXTURE_TIERS.join("|")} (default detailed).`);
@@ -12434,7 +12818,7 @@ async function runGenerate(kind, opts) {
12434
12818
  return;
12435
12819
  }
12436
12820
  try {
12437
- const bytes = await fs31.readFile(opts.inpaintUrl);
12821
+ const bytes = await fs32.readFile(opts.inpaintUrl);
12438
12822
  opts = { ...opts, inpaintUrl: `data:image/png;base64,${bytes.toString("base64")}` };
12439
12823
  } catch {
12440
12824
  log.error(`Couldn't read the --inpaint mask at ${opts.inpaintUrl}.`);
@@ -12603,7 +12987,7 @@ async function reportGlassTerminal(view, outDir, log, json) {
12603
12987
  return;
12604
12988
  }
12605
12989
  await recordTerminal(view.id, "completed", files.map((f) => f.url));
12606
- await fs31.mkdir(outDir, { recursive: true });
12990
+ await fs32.mkdir(outDir, { recursive: true });
12607
12991
  const solved = [];
12608
12992
  for (let i = 0; i < files.length; i++) {
12609
12993
  const f = files[i];
@@ -12635,8 +13019,8 @@ async function reportGlassTerminal(view, outDir, log, json) {
12635
13019
  });
12636
13020
  continue;
12637
13021
  }
12638
- const outPath = path30.join(outDir, `glass-${i + 1}.png`);
12639
- await fs31.writeFile(outPath, PNG4.sync.write(r.png));
13022
+ const outPath = path31.join(outDir, `glass-${i + 1}.png`);
13023
+ await fs32.writeFile(outPath, PNG4.sync.write(r.png));
12640
13024
  solved.push({
12641
13025
  path: outPath,
12642
13026
  url: f.url,
@@ -13266,8 +13650,8 @@ function writeJson(value) {
13266
13650
  }
13267
13651
 
13268
13652
  // src/commands/model-sub.ts
13269
- import fs32 from "fs/promises";
13270
- import path31 from "path";
13653
+ import fs33 from "fs/promises";
13654
+ import path32 from "path";
13271
13655
  var MODEL_SUBCOMMANDS = ["segment", "rig", "animate", "import"];
13272
13656
  function apiErrorMessage(data, fallback) {
13273
13657
  if (typeof data !== "object" || data === null) return fallback;
@@ -13282,29 +13666,29 @@ async function importModelFile(args) {
13282
13666
  const { log } = args;
13283
13667
  const filePath = args.filePath.trim();
13284
13668
  if (!/\.glb$/i.test(filePath)) {
13285
- log.error(`\`import\` takes a .glb file (binary glTF 2.0). Export ${path31.basename(filePath) || "the model"} as GLB first \u2014 Blender: File \u2192 Export \u2192 glTF 2.0, format "glTF Binary".`);
13669
+ log.error(`\`import\` takes a .glb file (binary glTF 2.0). Export ${path32.basename(filePath) || "the model"} as GLB first \u2014 Blender: File \u2192 Export \u2192 glTF 2.0, format "glTF Binary".`);
13286
13670
  return null;
13287
13671
  }
13288
13672
  let bytes;
13289
13673
  try {
13290
- bytes = await fs32.readFile(filePath);
13674
+ bytes = await fs33.readFile(filePath);
13291
13675
  } catch {
13292
13676
  log.error(`Couldn't read ${filePath}.`);
13293
13677
  return null;
13294
13678
  }
13295
13679
  if (bytes.byteLength > MODEL_IMPORT_MAX_BYTES) {
13296
- log.error(`${path31.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.`);
13680
+ log.error(`${path32.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.`);
13297
13681
  return null;
13298
13682
  }
13299
13683
  if (bytes.byteLength < 12 || bytes.readUInt32LE(0) !== GLB_MAGIC) {
13300
- log.error(`${path31.basename(filePath)} is not a GLB (no glTF magic). A .gltf + .bin pair must be exported as one binary .glb.`);
13684
+ log.error(`${path32.basename(filePath)} is not a GLB (no glTF magic). A .gltf + .bin pair must be exported as one binary .glb.`);
13301
13685
  return null;
13302
13686
  }
13303
13687
  const headers = { "Content-Type": "application/json", Authorization: `Bearer ${args.token}` };
13304
13688
  const minted = await apiFetch(`${args.apiUrl}/api/generations/import`, {
13305
13689
  method: "POST",
13306
13690
  headers,
13307
- body: JSON.stringify({ filename: path31.basename(filePath), bytes: bytes.byteLength, contentType: "model/gltf-binary" })
13691
+ body: JSON.stringify({ filename: path32.basename(filePath), bytes: bytes.byteLength, contentType: "model/gltf-binary" })
13308
13692
  });
13309
13693
  if (printedStructuredError(minted)) return null;
13310
13694
  if (!minted.ok) {
@@ -13313,7 +13697,7 @@ async function importModelFile(args) {
13313
13697
  return null;
13314
13698
  }
13315
13699
  const { id, uploadUrl, url } = await minted.json();
13316
- log.dim(` uploading ${path31.basename(filePath)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
13700
+ log.dim(` uploading ${path32.basename(filePath)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
13317
13701
  const put = await fetch(uploadUrl, {
13318
13702
  method: "PUT",
13319
13703
  headers: { "Content-Type": "model/gltf-binary", "Content-Length": String(bytes.byteLength) },
@@ -13461,7 +13845,7 @@ async function runModelAnimate(opts) {
13461
13845
  }
13462
13846
 
13463
13847
  // src/commands/wait.ts
13464
- import path32 from "path";
13848
+ import path33 from "path";
13465
13849
  var MAX_TIMEOUT_SEC = 2e6;
13466
13850
  var STILL_RUNNING_EXIT = 2;
13467
13851
  var TERMINAL2 = /* @__PURE__ */ new Set(["completed", "failed"]);
@@ -13668,7 +14052,7 @@ async function runWaitAll(opts) {
13668
14052
  if (v?.status !== "completed" || !v.files?.length) continue;
13669
14053
  const local = localDeliveryFor("tools", { ...opts, outDir: opts.outDir ?? e.outDir }, e.prompt || e.kind);
13670
14054
  if (!local) continue;
13671
- const outDir = path32.join(path32.relative(process.cwd(), cwd) || ".", local.outDir);
14055
+ const outDir = path33.join(path33.relative(process.cwd(), cwd) || ".", local.outDir);
13672
14056
  const target = { kind: e.kind, prompt: local.prompt, id: e.id, outDir };
13673
14057
  const missing = await undeliveredFiles(v.files, target);
13674
14058
  if (missing.length === 0) continue;
@@ -13780,8 +14164,8 @@ async function toRow(e, v, cwd) {
13780
14164
  }
13781
14165
 
13782
14166
  // src/commands/controller.ts
13783
- import fs34 from "fs/promises";
13784
- import path34 from "path";
14167
+ import fs35 from "fs/promises";
14168
+ import path35 from "path";
13785
14169
 
13786
14170
  // ../../packages/meshy-animation-catalog/src/index.ts
13787
14171
  import { createHash as createHash4 } from "crypto";
@@ -22625,9 +23009,9 @@ function searchMeshyAnimations(query, options = {}) {
22625
23009
  }
22626
23010
 
22627
23011
  // src/lib/anims.ts
22628
- import fs33 from "fs/promises";
22629
- import path33 from "path";
22630
- var ANIMS_DEST = path33.join("public", "assets", "anims");
23012
+ import fs34 from "fs/promises";
23013
+ import path34 from "path";
23014
+ var ANIMS_DEST = path34.join("public", "assets", "anims");
22631
23015
  var HIDDEN_TAG = "reference";
22632
23016
  async function runAnims(opts) {
22633
23017
  const log = createLogger({ quiet: opts.quiet });
@@ -22643,7 +23027,7 @@ async function runAnims(opts) {
22643
23027
  printCatalog(log, manifest, selectors);
22644
23028
  return;
22645
23029
  }
22646
- const controllerMarker = path33.join(root, "src", "controllers", "character");
23030
+ const controllerMarker = path34.join(root, "src", "controllers", "character");
22647
23031
  if (!await exists3(controllerMarker)) {
22648
23032
  log.error(
22649
23033
  `No character controller in this game (missing ${c.cyan("src/controllers/character/")}).`
@@ -22652,11 +23036,11 @@ async function runAnims(opts) {
22652
23036
  process.exitCode = 1;
22653
23037
  return;
22654
23038
  }
22655
- const destDir = path33.join(root, ANIMS_DEST);
22656
- const gameManifestPath = path33.join(destDir, "manifest.json");
23039
+ const destDir = path34.join(root, ANIMS_DEST);
23040
+ const gameManifestPath = path34.join(destDir, "manifest.json");
22657
23041
  if (opts.reset) {
22658
- await fs33.rm(destDir, { recursive: true, force: true });
22659
- log.step(`Cleared ${c.cyan(ANIMS_DEST + path33.sep)} (--reset)`);
23042
+ await fs34.rm(destDir, { recursive: true, force: true });
23043
+ log.step(`Cleared ${c.cyan(ANIMS_DEST + path34.sep)} (--reset)`);
22660
23044
  }
22661
23045
  if (selectors.length === 0) {
22662
23046
  const installed = await readGameManifest(gameManifestPath);
@@ -22694,35 +23078,35 @@ async function runAnims(opts) {
22694
23078
  }
22695
23079
  }
22696
23080
  const wanted = [...byName2.values()].sort((a, b) => a.name.localeCompare(b.name));
22697
- const cacheDir = path33.join(
23081
+ const cacheDir = path34.join(
22698
23082
  opts.cacheDir ?? getAnimsCacheDir(),
22699
23083
  `${manifest.library}-v${manifest.version}`
22700
23084
  );
22701
- await fs33.mkdir(cacheDir, { recursive: true });
22702
- await fs33.mkdir(destDir, { recursive: true });
23085
+ await fs34.mkdir(cacheDir, { recursive: true });
23086
+ await fs34.mkdir(destDir, { recursive: true });
22703
23087
  const base = getAnimsBase(opts.animsBase);
22704
23088
  let installedCount = 0;
22705
23089
  let presentCount = 0;
22706
23090
  let addedBytes = 0;
22707
23091
  const failures = [];
22708
23092
  for (const entry of wanted) {
22709
- const dest = path33.join(destDir, entry.file);
23093
+ const dest = path34.join(destDir, entry.file);
22710
23094
  if (await hasSize(dest, entry.bytes)) {
22711
23095
  presentCount++;
22712
23096
  continue;
22713
23097
  }
22714
23098
  try {
22715
- const cached = path33.join(cacheDir, entry.file);
23099
+ const cached = path34.join(cacheDir, entry.file);
22716
23100
  if (!await hasSize(cached, entry.bytes)) {
22717
23101
  const res = await fetch(base + entry.file);
22718
23102
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
22719
23103
  const buf = Buffer.from(await res.arrayBuffer());
22720
- await fs33.writeFile(cached, buf);
23104
+ await fs34.writeFile(cached, buf);
22721
23105
  }
22722
- await fs33.copyFile(cached, dest);
23106
+ await fs34.copyFile(cached, dest);
22723
23107
  installedCount++;
22724
23108
  addedBytes += entry.bytes;
22725
- log.dim(` ${path33.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
23109
+ log.dim(` ${path34.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
22726
23110
  } catch (err) {
22727
23111
  failures.push(`${entry.name} (${err instanceof Error ? err.message : String(err)})`);
22728
23112
  }
@@ -22738,13 +23122,13 @@ async function runAnims(opts) {
22738
23122
  version: manifest.version,
22739
23123
  clips: [...union].sort((a, b) => a.localeCompare(b))
22740
23124
  };
22741
- await fs33.writeFile(gameManifestPath, JSON.stringify(gameManifest, null, 2) + "\n");
23125
+ await fs34.writeFile(gameManifestPath, JSON.stringify(gameManifest, null, 2) + "\n");
22742
23126
  log.plain("");
22743
23127
  const parts = [`${installedCount} clip${installedCount === 1 ? "" : "s"} installed`];
22744
23128
  if (presentCount > 0) parts.push(`${presentCount} already present`);
22745
23129
  if (bundledSkips > 0) parts.push(`${bundledSkips} already bundled in animation-library.glb`);
22746
23130
  log.success(
22747
- `${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path33.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
23131
+ `${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path34.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
22748
23132
  );
22749
23133
  for (const [selector, entries] of resolved) {
22750
23134
  const names2 = entries.filter((e) => !coreNames.has(e.name)).map((e) => e.name);
@@ -22776,8 +23160,8 @@ async function loadManifest(baseOverride) {
22776
23160
  }
22777
23161
  } catch {
22778
23162
  }
22779
- const snapshotPath = path33.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
22780
- const manifest = JSON.parse(await fs33.readFile(snapshotPath, "utf8"));
23163
+ const snapshotPath = path34.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
23164
+ const manifest = JSON.parse(await fs34.readFile(snapshotPath, "utf8"));
22781
23165
  return { manifest, source: "snapshot" };
22782
23166
  }
22783
23167
  function resolveSelectors(manifest, selectors) {
@@ -22895,21 +23279,21 @@ function printCatalog(log, manifest, selectors) {
22895
23279
  }
22896
23280
  async function readGameManifest(file) {
22897
23281
  try {
22898
- return JSON.parse(await fs33.readFile(file, "utf8"));
23282
+ return JSON.parse(await fs34.readFile(file, "utf8"));
22899
23283
  } catch {
22900
23284
  return null;
22901
23285
  }
22902
23286
  }
22903
23287
  async function hasSize(file, bytes) {
22904
23288
  try {
22905
- return (await fs33.stat(file)).size === bytes;
23289
+ return (await fs34.stat(file)).size === bytes;
22906
23290
  } catch {
22907
23291
  return false;
22908
23292
  }
22909
23293
  }
22910
23294
  async function exists3(p) {
22911
23295
  try {
22912
- await fs33.access(p);
23296
+ await fs34.access(p);
22913
23297
  return true;
22914
23298
  } catch {
22915
23299
  return false;
@@ -23110,8 +23494,8 @@ var CONTROLLER_FILE_SETS = {
23110
23494
  ]
23111
23495
  }
23112
23496
  };
23113
- var CODE_DEST = path34.join("src", "controllers");
23114
- var ASSETS_DEST = path34.join("public", "assets");
23497
+ var CODE_DEST = path35.join("src", "controllers");
23498
+ var ASSETS_DEST = path35.join("public", "assets");
23115
23499
  async function runController(opts) {
23116
23500
  const log = createLogger({ quiet: opts.quiet });
23117
23501
  if (opts.kind?.trim() === "anims") {
@@ -23128,31 +23512,31 @@ async function runController(opts) {
23128
23512
  process.exitCode = 1;
23129
23513
  return;
23130
23514
  }
23131
- const srcDir = path34.join(getTemplatesDir(), "controllers");
23515
+ const srcDir = path35.join(getTemplatesDir(), "controllers");
23132
23516
  const root = opts.cwd ?? process.cwd();
23133
23517
  const set = CONTROLLER_FILE_SETS[kind];
23134
23518
  log.plain(c.bold(`genex controller ${kind}`));
23135
23519
  log.plain("");
23136
- log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path34.sep)}`);
23520
+ log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path35.sep)}`);
23137
23521
  const plan2 = [
23138
- ...set.code.map((rel) => ({ from: rel, rel: path34.join(CODE_DEST, rel) })),
23522
+ ...set.code.map((rel) => ({ from: rel, rel: path35.join(CODE_DEST, rel) })),
23139
23523
  ...set.assets.map((rel) => ({
23140
23524
  from: rel,
23141
- rel: path34.join(ASSETS_DEST, path34.basename(rel))
23525
+ rel: path35.join(ASSETS_DEST, path35.basename(rel))
23142
23526
  }))
23143
23527
  ];
23144
23528
  let copied = 0;
23145
23529
  let skipped = 0;
23146
23530
  try {
23147
23531
  for (const file of plan2) {
23148
- const dest = path34.join(root, file.rel);
23532
+ const dest = path35.join(root, file.rel);
23149
23533
  if (!opts.force && await exists4(dest)) {
23150
23534
  skipped++;
23151
23535
  log.dim(` skipped ${file.rel} (exists \u2014 use --force to overwrite)`);
23152
23536
  continue;
23153
23537
  }
23154
- await fs34.mkdir(path34.dirname(dest), { recursive: true });
23155
- await fs34.copyFile(path34.join(srcDir, file.from), dest);
23538
+ await fs35.mkdir(path35.dirname(dest), { recursive: true });
23539
+ await fs35.copyFile(path35.join(srcDir, file.from), dest);
23156
23540
  copied++;
23157
23541
  log.dim(` ${file.rel}`);
23158
23542
  }
@@ -23205,7 +23589,7 @@ async function runController(opts) {
23205
23589
  for (const line of set.sketch) {
23206
23590
  log.dim(` ${line}`);
23207
23591
  }
23208
- if (kind === "character" && !await exists4(path34.join(root, ASSETS_DEST, "meshy-character.json"))) {
23592
+ if (kind === "character" && !await exists4(path35.join(root, ASSETS_DEST, "meshy-character.json"))) {
23209
23593
  log.plain("");
23210
23594
  log.plain(
23211
23595
  ` ${stepOffset + 3}. This game has no generated character yet \u2014 ${c.cyan(
@@ -23237,9 +23621,9 @@ async function installMeshyCharacterManifest(args) {
23237
23621
  throw new Error("The API returned an invalid Meshy character manifest.");
23238
23622
  }
23239
23623
  assertCompleteMeshyControllerPack(manifest);
23240
- const destination = path34.join(args.root, ASSETS_DEST, "meshy-character.json");
23241
- await fs34.mkdir(path34.dirname(destination), { recursive: true });
23242
- await fs34.writeFile(
23624
+ const destination = path35.join(args.root, ASSETS_DEST, "meshy-character.json");
23625
+ await fs35.mkdir(path35.dirname(destination), { recursive: true });
23626
+ await fs35.writeFile(
23243
23627
  destination,
23244
23628
  `${JSON.stringify(manifest, null, 2)}
23245
23629
  `
@@ -23384,14 +23768,14 @@ function assertCompleteMeshyControllerPack(manifest) {
23384
23768
  }
23385
23769
  async function installFallbackAvatar(args) {
23386
23770
  const { root, srcDir, log } = args;
23387
- const dest = path34.join(root, ASSETS_DEST, "avatar.vrm");
23388
- await fs34.mkdir(path34.dirname(dest), { recursive: true });
23389
- await fs34.copyFile(path34.join(srcDir, "assets", "default-avatar.vrm"), dest);
23771
+ const dest = path35.join(root, ASSETS_DEST, "avatar.vrm");
23772
+ await fs35.mkdir(path35.dirname(dest), { recursive: true });
23773
+ await fs35.copyFile(path35.join(srcDir, "assets", "default-avatar.vrm"), dest);
23390
23774
  log.dim(" public/assets/avatar.vrm (fallback avatar \u2014 bundled CC0 default)");
23391
23775
  }
23392
23776
  async function exists4(p) {
23393
23777
  try {
23394
- await fs34.access(p);
23778
+ await fs35.access(p);
23395
23779
  return true;
23396
23780
  } catch {
23397
23781
  return false;
@@ -23399,8 +23783,8 @@ async function exists4(p) {
23399
23783
  }
23400
23784
 
23401
23785
  // src/commands/character.ts
23402
- import fs35 from "fs/promises";
23403
- import path35 from "path";
23786
+ import fs36 from "fs/promises";
23787
+ import path36 from "path";
23404
23788
  function exactAnimation(selector) {
23405
23789
  const trimmed = selector.trim();
23406
23790
  if (/^-?\d+$/.test(trimmed)) return animationById(Number(trimmed));
@@ -23485,7 +23869,7 @@ function showAmbiguity(selector, candidates, log, json = false) {
23485
23869
  }
23486
23870
  process.exitCode = 1;
23487
23871
  }
23488
- var INSTALLED_MANIFEST = path35.join("public", "assets", "meshy-character.json");
23872
+ var INSTALLED_MANIFEST = path36.join("public", "assets", "meshy-character.json");
23489
23873
  async function resolveAdoptTarget(selector) {
23490
23874
  const trimmed = selector?.trim();
23491
23875
  if (trimmed && !trimmed.endsWith(".json")) {
@@ -23494,7 +23878,7 @@ async function resolveAdoptTarget(selector) {
23494
23878
  const file = trimmed ?? INSTALLED_MANIFEST;
23495
23879
  let raw;
23496
23880
  try {
23497
- raw = await fs35.readFile(file, "utf8");
23881
+ raw = await fs36.readFile(file, "utf8");
23498
23882
  } catch {
23499
23883
  return {
23500
23884
  ok: false,
@@ -23619,7 +24003,7 @@ async function runCharacterImport(opts) {
23619
24003
  opts,
23620
24004
  ctx,
23621
24005
  kind: "character",
23622
- prompt: `Import ${path35.basename(filePath)} as a rigged character`,
24006
+ prompt: `Import ${path36.basename(filePath)} as a rigged character`,
23623
24007
  createPath: "/api/characters/import",
23624
24008
  body,
23625
24009
  quote: price,
@@ -24164,22 +24548,22 @@ async function context2(opts) {
24164
24548
  if (!token) return null;
24165
24549
  return { token, apiUrl };
24166
24550
  }
24167
- async function readVideo(path52, log) {
24551
+ async function readVideo(path53, log) {
24168
24552
  let bytes;
24169
24553
  try {
24170
- bytes = await readFile(path52);
24554
+ bytes = await readFile(path53);
24171
24555
  } catch {
24172
- log.error(`Can't read ${path52}.`);
24556
+ log.error(`Can't read ${path53}.`);
24173
24557
  return null;
24174
24558
  }
24175
24559
  if (bytes.byteLength > MAX_VIDEO_BYTES) {
24176
- log.error(`${basename(path52)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
24560
+ log.error(`${basename(path53)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
24177
24561
  return null;
24178
24562
  }
24179
24563
  return bytes;
24180
24564
  }
24181
- async function uploadVideo(apiUrl, token, characterId, path52, bytes, log) {
24182
- const contentType = /\.mov$/i.test(path52) ? "video/quicktime" : "video/mp4";
24565
+ async function uploadVideo(apiUrl, token, characterId, path53, bytes, log) {
24566
+ const contentType = /\.mov$/i.test(path53) ? "video/quicktime" : "video/mp4";
24183
24567
  const minted = await apiFetch(
24184
24568
  `${apiUrl}/api/characters/${encodeURIComponent(characterId)}/motions/video-url`,
24185
24569
  {
@@ -24194,7 +24578,7 @@ async function uploadVideo(apiUrl, token, characterId, path52, bytes, log) {
24194
24578
  return null;
24195
24579
  }
24196
24580
  const { uploadUrl, videoUrl } = await minted.json();
24197
- log.dim(` uploading ${basename(path52)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
24581
+ log.dim(` uploading ${basename(path53)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
24198
24582
  const put = await fetch(uploadUrl, {
24199
24583
  method: "PUT",
24200
24584
  headers: { "Content-Type": contentType, "Content-Length": String(bytes.byteLength) },
@@ -24526,8 +24910,8 @@ function rank(items, query) {
24526
24910
  }
24527
24911
 
24528
24912
  // src/commands/motion.ts
24529
- import fs36 from "fs/promises";
24530
- import path36 from "path";
24913
+ import fs37 from "fs/promises";
24914
+ import path37 from "path";
24531
24915
 
24532
24916
  // src/lib/motion/npz.ts
24533
24917
  import zlib2 from "zlib";
@@ -25778,7 +26162,7 @@ async function motionGen(opts, log) {
25778
26162
  }
25779
26163
  if (opts.constraintsPath !== void 0) {
25780
26164
  try {
25781
- const raw = await fs36.readFile(opts.constraintsPath, "utf8");
26165
+ const raw = await fs37.readFile(opts.constraintsPath, "utf8");
25782
26166
  generationOptions.constraints = JSON.parse(raw);
25783
26167
  } catch {
25784
26168
  log.error(`Couldn't read the --constraints JSON at ${opts.constraintsPath}.`);
@@ -25802,10 +26186,10 @@ async function motionGen(opts, log) {
25802
26186
  async function expandTakes(selectors) {
25803
26187
  const out = [];
25804
26188
  for (const sel of selectors) {
25805
- const st = await fs36.stat(sel).catch(() => null);
26189
+ const st = await fs37.stat(sel).catch(() => null);
25806
26190
  if (st?.isDirectory()) {
25807
- const names2 = await fs36.readdir(sel);
25808
- for (const n of names2.sort()) if (n.endsWith(".npz")) out.push(path36.join(sel, n));
26191
+ const names2 = await fs37.readdir(sel);
26192
+ for (const n of names2.sort()) if (n.endsWith(".npz")) out.push(path37.join(sel, n));
25809
26193
  } else if (st?.isFile()) {
25810
26194
  out.push(sel);
25811
26195
  } else {
@@ -25840,7 +26224,7 @@ async function motionVerify(opts, log) {
25840
26224
  let gates = DEFAULT_GATES;
25841
26225
  if (opts.gatesPath) {
25842
26226
  try {
25843
- gates = mergeGates(DEFAULT_GATES, JSON.parse(await fs36.readFile(opts.gatesPath, "utf8")));
26227
+ gates = mergeGates(DEFAULT_GATES, JSON.parse(await fs37.readFile(opts.gatesPath, "utf8")));
25844
26228
  } catch {
25845
26229
  log.error(`Couldn't read the --gates JSON at ${opts.gatesPath}.`);
25846
26230
  process.exitCode = 1;
@@ -25862,9 +26246,9 @@ async function motionVerify(opts, log) {
25862
26246
  }
25863
26247
  const reports = [];
25864
26248
  for (const file of files) {
25865
- const stem = path36.basename(file).replace(/\.npz$/, "");
26249
+ const stem = path37.basename(file).replace(/\.npz$/, "");
25866
26250
  try {
25867
- reports.push(analyzeTake(stem, await fs36.readFile(file), gates));
26251
+ reports.push(analyzeTake(stem, await fs37.readFile(file), gates));
25868
26252
  } catch (err) {
25869
26253
  reports.push({
25870
26254
  take: stem,
@@ -25902,7 +26286,7 @@ async function motionCompile(opts, log) {
25902
26286
  let cfg = DEFAULT_MOTION_CONFIG;
25903
26287
  if (opts.configPath) {
25904
26288
  try {
25905
- const patch = JSON.parse(await fs36.readFile(opts.configPath, "utf8"));
26289
+ const patch = JSON.parse(await fs37.readFile(opts.configPath, "utf8"));
25906
26290
  cfg = { ...DEFAULT_MOTION_CONFIG, ...patch, idleLoop: { ...DEFAULT_MOTION_CONFIG.idleLoop, ...patch.idleLoop } };
25907
26291
  } catch {
25908
26292
  log.error(`Couldn't read the --config JSON at ${opts.configPath}.`);
@@ -25920,16 +26304,16 @@ async function motionCompile(opts, log) {
25920
26304
  }
25921
26305
  const inputs = [];
25922
26306
  for (const file of files) {
25923
- const stem = path36.basename(file).replace(/\.npz$/, "");
26307
+ const stem = path37.basename(file).replace(/\.npz$/, "");
25924
26308
  try {
25925
- inputs.push({ stem, take: loadTake(await fs36.readFile(file)) });
26309
+ inputs.push({ stem, take: loadTake(await fs37.readFile(file)) });
25926
26310
  } catch (err) {
25927
26311
  log.error(`${stem}: ${err instanceof Error ? err.message : String(err)}`);
25928
26312
  process.exitCode = 1;
25929
26313
  return;
25930
26314
  }
25931
26315
  }
25932
- const setName = opts.set ?? path36.basename(opts.out).replace(/\.json$/, "");
26316
+ const setName = opts.set ?? path37.basename(opts.out).replace(/\.json$/, "");
25933
26317
  let result;
25934
26318
  try {
25935
26319
  result = compileSet(inputs, setName, cfg);
@@ -25944,9 +26328,9 @@ async function motionCompile(opts, log) {
25944
26328
  process.exitCode = 1;
25945
26329
  return;
25946
26330
  }
25947
- await fs36.mkdir(path36.dirname(path36.resolve(opts.out)), { recursive: true });
26331
+ await fs37.mkdir(path37.dirname(path37.resolve(opts.out)), { recursive: true });
25948
26332
  const json = JSON.stringify(result.data);
25949
- await fs36.writeFile(opts.out, json);
26333
+ await fs37.writeFile(opts.out, json);
25950
26334
  if (opts.json) {
25951
26335
  writeJson({ out: opts.out, set: setName, tracks: Object.keys(result.data.gaits), bytes: json.length });
25952
26336
  return;
@@ -25964,9 +26348,9 @@ var MOTION_RUNTIME_FILES = [
25964
26348
  var MOTION_PRESETS = {
25965
26349
  rifle: ["sets/rifle.json", "sets/jumps.json"]
25966
26350
  };
25967
- var MOTION_DEST = path36.join("src", "motion");
26351
+ var MOTION_DEST = path37.join("src", "motion");
25968
26352
  async function motionInstall(opts, log) {
25969
- const srcDir = path36.join(getTemplatesDir(), "motion");
26353
+ const srcDir = path37.join(getTemplatesDir(), "motion");
25970
26354
  const root = opts.cwd ?? process.cwd();
25971
26355
  const preset = opts.set;
25972
26356
  if (preset !== void 0 && !MOTION_PRESETS[preset]) {
@@ -25977,21 +26361,21 @@ async function motionInstall(opts, log) {
25977
26361
  const files = [...MOTION_RUNTIME_FILES, ...preset ? MOTION_PRESETS[preset] : []];
25978
26362
  log.plain(c.bold(`genex motion install${preset ? ` --set ${preset}` : ""}`));
25979
26363
  log.plain("");
25980
- log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST + path36.sep)}`);
26364
+ log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST + path37.sep)}`);
25981
26365
  let copied = 0, skipped = 0;
25982
26366
  try {
25983
26367
  for (const rel of files) {
25984
- const dest = path36.join(root, MOTION_DEST, rel);
25985
- const exists5 = await fs36.access(dest).then(() => true, () => false);
26368
+ const dest = path37.join(root, MOTION_DEST, rel);
26369
+ const exists5 = await fs37.access(dest).then(() => true, () => false);
25986
26370
  if (!opts.force && exists5) {
25987
26371
  skipped++;
25988
- log.dim(` skipped ${path36.join(MOTION_DEST, rel)} (exists \u2014 use --force to overwrite)`);
26372
+ log.dim(` skipped ${path37.join(MOTION_DEST, rel)} (exists \u2014 use --force to overwrite)`);
25989
26373
  continue;
25990
26374
  }
25991
- await fs36.mkdir(path36.dirname(dest), { recursive: true });
25992
- await fs36.copyFile(path36.join(srcDir, rel), dest);
26375
+ await fs37.mkdir(path37.dirname(dest), { recursive: true });
26376
+ await fs37.copyFile(path37.join(srcDir, rel), dest);
25993
26377
  copied++;
25994
- log.dim(` ${path36.join(MOTION_DEST, rel)}`);
26378
+ log.dim(` ${path37.join(MOTION_DEST, rel)}`);
25995
26379
  }
25996
26380
  } catch (err) {
25997
26381
  log.error(`Copy failed: ${String(err)}`);
@@ -26032,7 +26416,7 @@ async function motionConstraints(opts, log) {
26032
26416
  }
26033
26417
  const doc = directionConstraint(dir, speed, duration);
26034
26418
  const out = opts.out ?? "constraints.json";
26035
- await fs36.writeFile(out, JSON.stringify(doc));
26419
+ await fs37.writeFile(out, JSON.stringify(doc));
26036
26420
  if (opts.json) {
26037
26421
  writeJson({ out, dir: dirName, speed, duration, waypoints: doc[0].frame_indices.length });
26038
26422
  return;
@@ -26069,14 +26453,14 @@ async function runMotion(opts) {
26069
26453
  }
26070
26454
 
26071
26455
  // src/commands/blender.ts
26072
- import fs37 from "fs/promises";
26073
- import path37 from "path";
26456
+ import fs38 from "fs/promises";
26457
+ import path38 from "path";
26074
26458
  var SUBS3 = ["demo", "exec", "snap", "scene", "import", "export", "reset", "mcp", "serve", "seat", "release"];
26075
26459
  var DEFAULT_OUT_DIR = "assets/blender";
26076
26460
  async function writeB64(dir, name, b64) {
26077
- await fs37.mkdir(dir, { recursive: true });
26078
- const p = path37.join(dir, name);
26079
- await fs37.writeFile(p, Buffer.from(b64, "base64"));
26461
+ await fs38.mkdir(dir, { recursive: true });
26462
+ const p = path38.join(dir, name);
26463
+ await fs38.writeFile(p, Buffer.from(b64, "base64"));
26080
26464
  return p;
26081
26465
  }
26082
26466
  function reportScene(log, s) {
@@ -26160,7 +26544,7 @@ async function runBlender(opts) {
26160
26544
  log.plain(rest.join("\n"));
26161
26545
  return 1;
26162
26546
  }
26163
- const outDir = path37.resolve(opts.outDir ?? DEFAULT_OUT_DIR);
26547
+ const outDir = path38.resolve(opts.outDir ?? DEFAULT_OUT_DIR);
26164
26548
  const mode = opts.mode;
26165
26549
  if (mode !== void 0 && !isRenderMode(mode)) {
26166
26550
  log.error(`Unknown --mode ${mode}. Use one of: ${RENDER_MODES.join(", ")}.`);
@@ -26200,14 +26584,14 @@ async function runBlender(opts) {
26200
26584
  return 0;
26201
26585
  }
26202
26586
  case "export": {
26203
- const target = opts.out ?? path37.join(outDir, "scene.glb");
26587
+ const target = opts.out ?? path38.join(outDir, "scene.glb");
26204
26588
  const r = await blenderCall(base, "/export", { path: "/tmp/genex-export.glb" });
26205
26589
  if (!r.glbBase64) {
26206
26590
  log.error(`/export answered with no GLB bytes${r.uploaded ? " (it was uploaded, not inlined)" : ""}`);
26207
26591
  return 1;
26208
26592
  }
26209
- await fs37.mkdir(path37.dirname(target), { recursive: true });
26210
- await fs37.writeFile(target, Buffer.from(r.glbBase64, "base64"));
26593
+ await fs38.mkdir(path38.dirname(target), { recursive: true });
26594
+ await fs38.writeFile(target, Buffer.from(r.glbBase64, "base64"));
26211
26595
  log.success(`Exported ${r.bytes ?? 0} bytes`);
26212
26596
  log.plain(` ${c.cyan(target)}`);
26213
26597
  return 0;
@@ -26240,12 +26624,12 @@ async function runBlender(opts) {
26240
26624
  return 1;
26241
26625
  }
26242
26626
  try {
26243
- script = await fs37.readFile(opts.input, "utf8");
26627
+ script = await fs38.readFile(opts.input, "utf8");
26244
26628
  } catch {
26245
26629
  log.error(`Can't read ${opts.input}`);
26246
26630
  return 1;
26247
26631
  }
26248
- label = path37.basename(opts.input);
26632
+ label = path38.basename(opts.input);
26249
26633
  }
26250
26634
  const r = await blenderCall(base, "/exec", { script, ...mode ? { mode } : {}, sheet: { formats: SHEET_FORMATS } });
26251
26635
  if (r.stdout?.trim()) log.plain(r.stdout.trimEnd());
@@ -26364,10 +26748,10 @@ print(f"castle: {n} objects")
26364
26748
  `;
26365
26749
 
26366
26750
  // src/commands/asset-new.ts
26367
- import fs38 from "fs";
26751
+ import fs39 from "fs";
26368
26752
  import fsp from "fs/promises";
26369
- import path38 from "path";
26370
- import { pathToFileURL } from "url";
26753
+ import path39 from "path";
26754
+ import { pathToFileURL as pathToFileURL2 } from "url";
26371
26755
  var EXTRA_FILES = [
26372
26756
  "genex-asset.example.json",
26373
26757
  "public/fonts/ZalandoSansSemiExpanded-variable.woff2",
@@ -26460,7 +26844,7 @@ Every milestone ends with \`npm run verify\`, then \`npx genex preview --no-buil
26460
26844
  }
26461
26845
  async function runAssetNew(options) {
26462
26846
  const log = createLogger();
26463
- const cwd = options.dir ? path38.resolve(options.dir) : process.cwd();
26847
+ const cwd = options.dir ? path39.resolve(options.dir) : process.cwd();
26464
26848
  const slug = options.assetSlug;
26465
26849
  if (!slug) {
26466
26850
  log.error('Usage: genex asset new <slug> [--title "<Name>"] [--dims WxDxH] [--tri-band LOW-HIGH] [--holder "<your name>"] [--out <dir>]');
@@ -26470,14 +26854,14 @@ async function runAssetNew(options) {
26470
26854
  log.error(`The slug must be kebab-case (letters, digits, dashes), got: ${slug}`);
26471
26855
  return 1;
26472
26856
  }
26473
- const templateDir = path38.join(getTemplatesDir(), "asset-viewer");
26474
- if (!fs38.existsSync(templateDir)) {
26857
+ const templateDir = path39.join(getTemplatesDir(), "asset-viewer");
26858
+ if (!fs39.existsSync(templateDir)) {
26475
26859
  log.error(`Vendored asset-viewer template not found at ${templateDir} \u2014 reinstall the CLI.`);
26476
26860
  return 1;
26477
26861
  }
26478
- const manifestTools = await import(pathToFileURL(path38.join(templateDir, "tools", "emit-manifest.mjs")).href);
26862
+ const manifestTools = await import(pathToFileURL2(path39.join(templateDir, "tools", "emit-manifest.mjs")).href);
26479
26863
  const { SHARED_FILES, PARITY_FILENAME, hashSharedFiles, pascalCase } = manifestTools;
26480
- const lockPath = path38.join(templateDir, "shared-files.sha256.json");
26864
+ const lockPath = path39.join(templateDir, "shared-files.sha256.json");
26481
26865
  const lock = JSON.parse(await fsp.readFile(lockPath, "utf8"));
26482
26866
  const actual = hashSharedFiles(templateDir);
26483
26867
  const drifted = SHARED_FILES.filter((rel) => lock.files[rel] !== actual[rel]);
@@ -26490,8 +26874,8 @@ async function runAssetNew(options) {
26490
26874
  const triBand = parseBand(options.triBand ?? "500-8000");
26491
26875
  const holder = options.holder?.trim() || HOLDER_PLACEHOLDER;
26492
26876
  const year = (/* @__PURE__ */ new Date()).getUTCFullYear();
26493
- const outDir = path38.resolve(cwd, options.out ?? slug);
26494
- if (fs38.existsSync(outDir) && fs38.readdirSync(outDir).length > 0 && !options.force) {
26877
+ const outDir = path39.resolve(cwd, options.out ?? slug);
26878
+ if (fs39.existsSync(outDir) && fs39.readdirSync(outDir).length > 0 && !options.force) {
26495
26879
  log.error(`${outDir} already holds files. Nothing was touched \u2014 pass --force to write into it anyway.`);
26496
26880
  return 1;
26497
26881
  }
@@ -26519,24 +26903,24 @@ async function runAssetNew(options) {
26519
26903
  };
26520
26904
  await fsp.mkdir(outDir, { recursive: true });
26521
26905
  for (const rel of [...SHARED_FILES, ...EXTRA_FILES]) {
26522
- const to = path38.join(outDir, rel);
26523
- await fsp.mkdir(path38.dirname(to), { recursive: true });
26524
- await fsp.copyFile(path38.join(templateDir, rel), to);
26906
+ const to = path39.join(outDir, rel);
26907
+ await fsp.mkdir(path39.dirname(to), { recursive: true });
26908
+ await fsp.copyFile(path39.join(templateDir, rel), to);
26525
26909
  }
26526
- const pkg = fillTemplate(await fsp.readFile(path38.join(templateDir, "package.json"), "utf8"), {
26910
+ const pkg = fillTemplate(await fsp.readFile(path39.join(templateDir, "package.json"), "utf8"), {
26527
26911
  slug,
26528
26912
  name,
26529
26913
  version
26530
26914
  });
26531
- await fsp.writeFile(path38.join(outDir, "package.json"), pkg, "utf8");
26532
- await fsp.writeFile(path38.join(outDir, "asset.config.json"), JSON.stringify(config, null, 2) + "\n", "utf8");
26533
- await fsp.writeFile(path38.join(outDir, ".gitignore"), GITIGNORE, "utf8");
26915
+ await fsp.writeFile(path39.join(outDir, "package.json"), pkg, "utf8");
26916
+ await fsp.writeFile(path39.join(outDir, "asset.config.json"), JSON.stringify(config, null, 2) + "\n", "utf8");
26917
+ await fsp.writeFile(path39.join(outDir, ".gitignore"), GITIGNORE, "utf8");
26534
26918
  await fsp.writeFile(
26535
- path38.join(outDir, "DESIGN.md"),
26919
+ path39.join(outDir, "DESIGN.md"),
26536
26920
  designDoc({ name, slug, sizeMeters, triBand, holder }),
26537
26921
  "utf8"
26538
26922
  );
26539
- const placeholder = await fsp.readFile(path38.join(templateDir, "src", "asset", "PLACEHOLDER.ts"), "utf8");
26923
+ const placeholder = await fsp.readFile(path39.join(templateDir, "src", "asset", "PLACEHOLDER.ts"), "utf8");
26540
26924
  const seeded = seedAssetSource(placeholder, {
26541
26925
  slug,
26542
26926
  name,
@@ -26547,8 +26931,8 @@ async function runAssetNew(options) {
26547
26931
  pascalCase
26548
26932
  });
26549
26933
  const assetFilename = `${slug.replace(/-([a-z0-9])/g, (_, ch) => ch.toUpperCase())}.ts`;
26550
- await fsp.mkdir(path38.join(outDir, "src", "asset"), { recursive: true });
26551
- await fsp.writeFile(path38.join(outDir, "src", "asset", assetFilename), seeded, "utf8");
26934
+ await fsp.mkdir(path39.join(outDir, "src", "asset"), { recursive: true });
26935
+ await fsp.writeFile(path39.join(outDir, "src", "asset", assetFilename), seeded, "utf8");
26552
26936
  const copied = hashSharedFiles(outDir);
26553
26937
  const mismatched = SHARED_FILES.filter((rel) => copied[rel] !== lock.files[rel]);
26554
26938
  if (mismatched.length) {
@@ -26556,7 +26940,7 @@ async function runAssetNew(options) {
26556
26940
  return 1;
26557
26941
  }
26558
26942
  await fsp.writeFile(
26559
- path38.join(outDir, PARITY_FILENAME),
26943
+ path39.join(outDir, PARITY_FILENAME),
26560
26944
  JSON.stringify(
26561
26945
  {
26562
26946
  note: "Recorded by `genex asset new`. Gate G13 recomputes these; do not hand-edit the viewer.",
@@ -26600,14 +26984,14 @@ async function runAssetNew(options) {
26600
26984
  }
26601
26985
 
26602
26986
  // src/commands/tools.ts
26603
- import path41 from "path";
26987
+ import path42 from "path";
26604
26988
 
26605
26989
  // src/commands/doctor.ts
26606
- import path40 from "path";
26990
+ import path41 from "path";
26607
26991
 
26608
26992
  // src/commands/llm.ts
26609
- import fs39 from "fs/promises";
26610
- import path39 from "path";
26993
+ import fs40 from "fs/promises";
26994
+ import path40 from "path";
26611
26995
  import { randomUUID as randomUUID2 } from "crypto";
26612
26996
  var SUBS4 = ["models", "bench", "price", "status", "cancel"];
26613
26997
  var LLM_BENCH_APPROVAL_REQUIRED = "STOP: `genex llm bench` runs the real model and spends YOUR OWN credits \u2014 your balance, not a player's, and a spend this build's asset allowance does not count. Re-run with --max-credits <n> --user-approved only after the person at the keyboard agreed to the number.";
@@ -27129,7 +27513,7 @@ async function runBench(args) {
27129
27513
  let schema;
27130
27514
  if (opts.schemaPath) {
27131
27515
  try {
27132
- schema = JSON.parse(await fs39.readFile(path39.resolve(cwd, opts.schemaPath), "utf8"));
27516
+ schema = JSON.parse(await fs40.readFile(path40.resolve(cwd, opts.schemaPath), "utf8"));
27133
27517
  } catch (err) {
27134
27518
  benchFailed(opts, log, `Couldn't read --schema ${opts.schemaPath}: ${err instanceof Error ? err.message : String(err)}`);
27135
27519
  return;
@@ -27511,9 +27895,9 @@ async function pollSettled(call3, url, timeoutSec) {
27511
27895
  }
27512
27896
  async function saveBench(cwd, record) {
27513
27897
  try {
27514
- const file = path39.join(cwd, LLM_BENCH_FILE);
27515
- await fs39.mkdir(path39.dirname(file), { recursive: true });
27516
- await fs39.writeFile(file, JSON.stringify(record, null, 2) + "\n");
27898
+ const file = path40.join(cwd, LLM_BENCH_FILE);
27899
+ await fs40.mkdir(path40.dirname(file), { recursive: true });
27900
+ await fs40.writeFile(file, JSON.stringify(record, null, 2) + "\n");
27517
27901
  return LLM_BENCH_FILE;
27518
27902
  } catch {
27519
27903
  return null;
@@ -27726,7 +28110,7 @@ async function cancelCall(args) {
27726
28110
  async function readSavedBench(cwd) {
27727
28111
  let parsed;
27728
28112
  try {
27729
- parsed = JSON.parse(await fs39.readFile(path39.join(cwd, LLM_BENCH_FILE), "utf8"));
28113
+ parsed = JSON.parse(await fs40.readFile(path40.join(cwd, LLM_BENCH_FILE), "utf8"));
27730
28114
  } catch {
27731
28115
  return null;
27732
28116
  }
@@ -28158,7 +28542,7 @@ async function fetchLegalStatus(apiUrl, token) {
28158
28542
  }
28159
28543
  async function firstSkillsMarker() {
28160
28544
  for (const target of resolveAgentTargets()) {
28161
- const marker = await readSkillsMarker(path40.join(target.baseDir, "skills"));
28545
+ const marker = await readSkillsMarker(path41.join(target.baseDir, "skills"));
28162
28546
  if (marker) return marker;
28163
28547
  }
28164
28548
  return null;
@@ -28210,11 +28594,11 @@ async function runTools(opts) {
28210
28594
  let totalUpdated = 0;
28211
28595
  const refused = [];
28212
28596
  for (const t of targets) {
28213
- const dest = path41.join(t.baseDir, "skills");
28597
+ const dest = path42.join(t.baseDir, "skills");
28214
28598
  let copied;
28215
28599
  let updated;
28216
28600
  try {
28217
- ({ copied, updated } = await copyTemplates(path41.join(templatesDir, "skills"), dest, {
28601
+ ({ copied, updated } = await copyTemplates(path42.join(templatesDir, "skills"), dest, {
28218
28602
  filter: (rel) => skillFamilyFilter("tools")(`skills/${rel}`)
28219
28603
  }));
28220
28604
  await pruneRemovedSkills(dest, log);
@@ -28286,8 +28670,8 @@ async function runTools(opts) {
28286
28670
  }
28287
28671
 
28288
28672
  // src/commands/budget.ts
28289
- import fs40 from "fs/promises";
28290
- import path42 from "path";
28673
+ import fs41 from "fs/promises";
28674
+ import path43 from "path";
28291
28675
  var BUDGET_SOURCE_LINE = "Live from your account; prices can change without a deploy.";
28292
28676
  var BUDGET_APPROVAL_REQUIRED = "STOP: raising the asset allowance requires the player's explicit approval. Re-run with --user-approved only after they agreed to the number.";
28293
28677
  function internalKind(kind) {
@@ -28295,7 +28679,7 @@ function internalKind(kind) {
28295
28679
  }
28296
28680
  async function isGenexProject(cwd) {
28297
28681
  try {
28298
- await fs40.access(path42.join(cwd, ".genex"));
28682
+ await fs41.access(path43.join(cwd, ".genex"));
28299
28683
  return true;
28300
28684
  } catch {
28301
28685
  return false;
@@ -28444,39 +28828,39 @@ async function runBudget(opts = {}) {
28444
28828
  }
28445
28829
 
28446
28830
  // src/commands/player.ts
28447
- import fs45 from "fs";
28831
+ import fs46 from "fs";
28448
28832
  import fsp2 from "fs/promises";
28449
28833
  import os13 from "os";
28450
- import path48 from "path";
28834
+ import path49 from "path";
28451
28835
  import { spawn as spawn8 } from "child_process";
28452
28836
 
28453
28837
  // src/lib/player/paths.ts
28454
28838
  import os9 from "os";
28455
- import path43 from "path";
28839
+ import path44 from "path";
28456
28840
  var PLAYER_DRY_RUN_ENV = "GENEX_PLAYER_DRY_RUN";
28457
28841
  function playerDryRun() {
28458
28842
  const raw = process.env[PLAYER_DRY_RUN_ENV];
28459
28843
  return raw === "1" || raw === "true";
28460
28844
  }
28461
28845
  function playerPaths(home = os9.homedir()) {
28462
- const dir = home === os9.homedir() ? getGenexDir() : path43.join(home, ".genex");
28463
- const logDir = path43.join(dir, "logs");
28846
+ const dir = home === os9.homedir() ? getGenexDir() : path44.join(home, ".genex");
28847
+ const logDir = path44.join(dir, "logs");
28464
28848
  return {
28465
- credential: path43.join(dir, "player.json"),
28466
- state: path43.join(dir, "player-state.json"),
28467
- relaunch: path43.join(dir, "player-relaunch.json"),
28849
+ credential: path44.join(dir, "player.json"),
28850
+ state: path44.join(dir, "player-state.json"),
28851
+ relaunch: path44.join(dir, "player-relaunch.json"),
28468
28852
  logDir,
28469
- log: path43.join(logDir, "player.log"),
28470
- errorLog: path43.join(logDir, "player.error.log")
28853
+ log: path44.join(logDir, "player.log"),
28854
+ errorLog: path44.join(logDir, "player.error.log")
28471
28855
  };
28472
28856
  }
28473
28857
 
28474
28858
  // src/lib/player/store.ts
28475
- import fs41 from "fs/promises";
28476
- import path44 from "path";
28859
+ import fs42 from "fs/promises";
28860
+ import path45 from "path";
28477
28861
  async function readPlayerInstall(home) {
28478
28862
  try {
28479
- const raw = await fs41.readFile(playerPaths(home).credential, "utf8");
28863
+ const raw = await fs42.readFile(playerPaths(home).credential, "utf8");
28480
28864
  const parsed = JSON.parse(raw);
28481
28865
  if (!parsed || typeof parsed.token !== "string" || typeof parsed.apiUrl !== "string") return null;
28482
28866
  return parsed;
@@ -28506,7 +28890,7 @@ function describeInstall(install2) {
28506
28890
  }
28507
28891
  async function readPlayerState(home) {
28508
28892
  try {
28509
- return JSON.parse(await fs41.readFile(playerPaths(home).state, "utf8"));
28893
+ return JSON.parse(await fs42.readFile(playerPaths(home).state, "utf8"));
28510
28894
  } catch {
28511
28895
  return null;
28512
28896
  }
@@ -28514,8 +28898,8 @@ async function readPlayerState(home) {
28514
28898
  async function writePlayerState(state, home) {
28515
28899
  const file = playerPaths(home).state;
28516
28900
  try {
28517
- await fs41.mkdir(path44.dirname(file), { recursive: true, mode: 448 });
28518
- await fs41.writeFile(file, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
28901
+ await fs42.mkdir(path45.dirname(file), { recursive: true, mode: 448 });
28902
+ await fs42.writeFile(file, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
28519
28903
  } catch {
28520
28904
  }
28521
28905
  }
@@ -28523,8 +28907,8 @@ async function removePlayerFiles(home) {
28523
28907
  const paths = playerPaths(home);
28524
28908
  const removed = [];
28525
28909
  for (const file of [paths.credential, paths.state, paths.relaunch]) {
28526
- const existed = await fs41.stat(file).then(() => true, () => false);
28527
- await fs41.rm(file, { force: true }).catch(() => {
28910
+ const existed = await fs42.stat(file).then(() => true, () => false);
28911
+ await fs42.rm(file, { force: true }).catch(() => {
28528
28912
  });
28529
28913
  if (existed) removed.push(file);
28530
28914
  }
@@ -28658,14 +29042,14 @@ var PlayerClient = class {
28658
29042
  this.version = options.version ?? getCliVersion();
28659
29043
  this.timeoutMs = options.timeoutMs ?? 3e4;
28660
29044
  }
28661
- async call(path52, init = {}, provider) {
29045
+ async call(path53, init = {}, provider) {
28662
29046
  const headers = {
28663
29047
  Authorization: `Bearer ${this.token}`,
28664
29048
  [PLAYER_VERSION_HEADER]: this.version,
28665
29049
  ...provider ? { [EXTERNAL_PROVIDER_HEADER]: provider } : {},
28666
29050
  ...init.body === void 0 ? {} : { "Content-Type": "application/json" }
28667
29051
  };
28668
- const res = await apiFetch(`${this.apiUrl}${path52}`, {
29052
+ const res = await apiFetch(`${this.apiUrl}${path53}`, {
28669
29053
  ...init,
28670
29054
  headers,
28671
29055
  signal: AbortSignal.timeout(this.timeoutMs)
@@ -28734,10 +29118,10 @@ var PlayerClient = class {
28734
29118
 
28735
29119
  // src/lib/player/runners.ts
28736
29120
  import { spawn as spawn6, spawnSync } from "child_process";
28737
- import fs42 from "fs/promises";
29121
+ import fs43 from "fs/promises";
28738
29122
  import fsSync from "fs";
28739
29123
  import os10 from "os";
28740
- import path45 from "path";
29124
+ import path46 from "path";
28741
29125
  import { randomUUID as randomUUID3 } from "crypto";
28742
29126
  var WATCHER_RUNNERS = ["claude", "codex"];
28743
29127
  var RUNNER_FOR_FAMILY = { claude: "claude", chatgpt: "codex" };
@@ -28795,7 +29179,7 @@ function resolveOnPath(name, env = process.env, platform = process.platform) {
28795
29179
  const candidates = win ? [`${name}.cmd`, `${name}.exe`, `${name}.bat`, name] : [name];
28796
29180
  for (const dir of dirs) {
28797
29181
  for (const candidate of candidates) {
28798
- const file = path45.join(dir, candidate);
29182
+ const file = path46.join(dir, candidate);
28799
29183
  try {
28800
29184
  const stat = fsSync.statSync(file);
28801
29185
  if (!stat.isFile()) continue;
@@ -28959,17 +29343,17 @@ async function runJob(job, options = {}) {
28959
29343
  if (!resolved) {
28960
29344
  return { ok: false, release: "runner_unavailable", detail: `missing:${runner}`, durationMs: Date.now() - started };
28961
29345
  }
28962
- const cwd = path45.join(os10.tmpdir(), `genex-player-${randomUUID3()}`);
28963
- await fs42.mkdir(cwd, { recursive: true, mode: 448 });
28964
- const outFile = path45.join(cwd, "result.out");
29346
+ const cwd = path46.join(os10.tmpdir(), `genex-player-${randomUUID3()}`);
29347
+ await fs43.mkdir(cwd, { recursive: true, mode: 448 });
29348
+ const outFile = path46.join(cwd, "result.out");
28965
29349
  let argv;
28966
29350
  if (runner === "claude") {
28967
29351
  argv = claudeArgv(job, policy.claude ?? FALLBACK_POLICY.claude ?? {});
28968
29352
  } else {
28969
29353
  let schemaFile;
28970
29354
  if (job.outputFormat === "json" && job.schema !== void 0) {
28971
- schemaFile = path45.join(cwd, "schema.json");
28972
- await fs42.writeFile(schemaFile, JSON.stringify(job.schema), { mode: 384 });
29355
+ schemaFile = path46.join(cwd, "schema.json");
29356
+ await fs43.writeFile(schemaFile, JSON.stringify(job.schema), { mode: 384 });
28973
29357
  }
28974
29358
  argv = codexArgv(job, policy.codex ?? FALLBACK_POLICY.codex ?? {}, { cwd, outFile, ...schemaFile ? { schemaFile } : {} });
28975
29359
  }
@@ -29033,7 +29417,7 @@ async function runJob(job, options = {}) {
29033
29417
  if (!read2.ok) return { ok: false, release: "failed", detail: read2.detail, exitCode: code, durationMs };
29034
29418
  return { ok: true, output: read2.output, exitCode: code, durationMs };
29035
29419
  }
29036
- const text = await fs42.readFile(outFile, "utf8").catch(() => "");
29420
+ const text = await fs43.readFile(outFile, "utf8").catch(() => "");
29037
29421
  if (code !== 0 && !text.trim()) {
29038
29422
  const classified = classifyFailure(`${stderrText} exit ${code}`);
29039
29423
  return { ok: false, ...classified, detail: `exit_${code}`, exitCode: code, durationMs };
@@ -29042,15 +29426,15 @@ async function runJob(job, options = {}) {
29042
29426
  if (!read.ok) return { ok: false, release: "failed", detail: read.detail, exitCode: code, durationMs };
29043
29427
  return { ok: true, output: read.output, exitCode: code, durationMs };
29044
29428
  } finally {
29045
- await fs42.rm(cwd, { recursive: true, force: true }).catch(() => {
29429
+ await fs43.rm(cwd, { recursive: true, force: true }).catch(() => {
29046
29430
  });
29047
29431
  }
29048
29432
  }
29049
29433
 
29050
29434
  // src/lib/player/service.ts
29051
- import fs43 from "fs/promises";
29435
+ import fs44 from "fs/promises";
29052
29436
  import os11 from "os";
29053
- import path46 from "path";
29437
+ import path47 from "path";
29054
29438
  import { execFile } from "child_process";
29055
29439
  import { promisify } from "util";
29056
29440
  var run2 = promisify(execFile);
@@ -29067,9 +29451,9 @@ function servicePlatform(platform = process.platform) {
29067
29451
  }
29068
29452
  function servicePaths(platform, home = os11.homedir()) {
29069
29453
  const { log, errorLog } = playerPaths(home);
29070
- if (platform === "darwin") return { log, errorLog, unit: path46.join(home, "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`) };
29071
- if (platform === "linux") return { log, errorLog, unit: path46.join(home, ".config", "systemd", "user", SYSTEMD_UNIT) };
29072
- return { log, errorLog, unit: path46.join(home, "AppData", "Roaming", "Microsoft", "Windows", "Start Menu", "Programs", "Startup", "genex-player.cmd") };
29454
+ if (platform === "darwin") return { log, errorLog, unit: path47.join(home, "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`) };
29455
+ if (platform === "linux") return { log, errorLog, unit: path47.join(home, ".config", "systemd", "user", SYSTEMD_UNIT) };
29456
+ return { log, errorLog, unit: path47.join(home, "AppData", "Roaming", "Microsoft", "Windows", "Start Menu", "Programs", "Startup", "genex-player.cmd") };
29073
29457
  }
29074
29458
  function xmlEscape(value) {
29075
29459
  return value.replace(/[<>&'"]/g, (ch) => ({ "<": "&lt;", ">": "&gt;", "&": "&amp;", "'": "&apos;", '"': "&quot;" })[ch]);
@@ -29148,9 +29532,9 @@ function autostartCommand(opts) {
29148
29532
  }
29149
29533
  async function installService(input, platform = servicePlatform()) {
29150
29534
  const paths = servicePaths(platform, input.home);
29151
- await fs43.mkdir(path46.dirname(paths.unit), { recursive: true });
29152
- await fs43.mkdir(path46.dirname(paths.log), { recursive: true, mode: 448 });
29153
- await fs43.writeFile(paths.unit, unitFile(platform, input, paths), { mode: platform === "win32" ? 420 : 384 });
29535
+ await fs44.mkdir(path47.dirname(paths.unit), { recursive: true });
29536
+ await fs44.mkdir(path47.dirname(paths.log), { recursive: true, mode: 448 });
29537
+ await fs44.writeFile(paths.unit, unitFile(platform, input, paths), { mode: platform === "win32" ? 420 : 384 });
29154
29538
  if (playerDryRun()) return { unit: paths.unit, loaded: false, detail: "dry run \u2014 the unit was written, the system was not asked" };
29155
29539
  if (platform === "darwin") {
29156
29540
  const domain = `gui/${process.getuid?.() ?? 501}`;
@@ -29179,7 +29563,7 @@ async function installService(input, platform = servicePlatform()) {
29179
29563
  }
29180
29564
  async function serviceInstalled(home = os11.homedir(), platform = servicePlatform()) {
29181
29565
  const paths = servicePaths(platform, home);
29182
- return await fs43.stat(paths.unit).then(() => paths.unit, () => null);
29566
+ return await fs44.stat(paths.unit).then(() => paths.unit, () => null);
29183
29567
  }
29184
29568
  async function stopService(home = os11.homedir(), platform = servicePlatform()) {
29185
29569
  const paths = servicePaths(platform, home);
@@ -29209,15 +29593,15 @@ async function uninstallService(home = os11.homedir(), platform = servicePlatfor
29209
29593
  });
29210
29594
  }
29211
29595
  }
29212
- await fs43.rm(paths.unit, { force: true }).catch(() => {
29596
+ await fs44.rm(paths.unit, { force: true }).catch(() => {
29213
29597
  });
29214
29598
  return { unit: paths.unit, loaded: false, detail: present ? "removed" : "nothing installed" };
29215
29599
  }
29216
29600
 
29217
29601
  // src/lib/player/relaunch.ts
29218
- import fs44 from "fs/promises";
29602
+ import fs45 from "fs/promises";
29219
29603
  import fsSync2 from "fs";
29220
- import path47 from "path";
29604
+ import path48 from "path";
29221
29605
  import { spawn as spawn7 } from "child_process";
29222
29606
  var RELAUNCH_COOLDOWN_MS = 10 * 60 * 1e3;
29223
29607
  function relaunchPlan(input) {
@@ -29245,7 +29629,7 @@ function relaunchPossible() {
29245
29629
  }
29246
29630
  async function relaunchOnCooldown(home, now = Date.now()) {
29247
29631
  try {
29248
- const raw = await fs44.readFile(playerPaths(home).relaunch, "utf8");
29632
+ const raw = await fs45.readFile(playerPaths(home).relaunch, "utf8");
29249
29633
  const at = Date.parse(JSON.parse(raw).at ?? "");
29250
29634
  return Number.isFinite(at) && now - at < RELAUNCH_COOLDOWN_MS;
29251
29635
  } catch {
@@ -29255,8 +29639,8 @@ async function relaunchOnCooldown(home, now = Date.now()) {
29255
29639
  async function markRelaunch(version, home) {
29256
29640
  const file = playerPaths(home).relaunch;
29257
29641
  try {
29258
- await fs44.mkdir(path47.dirname(file), { recursive: true, mode: 448 });
29259
- await fs44.writeFile(file, JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), version }) + "\n", { mode: 384 });
29642
+ await fs45.mkdir(path48.dirname(file), { recursive: true, mode: 448 });
29643
+ await fs45.writeFile(file, JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), version }) + "\n", { mode: 384 });
29260
29644
  } catch {
29261
29645
  }
29262
29646
  }
@@ -29280,7 +29664,7 @@ async function relaunchWatcher(input) {
29280
29664
  let err;
29281
29665
  const paths = playerPaths(input.home);
29282
29666
  try {
29283
- await fs44.mkdir(paths.logDir, { recursive: true, mode: 448 });
29667
+ await fs45.mkdir(paths.logDir, { recursive: true, mode: 448 });
29284
29668
  out = fsSync2.openSync(paths.log, "a", 384);
29285
29669
  err = fsSync2.openSync(paths.errorLog, "a", 384);
29286
29670
  } catch {
@@ -29761,8 +30145,8 @@ async function spawnDetachedRun(apiUrl, opts) {
29761
30145
  try {
29762
30146
  const paths = playerPaths();
29763
30147
  await fsp2.mkdir(paths.logDir, { recursive: true, mode: 448 });
29764
- out = fs45.openSync(paths.log, "a", 384);
29765
- err = fs45.openSync(paths.errorLog, "a", 384);
30148
+ out = fs46.openSync(paths.log, "a", 384);
30149
+ err = fs46.openSync(paths.errorLog, "a", 384);
29766
30150
  const args = [process.argv[1] ?? "", "player", "run", ...opts.apiUrl ? ["--api-url", apiUrl] : []];
29767
30151
  const child = spawn8(process.execPath, args, { detached: true, stdio: ["ignore", out, err] });
29768
30152
  child.on("error", () => {
@@ -29773,7 +30157,7 @@ async function spawnDetachedRun(apiUrl, opts) {
29773
30157
  return false;
29774
30158
  } finally {
29775
30159
  for (const fd of [out, err]) if (fd !== void 0) try {
29776
- fs45.closeSync(fd);
30160
+ fs46.closeSync(fd);
29777
30161
  } catch {
29778
30162
  }
29779
30163
  }
@@ -29972,7 +30356,7 @@ async function uninstall(opts, log) {
29972
30356
  writeJsonLine2({ command: "player uninstall", status: "ok", error: null, unit: service.unit, detail: service.detail, removed });
29973
30357
  return;
29974
30358
  }
29975
- log.success(`Removed: ${service.detail} (${path48.basename(service.unit)})`);
30359
+ log.success(`Removed: ${service.detail} (${path49.basename(service.unit)})`);
29976
30360
  for (const file of removed) log.dim(` deleted ${file}`);
29977
30361
  if (removed.length === 0) log.dim(" no credential was stored here");
29978
30362
  log.plain("");
@@ -29990,21 +30374,21 @@ function writeJsonLine2(value) {
29990
30374
  }
29991
30375
 
29992
30376
  // src/commands/native.ts
29993
- import fs51 from "fs/promises";
30377
+ import fs52 from "fs/promises";
29994
30378
  import os15 from "os";
29995
- import path51 from "path";
30379
+ import path52 from "path";
29996
30380
 
29997
30381
  // src/lib/native-build.ts
29998
30382
  import { createHash as createHash5 } from "crypto";
29999
30383
  import { createReadStream } from "fs";
30000
- import fs47 from "fs/promises";
30384
+ import fs48 from "fs/promises";
30001
30385
  import os14 from "os";
30002
- import path49 from "path";
30386
+ import path50 from "path";
30003
30387
  import { execFile as execFile2 } from "child_process";
30004
30388
  import { promisify as promisify2 } from "util";
30005
30389
 
30006
30390
  // src/lib/native-zip.ts
30007
- import fs46 from "fs/promises";
30391
+ import fs47 from "fs/promises";
30008
30392
  import zlib3 from "zlib";
30009
30393
  var EOCD_SIGNATURE2 = 101010256;
30010
30394
  var EOCD64_LOCATOR_SIGNATURE = 117853008;
@@ -30106,7 +30490,7 @@ function entryAt(data, at) {
30106
30490
  return { entry, madeBy, external, next: at + 46 + nameLength + extraLength + commentLength };
30107
30491
  }
30108
30492
  async function readZipEntries(filePath) {
30109
- const file = await fs46.open(filePath, "r");
30493
+ const file = await fs47.open(filePath, "r");
30110
30494
  try {
30111
30495
  const { size } = await file.stat();
30112
30496
  const directory = await findDirectory2(file, size);
@@ -30125,7 +30509,7 @@ async function readZipEntries(filePath) {
30125
30509
  }
30126
30510
  }
30127
30511
  async function readZipEntryHead(filePath, entry, maxBytes) {
30128
- const file = await fs46.open(filePath, "r");
30512
+ const file = await fs47.open(filePath, "r");
30129
30513
  try {
30130
30514
  return await entryHead(file, entry, maxBytes);
30131
30515
  } finally {
@@ -30154,7 +30538,7 @@ function wantedMode(mode, directory, program) {
30154
30538
  return (mode | S_IFREG2 | EXECUTE_BITS) >>> 0;
30155
30539
  }
30156
30540
  async function markPrograms(filePath) {
30157
- const file = await fs46.open(filePath, "r+");
30541
+ const file = await fs47.open(filePath, "r+");
30158
30542
  try {
30159
30543
  const { size } = await file.stat();
30160
30544
  const directory = await findDirectory2(file, size);
@@ -30234,7 +30618,7 @@ function rootApps(entries) {
30234
30618
  }
30235
30619
  function launchableExes(entries) {
30236
30620
  const exes = entries.filter((e) => !isDirectory(e) && e.name.toLowerCase().endsWith(".exe"));
30237
- const games = exes.filter((e) => e.name.split("/").length <= ENTRY_DEPTH_MAX && !HELPER_EXE.test(path49.posix.basename(e.name)));
30621
+ const games = exes.filter((e) => e.name.split("/").length <= ENTRY_DEPTH_MAX && !HELPER_EXE.test(path50.posix.basename(e.name)));
30238
30622
  const depth = (name) => name.split("/").length;
30239
30623
  return games.map((e) => e.name).sort((a, b) => depth(a) - depth(b) || a.localeCompare(b));
30240
30624
  }
@@ -30316,7 +30700,7 @@ function exeArch(head) {
30316
30700
  }
30317
30701
  function engineOf(entries) {
30318
30702
  const names2 = entries.map((e) => e.name.toLowerCase());
30319
- const base = (name) => path49.posix.basename(name.replace(/\/$/, ""));
30703
+ const base = (name) => path50.posix.basename(name.replace(/\/$/, ""));
30320
30704
  if (names2.some((n) => /^unityplayer\.(dll|dylib|so)$/.test(base(n)) || base(n) === "globalgamemanagers")) return "unity";
30321
30705
  if (names2.some((n) => n.startsWith("engine/binaries/") || /\.app\/contents\/ue[45]?\//.test(n) || n.includes("/engine/binaries/")))
30322
30706
  return "unreal";
@@ -30360,7 +30744,7 @@ async function describeNativeArchive(zipPath, asked = {}) {
30360
30744
  try {
30361
30745
  entries = await readZipEntries(zipPath);
30362
30746
  } catch (err) {
30363
- throw new NativeBuildError(`Couldn't read ${path49.basename(zipPath)} as a zip: ${err.message}`);
30747
+ throw new NativeBuildError(`Couldn't read ${path50.basename(zipPath)} as a zip: ${err.message}`);
30364
30748
  }
30365
30749
  if (entries.length === 0) throw new NativeBuildError("The archive is empty.");
30366
30750
  const platform = platformOf(entries, asked.platform);
@@ -30382,14 +30766,14 @@ async function describeNativeArchive(zipPath, asked = {}) {
30382
30766
  ...mac?.appVersion ? { appVersion: mac.appVersion } : {}
30383
30767
  };
30384
30768
  }
30385
- function buildVersion(asked, appVersion, sha2564) {
30769
+ function buildVersion(asked, appVersion, sha2565) {
30386
30770
  if (asked !== void 0) {
30387
30771
  if (asked.length > VERSION_MAX || !VERSION2.test(asked)) {
30388
30772
  throw new NativeBuildError(`--build-version "${asked}" may hold only letters, digits and . _ + - (at most ${VERSION_MAX}).`);
30389
30773
  }
30390
30774
  return asked;
30391
30775
  }
30392
- const hash = sha2564.slice(0, VERSION_SHA_CHARS);
30776
+ const hash = sha2565.slice(0, VERSION_SHA_CHARS);
30393
30777
  const app = (appVersion ?? "").replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[^A-Za-z0-9]+/, "").slice(0, VERSION_MAX - hash.length - 1);
30394
30778
  return app ? `${app}+${hash}` : hash;
30395
30779
  }
@@ -30405,28 +30789,28 @@ async function hashFile(filePath, onProgress) {
30405
30789
  }
30406
30790
  async function packTarget(dir) {
30407
30791
  if (dir.toLowerCase().endsWith(".app")) return { target: dir, keepParent: true };
30408
- const children = await fs47.readdir(dir, { withFileTypes: true });
30792
+ const children = await fs48.readdir(dir, { withFileTypes: true });
30409
30793
  const apps = children.filter((d) => d.isDirectory() && d.name.toLowerCase().endsWith(".app"));
30410
- if (apps.length === 1) return { target: path49.join(dir, apps[0].name), keepParent: true };
30794
+ if (apps.length === 1) return { target: path50.join(dir, apps[0].name), keepParent: true };
30411
30795
  return { target: dir, keepParent: false };
30412
30796
  }
30413
- var windowsTar = () => path49.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "tar.exe");
30797
+ var windowsTar = () => path50.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "tar.exe");
30414
30798
  async function zipFolder(target, keepParent, zipPath, opts) {
30415
30799
  const platform = opts.platform ?? process.platform;
30416
30800
  if (platform === "darwin") {
30417
30801
  await run4("ditto", ["-c", "-k", "--sequesterRsrc", ...keepParent ? ["--keepParent"] : [], target, zipPath]);
30418
30802
  return;
30419
30803
  }
30420
- const cwd = keepParent ? path49.dirname(target) : target;
30804
+ const cwd = keepParent ? path50.dirname(target) : target;
30421
30805
  if (platform === "win32") {
30422
- const names2 = keepParent ? [path49.basename(target)] : await fs47.readdir(target);
30806
+ const names2 = keepParent ? [path50.basename(target)] : await fs48.readdir(target);
30423
30807
  await run4(opts.tar ?? windowsTar(), ["-a", "-c", "-f", zipPath, ...names2], { cwd });
30424
30808
  return;
30425
30809
  }
30426
- await run4("zip", ["-q", "-r", "-y", zipPath, keepParent ? path49.basename(target) : "."], { cwd });
30810
+ await run4("zip", ["-q", "-r", "-y", zipPath, keepParent ? path50.basename(target) : "."], { cwd });
30427
30811
  }
30428
30812
  async function packNativeBuild(input, opts = {}) {
30429
- const stat = await fs47.stat(input).catch(() => null);
30813
+ const stat = await fs48.stat(input).catch(() => null);
30430
30814
  if (!stat) throw new NativeBuildError(`${input} does not exist.`);
30431
30815
  if (stat.isFile()) {
30432
30816
  if (!input.toLowerCase().endsWith(".zip")) throw new NativeBuildError("Pass a .zip, a .app, or the build's folder.");
@@ -30434,9 +30818,9 @@ async function packNativeBuild(input, opts = {}) {
30434
30818
  } };
30435
30819
  }
30436
30820
  const { target, keepParent } = await packTarget(input);
30437
- const dir = await fs47.mkdtemp(path49.join(os14.tmpdir(), "genex-native-"));
30438
- const zipPath = path49.join(dir, `${path49.basename(target).replace(/[^A-Za-z0-9._-]+/g, "-") || "build"}.zip`);
30439
- const cleanup = () => fs47.rm(dir, { recursive: true, force: true });
30821
+ const dir = await fs48.mkdtemp(path50.join(os14.tmpdir(), "genex-native-"));
30822
+ const zipPath = path50.join(dir, `${path50.basename(target).replace(/[^A-Za-z0-9._-]+/g, "-") || "build"}.zip`);
30823
+ const cleanup = () => fs48.rm(dir, { recursive: true, force: true });
30440
30824
  try {
30441
30825
  await zipFolder(target, keepParent, zipPath, opts);
30442
30826
  await markPrograms(zipPath);
@@ -30452,7 +30836,7 @@ async function packNativeBuild(input, opts = {}) {
30452
30836
  }
30453
30837
 
30454
30838
  // src/lib/native-upload.ts
30455
- import fs48 from "fs/promises";
30839
+ import fs49 from "fs/promises";
30456
30840
  var PART_CONCURRENCY = 2;
30457
30841
  async function call(ctx, route, init, what, log) {
30458
30842
  let res;
@@ -30498,7 +30882,7 @@ async function uploadNativeArchive(grant, zipPath, sizeBytes, log, onProgress) {
30498
30882
  const base = grant.uploadUrl.replace(/\/+$/, "");
30499
30883
  const encoded = grant.uploadPath.split("/").map(encodeURIComponent).join("/");
30500
30884
  const auth = { Authorization: `Bearer ${grant.token}` };
30501
- const file = await fs48.open(zipPath, "r");
30885
+ const file = await fs49.open(zipPath, "r");
30502
30886
  try {
30503
30887
  if (sizeBytes <= grant.limits.singlePutMaxBytes) {
30504
30888
  const body = await readPart(file, 0, sizeBytes);
@@ -30601,7 +30985,7 @@ async function listInGallery(ctx, body, log) {
30601
30985
  }
30602
30986
 
30603
30987
  // src/lib/cloud-build.ts
30604
- import fs49 from "fs/promises";
30988
+ import fs50 from "fs/promises";
30605
30989
  var CLOUD_BUILD_PLATFORMS = ["windows", "linux", "mac"];
30606
30990
  var isCloudBuildPlatform = (value) => CLOUD_BUILD_PLATFORMS.includes(value);
30607
30991
  var CLOUD_BUILD_JOB_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
@@ -30718,7 +31102,7 @@ async function uploadCloudBuildSource(upload, zipPath, sizeBytes, log, onProgres
30718
31102
  log.error(`The API sent ${parts.length} upload URLs for a ${expected}-part project. Run this again.`);
30719
31103
  return null;
30720
31104
  }
30721
- const file = await fs49.open(zipPath, "r");
31105
+ const file = await fs50.open(zipPath, "r");
30722
31106
  const done = [];
30723
31107
  let sent = 0;
30724
31108
  let next = 0;
@@ -30752,8 +31136,8 @@ async function uploadCloudBuildSource(upload, zipPath, sizeBytes, log, onProgres
30752
31136
  }
30753
31137
 
30754
31138
  // src/lib/source-zip.ts
30755
- import fs50 from "fs/promises";
30756
- import path50 from "path";
31139
+ import fs51 from "fs/promises";
31140
+ import path51 from "path";
30757
31141
  import zlib4 from "zlib";
30758
31142
  var SOURCE_ZIP_ROOT_EXCLUDES = [".genex", "Intermediate", "Saved", "DerivedDataCache", "Binaries"];
30759
31143
  var SOURCE_ZIP_EXCLUDES_ANYWHERE = [".git", ".DS_Store", ".env"];
@@ -30771,23 +31155,23 @@ var U32_MAX2 = 4294967295;
30771
31155
  var CHUNK = 1024 * 1024;
30772
31156
  var isExcludedAnywhere = (name) => SOURCE_ZIP_EXCLUDES_ANYWHERE.includes(name) || ENV_VARIANT.test(name) && name !== ENV_TEMPLATE;
30773
31157
  function inside(root, target) {
30774
- const rel = path50.relative(root, target);
30775
- return rel === "" || rel !== ".." && !rel.startsWith(`..${path50.sep}`) && !path50.isAbsolute(rel);
31158
+ const rel = path51.relative(root, target);
31159
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${path51.sep}`) && !path51.isAbsolute(rel);
30776
31160
  }
30777
31161
  async function plan(root) {
30778
31162
  const entries = [];
30779
31163
  const skippedLinks = [];
30780
31164
  const walk2 = async (dir, rel) => {
30781
- const children = await fs50.readdir(dir, { withFileTypes: true });
31165
+ const children = await fs51.readdir(dir, { withFileTypes: true });
30782
31166
  let kept = 0;
30783
31167
  for (const child of children) {
30784
31168
  const name = child.name;
30785
31169
  const childRel = rel ? `${rel}/${name}` : name;
30786
- const abs = path50.join(dir, name);
31170
+ const abs = path51.join(dir, name);
30787
31171
  if (isExcludedAnywhere(name)) continue;
30788
31172
  if (!rel && SOURCE_ZIP_ROOT_EXCLUDES.includes(name)) continue;
30789
31173
  if (child.isSymbolicLink()) {
30790
- const target = await fs50.realpath(abs).catch(() => null);
31174
+ const target = await fs51.realpath(abs).catch(() => null);
30791
31175
  if (target !== null && !inside(root, target)) {
30792
31176
  throw new SourceZipError(
30793
31177
  `${childRel} is a link to ${target}, outside the folder being built. Copy what it points to into the project, or remove the link.`
@@ -30801,7 +31185,7 @@ async function plan(root) {
30801
31185
  await walk2(abs, childRel);
30802
31186
  } else if (child.isFile()) {
30803
31187
  kept++;
30804
- const { size } = await fs50.stat(abs);
31188
+ const { size } = await fs51.stat(abs);
30805
31189
  entries.push({ name: childRel, abs, size });
30806
31190
  }
30807
31191
  }
@@ -30847,8 +31231,8 @@ var Out = class {
30847
31231
  }
30848
31232
  };
30849
31233
  async function writeSourceZip(root, zipPath, opts = {}) {
30850
- const { entries, skippedLinks } = await plan(await fs50.realpath(root));
30851
- const file = await fs50.open(zipPath, "w");
31234
+ const { entries, skippedLinks } = await plan(await fs51.realpath(root));
31235
+ const file = await fs51.open(zipPath, "w");
30852
31236
  const out = new Out(file);
30853
31237
  const central = [];
30854
31238
  let contentBytes = 0;
@@ -30875,7 +31259,7 @@ async function writeSourceZip(root, zipPath, opts = {}) {
30875
31259
  await out.write(Buffer.concat([local, name, localExtra]));
30876
31260
  let crc = 0;
30877
31261
  if (entry.abs) {
30878
- const source = await fs50.open(entry.abs, "r");
31262
+ const source = await fs51.open(entry.abs, "r");
30879
31263
  try {
30880
31264
  let read = 0;
30881
31265
  for (; ; ) {
@@ -30966,12 +31350,12 @@ function zip64Extra(values) {
30966
31350
  return extra;
30967
31351
  }
30968
31352
  async function resolveZipSource(dir, cwd = process.cwd()) {
30969
- const abs = path50.resolve(cwd, dir);
30970
- const stat = await fs50.lstat(abs).catch(() => null);
31353
+ const abs = path51.resolve(cwd, dir);
31354
+ const stat = await fs51.lstat(abs).catch(() => null);
30971
31355
  if (!stat) return { refused: `--source ${abs} does not exist.` };
30972
31356
  if (stat.isSymbolicLink()) return { refused: `--source ${abs} is a symbolic link. Pass the folder itself.` };
30973
31357
  if (!stat.isDirectory()) return { refused: `--source ${abs} is not a folder.` };
30974
- return { dir: await fs50.realpath(abs) };
31358
+ return { dir: await fs51.realpath(abs) };
30975
31359
  }
30976
31360
 
30977
31361
  // src/commands/native.ts
@@ -31063,10 +31447,10 @@ async function runPublish2(opts, log) {
31063
31447
  const description = await describeNativeArchive(packed.zipPath, opts);
31064
31448
  log.step(describeLine(description));
31065
31449
  log.step("Hashing the archive\u2026");
31066
- const { sha256: sha2564, sizeBytes } = await hashFile(packed.zipPath);
31067
- const version = buildVersion(opts.buildVersion, description.appVersion, sha2564);
31450
+ const { sha256: sha2565, sizeBytes } = await hashFile(packed.zipPath);
31451
+ const version = buildVersion(opts.buildVersion, description.appVersion, sha2565);
31068
31452
  const { platform, arch, entry, installedBytes, minOs, engine } = description;
31069
- const grant = await requestNativeUpload(ctx, { platform, arch, sha256: sha2564, sizeBytes }, log);
31453
+ const grant = await requestNativeUpload(ctx, { platform, arch, sha256: sha2565, sizeBytes }, log);
31070
31454
  if (!grant) return false;
31071
31455
  if (grant.uploaded) {
31072
31456
  log.step(`Already uploaded (${sizeLine(sizeBytes)}).`);
@@ -31077,7 +31461,7 @@ async function runPublish2(opts, log) {
31077
31461
  }
31078
31462
  const build = await publishNativeBuild(
31079
31463
  ctx,
31080
- { platform, arch, sha256: sha2564, sizeBytes, installedBytes, version, entry, ...minOs ? { minOs } : {}, ...engine ? { engine } : {} },
31464
+ { platform, arch, sha256: sha2565, sizeBytes, installedBytes, version, entry, ...minOs ? { minOs } : {}, ...engine ? { engine } : {} },
31081
31465
  log
31082
31466
  );
31083
31467
  if (!build) return false;
@@ -31224,8 +31608,8 @@ async function runBuild(opts, log) {
31224
31608
  }
31225
31609
  const ctx = await context3(opts, log);
31226
31610
  if (!ctx) return false;
31227
- const tmp = await fs51.mkdtemp(path51.join(os15.tmpdir(), "genex-cloud-build-"));
31228
- const zipPath = path51.join(tmp, "source.zip");
31611
+ const tmp = await fs52.mkdtemp(path52.join(os15.tmpdir(), "genex-cloud-build-"));
31612
+ const zipPath = path52.join(tmp, "source.zip");
31229
31613
  try {
31230
31614
  log.step(`Zipping ${source.dir}\u2026`);
31231
31615
  let zipped;
@@ -31245,11 +31629,11 @@ async function runBuild(opts, log) {
31245
31629
  const more = zipped.skippedLinks.length > 5 ? ` and ${zipped.skippedLinks.length - 5} more` : "";
31246
31630
  log.warn(`Left out ${zipped.skippedLinks.length} link(s) inside the folder: ${shown}${more}.`);
31247
31631
  }
31248
- const { sha256: sha2564, sizeBytes } = await hashFile(zipPath);
31632
+ const { sha256: sha2565, sizeBytes } = await hashFile(zipPath);
31249
31633
  log.step(`${zipped.entries} files, ${sizeLine(sizeBytes)} zipped.`);
31250
31634
  const body = {
31251
31635
  platform: opts.platform,
31252
- sourceSha256: sha2564,
31636
+ sourceSha256: sha2565,
31253
31637
  sourceBytes: sizeBytes,
31254
31638
  ...opts.buildVersion !== void 0 ? { version: opts.buildVersion } : {}
31255
31639
  };
@@ -31294,7 +31678,7 @@ async function runBuild(opts, log) {
31294
31678
  if (opts.json) console.log(JSON.stringify({ build }));
31295
31679
  return true;
31296
31680
  } finally {
31297
- await fs51.rm(tmp, { recursive: true, force: true });
31681
+ await fs52.rm(tmp, { recursive: true, force: true });
31298
31682
  }
31299
31683
  }
31300
31684
  async function runBuildStatus(opts, log) {