@genex-ai/cli-demo 1.36.20-dev.1078 → 1.38.0-dev.1079

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +635 -68
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3123,15 +3123,15 @@ async function detectSurfaceScan(cwd = process.cwd()) {
3123
3123
  detail: `repeat.set(${m[1]}, ${m[2]})`
3124
3124
  });
3125
3125
  }
3126
- for (const call2 of ctorArgs(content, "PointsMaterial")) {
3127
- if (/\b(map|alphaMap)\s*:/.test(call2.text)) continue;
3126
+ for (const call3 of ctorArgs(content, "PointsMaterial")) {
3127
+ if (/\b(map|alphaMap)\s*:/.test(call3.text)) continue;
3128
3128
  found.squarePoints.push({
3129
- where: `${rel}:${lineOf(content, call2.index)}`,
3129
+ where: `${rel}:${lineOf(content, call3.index)}`,
3130
3130
  detail: "PointsMaterial with no map/alphaMap"
3131
3131
  });
3132
3132
  }
3133
- for (const call2 of ctorArgs(content, "PerspectiveCamera")) {
3134
- const args = splitArgs(call2.text);
3133
+ for (const call3 of ctorArgs(content, "PerspectiveCamera")) {
3134
+ const args = splitArgs(call3.text);
3135
3135
  if (args.length < 4) continue;
3136
3136
  const near = Number(args[2]);
3137
3137
  const far = Number(args[3]);
@@ -3139,7 +3139,7 @@ async function detectSurfaceScan(cwd = process.cwd()) {
3139
3139
  const ratio = far / near;
3140
3140
  if (ratio <= DEPTH_RATIO_LIMIT) continue;
3141
3141
  found.depthRange.push({
3142
- where: `${rel}:${lineOf(content, call2.index)}`,
3142
+ where: `${rel}:${lineOf(content, call3.index)}`,
3143
3143
  detail: `near ${near} / far ${far}`,
3144
3144
  ratio
3145
3145
  });
@@ -3434,7 +3434,7 @@ async function detectShellApis(cwd = process.cwd()) {
3434
3434
  for (const m of content.matchAll(DOWNLOAD_ASSIGN)) add(found.download, m.index, "a.download");
3435
3435
  }
3436
3436
  for (const m of content.matchAll(DOWNLOAD_ATTR)) add(found.download, m.index, "a download link");
3437
- for (const call2 of dialogCalls(content)) add(found.dialogs, call2.index, `${call2.name}()`);
3437
+ for (const call3 of dialogCalls(content)) add(found.dialogs, call3.index, `${call3.name}()`);
3438
3438
  for (const m of content.matchAll(SERVICE_WORKER)) {
3439
3439
  add(found.serviceWorker, m.index, "serviceWorker.register");
3440
3440
  }
@@ -8658,10 +8658,10 @@ var PACKAGE_PART = "package";
8658
8658
  function packagePart(zip) {
8659
8659
  return { kind: PACKAGE_PART, bytes: zip, contentType: MediaContentType.Zip };
8660
8660
  }
8661
- async function uploadItemPart(call2, skuUrl2, part) {
8661
+ async function uploadItemPart(call3, skuUrl2, part) {
8662
8662
  const isPackage = part.kind === PACKAGE_PART;
8663
8663
  const base = isPackage ? `${skuUrl2}/package` : `${skuUrl2}/media`;
8664
- const post = (url, body) => call2(url, { method: "POST", body: JSON.stringify(body) });
8664
+ const post = (url, body) => call3(url, { method: "POST", body: JSON.stringify(body) });
8665
8665
  const urlRes = await post(`${base}/upload-url`, isPackage ? { bytes: part.bytes.length } : { kind: part.kind, bytes: part.bytes.length, contentType: part.contentType });
8666
8666
  if (!urlRes.ok) return { ok: false, refused: urlRes, what: `upload the ${part.kind}` };
8667
8667
  const { uploadUrl, key } = await urlRes.json();
@@ -8680,19 +8680,19 @@ function bearerCaller(token) {
8680
8680
  const auth = { Authorization: `Bearer ${token}`, "Content-Type": "application/json" };
8681
8681
  return (url, init) => apiFetch(url, { ...init, headers: { ...auth, ...init?.headers ?? {} } });
8682
8682
  }
8683
- async function readPriceGrid(call2, apiUrl, projectId) {
8684
- const res = await call2(`${apiUrl}/api/coin/projects/${encodeURIComponent(projectId)}/skus`).catch(() => null);
8683
+ async function readPriceGrid(call3, apiUrl, projectId) {
8684
+ const res = await call3(`${apiUrl}/api/coin/projects/${encodeURIComponent(projectId)}/skus`).catch(() => null);
8685
8685
  if (!res?.ok) return null;
8686
8686
  const body = await res.json().catch(() => null);
8687
8687
  const grid = body?.priceGrid;
8688
8688
  return Array.isArray(grid) && grid.every((n) => typeof n === "number") ? grid : null;
8689
8689
  }
8690
- async function storeTakesFree(call2, apiUrl) {
8691
- const res = await call2(`${apiUrl}/api/store/catalog?limit=1`).catch(() => null);
8690
+ async function storeTakesFree(call3, apiUrl) {
8691
+ const res = await call3(`${apiUrl}/api/store/catalog?limit=1`).catch(() => null);
8692
8692
  return res?.ok === true;
8693
8693
  }
8694
- async function priceGridHint(call2, apiUrl, projectId) {
8695
- const [grid, free] = await Promise.all([readPriceGrid(call2, apiUrl, projectId), storeTakesFree(call2, apiUrl)]);
8694
+ async function priceGridHint(call3, apiUrl, projectId) {
8695
+ const [grid, free] = await Promise.all([readPriceGrid(call3, apiUrl, projectId), storeTakesFree(call3, apiUrl)]);
8696
8696
  return grid ? priceGridLine(grid, free) : null;
8697
8697
  }
8698
8698
  async function openLinkedGame(opts, log, rerun) {
@@ -9892,14 +9892,14 @@ async function runDomain(opts) {
9892
9892
  }
9893
9893
  const base = `${apiUrl}/api/projects/${encodeURIComponent(meta.id)}/domains`;
9894
9894
  const auth = { Authorization: `Bearer ${token}` };
9895
- const call2 = (url, init) => apiFetch(url, { ...init, headers: { ...auth, "Content-Type": "application/json", ...init?.headers ?? {} } });
9895
+ const call3 = (url, init) => apiFetch(url, { ...init, headers: { ...auth, "Content-Type": "application/json", ...init?.headers ?? {} } });
9896
9896
  let res;
9897
9897
  try {
9898
- if (sub === "list") res = await call2(base);
9899
- else if (sub === "add") res = await call2(base, { method: "POST", body: JSON.stringify({ hostname }) });
9898
+ if (sub === "list") res = await call3(base);
9899
+ else if (sub === "add") res = await call3(base, { method: "POST", body: JSON.stringify({ hostname }) });
9900
9900
  else if (sub === "verify")
9901
- res = await call2(`${base}/${encodeURIComponent(hostname)}/verify`, { method: "POST" });
9902
- else res = await call2(`${base}/${encodeURIComponent(hostname)}`, { method: "DELETE" });
9901
+ res = await call3(`${base}/${encodeURIComponent(hostname)}/verify`, { method: "POST" });
9902
+ else res = await call3(`${base}/${encodeURIComponent(hostname)}`, { method: "DELETE" });
9903
9903
  } catch (err) {
9904
9904
  log.error(`Couldn't reach the API at ${apiUrl}.`);
9905
9905
  log.dim(` ${String(err)}`);
@@ -23134,7 +23134,7 @@ async function runController(opts) {
23134
23134
  log.plain(c.bold(`genex controller ${kind}`));
23135
23135
  log.plain("");
23136
23136
  log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path34.sep)}`);
23137
- const plan = [
23137
+ const plan2 = [
23138
23138
  ...set.code.map((rel) => ({ from: rel, rel: path34.join(CODE_DEST, rel) })),
23139
23139
  ...set.assets.map((rel) => ({
23140
23140
  from: rel,
@@ -23144,7 +23144,7 @@ async function runController(opts) {
23144
23144
  let copied = 0;
23145
23145
  let skipped = 0;
23146
23146
  try {
23147
- for (const file of plan) {
23147
+ for (const file of plan2) {
23148
23148
  const dest = path34.join(root, file.rel);
23149
23149
  if (!opts.force && await exists4(dest)) {
23150
23150
  skipped++;
@@ -24164,22 +24164,22 @@ async function context2(opts) {
24164
24164
  if (!token) return null;
24165
24165
  return { token, apiUrl };
24166
24166
  }
24167
- async function readVideo(path50, log) {
24167
+ async function readVideo(path52, log) {
24168
24168
  let bytes;
24169
24169
  try {
24170
- bytes = await readFile(path50);
24170
+ bytes = await readFile(path52);
24171
24171
  } catch {
24172
- log.error(`Can't read ${path50}.`);
24172
+ log.error(`Can't read ${path52}.`);
24173
24173
  return null;
24174
24174
  }
24175
24175
  if (bytes.byteLength > MAX_VIDEO_BYTES) {
24176
- log.error(`${basename(path50)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
24176
+ log.error(`${basename(path52)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
24177
24177
  return null;
24178
24178
  }
24179
24179
  return bytes;
24180
24180
  }
24181
- async function uploadVideo(apiUrl, token, characterId, path50, bytes, log) {
24182
- const contentType = /\.mov$/i.test(path50) ? "video/quicktime" : "video/mp4";
24181
+ async function uploadVideo(apiUrl, token, characterId, path52, bytes, log) {
24182
+ const contentType = /\.mov$/i.test(path52) ? "video/quicktime" : "video/mp4";
24183
24183
  const minted = await apiFetch(
24184
24184
  `${apiUrl}/api/characters/${encodeURIComponent(characterId)}/motions/video-url`,
24185
24185
  {
@@ -24194,7 +24194,7 @@ async function uploadVideo(apiUrl, token, characterId, path50, bytes, log) {
24194
24194
  return null;
24195
24195
  }
24196
24196
  const { uploadUrl, videoUrl } = await minted.json();
24197
- log.dim(` uploading ${basename(path50)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
24197
+ log.dim(` uploading ${basename(path52)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
24198
24198
  const put = await fetch(uploadUrl, {
24199
24199
  method: "PUT",
24200
24200
  headers: { "Content-Type": contentType, "Content-Length": String(bytes.byteLength) },
@@ -24206,9 +24206,9 @@ async function uploadVideo(apiUrl, token, characterId, path50, bytes, log) {
24206
24206
  }
24207
24207
  return videoUrl;
24208
24208
  }
24209
- function printPlan(plan, log, creature) {
24209
+ function printPlan(plan2, log, creature) {
24210
24210
  log.plain(c.bold(creature ? "Creature animation plan" : "Animation plan"));
24211
- for (const item of plan.items) {
24211
+ for (const item of plan2.items) {
24212
24212
  const count = item.clips > 1 ? ` (${item.clips} clips)` : "";
24213
24213
  log.plain(` \xB7 ${c.bold(item.request)}${count} \u2014 ${item.explanation}`);
24214
24214
  if (item.supersededSlots.length > 0) {
@@ -24217,14 +24217,14 @@ function printPlan(plan, log, creature) {
24217
24217
  );
24218
24218
  }
24219
24219
  }
24220
- if (plan.secondsApplied === false) {
24220
+ if (plan2.secondsApplied === false) {
24221
24221
  log.plain(
24222
24222
  ` ${c.dim("--duration sets the reference-video length; none of these verbs took the video route, so it did nothing.")}`
24223
24223
  );
24224
24224
  }
24225
24225
  log.plain("");
24226
- if (plan.credits > 0) log.dim(` ${plan.totalClips} clips \xB7 ${plan.credits} credits`);
24227
- else log.dim(` ${plan.totalClips} clips`);
24226
+ if (plan2.credits > 0) log.dim(` ${plan2.totalClips} clips \xB7 ${plan2.credits} credits`);
24227
+ else log.dim(` ${plan2.totalClips} clips`);
24228
24228
  log.plain("");
24229
24229
  }
24230
24230
  async function runCharacterAnimateMotion(opts) {
@@ -26959,10 +26959,10 @@ async function reportModels(apiUrl, token, opts, log, toolsOnly = false) {
26959
26959
  shown === lane.models ? ` ${lane.models.length} model${lane.models.length === 1 ? "" : "s"} live:` : ` ${shown.length} featured model${shown.length === 1 ? "" : "s"} of ${lane.models.length} live:`
26960
26960
  );
26961
26961
  for (const m of shown) {
26962
- const plan = m.personalPlan ? ` \xB7 personal plan: ${m.personalPlan}` : "";
26962
+ const plan2 = m.personalPlan ? ` \xB7 personal plan: ${m.personalPlan}` : "";
26963
26963
  const kind = m.kind === "typesafe" ? " \xB7 judge (classifier): judge() under a budget, not generate() or bench" : "";
26964
26964
  log.plain(
26965
- ` ${c.cyan(m.id)} ${m.label} \u2014 ${usdPerMillion(m.inputUsdPerMillion)} in / ${usdPerMillion(m.outputUsdPerMillion)} out per million tokens${plan}${kind}`
26965
+ ` ${c.cyan(m.id)} ${m.label} \u2014 ${usdPerMillion(m.inputUsdPerMillion)} in / ${usdPerMillion(m.outputUsdPerMillion)} out per million tokens${plan2}${kind}`
26966
26966
  );
26967
26967
  }
26968
26968
  if (hiddenCount > 0) {
@@ -27125,7 +27125,7 @@ async function runBench(args) {
27125
27125
  const samples = opts.samples ?? DEFAULT_BENCH_SAMPLES;
27126
27126
  const prompt = opts.benchPrompt.trim();
27127
27127
  const auth = { Authorization: `Bearer ${token}`, "Content-Type": "application/json" };
27128
- const call2 = (url, init) => apiFetch(url, { ...init, headers: { ...auth, ...init?.headers ?? {} } });
27128
+ const call3 = (url, init) => apiFetch(url, { ...init, headers: { ...auth, ...init?.headers ?? {} } });
27129
27129
  let schema;
27130
27130
  if (opts.schemaPath) {
27131
27131
  try {
@@ -27204,7 +27204,7 @@ async function runBench(args) {
27204
27204
  if (!opts.json) log.warn("Stopping \u2014 cancelling the attempt in flight.");
27205
27205
  void (async () => {
27206
27206
  if (target) {
27207
- await call2(`${base}/${encodeURIComponent(target)}/cancel`, { method: "POST" }).catch(() => {
27207
+ await call3(`${base}/${encodeURIComponent(target)}/cancel`, { method: "POST" }).catch(() => {
27208
27208
  });
27209
27209
  }
27210
27210
  process.exit(130);
@@ -27213,7 +27213,7 @@ async function runBench(args) {
27213
27213
  process.once("SIGINT", onSigint);
27214
27214
  try {
27215
27215
  for (let i = 0; i < samples; i++) {
27216
- const res = await call2(base, {
27216
+ const res = await call3(base, {
27217
27217
  method: "POST",
27218
27218
  body: JSON.stringify({
27219
27219
  maxCredits,
@@ -27227,7 +27227,7 @@ async function runBench(args) {
27227
27227
  })
27228
27228
  });
27229
27229
  if (!res.ok) {
27230
- const verdict = await explainBenchRefusal(res, call2, base, log, opts, i, results.length);
27230
+ const verdict = await explainBenchRefusal(res, call3, base, log, opts, i, results.length);
27231
27231
  if (verdict.refusal) refusal2 = verdict.refusal;
27232
27232
  if (verdict.stop) break;
27233
27233
  continue;
@@ -27238,7 +27238,7 @@ async function runBench(args) {
27238
27238
  continue;
27239
27239
  }
27240
27240
  inflight = started.id;
27241
- const settled = await pollSettled(call2, `${base}/${encodeURIComponent(started.id)}`, opts.timeoutSec ?? DEFAULT_SAMPLE_TIMEOUT_SEC);
27241
+ const settled = await pollSettled(call3, `${base}/${encodeURIComponent(started.id)}`, opts.timeoutSec ?? DEFAULT_SAMPLE_TIMEOUT_SEC);
27242
27242
  inflight = null;
27243
27243
  const row = sampleFrom(started.id, settled);
27244
27244
  results.push(row);
@@ -27441,7 +27441,7 @@ function printAverage(log, record, attempts) {
27441
27441
  log.dim(` = that average rounded up to a whole credit. disclosure.estimatedCreditsPerPeriod = your calls per period \xD7 ${formatCredits(record.averageCreditsPerCall)}, rounded up.`);
27442
27442
  }
27443
27443
  }
27444
- async function explainBenchRefusal(res, call2, base, log, opts, index, settledSoFar) {
27444
+ async function explainBenchRefusal(res, call3, base, log, opts, index, settledSoFar) {
27445
27445
  if (printedStructuredError(res)) {
27446
27446
  const printed = await res.json().catch(() => ({}));
27447
27447
  return { stop: true, refusal: { status: res.status, error: typeof printed.error === "string" ? printed.error : null, slotsHeld: null, slotLimit: null } };
@@ -27449,7 +27449,7 @@ async function explainBenchRefusal(res, call2, base, log, opts, index, settledSo
27449
27449
  const body = await res.json().catch(() => ({}));
27450
27450
  const refusal2 = { status: res.status, error: body.error ?? null, slotsHeld: null, slotLimit: null };
27451
27451
  if (res.status === 429 && body.error === "generation_limit") {
27452
- const counts = serverSlotCounts(body) ?? await readSlotCounts(call2, base);
27452
+ const counts = serverSlotCounts(body) ?? await readSlotCounts(call3, base);
27453
27453
  refusal2.slotsHeld = counts?.held ?? null;
27454
27454
  refusal2.slotLimit = counts?.limit ?? null;
27455
27455
  if (!opts.json) log.error(` Sample ${index + 1} ${benchSlotsHeldSentence(counts)}`);
@@ -27490,17 +27490,17 @@ function printProviderRefusal(log, status2, message, awaitingBill = false) {
27490
27490
  log.dim(` A ${status2} here is this stand's provider configuration refusing the model \u2014 a message for the operator, not something to fix in the game.`);
27491
27491
  }
27492
27492
  }
27493
- async function readSlotCounts(call2, base) {
27494
- const res = await call2(`${base}?scope=open`).catch(() => null);
27493
+ async function readSlotCounts(call3, base) {
27494
+ const res = await call3(`${base}?scope=open`).catch(() => null);
27495
27495
  if (!res?.ok) return null;
27496
27496
  const body = await res.json().catch(() => null);
27497
27497
  return serverSlotCounts(body);
27498
27498
  }
27499
- async function pollSettled(call2, url, timeoutSec) {
27499
+ async function pollSettled(call3, url, timeoutSec) {
27500
27500
  const deadline = Date.now() + timeoutSec * 1e3;
27501
27501
  let last = null;
27502
27502
  while (Date.now() < deadline) {
27503
- const res = await call2(url).catch(() => null);
27503
+ const res = await call3(url).catch(() => null);
27504
27504
  if (res?.ok) {
27505
27505
  last = await res.json().catch(() => null);
27506
27506
  if (last?.billingStatus === "final") return last;
@@ -27563,9 +27563,9 @@ function emptyBenchJson(apiUrl, projectId, prompt, outputFormat, samples, maxCre
27563
27563
  }
27564
27564
  async function reportStatus(args) {
27565
27565
  const { apiUrl, token, projectId, opts, log } = args;
27566
- const call2 = (url, init) => apiFetch(url, { ...init, headers: { Authorization: `Bearer ${token}`, ...init?.headers ?? {} } });
27566
+ const call3 = (url, init) => apiFetch(url, { ...init, headers: { Authorization: `Bearer ${token}`, ...init?.headers ?? {} } });
27567
27567
  const base = `${apiUrl}/api/runtime/development/projects/${encodeURIComponent(projectId)}/generations`;
27568
- const res = await call2(`${base}?scope=open`).catch(() => null);
27568
+ const res = await call3(`${base}?scope=open`).catch(() => null);
27569
27569
  if (!res || !res.ok) {
27570
27570
  if (res && printedStructuredError(res)) {
27571
27571
  if (opts.json) writeJsonLine(statusJson(apiUrl, projectId, "failed", `http_${res.status}`, [], null, null));
@@ -27656,14 +27656,14 @@ function statusJson(apiUrl, projectId, status2, error, rows, slotsHeld, slotLimi
27656
27656
  }
27657
27657
  async function cancelCall(args) {
27658
27658
  const { apiUrl, token, projectId, id, opts, log } = args;
27659
- const call2 = (url, init) => apiFetch(url, { ...init, headers: { Authorization: `Bearer ${token}`, ...init?.headers ?? {} } });
27659
+ const call3 = (url, init) => apiFetch(url, { ...init, headers: { Authorization: `Bearer ${token}`, ...init?.headers ?? {} } });
27660
27660
  const base = `${apiUrl}/api/runtime/development/projects/${encodeURIComponent(projectId)}/generations`;
27661
27661
  const rowUrl = `${base}/${encodeURIComponent(id)}`;
27662
27662
  const done = (status2, error, reason, row2) => {
27663
27663
  if (opts.json) writeJsonLine({ command: "llm cancel", status: status2, error, id, cancelled: reason === "cancelled", reason, generation: row2 });
27664
27664
  if (status2 === "failed") process.exitCode = 1;
27665
27665
  };
27666
- const read = await call2(rowUrl).catch(() => null);
27666
+ const read = await call3(rowUrl).catch(() => null);
27667
27667
  if (!read || !read.ok) {
27668
27668
  if (read && printedStructuredError(read)) return done("failed", `http_${read.status}`, null, null);
27669
27669
  const body = read ? await read.json().catch(() => ({})) : {};
@@ -27702,7 +27702,7 @@ async function cancelCall(args) {
27702
27702
  }
27703
27703
  return done("ok", null, state === "awaiting bill" ? "awaiting_bill" : "already_final", row);
27704
27704
  }
27705
- const res = await call2(`${rowUrl}/cancel`, { method: "POST" }).catch(() => null);
27705
+ const res = await call3(`${rowUrl}/cancel`, { method: "POST" }).catch(() => null);
27706
27706
  if (!res || !res.ok) {
27707
27707
  if (res && printedStructuredError(res)) return done("failed", `http_${res.status}`, null, row);
27708
27708
  const body = res ? await res.json().catch(() => ({})) : {};
@@ -28658,14 +28658,14 @@ var PlayerClient = class {
28658
28658
  this.version = options.version ?? getCliVersion();
28659
28659
  this.timeoutMs = options.timeoutMs ?? 3e4;
28660
28660
  }
28661
- async call(path50, init = {}, provider) {
28661
+ async call(path52, init = {}, provider) {
28662
28662
  const headers = {
28663
28663
  Authorization: `Bearer ${this.token}`,
28664
28664
  [PLAYER_VERSION_HEADER]: this.version,
28665
28665
  ...provider ? { [EXTERNAL_PROVIDER_HEADER]: provider } : {},
28666
28666
  ...init.body === void 0 ? {} : { "Content-Type": "application/json" }
28667
28667
  };
28668
- const res = await apiFetch(`${this.apiUrl}${path50}`, {
28668
+ const res = await apiFetch(`${this.apiUrl}${path52}`, {
28669
28669
  ...init,
28670
28670
  headers,
28671
28671
  signal: AbortSignal.timeout(this.timeoutMs)
@@ -29261,20 +29261,20 @@ async function markRelaunch(version, home) {
29261
29261
  }
29262
29262
  }
29263
29263
  async function relaunchWatcher(input) {
29264
- const plan = relaunchPlan(input);
29264
+ const plan2 = relaunchPlan(input);
29265
29265
  const log = input.log ?? (() => {
29266
29266
  });
29267
- if (input.relaunched) return { launched: false, plan, reason: "relaunched_child" };
29268
- if (!relaunchPossible()) return { launched: false, plan, reason: "no_npx" };
29269
- if (await relaunchOnCooldown(input.home)) return { launched: false, plan, reason: "cooldown" };
29267
+ if (input.relaunched) return { launched: false, plan: plan2, reason: "relaunched_child" };
29268
+ if (!relaunchPossible()) return { launched: false, plan: plan2, reason: "no_npx" };
29269
+ if (await relaunchOnCooldown(input.home)) return { launched: false, plan: plan2, reason: "cooldown" };
29270
29270
  await markRelaunch(input.version, input.home);
29271
29271
  if (supervisedWatcher()) {
29272
29272
  log(`player: stopping so the login item restarts as ${input.version} (it resolves ${PLAYER_AUTOSTART_SPEC} each start)`);
29273
- return { launched: false, plan, reason: "supervisor_restarts" };
29273
+ return { launched: false, plan: plan2, reason: "supervisor_restarts" };
29274
29274
  }
29275
29275
  if (playerDryRun()) {
29276
- log(`player: would relaunch: ${JSON.stringify([plan.launcher, ...plan.args])}`);
29277
- return { launched: true, plan, reason: "dry_run" };
29276
+ log(`player: would relaunch: ${JSON.stringify([plan2.launcher, ...plan2.args])}`);
29277
+ return { launched: true, plan: plan2, reason: "dry_run" };
29278
29278
  }
29279
29279
  let out;
29280
29280
  let err;
@@ -29286,7 +29286,7 @@ async function relaunchWatcher(input) {
29286
29286
  } catch {
29287
29287
  }
29288
29288
  try {
29289
- const child = spawn7(plan.launcher, plan.args, {
29289
+ const child = spawn7(plan2.launcher, plan2.args, {
29290
29290
  detached: true,
29291
29291
  stdio: ["ignore", out ?? "ignore", err ?? "ignore"]
29292
29292
  });
@@ -29299,7 +29299,7 @@ async function relaunchWatcher(input) {
29299
29299
  }
29300
29300
  }
29301
29301
  log(`player: relaunching as ${input.version}`);
29302
- return { launched: true, plan };
29302
+ return { launched: true, plan: plan2 };
29303
29303
  }
29304
29304
 
29305
29305
  // src/lib/player/loop.ts
@@ -29989,6 +29989,11 @@ function writeJsonLine2(value) {
29989
29989
  `);
29990
29990
  }
29991
29991
 
29992
+ // src/commands/native.ts
29993
+ import fs51 from "fs/promises";
29994
+ import os15 from "os";
29995
+ import path51 from "path";
29996
+
29992
29997
  // src/lib/native-build.ts
29993
29998
  import { createHash as createHash5 } from "crypto";
29994
29999
  import { createReadStream } from "fs";
@@ -30595,6 +30600,380 @@ async function listInGallery(ctx, body, log) {
30595
30600
  return null;
30596
30601
  }
30597
30602
 
30603
+ // src/lib/cloud-build.ts
30604
+ import fs49 from "fs/promises";
30605
+ var CLOUD_BUILD_PLATFORMS = ["windows", "linux", "mac"];
30606
+ var isCloudBuildPlatform = (value) => CLOUD_BUILD_PLATFORMS.includes(value);
30607
+ var CLOUD_BUILD_JOB_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
30608
+ var UPLOAD_INCOMPLETE = "cloud_build_upload_incomplete";
30609
+ var PART_ATTEMPTS = 3;
30610
+ var PART_BACKOFF_MS = [500, 2e3];
30611
+ var PART_CONCURRENCY2 = 2;
30612
+ async function call2(ctx, route, init, log, silent) {
30613
+ let res;
30614
+ try {
30615
+ res = await apiFetch(`${ctx.apiUrl}/api/games/${ctx.projectId}${route}`, {
30616
+ ...init,
30617
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${ctx.token}`, ...init.headers ?? {} }
30618
+ });
30619
+ } catch (err) {
30620
+ log.error(`Couldn't reach the API at ${ctx.apiUrl}: ${String(err)}`);
30621
+ return { ok: false, refusal: { status: 0, body: null } };
30622
+ }
30623
+ const body = await res.clone().json().catch(() => null);
30624
+ if (res.ok && body) return { ok: true, value: body, status: res.status };
30625
+ const error = typeof body?.error === "string" ? body.error : void 0;
30626
+ const refusal2 = { status: res.status, body: body && typeof body === "object" ? body : null, ...error ? { error } : {} };
30627
+ if (res.ok) {
30628
+ log.error(`The API answered HTTP ${res.status} with no build.`);
30629
+ return { ok: false, refusal: refusal2 };
30630
+ }
30631
+ if (silent !== void 0 && error === silent) return { ok: false, refusal: refusal2 };
30632
+ if (res.status === 401) {
30633
+ log.error("Not authorized (HTTP 401) \u2014 your sign-in expired or was revoked.");
30634
+ log.plain(" Fix: run `genex link <your-game>` and sign in as the game's owner, then re-run this command.");
30635
+ } else if (res.status === 404) {
30636
+ log.error(
30637
+ route.includes("/cloud-builds/") ? "No such build for this game (HTTP 404)." : "This account doesn't own this game, or Build on Genex is not on for this stand (HTTP 404)."
30638
+ );
30639
+ } else if (res.status === 429 && error === "rate_limited") {
30640
+ log.error(`Rate limited starting a build (HTTP 429).${retryAfterHint(res)}`);
30641
+ } else if (error === "cloud_build_unavailable") {
30642
+ log.error("Build on Genex isn't available on this stand right now (HTTP 503). Try again later.");
30643
+ } else {
30644
+ const reason = await serverReason(res);
30645
+ log.error(reason ?? `The API refused the build (HTTP ${res.status}).`);
30646
+ }
30647
+ return { ok: false, refusal: refusal2 };
30648
+ }
30649
+ async function startCloudBuild(ctx, body, log) {
30650
+ const answer = await call2(ctx, "/cloud-builds", { method: "POST", body: JSON.stringify(body) }, log);
30651
+ if (!answer.ok) return answer;
30652
+ const v = answer.value;
30653
+ if (!v.build) {
30654
+ log.error(`The API answered HTTP ${answer.status} with no build.`);
30655
+ return { ok: false, refusal: { status: answer.status, body: answer.value } };
30656
+ }
30657
+ return {
30658
+ ok: true,
30659
+ status: answer.status,
30660
+ value: {
30661
+ status: answer.status,
30662
+ build: v.build,
30663
+ ...v.upload ? { upload: v.upload } : {},
30664
+ ...v.deduplicated ? { deduplicated: true } : {}
30665
+ }
30666
+ };
30667
+ }
30668
+ async function completeCloudBuildSource(ctx, jobId, parts, log) {
30669
+ const route = `/cloud-builds/${encodeURIComponent(jobId)}/source`;
30670
+ const answer = await call2(ctx, route, { method: "POST", body: JSON.stringify({ parts }) }, log, UPLOAD_INCOMPLETE);
30671
+ if (!answer.ok) return answer;
30672
+ const build = answer.value.build;
30673
+ if (!build) {
30674
+ log.error(`The API answered HTTP ${answer.status} with no build.`);
30675
+ return { ok: false, refusal: { status: answer.status, body: answer.value } };
30676
+ }
30677
+ return { ok: true, status: answer.status, value: build };
30678
+ }
30679
+ async function readCloudBuild(ctx, jobId, log) {
30680
+ const answer = await call2(ctx, `/cloud-builds/${encodeURIComponent(jobId)}`, { method: "GET" }, log);
30681
+ if (!answer.ok) return answer;
30682
+ const build = answer.value.build;
30683
+ if (!build) {
30684
+ log.error(`The API answered HTTP ${answer.status} with no build.`);
30685
+ return { ok: false, refusal: { status: answer.status, body: answer.value } };
30686
+ }
30687
+ return { ok: true, status: answer.status, value: build };
30688
+ }
30689
+ async function readPart2(file, position, length) {
30690
+ const buffer = Buffer.alloc(length);
30691
+ const { bytesRead } = await file.read(buffer, 0, length, position);
30692
+ if (bytesRead !== length) throw new Error("the project zip changed while it was being uploaded");
30693
+ return buffer;
30694
+ }
30695
+ async function putPart(url, bytes, sleep7) {
30696
+ for (let attempt = 0; attempt < PART_ATTEMPTS; attempt++) {
30697
+ let res = null;
30698
+ try {
30699
+ res = await fetch(url, { method: "PUT", body: bytes });
30700
+ } catch {
30701
+ res = null;
30702
+ }
30703
+ if (res?.ok) {
30704
+ const etag = res.headers.get("etag");
30705
+ await res.arrayBuffer().catch(() => void 0);
30706
+ if (etag) return etag;
30707
+ } else if (res && res.status < 500 && res.status !== 408 && res.status !== 429) {
30708
+ return null;
30709
+ }
30710
+ if (attempt < PART_ATTEMPTS - 1) await sleep7(PART_BACKOFF_MS[attempt] ?? 2e3);
30711
+ }
30712
+ return null;
30713
+ }
30714
+ async function uploadCloudBuildSource(upload, zipPath, sizeBytes, log, onProgress, sleep7 = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
30715
+ const parts = [...upload.parts].sort((a, b) => a.partNumber - b.partNumber);
30716
+ const expected = Math.ceil(sizeBytes / upload.partBytes);
30717
+ if (parts.length !== expected || parts.some((p, i) => p.partNumber !== i + 1)) {
30718
+ log.error(`The API sent ${parts.length} upload URLs for a ${expected}-part project. Run this again.`);
30719
+ return null;
30720
+ }
30721
+ const file = await fs49.open(zipPath, "r");
30722
+ const done = [];
30723
+ let sent = 0;
30724
+ let next = 0;
30725
+ let failed = false;
30726
+ try {
30727
+ const worker = async () => {
30728
+ while (!failed) {
30729
+ const index = next++;
30730
+ const part = parts[index];
30731
+ if (!part) return;
30732
+ const start = index * upload.partBytes;
30733
+ const length = Math.min(upload.partBytes, sizeBytes - start);
30734
+ const bytes = await readPart2(file, start, length);
30735
+ const etag = await putPart(part.url, bytes, sleep7);
30736
+ if (!etag) {
30737
+ failed = true;
30738
+ log.error(`Upload failed (part ${part.partNumber}/${parts.length}).`);
30739
+ return;
30740
+ }
30741
+ done.push({ partNumber: part.partNumber, etag });
30742
+ sent += length;
30743
+ onProgress?.(sent);
30744
+ }
30745
+ };
30746
+ await Promise.all(Array.from({ length: Math.min(PART_CONCURRENCY2, parts.length) }, () => worker()));
30747
+ } finally {
30748
+ await file.close();
30749
+ }
30750
+ if (failed) return null;
30751
+ return done.sort((a, b) => a.partNumber - b.partNumber);
30752
+ }
30753
+
30754
+ // src/lib/source-zip.ts
30755
+ import fs50 from "fs/promises";
30756
+ import path50 from "path";
30757
+ import zlib4 from "zlib";
30758
+ var SOURCE_ZIP_ROOT_EXCLUDES = [".genex", "Intermediate", "Saved", "DerivedDataCache", "Binaries"];
30759
+ var SOURCE_ZIP_EXCLUDES_ANYWHERE = [".git", ".DS_Store", ".env"];
30760
+ var ENV_VARIANT = /^\.env\./;
30761
+ var ENV_TEMPLATE = ".env.example";
30762
+ var SourceZipError = class extends Error {
30763
+ };
30764
+ var DOS_TIME = 0;
30765
+ var DOS_DATE2 = 0 << 9 | 1 << 5 | 1;
30766
+ var UTF8_FLAG = 2048;
30767
+ var VERSION_STORED = 20;
30768
+ var VERSION_ZIP64 = 45;
30769
+ var U16_MAX2 = 65535;
30770
+ var U32_MAX2 = 4294967295;
30771
+ var CHUNK = 1024 * 1024;
30772
+ var isExcludedAnywhere = (name) => SOURCE_ZIP_EXCLUDES_ANYWHERE.includes(name) || ENV_VARIANT.test(name) && name !== ENV_TEMPLATE;
30773
+ function inside(root, target) {
30774
+ const rel = path50.relative(root, target);
30775
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${path50.sep}`) && !path50.isAbsolute(rel);
30776
+ }
30777
+ async function plan(root) {
30778
+ const entries = [];
30779
+ const skippedLinks = [];
30780
+ const walk2 = async (dir, rel) => {
30781
+ const children = await fs50.readdir(dir, { withFileTypes: true });
30782
+ let kept = 0;
30783
+ for (const child of children) {
30784
+ const name = child.name;
30785
+ const childRel = rel ? `${rel}/${name}` : name;
30786
+ const abs = path50.join(dir, name);
30787
+ if (isExcludedAnywhere(name)) continue;
30788
+ if (!rel && SOURCE_ZIP_ROOT_EXCLUDES.includes(name)) continue;
30789
+ if (child.isSymbolicLink()) {
30790
+ const target = await fs50.realpath(abs).catch(() => null);
30791
+ if (target !== null && !inside(root, target)) {
30792
+ throw new SourceZipError(
30793
+ `${childRel} is a link to ${target}, outside the folder being built. Copy what it points to into the project, or remove the link.`
30794
+ );
30795
+ }
30796
+ skippedLinks.push(childRel);
30797
+ continue;
30798
+ }
30799
+ if (child.isDirectory()) {
30800
+ kept++;
30801
+ await walk2(abs, childRel);
30802
+ } else if (child.isFile()) {
30803
+ kept++;
30804
+ const { size } = await fs50.stat(abs);
30805
+ entries.push({ name: childRel, abs, size });
30806
+ }
30807
+ }
30808
+ if (rel && kept === 0) entries.push({ name: `${rel}/`, size: 0 });
30809
+ };
30810
+ await walk2(root, "");
30811
+ entries.sort((a, b) => Buffer.compare(Buffer.from(a.name, "utf8"), Buffer.from(b.name, "utf8")));
30812
+ skippedLinks.sort();
30813
+ return { entries, skippedLinks };
30814
+ }
30815
+ var crcTable = (() => {
30816
+ const table = new Uint32Array(256);
30817
+ for (let n = 0; n < 256; n++) {
30818
+ let c2 = n;
30819
+ for (let k = 0; k < 8; k++) c2 = c2 & 1 ? 3988292384 ^ c2 >>> 1 : c2 >>> 1;
30820
+ table[n] = c2 >>> 0;
30821
+ }
30822
+ return table;
30823
+ })();
30824
+ var zlibCrc = zlib4.crc32;
30825
+ function crc32(data, value) {
30826
+ if (zlibCrc) return zlibCrc(data, value);
30827
+ let c2 = (value ^ U32_MAX2) >>> 0;
30828
+ for (let i = 0; i < data.length; i++) c2 = crcTable[(c2 ^ data[i]) & 255] ^ c2 >>> 8;
30829
+ return (c2 ^ U32_MAX2) >>> 0;
30830
+ }
30831
+ var Out = class {
30832
+ position = 0;
30833
+ file;
30834
+ constructor(file) {
30835
+ this.file = file;
30836
+ }
30837
+ async write(buffer) {
30838
+ let done = 0;
30839
+ while (done < buffer.length) {
30840
+ const { bytesWritten } = await this.file.write(buffer, done, buffer.length - done, this.position + done);
30841
+ done += bytesWritten;
30842
+ }
30843
+ this.position += buffer.length;
30844
+ }
30845
+ async patch(at, buffer) {
30846
+ await this.file.write(buffer, 0, buffer.length, at);
30847
+ }
30848
+ };
30849
+ async function writeSourceZip(root, zipPath, opts = {}) {
30850
+ const { entries, skippedLinks } = await plan(await fs50.realpath(root));
30851
+ const file = await fs50.open(zipPath, "w");
30852
+ const out = new Out(file);
30853
+ const central = [];
30854
+ let contentBytes = 0;
30855
+ const chunk = Buffer.alloc(CHUNK);
30856
+ try {
30857
+ for (const entry of entries) {
30858
+ const name = Buffer.from(entry.name, "utf8");
30859
+ const offset = out.position;
30860
+ const bigSize = opts.forceZip64 || entry.size >= U32_MAX2;
30861
+ const bigOffset = opts.forceZip64 || offset >= U32_MAX2;
30862
+ const localExtra = bigSize ? zip64Extra([entry.size, entry.size]) : Buffer.alloc(0);
30863
+ const local = Buffer.alloc(30);
30864
+ local.writeUInt32LE(67324752, 0);
30865
+ local.writeUInt16LE(bigSize ? VERSION_ZIP64 : VERSION_STORED, 4);
30866
+ local.writeUInt16LE(UTF8_FLAG, 6);
30867
+ local.writeUInt16LE(0, 8);
30868
+ local.writeUInt16LE(DOS_TIME, 10);
30869
+ local.writeUInt16LE(DOS_DATE2, 12);
30870
+ local.writeUInt32LE(0, 14);
30871
+ local.writeUInt32LE(bigSize ? U32_MAX2 : entry.size, 18);
30872
+ local.writeUInt32LE(bigSize ? U32_MAX2 : entry.size, 22);
30873
+ local.writeUInt16LE(name.length, 26);
30874
+ local.writeUInt16LE(localExtra.length, 28);
30875
+ await out.write(Buffer.concat([local, name, localExtra]));
30876
+ let crc = 0;
30877
+ if (entry.abs) {
30878
+ const source = await fs50.open(entry.abs, "r");
30879
+ try {
30880
+ let read = 0;
30881
+ for (; ; ) {
30882
+ const { bytesRead } = await source.read(chunk, 0, CHUNK, read);
30883
+ if (bytesRead === 0) break;
30884
+ read += bytesRead;
30885
+ if (read > entry.size) break;
30886
+ const piece = chunk.subarray(0, bytesRead);
30887
+ crc = crc32(piece, crc);
30888
+ await out.write(piece);
30889
+ }
30890
+ if (read !== entry.size) throw new SourceZipError(`${entry.name} changed while it was being zipped. Close the editor and run this again.`);
30891
+ } finally {
30892
+ await source.close();
30893
+ }
30894
+ contentBytes += entry.size;
30895
+ const crcBytes = Buffer.alloc(4);
30896
+ crcBytes.writeUInt32LE(crc >>> 0, 0);
30897
+ await out.patch(offset + 14, crcBytes);
30898
+ }
30899
+ const centralFields = [];
30900
+ if (bigSize) centralFields.push(entry.size, entry.size);
30901
+ if (bigOffset) centralFields.push(offset);
30902
+ const centralExtra = centralFields.length ? zip64Extra(centralFields) : Buffer.alloc(0);
30903
+ const needs64 = bigSize || bigOffset;
30904
+ const head = Buffer.alloc(46);
30905
+ head.writeUInt32LE(33639248, 0);
30906
+ head.writeUInt16LE(needs64 ? VERSION_ZIP64 : VERSION_STORED, 4);
30907
+ head.writeUInt16LE(needs64 ? VERSION_ZIP64 : VERSION_STORED, 6);
30908
+ head.writeUInt16LE(UTF8_FLAG, 8);
30909
+ head.writeUInt16LE(0, 10);
30910
+ head.writeUInt16LE(DOS_TIME, 12);
30911
+ head.writeUInt16LE(DOS_DATE2, 14);
30912
+ head.writeUInt32LE(crc >>> 0, 16);
30913
+ head.writeUInt32LE(bigSize ? U32_MAX2 : entry.size, 20);
30914
+ head.writeUInt32LE(bigSize ? U32_MAX2 : entry.size, 24);
30915
+ head.writeUInt16LE(name.length, 28);
30916
+ head.writeUInt16LE(centralExtra.length, 30);
30917
+ head.writeUInt16LE(0, 32);
30918
+ head.writeUInt16LE(0, 34);
30919
+ head.writeUInt16LE(0, 36);
30920
+ head.writeUInt32LE(entry.abs ? 0 : 16, 38);
30921
+ head.writeUInt32LE(bigOffset ? U32_MAX2 : offset, 42);
30922
+ central.push(head, name, centralExtra);
30923
+ }
30924
+ const directoryOffset = out.position;
30925
+ const directory = Buffer.concat(central);
30926
+ await out.write(directory);
30927
+ const count = entries.length;
30928
+ const end64 = opts.forceZip64 || count >= U16_MAX2 || directory.length >= U32_MAX2 || directoryOffset >= U32_MAX2;
30929
+ if (end64) {
30930
+ const recordOffset = out.position;
30931
+ const record = Buffer.alloc(56);
30932
+ record.writeUInt32LE(101075792, 0);
30933
+ record.writeBigUInt64LE(44n, 4);
30934
+ record.writeUInt16LE(VERSION_ZIP64, 12);
30935
+ record.writeUInt16LE(VERSION_ZIP64, 14);
30936
+ record.writeUInt32LE(0, 16);
30937
+ record.writeUInt32LE(0, 20);
30938
+ record.writeBigUInt64LE(BigInt(count), 24);
30939
+ record.writeBigUInt64LE(BigInt(count), 32);
30940
+ record.writeBigUInt64LE(BigInt(directory.length), 40);
30941
+ record.writeBigUInt64LE(BigInt(directoryOffset), 48);
30942
+ const locator = Buffer.alloc(20);
30943
+ locator.writeUInt32LE(117853008, 0);
30944
+ locator.writeUInt32LE(0, 4);
30945
+ locator.writeBigUInt64LE(BigInt(recordOffset), 8);
30946
+ locator.writeUInt32LE(1, 16);
30947
+ await out.write(Buffer.concat([record, locator]));
30948
+ }
30949
+ const end = Buffer.alloc(22);
30950
+ end.writeUInt32LE(101010256, 0);
30951
+ end.writeUInt16LE(end64 ? U16_MAX2 : count, 8);
30952
+ end.writeUInt16LE(end64 ? U16_MAX2 : count, 10);
30953
+ end.writeUInt32LE(end64 ? U32_MAX2 : directory.length, 12);
30954
+ end.writeUInt32LE(end64 ? U32_MAX2 : directoryOffset, 16);
30955
+ await out.write(end);
30956
+ } finally {
30957
+ await file.close();
30958
+ }
30959
+ return { entries: entries.length, contentBytes, skippedLinks };
30960
+ }
30961
+ function zip64Extra(values) {
30962
+ const extra = Buffer.alloc(4 + values.length * 8);
30963
+ extra.writeUInt16LE(1, 0);
30964
+ extra.writeUInt16LE(values.length * 8, 2);
30965
+ values.forEach((v, i) => extra.writeBigUInt64LE(BigInt(v), 4 + i * 8));
30966
+ return extra;
30967
+ }
30968
+ async function resolveZipSource(dir, cwd = process.cwd()) {
30969
+ const abs = path50.resolve(cwd, dir);
30970
+ const stat = await fs50.lstat(abs).catch(() => null);
30971
+ if (!stat) return { refused: `--source ${abs} does not exist.` };
30972
+ if (stat.isSymbolicLink()) return { refused: `--source ${abs} is a symbolic link. Pass the folder itself.` };
30973
+ if (!stat.isDirectory()) return { refused: `--source ${abs} is not a folder.` };
30974
+ return { dir: await fs50.realpath(abs) };
30975
+ }
30976
+
30598
30977
  // src/commands/native.ts
30599
30978
  var PROGRESS_STEPS = 10;
30600
30979
  var MB2 = 1024 * 1024;
@@ -30638,9 +31017,9 @@ async function context3(opts, log) {
30638
31017
  }
30639
31018
  return { apiUrl, projectId: meta.id, token };
30640
31019
  }
30641
- function refusedInHostedSession(log) {
31020
+ function refusedInHostedSession(log, line = "Native builds are published from the creator's own computer, not from a hosted session.") {
30642
31021
  if (!inHostedSession()) return false;
30643
- log.error("Native builds are published from the creator's own computer, not from a hosted session.");
31022
+ log.error(line);
30644
31023
  return true;
30645
31024
  }
30646
31025
  function sizeLine(bytes) {
@@ -30779,16 +31158,184 @@ async function runRemove(opts, log) {
30779
31158
  log.success(`Removed the ${opts.platform} ${opts.arch} build. Copies already installed keep working.`);
30780
31159
  return true;
30781
31160
  }
31161
+ var CLOUD_HOSTED_REFUSAL = "Build on Genex is started from the creator's own computer, not from a hosted session.";
31162
+ var FAILURE_WORDS = {
31163
+ "engine-unsupported": "the project's Unreal version isn't one the build machine has",
31164
+ compile: "the game's code didn't compile",
31165
+ cook: "cooking the game's content failed",
31166
+ timeout: "the build ran past its time limit",
31167
+ quota: "this account has used its builds for the month",
31168
+ "source-too-big": "the project is over the size limit",
31169
+ "not-on-genex": "the game has no page on Genex yet",
31170
+ failed: "the build machine stopped or its result failed the checks"
31171
+ };
31172
+ var STATE_WORDS = {
31173
+ queued: "Queued",
31174
+ building: "Building",
31175
+ publishing: "Publishing",
31176
+ done: "Built",
31177
+ failed: "Failed"
31178
+ };
31179
+ function printRefusal(opts, refusal2) {
31180
+ if (opts.json && refusal2.body) console.log(JSON.stringify(refusal2.body));
31181
+ }
31182
+ function stateLine(build) {
31183
+ const state = STATE_WORDS[build.state] ?? build.state;
31184
+ if (build.state !== "failed") return `${state} \u2014 ${build.platform} build ${build.jobId}`;
31185
+ const why = build.failure ? `: ${FAILURE_WORDS[build.failure] ?? build.failure} (${build.failure})` : "";
31186
+ return `${state}${why} \u2014 ${build.platform} build ${build.jobId}`;
31187
+ }
31188
+ function reportCloudBuild(build, how, log) {
31189
+ if (how === "deduplicated") {
31190
+ log.success(`Already built (no change since): ${stateLine(build)}`);
31191
+ return;
31192
+ }
31193
+ const line = how === "running" ? `Already building: ${stateLine(build)}` : stateLine(build);
31194
+ if (build.state === "failed") log.warn(line);
31195
+ else log.success(line);
31196
+ if (build.state !== "done" && build.state !== "failed") {
31197
+ log.dim(` Follow it: genex native build-status ${build.jobId}`);
31198
+ log.dim(" When it is done the build is attached to the game's page (held for review unless you are a Genex admin).");
31199
+ }
31200
+ }
31201
+ async function runBuild(opts, log) {
31202
+ if (refusedInHostedSession(log, CLOUD_HOSTED_REFUSAL)) return false;
31203
+ if (!opts.platform || !isCloudBuildPlatform(opts.platform)) {
31204
+ log.error("Which platform? `genex native build --platform windows --source <folder>` (windows, linux or mac).");
31205
+ return false;
31206
+ }
31207
+ if (!opts.sourceDir) {
31208
+ log.error("Which project? `genex native build --platform windows --source <folder>` \u2014 the folder holding the .uproject.");
31209
+ return false;
31210
+ }
31211
+ if (opts.buildVersion !== void 0) {
31212
+ try {
31213
+ buildVersion(opts.buildVersion, void 0, "");
31214
+ } catch (err) {
31215
+ if (!(err instanceof NativeBuildError)) throw err;
31216
+ log.error(err.message);
31217
+ return false;
31218
+ }
31219
+ }
31220
+ const source = await resolveZipSource(opts.sourceDir);
31221
+ if ("refused" in source) {
31222
+ log.error(source.refused);
31223
+ return false;
31224
+ }
31225
+ const ctx = await context3(opts, log);
31226
+ 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");
31229
+ try {
31230
+ log.step(`Zipping ${source.dir}\u2026`);
31231
+ let zipped;
31232
+ try {
31233
+ zipped = await writeSourceZip(source.dir, zipPath);
31234
+ } catch (err) {
31235
+ if (!(err instanceof SourceZipError)) throw err;
31236
+ log.error(err.message);
31237
+ return false;
31238
+ }
31239
+ if (zipped.entries === 0) {
31240
+ log.error(`${source.dir} has nothing to build in it.`);
31241
+ return false;
31242
+ }
31243
+ if (zipped.skippedLinks.length > 0) {
31244
+ const shown = zipped.skippedLinks.slice(0, 5).join(", ");
31245
+ const more = zipped.skippedLinks.length > 5 ? ` and ${zipped.skippedLinks.length - 5} more` : "";
31246
+ log.warn(`Left out ${zipped.skippedLinks.length} link(s) inside the folder: ${shown}${more}.`);
31247
+ }
31248
+ const { sha256: sha2564, sizeBytes } = await hashFile(zipPath);
31249
+ log.step(`${zipped.entries} files, ${sizeLine(sizeBytes)} zipped.`);
31250
+ const body = {
31251
+ platform: opts.platform,
31252
+ sourceSha256: sha2564,
31253
+ sourceBytes: sizeBytes,
31254
+ ...opts.buildVersion !== void 0 ? { version: opts.buildVersion } : {}
31255
+ };
31256
+ const started = await startCloudBuild(ctx, body, log);
31257
+ if (!started.ok) {
31258
+ printRefusal(opts, started.refusal);
31259
+ return false;
31260
+ }
31261
+ let build = started.value.build;
31262
+ let upload = started.value.upload;
31263
+ const how = started.value.deduplicated ? "deduplicated" : started.value.status === 201 ? "new" : "running";
31264
+ let uploaded = false;
31265
+ for (let round2 = 0; upload; round2++) {
31266
+ log.step(`Uploading ${sizeLine(sizeBytes)}\u2026`);
31267
+ const parts = await uploadCloudBuildSource(upload, zipPath, sizeBytes, log, progress(log, "uploaded", sizeBytes));
31268
+ if (!parts) return false;
31269
+ const done = await completeCloudBuildSource(ctx, build.jobId, parts, log);
31270
+ if (done.ok) {
31271
+ build = done.value;
31272
+ uploaded = true;
31273
+ break;
31274
+ }
31275
+ if (done.refusal.error !== UPLOAD_INCOMPLETE) {
31276
+ printRefusal(opts, done.refusal);
31277
+ return false;
31278
+ }
31279
+ if (round2 > 0) {
31280
+ log.error("The upload still isn't complete after sending it twice. Run this again.");
31281
+ printRefusal(opts, done.refusal);
31282
+ return false;
31283
+ }
31284
+ log.warn("Some parts did not arrive. Sending the project again\u2026");
31285
+ const again = await startCloudBuild(ctx, body, log);
31286
+ if (!again.ok) {
31287
+ printRefusal(opts, again.refusal);
31288
+ return false;
31289
+ }
31290
+ build = again.value.build;
31291
+ upload = again.value.upload;
31292
+ }
31293
+ reportCloudBuild(build, how === "running" && uploaded ? "new" : how, log);
31294
+ if (opts.json) console.log(JSON.stringify({ build }));
31295
+ return true;
31296
+ } finally {
31297
+ await fs51.rm(tmp, { recursive: true, force: true });
31298
+ }
31299
+ }
31300
+ async function runBuildStatus(opts, log) {
31301
+ if (refusedInHostedSession(log, CLOUD_HOSTED_REFUSAL)) return false;
31302
+ if (!opts.jobId || !CLOUD_BUILD_JOB_ID.test(opts.jobId)) {
31303
+ log.error(
31304
+ opts.jobId ? `"${opts.jobId}" is not a build id. \`genex native build\` prints one (cb-\u2026).` : "Which build? `genex native build-status <jobId>` \u2014 the id `genex native build` printed."
31305
+ );
31306
+ return false;
31307
+ }
31308
+ const ctx = await context3(opts, log);
31309
+ if (!ctx) return false;
31310
+ const read = await readCloudBuild(ctx, opts.jobId, log);
31311
+ if (!read.ok) {
31312
+ printRefusal(opts, read.refusal);
31313
+ return false;
31314
+ }
31315
+ const build = read.value;
31316
+ if (opts.json) {
31317
+ console.log(JSON.stringify({ build }));
31318
+ return true;
31319
+ }
31320
+ if (build.state === "failed") log.warn(stateLine(build));
31321
+ else log.success(stateLine(build));
31322
+ log.dim(` Started ${build.startedAt}${build.finishedAt ? `, finished ${build.finishedAt}` : ""}.`);
31323
+ return true;
31324
+ }
30782
31325
  var SUBCOMMANDS2 = {
30783
31326
  publish: runPublish2,
30784
31327
  list: runList2,
30785
- remove: runRemove
31328
+ remove: runRemove,
31329
+ build: runBuild,
31330
+ "build-status": runBuildStatus
30786
31331
  };
30787
31332
  async function runNative(opts) {
30788
31333
  const log = loggerFor2(opts);
30789
31334
  const run5 = opts.name ? SUBCOMMANDS2[opts.name] : void 0;
30790
31335
  if (!run5) {
30791
- log.error("Unknown native command. Use `genex native publish <path>`, `genex native list` or `genex native remove`.");
31336
+ log.error(
31337
+ "Unknown native command. Use `genex native publish <path>`, `genex native list`, `genex native remove`, `genex native build` or `genex native build-status <jobId>`."
31338
+ );
30792
31339
  process.exitCode = 1;
30793
31340
  return;
30794
31341
  }
@@ -30987,10 +31534,13 @@ ${c.bold("Usage")}
30987
31534
  version back, instantly \u2014 your code is not
30988
31535
  changed, only what the play URL serves.
30989
31536
  genex native <sub> [options] This game's Mac/Windows/Linux builds for the Genex
30990
- desktop app: publish <path> | list | remove.
31537
+ desktop app: publish <path> | list | remove |
31538
+ build | build-status <jobId>.
30991
31539
  "publish" takes a .zip, a .app or the build's
30992
31540
  folder and makes it what desktop players install
30993
31541
  for its platform and processor, live at once.
31542
+ "build --platform windows --source <folder>" has
31543
+ Genex build the Windows app of an Unreal project.
30994
31544
  genex web remove [--yes] [--json] Remove this game's web version and keep its
30995
31545
  Mac/Windows/Linux apps: the page shows the apps
30996
31546
  again. Refused for a game with no app.
@@ -31866,6 +32416,21 @@ ${c.bold("Usage")}
31866
32416
  genex native remove --platform <p> --arch <a> --yes
31867
32417
  Stop offering one build. Installed copies keep working.
31868
32418
 
32419
+ ${c.bold("Build on Genex")} (an Unreal project, built for a system this computer can't build for)
32420
+ genex native build --platform windows --source <folder> [--build-version <v>]
32421
+ Zip the project folder (the same files always give
32422
+ the same zip, so an unchanged project is not built
32423
+ twice), upload it privately, and build it on a Genex
32424
+ machine. The result is attached to the game's page
32425
+ like a published build. Left out: .git, .genex,
32426
+ .DS_Store, .env files, and the top-level Intermediate,
32427
+ Saved, DerivedDataCache and Binaries folders. A link
32428
+ out of the folder is refused; one inside it is skipped.
32429
+ genex native build-status <jobId> Where that build stands: queued, building,
32430
+ publishing, done or failed (with why).
32431
+ --json One JSON object on stdout ({"build": \u2026}); a refusal
32432
+ prints the server's error body and exits 1.
32433
+
31869
32434
  ${c.bold("Read from the build, or set")}
31870
32435
  --platform mac|windows|linux A .app at the archive's root is mac, an .exe windows.
31871
32436
  --arch arm64|x64|universal From the app's executable header.
@@ -32357,6 +32922,8 @@ function parseArgs(argv) {
32357
32922
  } else if (parsed.command === "native") {
32358
32923
  if (parsed.options.name === "publish" && parsed.options.nativePath === void 0) {
32359
32924
  parsed.options.nativePath = arg;
32925
+ } else if (parsed.options.name === "build-status" && parsed.options.jobId === void 0) {
32926
+ parsed.options.jobId = arg;
32360
32927
  } else {
32361
32928
  parsed.error = `Unexpected argument: ${arg}`;
32362
32929
  return parsed;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genex-ai/cli-demo",
3
- "version": "1.36.20-dev.1078",
3
+ "version": "1.38.0-dev.1079",
4
4
  "description": "Set up your project's agent workspace (.claude/.codex/.cursor in the game folder), authorize, create a game project, generate AI assets, and publish (genex CLI).",
5
5
  "type": "module",
6
6
  "bin": {