@genex-ai/cli-demo 1.5.2-dev.398 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +288 -373
- package/package.json +1 -1
- package/templates/asset-viewer/asset.config.json +25 -0
- package/templates/asset-viewer/genex-asset.example.json +222 -0
- package/templates/asset-viewer/index.html +200 -0
- package/templates/asset-viewer/package.json +24 -0
- package/templates/asset-viewer/public/fonts/Geist-variable.woff2 +0 -0
- package/templates/asset-viewer/public/fonts/GeistMono-variable.woff2 +0 -0
- package/templates/asset-viewer/shared-files.sha256.json +14 -0
- package/templates/asset-viewer/src/asset/PLACEHOLDER.ts +237 -0
- package/templates/asset-viewer/src/main.js +149 -0
- package/templates/asset-viewer/src/viewer/gates-overlay.js +521 -0
- package/templates/asset-viewer/src/viewer/hud.js +73 -0
- package/templates/asset-viewer/src/viewer/stage.js +760 -0
- package/templates/asset-viewer/tools/emit-manifest.mjs +653 -0
- package/templates/asset-viewer/tools/gates.mjs +682 -0
- package/templates/asset-viewer/tools/stamp-manifest.mjs +134 -0
- package/templates/asset-viewer/vite.config.js +24 -0
- package/templates/skills/genex-ai-texture/SKILL.md +1 -1
- package/templates/skills/genex-ai-video/SKILL.md +1 -1
- package/templates/skills/genex-asset-author/SKILL.md +101 -0
- package/templates/skills/genex-game-director/references/routing-map.md +1 -1
- package/templates/skills/genex-getting-started/SKILL.md +2 -2
- package/templates/skills/genex-threejs-visual-validation/SKILL.md +3 -7
- package/templates/skills/genex-updates/SKILL.md +1 -1
- package/templates/skills/genex-monetization/SKILL.md +0 -261
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import fs from "fs";
|
|
|
8
8
|
import os from "os";
|
|
9
9
|
import path from "path";
|
|
10
10
|
import { fileURLToPath } from "url";
|
|
11
|
-
var RAW_CHANNEL = "
|
|
11
|
+
var RAW_CHANNEL = "latest";
|
|
12
12
|
var CLI_CHANNEL = RAW_CHANNEL === "dev" ? "dev" : "latest";
|
|
13
13
|
var STANDS = {
|
|
14
14
|
prod: { api: "https://api.genex.games", dashboard: "https://genex.games" },
|
|
@@ -1014,7 +1014,6 @@ Important note: put soul into your creations, with many details and love. Aim to
|
|
|
1014
1014
|
16. Never add debug-only code to the game to check your own work \u2014 no hidden test modes, no special URL parameters, no forced-visible flags, no auth mocks, no pixel-sampling hooks. \`?genex_local_test=1\` is the platform's own supported mode and is fine; your own bypass is not. (The multiplayer skill's small build identifier, token-free status line, and connected-quorum watchdog are production supportability, not a bypass \u2014 keep those.)
|
|
1015
1015
|
17. Input directions match their labels: A/\u2190 moves or turns the player screen-LEFT, D/\u2192 screen-RIGHT, mouse-up looks up, and drag-pan axes share ONE convention. The cursor is either the gameplay tool (RTS, card, builder) or locked away during play \u2014 keyboard-only games included. Check it in every milestone's smoke pass.
|
|
1016
1016
|
18. NEVER delete, empty, move, rename, or overwrite anything you did not create yourself. This folder may hold the player's own reference images, notes, sketches, or an earlier attempt \u2014 files that exist nowhere else and have no undo, no trash, no backup. A non-empty folder is normal and is NEVER something to clean up, and "start clean" is never a reason. That rules out \`rm\`/\`rm -rf\`, \`git clean\`, \`git checkout -- .\`, \`git reset --hard\` over their work, deleting to resolve a conflict or a stuck interactive prompt, and every setup tool's offer to empty a directory (\`--force\`, \`--overwrite\`, "Remove existing files") \u2014 scaffold into a fresh subfolder and copy in instead. You may add files and edit the ones you wrote. If a step genuinely cannot continue without removing something of theirs, STOP and ask, naming the exact files, and wait for a yes \u2014 "it looks like junk" is never that yes. This binds hardest during setup, where it runs fast and automatically before the player has asked for anything at all.
|
|
1017
|
-
19. If the game sells anything, it sells it for coin at a fixed, visible price, and NEVER sells chance. Run this test on any purchasable thing before building it: does the player pay (with coin, or with anything coin bought, directly or indirectly), is the outcome uncertain when they pay, and is there a prize they wanted \u2014 all three yes means it is paid randomness, and you build the deterministic version instead. That rules out loot boxes, gacha, mystery boxes, crates, card packs, prize wheels and raffles; wagering, staking, betting, coinflips and casino or slot mechanics denominated in coin; and donation prompts, tip jars or any player-to-player coin transfer, because coin buys goods and never just moves. Randomness the player EARNS by playing is gameplay, not commerce \u2014 an enemy dropping a random item, a chest found in the level, a procedural layout, a crit roll \u2014 and is completely fine. Every coin price renders its real-money equivalent beside it (the server sends one with every item), item prices sit on the platform's price grid so no player is left holding change they cannot spend, and nothing in a shop carries a countdown, "limited time", or a stock counter. When a request crosses one of these lines, name the mechanic, give the one-sentence reason, propose a specific compliant alternative, and build that \u2014 never the banned version "as an option", never a partial one, and never after asking the player to confirm they want it. Load \`$genex-monetization\` before building a shop.
|
|
1018
1017
|
${CONTRACT_END}
|
|
1019
1018
|
`;
|
|
1020
1019
|
var CLAUDE_IMPORT_LINE = "@AGENTS.md";
|
|
@@ -1238,7 +1237,7 @@ async function syncSkillsForTarget(target, templatesDir = getTemplatesDir(), ver
|
|
|
1238
1237
|
if (await readSkillsMarker(skillsDir) === version) return false;
|
|
1239
1238
|
const src = target.full ? templatesDir : path7.join(templatesDir, "skills");
|
|
1240
1239
|
const dest = target.full ? target.baseDir : skillsDir;
|
|
1241
|
-
await copyTemplates(src, dest, { exclude: ["controllers", "motion"] });
|
|
1240
|
+
await copyTemplates(src, dest, { exclude: ["controllers", "motion", "asset-viewer"] });
|
|
1242
1241
|
await pruneRemovedSkills(skillsDir, log);
|
|
1243
1242
|
await writeSkillsMarker(skillsDir, version);
|
|
1244
1243
|
return true;
|
|
@@ -1722,7 +1721,7 @@ async function runInit(opts) {
|
|
|
1722
1721
|
const dest = t.full ? t.baseDir : path10.join(t.baseDir, "skills");
|
|
1723
1722
|
const { copied, updated } = await copyTemplates(src, dest, {
|
|
1724
1723
|
force: opts.force,
|
|
1725
|
-
exclude: ["controllers", "motion"]
|
|
1724
|
+
exclude: ["controllers", "motion", "asset-viewer"]
|
|
1726
1725
|
});
|
|
1727
1726
|
await pruneRemovedSkills(path10.join(t.baseDir, "skills"), log);
|
|
1728
1727
|
await writeSkillsMarker(path10.join(t.baseDir, "skills"));
|
|
@@ -4964,307 +4963,6 @@ async function runMakeRemixable(opts) {
|
|
|
4964
4963
|
}
|
|
4965
4964
|
}
|
|
4966
4965
|
|
|
4967
|
-
// src/commands/domain.ts
|
|
4968
|
-
var SUBCOMMANDS = ["add", "list", "verify", "remove"];
|
|
4969
|
-
function statusWord(d) {
|
|
4970
|
-
if (d.status === "active") return c.green("live");
|
|
4971
|
-
if (d.status === "failed") return c.red("stopped");
|
|
4972
|
-
return c.dim("waiting for DNS");
|
|
4973
|
-
}
|
|
4974
|
-
async function runDomain(opts) {
|
|
4975
|
-
const log = createLogger({ quiet: opts.quiet });
|
|
4976
|
-
const sub = (opts.name ?? "list").trim();
|
|
4977
|
-
if (!SUBCOMMANDS.includes(sub)) {
|
|
4978
|
-
log.error(`Unknown subcommand \`${sub}\`. Use: ${SUBCOMMANDS.join(", ")}.`);
|
|
4979
|
-
process.exitCode = 1;
|
|
4980
|
-
return;
|
|
4981
|
-
}
|
|
4982
|
-
const needsHost = sub !== "list";
|
|
4983
|
-
const hostname = opts.hostname?.trim();
|
|
4984
|
-
if (needsHost && !hostname) {
|
|
4985
|
-
log.error(`\`genex domain ${sub}\` needs a hostname, e.g. ${c.cyan(`genex domain ${sub} play.yourdomain.com`)}.`);
|
|
4986
|
-
process.exitCode = 1;
|
|
4987
|
-
return;
|
|
4988
|
-
}
|
|
4989
|
-
const meta = await readProject();
|
|
4990
|
-
if (!meta?.id) {
|
|
4991
|
-
log.error("This folder isn't linked to a game.");
|
|
4992
|
-
log.dim(` Run ${c.cyan("genex link")} (or ${c.cyan("genex list")} to find the slug) first.`);
|
|
4993
|
-
process.exitCode = 1;
|
|
4994
|
-
return;
|
|
4995
|
-
}
|
|
4996
|
-
const apiUrl = getApiUrl(opts.apiUrl ?? meta.apiUrl);
|
|
4997
|
-
let token = opts.token ?? await readUserToken(opts.envPath);
|
|
4998
|
-
if (!token) {
|
|
4999
|
-
if (opts.noAuth) {
|
|
5000
|
-
log.error("Not signed in. Re-run without --no-auth to connect.");
|
|
5001
|
-
process.exitCode = 1;
|
|
5002
|
-
return;
|
|
5003
|
-
}
|
|
5004
|
-
log.plain("Not signed in \u2014 connecting\u2026");
|
|
5005
|
-
try {
|
|
5006
|
-
token = await authorize(apiUrl, getAuthUrl(opts.authUrl), {
|
|
5007
|
-
log,
|
|
5008
|
-
inlineWaitMs: opts.timeoutSec ? opts.timeoutSec * 1e3 : void 0
|
|
5009
|
-
});
|
|
5010
|
-
} catch (err) {
|
|
5011
|
-
if (err instanceof AuthPendingError) {
|
|
5012
|
-
printAuthHandoff(log, err);
|
|
5013
|
-
log.dim(`Then re-run ${c.cyan(`genex domain ${sub}`)}.`);
|
|
5014
|
-
return;
|
|
5015
|
-
}
|
|
5016
|
-
log.error(`Sign-in didn't complete: ${err instanceof Error ? err.message : String(err)}`);
|
|
5017
|
-
process.exitCode = 1;
|
|
5018
|
-
return;
|
|
5019
|
-
}
|
|
5020
|
-
await writeUserToken(token, opts.envPath);
|
|
5021
|
-
}
|
|
5022
|
-
const base = `${apiUrl}/api/projects/${encodeURIComponent(meta.id)}/domains`;
|
|
5023
|
-
const auth = { Authorization: `Bearer ${token}` };
|
|
5024
|
-
const call = (url, init2) => apiFetch(url, { ...init2, headers: { ...auth, "Content-Type": "application/json", ...init2?.headers ?? {} } });
|
|
5025
|
-
let res;
|
|
5026
|
-
try {
|
|
5027
|
-
if (sub === "list") res = await call(base);
|
|
5028
|
-
else if (sub === "add") res = await call(base, { method: "POST", body: JSON.stringify({ hostname }) });
|
|
5029
|
-
else if (sub === "verify")
|
|
5030
|
-
res = await call(`${base}/${encodeURIComponent(hostname)}/verify`, { method: "POST" });
|
|
5031
|
-
else res = await call(`${base}/${encodeURIComponent(hostname)}`, { method: "DELETE" });
|
|
5032
|
-
} catch (err) {
|
|
5033
|
-
log.error(`Couldn't reach the API at ${apiUrl}.`);
|
|
5034
|
-
log.dim(` ${String(err)}`);
|
|
5035
|
-
process.exitCode = 1;
|
|
5036
|
-
return;
|
|
5037
|
-
}
|
|
5038
|
-
if (!res.ok) {
|
|
5039
|
-
if (await printedStructuredError(res)) {
|
|
5040
|
-
process.exitCode = 1;
|
|
5041
|
-
return;
|
|
5042
|
-
}
|
|
5043
|
-
const body = await res.json().catch(() => null);
|
|
5044
|
-
if (res.status === 503) {
|
|
5045
|
-
log.error("Custom domains aren't available on this Genex environment yet.");
|
|
5046
|
-
} else if (res.status === 404) {
|
|
5047
|
-
log.error(sub === "list" || sub === "add" ? "That game wasn't found on this account." : `${hostname} isn't connected to this game.`);
|
|
5048
|
-
} else {
|
|
5049
|
-
log.error(body?.message ?? body?.error ?? `Request failed (${res.status}).`);
|
|
5050
|
-
}
|
|
5051
|
-
process.exitCode = 1;
|
|
5052
|
-
return;
|
|
5053
|
-
}
|
|
5054
|
-
const data = await res.json();
|
|
5055
|
-
if (opts.json) {
|
|
5056
|
-
log.plain(JSON.stringify(data, null, 2));
|
|
5057
|
-
return;
|
|
5058
|
-
}
|
|
5059
|
-
if (sub === "list") {
|
|
5060
|
-
const rows = data.domains ?? [];
|
|
5061
|
-
if (rows.length === 0) {
|
|
5062
|
-
log.plain("No domains connected to this game.");
|
|
5063
|
-
log.dim(` ${c.cyan("genex domain add play.yourdomain.com")} to connect one.`);
|
|
5064
|
-
return;
|
|
5065
|
-
}
|
|
5066
|
-
for (const d of rows) log.plain(` ${d.hostname.padEnd(34)} ${statusWord(d)}`);
|
|
5067
|
-
return;
|
|
5068
|
-
}
|
|
5069
|
-
if (sub === "add") {
|
|
5070
|
-
if (data.supported === false) {
|
|
5071
|
-
log.plain(String(data.message ?? "That domain's DNS host doesn't support one-click setup."));
|
|
5072
|
-
const manual = data.manual;
|
|
5073
|
-
const records = Array.isArray(manual?.records) ? manual.records : [];
|
|
5074
|
-
if (records.length > 0) {
|
|
5075
|
-
log.plain("");
|
|
5076
|
-
log.plain(" Add these two records at your DNS host:");
|
|
5077
|
-
log.plain("");
|
|
5078
|
-
for (const r of records) {
|
|
5079
|
-
log.plain(` ${c.cyan(String(r.type ?? ""))} ${String(r.name ?? "")}`);
|
|
5080
|
-
log.plain(` ${String(r.value ?? "")}`);
|
|
5081
|
-
}
|
|
5082
|
-
log.plain("");
|
|
5083
|
-
if (manual?.apexHint) {
|
|
5084
|
-
log.dim(" At a root domain your host may call the first one ALIAS or ANAME.");
|
|
5085
|
-
}
|
|
5086
|
-
log.dim(` Then run: genex domain verify ${String(data.hostname ?? "")}`);
|
|
5087
|
-
log.dim(" DNS can take a few minutes to spread.");
|
|
5088
|
-
return;
|
|
5089
|
-
}
|
|
5090
|
-
log.dim(" Your game stays reachable at its usual address.");
|
|
5091
|
-
return;
|
|
5092
|
-
}
|
|
5093
|
-
const applyUrl = String(data.applyUrl ?? "");
|
|
5094
|
-
const providerName = String(data.providerName ?? "your DNS host");
|
|
5095
|
-
log.success(`${data.hostname} can be connected through ${providerName}.`);
|
|
5096
|
-
log.plain("");
|
|
5097
|
-
log.plain(` Approve the DNS change here: ${c.cyan(applyUrl)}`);
|
|
5098
|
-
log.plain("");
|
|
5099
|
-
log.dim(" One click writes the records. Nothing to copy or paste.");
|
|
5100
|
-
if (!applyUrl.startsWith("https://")) {
|
|
5101
|
-
log.warn("That DNS host returned a setup link we could not verify \u2014 not opening it.");
|
|
5102
|
-
} else if (!opts.noOpen) {
|
|
5103
|
-
openBrowser(applyUrl, () => log.dim(" (Couldn't open a browser \u2014 use the link above.)"));
|
|
5104
|
-
}
|
|
5105
|
-
log.dim(` Then ${c.cyan(`genex domain verify ${String(data.hostname)}`)} once you have approved it.`);
|
|
5106
|
-
return;
|
|
5107
|
-
}
|
|
5108
|
-
if (sub === "verify") {
|
|
5109
|
-
const status = String(data.status ?? "");
|
|
5110
|
-
if (status === "active") log.success(`${hostname} is live.`);
|
|
5111
|
-
else if (status === "failed") log.error(`${hostname} is stopped \u2014 its verification record is missing.`);
|
|
5112
|
-
else log.plain(`${hostname} isn't verified yet \u2014 DNS changes can take a few minutes to spread.`);
|
|
5113
|
-
return;
|
|
5114
|
-
}
|
|
5115
|
-
log.success(`${hostname} disconnected.`);
|
|
5116
|
-
}
|
|
5117
|
-
|
|
5118
|
-
// src/commands/shop.ts
|
|
5119
|
-
var SUBS = ["list", "add", "set", "remove"];
|
|
5120
|
-
function money(cents) {
|
|
5121
|
-
return cents === void 0 ? "" : ` ($${(cents / 100).toFixed(2)})`;
|
|
5122
|
-
}
|
|
5123
|
-
async function runShop(opts) {
|
|
5124
|
-
const log = createLogger({ quiet: opts.quiet });
|
|
5125
|
-
const sub = opts.name?.trim() || "list";
|
|
5126
|
-
if (!SUBS.includes(sub)) {
|
|
5127
|
-
log.error(`Unknown subcommand ${c.cyan(sub)}. Use: ${SUBS.join(", ")}.`);
|
|
5128
|
-
process.exitCode = 1;
|
|
5129
|
-
return;
|
|
5130
|
-
}
|
|
5131
|
-
const meta = await readProject();
|
|
5132
|
-
if (!meta?.id) {
|
|
5133
|
-
log.error("This folder isn't linked to a game.");
|
|
5134
|
-
log.dim(` Run ${c.cyan("genex link")} (or ${c.cyan("genex list")} to find the slug) first.`);
|
|
5135
|
-
process.exitCode = 1;
|
|
5136
|
-
return;
|
|
5137
|
-
}
|
|
5138
|
-
const apiUrl = getApiUrl(opts.apiUrl ?? meta.apiUrl);
|
|
5139
|
-
let token = opts.token ?? await readUserToken(opts.envPath);
|
|
5140
|
-
if (!token) {
|
|
5141
|
-
if (opts.noAuth) {
|
|
5142
|
-
log.error("Not signed in. Re-run without --no-auth to connect.");
|
|
5143
|
-
process.exitCode = 1;
|
|
5144
|
-
return;
|
|
5145
|
-
}
|
|
5146
|
-
log.plain("Not signed in \u2014 connecting\u2026");
|
|
5147
|
-
try {
|
|
5148
|
-
token = await authorize(apiUrl, getAuthUrl(opts.authUrl), {
|
|
5149
|
-
log,
|
|
5150
|
-
inlineWaitMs: opts.timeoutSec ? opts.timeoutSec * 1e3 : void 0
|
|
5151
|
-
});
|
|
5152
|
-
} catch (err) {
|
|
5153
|
-
if (err instanceof AuthPendingError) {
|
|
5154
|
-
printAuthHandoff(log, err);
|
|
5155
|
-
log.dim(`Then re-run ${c.cyan(`genex shop ${sub}`)}.`);
|
|
5156
|
-
return;
|
|
5157
|
-
}
|
|
5158
|
-
log.error(`Sign-in didn't complete: ${err instanceof Error ? err.message : String(err)}`);
|
|
5159
|
-
process.exitCode = 1;
|
|
5160
|
-
return;
|
|
5161
|
-
}
|
|
5162
|
-
await writeUserToken(token, opts.envPath);
|
|
5163
|
-
}
|
|
5164
|
-
const auth = { Authorization: `Bearer ${token}`, "Content-Type": "application/json" };
|
|
5165
|
-
const call = (url, init2) => apiFetch(url, { ...init2, headers: { ...auth, ...init2?.headers ?? {} } });
|
|
5166
|
-
const projectSkus = `${apiUrl}/api/coin/projects/${encodeURIComponent(meta.id)}/skus`;
|
|
5167
|
-
async function fail3(res2, what) {
|
|
5168
|
-
if (printedStructuredError(res2)) {
|
|
5169
|
-
process.exitCode = 1;
|
|
5170
|
-
return;
|
|
5171
|
-
}
|
|
5172
|
-
process.exitCode = 1;
|
|
5173
|
-
const body = await res2.json().catch(() => ({}));
|
|
5174
|
-
if (res2.status === 404 && body.error === "not_found") {
|
|
5175
|
-
log.error("In-game purchases aren't enabled on this environment yet.");
|
|
5176
|
-
return;
|
|
5177
|
-
}
|
|
5178
|
-
log.error(body.message ?? `Couldn't ${what} (HTTP ${res2.status}).`);
|
|
5179
|
-
if (body.error === "price_off_grid") {
|
|
5180
|
-
log.dim(` See the full list any time: ${c.cyan("genex shop list")}`);
|
|
5181
|
-
}
|
|
5182
|
-
}
|
|
5183
|
-
if (sub === "list") {
|
|
5184
|
-
const res2 = await call(projectSkus);
|
|
5185
|
-
if (!res2.ok) return fail3(res2, "read your shop");
|
|
5186
|
-
const body = await res2.json();
|
|
5187
|
-
if (opts.json) {
|
|
5188
|
-
log.plain(JSON.stringify(body, null, 2));
|
|
5189
|
-
return;
|
|
5190
|
-
}
|
|
5191
|
-
if (body.items.length === 0) {
|
|
5192
|
-
log.plain("This game sells nothing yet.");
|
|
5193
|
-
log.dim(` Add something: ${c.cyan('genex shop add "Iron Key" --price 100')}`);
|
|
5194
|
-
log.dim(` Prices available: ${body.priceGrid.join(", ")} coin`);
|
|
5195
|
-
return;
|
|
5196
|
-
}
|
|
5197
|
-
log.plain(`${body.items.length} item${body.items.length === 1 ? "" : "s"}:`);
|
|
5198
|
-
for (const s of body.items) {
|
|
5199
|
-
const state = s.active ? "" : c.dim(" (retired)");
|
|
5200
|
-
log.plain(
|
|
5201
|
-
` ${c.cyan(s.id)} ${s.name} \u2014 ${s.priceCoins} coin${money(s.priceDisplayUsdCents)} ${c.dim(s.type)}${state}`
|
|
5202
|
-
);
|
|
5203
|
-
}
|
|
5204
|
-
log.dim(`Prices available: ${body.priceGrid.join(", ")} coin`);
|
|
5205
|
-
return;
|
|
5206
|
-
}
|
|
5207
|
-
if (sub === "add") {
|
|
5208
|
-
const name = opts.hostname?.trim();
|
|
5209
|
-
if (!name) {
|
|
5210
|
-
log.error(`\`genex shop add\` needs a name, e.g. ${c.cyan('genex shop add "Iron Key" --price 100')}.`);
|
|
5211
|
-
process.exitCode = 1;
|
|
5212
|
-
return;
|
|
5213
|
-
}
|
|
5214
|
-
if (!opts.price) {
|
|
5215
|
-
log.error(`\`genex shop add\` needs ${c.cyan("--price")}, in coin.`);
|
|
5216
|
-
process.exitCode = 1;
|
|
5217
|
-
return;
|
|
5218
|
-
}
|
|
5219
|
-
const res2 = await call(projectSkus, {
|
|
5220
|
-
method: "POST",
|
|
5221
|
-
body: JSON.stringify({
|
|
5222
|
-
name,
|
|
5223
|
-
priceCoins: opts.price,
|
|
5224
|
-
type: opts.type === "durable" ? "durable" : "consumable",
|
|
5225
|
-
...opts.icon ? { iconUrl: opts.icon } : {}
|
|
5226
|
-
})
|
|
5227
|
-
});
|
|
5228
|
-
if (!res2.ok) return fail3(res2, "add that item");
|
|
5229
|
-
const sku = await res2.json();
|
|
5230
|
-
if (opts.json) {
|
|
5231
|
-
log.plain(JSON.stringify(sku, null, 2));
|
|
5232
|
-
return;
|
|
5233
|
-
}
|
|
5234
|
-
log.success(`Added ${c.cyan(sku.name)} \u2014 ${sku.priceCoins} coin.`);
|
|
5235
|
-
log.plain(` id: ${c.cyan(sku.id)}`);
|
|
5236
|
-
log.dim(` Use it in the game: buy({ skuId: "${sku.id}" })`);
|
|
5237
|
-
return;
|
|
5238
|
-
}
|
|
5239
|
-
const skuId = opts.hostname?.trim();
|
|
5240
|
-
if (!skuId) {
|
|
5241
|
-
log.error(`\`genex shop ${sub}\` needs an item id \u2014 see ${c.cyan("genex shop list")}.`);
|
|
5242
|
-
process.exitCode = 1;
|
|
5243
|
-
return;
|
|
5244
|
-
}
|
|
5245
|
-
const skuUrl = `${apiUrl}/api/coin/skus/${encodeURIComponent(skuId)}`;
|
|
5246
|
-
if (sub === "set") {
|
|
5247
|
-
const patch = {};
|
|
5248
|
-
if (opts.rename) patch.name = opts.rename;
|
|
5249
|
-
if (opts.price) patch.priceCoins = opts.price;
|
|
5250
|
-
if (opts.icon) patch.iconUrl = opts.icon;
|
|
5251
|
-
if (Object.keys(patch).length === 0) {
|
|
5252
|
-
log.error(`Nothing to change. Pass ${c.cyan("--rename")}, ${c.cyan("--price")} or ${c.cyan("--icon")}.`);
|
|
5253
|
-
process.exitCode = 1;
|
|
5254
|
-
return;
|
|
5255
|
-
}
|
|
5256
|
-
const res2 = await call(skuUrl, { method: "PATCH", body: JSON.stringify(patch) });
|
|
5257
|
-
if (!res2.ok) return fail3(res2, "update that item");
|
|
5258
|
-
const sku = await res2.json();
|
|
5259
|
-
log.success(`Updated ${c.cyan(sku.name)} \u2014 ${sku.priceCoins} coin.`);
|
|
5260
|
-
return;
|
|
5261
|
-
}
|
|
5262
|
-
const res = await call(skuUrl, { method: "DELETE" });
|
|
5263
|
-
if (!res.ok) return fail3(res, "remove that item");
|
|
5264
|
-
log.success("Removed from the shop.");
|
|
5265
|
-
log.dim(" Players who already bought it keep it.");
|
|
5266
|
-
}
|
|
5267
|
-
|
|
5268
4966
|
// src/lib/promote.ts
|
|
5269
4967
|
async function promoteBuild(apiUrl, projectId, token, log) {
|
|
5270
4968
|
let res;
|
|
@@ -17564,22 +17262,22 @@ async function context2(opts) {
|
|
|
17564
17262
|
const project = await readProject();
|
|
17565
17263
|
return { token, apiUrl: getApiUrl(opts.apiUrl ?? project?.apiUrl) };
|
|
17566
17264
|
}
|
|
17567
|
-
async function readVideo(
|
|
17265
|
+
async function readVideo(path23, log) {
|
|
17568
17266
|
let bytes;
|
|
17569
17267
|
try {
|
|
17570
|
-
bytes = await readFile(
|
|
17268
|
+
bytes = await readFile(path23);
|
|
17571
17269
|
} catch {
|
|
17572
|
-
log.error(`Can't read ${
|
|
17270
|
+
log.error(`Can't read ${path23}.`);
|
|
17573
17271
|
return null;
|
|
17574
17272
|
}
|
|
17575
17273
|
if (bytes.byteLength > MAX_VIDEO_BYTES) {
|
|
17576
|
-
log.error(`${basename(
|
|
17274
|
+
log.error(`${basename(path23)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
|
|
17577
17275
|
return null;
|
|
17578
17276
|
}
|
|
17579
17277
|
return bytes;
|
|
17580
17278
|
}
|
|
17581
|
-
async function uploadVideo(apiUrl, token, characterId,
|
|
17582
|
-
const contentType = /\.mov$/i.test(
|
|
17279
|
+
async function uploadVideo(apiUrl, token, characterId, path23, bytes, log) {
|
|
17280
|
+
const contentType = /\.mov$/i.test(path23) ? "video/quicktime" : "video/mp4";
|
|
17583
17281
|
const minted = await apiFetch(
|
|
17584
17282
|
`${apiUrl}/api/characters/${encodeURIComponent(characterId)}/motions/video-url`,
|
|
17585
17283
|
{
|
|
@@ -17594,7 +17292,7 @@ async function uploadVideo(apiUrl, token, characterId, path22, bytes, log) {
|
|
|
17594
17292
|
return null;
|
|
17595
17293
|
}
|
|
17596
17294
|
const { uploadUrl, videoUrl } = await minted.json();
|
|
17597
|
-
log.dim(` uploading ${basename(
|
|
17295
|
+
log.dim(` uploading ${basename(path23)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
|
|
17598
17296
|
const put = await fetch(uploadUrl, {
|
|
17599
17297
|
method: "PUT",
|
|
17600
17298
|
headers: { "Content-Type": contentType, "Content-Length": String(bytes.byteLength) },
|
|
@@ -19452,6 +19150,241 @@ async function runMotion(opts) {
|
|
|
19452
19150
|
}
|
|
19453
19151
|
}
|
|
19454
19152
|
|
|
19153
|
+
// src/commands/asset-new.ts
|
|
19154
|
+
import fs23 from "fs";
|
|
19155
|
+
import fsp from "fs/promises";
|
|
19156
|
+
import path22 from "path";
|
|
19157
|
+
import { pathToFileURL } from "url";
|
|
19158
|
+
var EXTRA_FILES = [
|
|
19159
|
+
"genex-asset.example.json",
|
|
19160
|
+
"public/fonts/Geist-variable.woff2",
|
|
19161
|
+
"public/fonts/GeistMono-variable.woff2"
|
|
19162
|
+
];
|
|
19163
|
+
var HOLDER_PLACEHOLDER = "Your Name";
|
|
19164
|
+
function parseDims(input) {
|
|
19165
|
+
const parts = String(input).toLowerCase().split("x").map((p) => Number(p.trim()));
|
|
19166
|
+
if (parts.length !== 3 || parts.some((n) => !Number.isFinite(n) || n <= 0)) {
|
|
19167
|
+
throw new Error(`--dims must look like 0.36x0.36x0.70 (width x depth x height), got: ${input}`);
|
|
19168
|
+
}
|
|
19169
|
+
const [width, depth, height] = parts;
|
|
19170
|
+
return [width, height, depth];
|
|
19171
|
+
}
|
|
19172
|
+
function parseBand(input) {
|
|
19173
|
+
const parts = String(input).split("-").map((p) => Number(p.replace(/[_,\s]/g, "")));
|
|
19174
|
+
const [lo, hi] = parts;
|
|
19175
|
+
if (parts.length !== 2 || lo === void 0 || hi === void 0 || !Number.isInteger(lo) || !Number.isInteger(hi) || lo <= 0 || lo >= hi) {
|
|
19176
|
+
throw new Error(`--tri-band must look like 800-2000, got: ${input}`);
|
|
19177
|
+
}
|
|
19178
|
+
return [lo, hi];
|
|
19179
|
+
}
|
|
19180
|
+
function titleCase(slug) {
|
|
19181
|
+
return slug.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
19182
|
+
}
|
|
19183
|
+
function seedAssetSource(placeholder, opts) {
|
|
19184
|
+
const pascal = opts.pascalCase(opts.slug);
|
|
19185
|
+
let out = placeholder.replaceAll("Placeholder", pascal);
|
|
19186
|
+
const line = (pattern, replacement) => {
|
|
19187
|
+
if (!pattern.test(out)) {
|
|
19188
|
+
throw new Error(`the template's PLACEHOLDER.ts no longer carries the scaffold line ${pattern}`);
|
|
19189
|
+
}
|
|
19190
|
+
out = out.replace(pattern, replacement);
|
|
19191
|
+
};
|
|
19192
|
+
line(/^\/\/ .* - procedural Three\.js asset\.$/m, `// ${opts.name} - procedural Three.js asset.`);
|
|
19193
|
+
line(
|
|
19194
|
+
/^\/\/ MIT License - Copyright \(c\) \d+ .*$/m,
|
|
19195
|
+
`// MIT License - Copyright (c) ${opts.year} ${opts.holder}. Free to use, modify and ship.`
|
|
19196
|
+
);
|
|
19197
|
+
line(/^const SLUG = .*$/m, `const SLUG = '${opts.slug}';`);
|
|
19198
|
+
line(/^const NAME = .*$/m, `const NAME = '${opts.name.replaceAll("'", "\\'")}';`);
|
|
19199
|
+
line(
|
|
19200
|
+
/^const SIZE_METERS: \[number, number, number\] = .*$/m,
|
|
19201
|
+
`const SIZE_METERS: [number, number, number] = [${opts.sizeMeters.join(", ")}];`
|
|
19202
|
+
);
|
|
19203
|
+
line(/^const TARGET_TRIANGLES = .*$/m, `const TARGET_TRIANGLES = ${opts.targetTriangles};`);
|
|
19204
|
+
return out;
|
|
19205
|
+
}
|
|
19206
|
+
function fillTemplate(text, values) {
|
|
19207
|
+
return text.replace(/\{\{(\w+)\}\}/g, (_, key) => {
|
|
19208
|
+
const value = values[key];
|
|
19209
|
+
if (value === void 0) throw new Error(`unfilled placeholder: {{${key}}}`);
|
|
19210
|
+
return value;
|
|
19211
|
+
});
|
|
19212
|
+
}
|
|
19213
|
+
var GITIGNORE = ["node_modules/", "dist/", ".gates/", ".genex/", ".env", ".env.*", "!.env.example", ""].join("\n");
|
|
19214
|
+
function designDoc(opts) {
|
|
19215
|
+
return `# ${opts.name}
|
|
19216
|
+
|
|
19217
|
+
A free, MIT-licensed procedural Three.js asset for the Genex asset library.
|
|
19218
|
+
One file, one factory, no dependency but \`three\`.
|
|
19219
|
+
|
|
19220
|
+
- **slug:** \`${opts.slug}\`
|
|
19221
|
+
- **size:** ${opts.sizeMeters.map((n) => n.toFixed(2)).join(" \xD7 ")} m (x \xD7 y \xD7 z, y = height)
|
|
19222
|
+
- **triangle band:** ${opts.triBand[0].toLocaleString("en-US")} - ${opts.triBand[1].toLocaleString("en-US")}
|
|
19223
|
+
- **origin:** base centre, y = 0. **Units:** metres. **Facing:** Y-up, +Z forward.
|
|
19224
|
+
- **licence:** MIT, copyright ${opts.holder}. Publishing this asset releases it MIT \u2014
|
|
19225
|
+
the dashboard's Copy code button makes it copyable by anyone.
|
|
19226
|
+
|
|
19227
|
+
## Boundary note
|
|
19228
|
+
|
|
19229
|
+
This asset is **code**, not generated art: the lane is procedural geometry
|
|
19230
|
+
authored in TypeScript. No player character applies \u2014 the project is a viewer,
|
|
19231
|
+
not a game.
|
|
19232
|
+
|
|
19233
|
+
## Build plan & status
|
|
19234
|
+
|
|
19235
|
+
1. [ ] blockout - silhouette and real dimensions
|
|
19236
|
+
2. [ ] form-refinement - the details that make it read as manufactured
|
|
19237
|
+
3. [ ] material-pass - MeshStandardMaterial + DataTexture only
|
|
19238
|
+
4. [ ] interaction-pass - \`assetRuntime.nodes\`, \`dispose()\`, options
|
|
19239
|
+
|
|
19240
|
+
**Now:** milestone 1.
|
|
19241
|
+
|
|
19242
|
+
Every milestone ends with \`npm run verify\`, then \`npx genex preview --no-build\`.
|
|
19243
|
+
|
|
19244
|
+
## Decisions
|
|
19245
|
+
`;
|
|
19246
|
+
}
|
|
19247
|
+
async function runAssetNew(options) {
|
|
19248
|
+
const log = createLogger();
|
|
19249
|
+
const cwd = options.dir ? path22.resolve(options.dir) : process.cwd();
|
|
19250
|
+
const slug = options.assetSlug;
|
|
19251
|
+
if (!slug) {
|
|
19252
|
+
log.error('Usage: genex asset new <slug> [--title "<Name>"] [--dims WxDxH] [--tri-band LOW-HIGH] [--holder "<your name>"] [--out <dir>]');
|
|
19253
|
+
return 1;
|
|
19254
|
+
}
|
|
19255
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug)) {
|
|
19256
|
+
log.error(`The slug must be kebab-case (letters, digits, dashes), got: ${slug}`);
|
|
19257
|
+
return 1;
|
|
19258
|
+
}
|
|
19259
|
+
const templateDir = path22.join(getTemplatesDir(), "asset-viewer");
|
|
19260
|
+
if (!fs23.existsSync(templateDir)) {
|
|
19261
|
+
log.error(`Vendored asset-viewer template not found at ${templateDir} \u2014 reinstall the CLI.`);
|
|
19262
|
+
return 1;
|
|
19263
|
+
}
|
|
19264
|
+
const manifestTools = await import(pathToFileURL(path22.join(templateDir, "tools", "emit-manifest.mjs")).href);
|
|
19265
|
+
const { SHARED_FILES, PARITY_FILENAME, hashSharedFiles, pascalCase } = manifestTools;
|
|
19266
|
+
const lockPath = path22.join(templateDir, "shared-files.sha256.json");
|
|
19267
|
+
const lock = JSON.parse(await fsp.readFile(lockPath, "utf8"));
|
|
19268
|
+
const actual = hashSharedFiles(templateDir);
|
|
19269
|
+
const drifted = SHARED_FILES.filter((rel) => lock.files[rel] !== actual[rel]);
|
|
19270
|
+
if (drifted.length) {
|
|
19271
|
+
log.error(`The bundled viewer template is damaged (${drifted.join(", ")}) \u2014 reinstall the CLI.`);
|
|
19272
|
+
return 1;
|
|
19273
|
+
}
|
|
19274
|
+
const name = options.title ?? titleCase(slug);
|
|
19275
|
+
const sizeMeters = parseDims(options.dims ?? "1x1x1");
|
|
19276
|
+
const triBand = parseBand(options.triBand ?? "500-8000");
|
|
19277
|
+
const holder = options.holder?.trim() || HOLDER_PLACEHOLDER;
|
|
19278
|
+
const year = (/* @__PURE__ */ new Date()).getUTCFullYear();
|
|
19279
|
+
const outDir = path22.resolve(cwd, options.out ?? slug);
|
|
19280
|
+
if (fs23.existsSync(outDir) && fs23.readdirSync(outDir).length > 0 && !options.force) {
|
|
19281
|
+
log.error(`${outDir} already holds files. Nothing was touched \u2014 pass --force to write into it anyway.`);
|
|
19282
|
+
return 1;
|
|
19283
|
+
}
|
|
19284
|
+
const version = "1.0.0";
|
|
19285
|
+
const targetTriangles = Math.round((triBand[0] + triBand[1]) / 2);
|
|
19286
|
+
const config = {
|
|
19287
|
+
slug,
|
|
19288
|
+
name,
|
|
19289
|
+
summary: options.summary ?? `${name} - a free procedural Three.js asset, ${sizeMeters.map((n) => n.toFixed(2)).join(" \xD7 ")} m.`,
|
|
19290
|
+
tags: (options.tags ?? "prop").split(",").map((t) => t.trim()).filter(Boolean),
|
|
19291
|
+
materialClass: "unclassified",
|
|
19292
|
+
geometryClass: "unclassified",
|
|
19293
|
+
difficulty: "easy",
|
|
19294
|
+
version,
|
|
19295
|
+
minThreeRevision: 160,
|
|
19296
|
+
statedSizeMeters: sizeMeters,
|
|
19297
|
+
triBand,
|
|
19298
|
+
drawCallMax: 8,
|
|
19299
|
+
materialMax: 4,
|
|
19300
|
+
textureBytesMax: 4194304,
|
|
19301
|
+
maxTextureDimension: 1024,
|
|
19302
|
+
silhouetteCollapseRatio: 0.15,
|
|
19303
|
+
license: { holder, year },
|
|
19304
|
+
viewer: { cameraDistanceMul: 1 }
|
|
19305
|
+
};
|
|
19306
|
+
await fsp.mkdir(outDir, { recursive: true });
|
|
19307
|
+
for (const rel of [...SHARED_FILES, ...EXTRA_FILES]) {
|
|
19308
|
+
const to = path22.join(outDir, rel);
|
|
19309
|
+
await fsp.mkdir(path22.dirname(to), { recursive: true });
|
|
19310
|
+
await fsp.copyFile(path22.join(templateDir, rel), to);
|
|
19311
|
+
}
|
|
19312
|
+
const pkg = fillTemplate(await fsp.readFile(path22.join(templateDir, "package.json"), "utf8"), {
|
|
19313
|
+
slug,
|
|
19314
|
+
name,
|
|
19315
|
+
version
|
|
19316
|
+
});
|
|
19317
|
+
await fsp.writeFile(path22.join(outDir, "package.json"), pkg, "utf8");
|
|
19318
|
+
await fsp.writeFile(path22.join(outDir, "asset.config.json"), JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
19319
|
+
await fsp.writeFile(path22.join(outDir, ".gitignore"), GITIGNORE, "utf8");
|
|
19320
|
+
await fsp.writeFile(
|
|
19321
|
+
path22.join(outDir, "DESIGN.md"),
|
|
19322
|
+
designDoc({ name, slug, sizeMeters, triBand, holder }),
|
|
19323
|
+
"utf8"
|
|
19324
|
+
);
|
|
19325
|
+
const placeholder = await fsp.readFile(path22.join(templateDir, "src", "asset", "PLACEHOLDER.ts"), "utf8");
|
|
19326
|
+
const seeded = seedAssetSource(placeholder, {
|
|
19327
|
+
slug,
|
|
19328
|
+
name,
|
|
19329
|
+
sizeMeters,
|
|
19330
|
+
targetTriangles,
|
|
19331
|
+
holder,
|
|
19332
|
+
year,
|
|
19333
|
+
pascalCase
|
|
19334
|
+
});
|
|
19335
|
+
const assetFilename = `${slug.replace(/-([a-z0-9])/g, (_, ch) => ch.toUpperCase())}.ts`;
|
|
19336
|
+
await fsp.mkdir(path22.join(outDir, "src", "asset"), { recursive: true });
|
|
19337
|
+
await fsp.writeFile(path22.join(outDir, "src", "asset", assetFilename), seeded, "utf8");
|
|
19338
|
+
const copied = hashSharedFiles(outDir);
|
|
19339
|
+
const mismatched = SHARED_FILES.filter((rel) => copied[rel] !== lock.files[rel]);
|
|
19340
|
+
if (mismatched.length) {
|
|
19341
|
+
log.error(`Internal error: copy was not verbatim for ${mismatched.join(", ")}`);
|
|
19342
|
+
return 1;
|
|
19343
|
+
}
|
|
19344
|
+
await fsp.writeFile(
|
|
19345
|
+
path22.join(outDir, PARITY_FILENAME),
|
|
19346
|
+
JSON.stringify(
|
|
19347
|
+
{
|
|
19348
|
+
note: "Recorded by `genex asset new`. Gate G13 recomputes these; do not hand-edit the viewer.",
|
|
19349
|
+
templateVersion: lock.templateVersion,
|
|
19350
|
+
scaffoldedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19351
|
+
files: copied
|
|
19352
|
+
},
|
|
19353
|
+
null,
|
|
19354
|
+
2
|
|
19355
|
+
) + "\n",
|
|
19356
|
+
"utf8"
|
|
19357
|
+
);
|
|
19358
|
+
const lines = [
|
|
19359
|
+
"",
|
|
19360
|
+
` ${c.bold(name)} \u2192 ${outDir}`,
|
|
19361
|
+
` ${sizeMeters.map((n) => n.toFixed(2)).join(" \xD7 ")} m \xB7 ${triBand[0].toLocaleString("en-US")} - ${triBand[1].toLocaleString("en-US")} tris \xB7 viewer ${lock.templateVersion}`,
|
|
19362
|
+
""
|
|
19363
|
+
];
|
|
19364
|
+
if (holder === HOLDER_PLACEHOLDER) {
|
|
19365
|
+
lines.push(
|
|
19366
|
+
` ${c.yellow("\u26A0")} No --holder given: the MIT copyright line says "${HOLDER_PLACEHOLDER}".`,
|
|
19367
|
+
` Set the real name in asset.config.json (license.holder) AND line 2 of src/asset/${assetFilename}`,
|
|
19368
|
+
` before publishing \u2014 published assets are copyable by anyone under that name.`,
|
|
19369
|
+
""
|
|
19370
|
+
);
|
|
19371
|
+
}
|
|
19372
|
+
lines.push(
|
|
19373
|
+
" next:",
|
|
19374
|
+
` cd ${outDir}`,
|
|
19375
|
+
" npm install",
|
|
19376
|
+
" npx playwright-core install chromium # once per machine",
|
|
19377
|
+
` # author src/asset/${assetFilename} \u2014 the genex-asset-author skill carries the contract`,
|
|
19378
|
+
" npm run verify # build + 17 gates + manifest stamp",
|
|
19379
|
+
" npx genex init # create the project (sign-in if needed)",
|
|
19380
|
+
" npx genex preview --no-build # draft turntable to show the user",
|
|
19381
|
+
" npx genex publish --no-build --categories assets",
|
|
19382
|
+
""
|
|
19383
|
+
);
|
|
19384
|
+
log.plain(lines.join("\n"));
|
|
19385
|
+
return 0;
|
|
19386
|
+
}
|
|
19387
|
+
|
|
19455
19388
|
// src/index.ts
|
|
19456
19389
|
var GEN_KINDS = /* @__PURE__ */ new Set(["model", "skybox", "sfx", "music", "voice", "texture", "image", "video"]);
|
|
19457
19390
|
var HELP = `${c.bold("genex")} \u2014 set up your project's agent workspace, authorize, and publish 3D games.
|
|
@@ -19481,15 +19414,6 @@ ${c.bold("Usage")}
|
|
|
19481
19414
|
genex publish [options] Build + push + make live, then list it in the gallery.
|
|
19482
19415
|
genex make-remixable [options] Make THIS game remixable by everyone \u2014 migrates a
|
|
19483
19416
|
private source onto a public managed genex repo.
|
|
19484
|
-
genex domain <sub> [host] Play this game on a domain you own:
|
|
19485
|
-
add | list | verify | remove. "add" opens a
|
|
19486
|
-
one-click approval at your DNS host \u2014 no
|
|
19487
|
-
records to copy or paste.
|
|
19488
|
-
genex shop <sub> [name|id] What this game sells: list | add | set | remove.
|
|
19489
|
-
"add" prints the item id your game passes to
|
|
19490
|
-
buy({ skuId }) \u2014 the platform owns the catalog,
|
|
19491
|
-
so this is the only way one exists. Prices come
|
|
19492
|
-
off a fixed grid, printed on any refusal.
|
|
19493
19417
|
genex model "<prompt>" [options] Generate a 3D model (GLB); prints a public asset URL.
|
|
19494
19418
|
genex sfx "<prompt>" [options] Generate a sound effect (mp3); prints a public asset URL.
|
|
19495
19419
|
genex music "<prompt>" [options] Generate an instrumental music track (mp3); prints a
|
|
@@ -19564,6 +19488,13 @@ ${c.bold("Usage")}
|
|
|
19564
19488
|
extract | masks | text-color | trim | audit.
|
|
19565
19489
|
Pure local work (no API); the PNG tools read
|
|
19566
19490
|
a file or URL, audit scans the project.
|
|
19491
|
+
genex asset new <slug> Scaffold ONE asset project for the free MIT
|
|
19492
|
+
asset library (its own project \u2014 one asset,
|
|
19493
|
+
one turntable page). Only when the user asks
|
|
19494
|
+
to make or publish an asset \u2014 never offered
|
|
19495
|
+
unprompted. Options: --title --dims WxDxH
|
|
19496
|
+
--tri-band LOW-HIGH --holder "<your name>"
|
|
19497
|
+
--summary --tags --out --force.
|
|
19567
19498
|
|
|
19568
19499
|
${c.bold("Options for the generators (`model` `sfx` `music` `voice` `texture` `image` `video`)")}
|
|
19569
19500
|
--terrain (texture) seamless tiling surface for terrain/ground.
|
|
@@ -19730,12 +19661,6 @@ ${c.bold("Examples")}
|
|
|
19730
19661
|
genex publish
|
|
19731
19662
|
genex publish --categories games,vfx
|
|
19732
19663
|
genex make-remixable
|
|
19733
|
-
genex domain add play.mygame.com
|
|
19734
|
-
genex domain list
|
|
19735
|
-
genex shop list
|
|
19736
|
-
genex shop add "Iron Key" --price 100 --type durable
|
|
19737
|
-
genex shop set sku_123 --price 200
|
|
19738
|
-
genex shop remove sku_123
|
|
19739
19664
|
genex publish --no-push --title "My Game"
|
|
19740
19665
|
genex model "weathered wooden barrel with iron bands"
|
|
19741
19666
|
genex sfx "punchy laser zap" --duration 2
|
|
@@ -19787,10 +19712,6 @@ function parseArgs(argv) {
|
|
|
19787
19712
|
"--name",
|
|
19788
19713
|
"--repo",
|
|
19789
19714
|
"--remixed-from",
|
|
19790
|
-
"--price",
|
|
19791
|
-
"--icon",
|
|
19792
|
-
"--rename",
|
|
19793
|
-
"--type",
|
|
19794
19715
|
"--title",
|
|
19795
19716
|
"--description",
|
|
19796
19717
|
"--categories",
|
|
@@ -19844,7 +19765,13 @@ function parseArgs(argv) {
|
|
|
19844
19765
|
"--config",
|
|
19845
19766
|
"--set",
|
|
19846
19767
|
"--speed",
|
|
19847
|
-
"--video"
|
|
19768
|
+
"--video",
|
|
19769
|
+
// `genex asset new` value flags.
|
|
19770
|
+
"--dims",
|
|
19771
|
+
"--tri-band",
|
|
19772
|
+
"--holder",
|
|
19773
|
+
"--summary",
|
|
19774
|
+
"--tags"
|
|
19848
19775
|
]);
|
|
19849
19776
|
let i = 0;
|
|
19850
19777
|
while (i < argv.length) {
|
|
@@ -19862,9 +19789,6 @@ function parseArgs(argv) {
|
|
|
19862
19789
|
case "--no-auth":
|
|
19863
19790
|
parsed.options.noAuth = true;
|
|
19864
19791
|
break;
|
|
19865
|
-
case "--no-open":
|
|
19866
|
-
parsed.options.noOpen = true;
|
|
19867
|
-
break;
|
|
19868
19792
|
case "--no-push":
|
|
19869
19793
|
parsed.options.noPush = true;
|
|
19870
19794
|
break;
|
|
@@ -19999,20 +19923,15 @@ function parseArgs(argv) {
|
|
|
19999
19923
|
} else {
|
|
20000
19924
|
(parsed.options.selectors ??= []).push(arg);
|
|
20001
19925
|
}
|
|
20002
|
-
} else if (parsed.command === "
|
|
20003
|
-
|
|
20004
|
-
|
|
20005
|
-
|
|
20006
|
-
|
|
20007
|
-
}
|
|
20008
|
-
} else if (parsed.command === "domain") {
|
|
20009
|
-
if (!parsed.options.hostname) parsed.options.hostname = arg;
|
|
20010
|
-
else {
|
|
19926
|
+
} else if (parsed.command === "animations") {
|
|
19927
|
+
parsed.options.query = parsed.options.query ? `${parsed.options.query} ${arg}` : arg;
|
|
19928
|
+
} else if (parsed.command === "asset") {
|
|
19929
|
+
if (parsed.options.name === "new" && parsed.options.assetSlug === void 0) {
|
|
19930
|
+
parsed.options.assetSlug = arg;
|
|
19931
|
+
} else {
|
|
20011
19932
|
parsed.error = `Unexpected argument: ${arg}`;
|
|
20012
19933
|
return parsed;
|
|
20013
19934
|
}
|
|
20014
|
-
} else if (parsed.command === "animations") {
|
|
20015
|
-
parsed.options.query = parsed.options.query ? `${parsed.options.query} ${arg}` : arg;
|
|
20016
19935
|
} else {
|
|
20017
19936
|
parsed.error = `Unexpected argument: ${arg}`;
|
|
20018
19937
|
return parsed;
|
|
@@ -20149,6 +20068,21 @@ function applyValueFlag(options, flag, value) {
|
|
|
20149
20068
|
case "--set":
|
|
20150
20069
|
options.set = value;
|
|
20151
20070
|
break;
|
|
20071
|
+
case "--dims":
|
|
20072
|
+
options.dims = value;
|
|
20073
|
+
break;
|
|
20074
|
+
case "--tri-band":
|
|
20075
|
+
options.triBand = value;
|
|
20076
|
+
break;
|
|
20077
|
+
case "--holder":
|
|
20078
|
+
options.holder = value;
|
|
20079
|
+
break;
|
|
20080
|
+
case "--summary":
|
|
20081
|
+
options.summary = value;
|
|
20082
|
+
break;
|
|
20083
|
+
case "--tags":
|
|
20084
|
+
options.tags = value;
|
|
20085
|
+
break;
|
|
20152
20086
|
case "--speed": {
|
|
20153
20087
|
const n = Number(value);
|
|
20154
20088
|
if (!Number.isFinite(n) || n <= 0) {
|
|
@@ -20181,27 +20115,6 @@ function applyValueFlag(options, flag, value) {
|
|
|
20181
20115
|
options.duration = n;
|
|
20182
20116
|
break;
|
|
20183
20117
|
}
|
|
20184
|
-
case "--price": {
|
|
20185
|
-
const n = Number(value);
|
|
20186
|
-
if (!Number.isInteger(n) || n <= 0) {
|
|
20187
|
-
throw new Error(`Invalid --price value: ${value} (whole coin, e.g. 100)`);
|
|
20188
|
-
}
|
|
20189
|
-
options.price = n;
|
|
20190
|
-
break;
|
|
20191
|
-
}
|
|
20192
|
-
case "--icon":
|
|
20193
|
-
options.icon = value;
|
|
20194
|
-
break;
|
|
20195
|
-
case "--type": {
|
|
20196
|
-
if (value !== "consumable" && value !== "durable") {
|
|
20197
|
-
throw new Error(`Invalid --type value: ${value} (consumable or durable)`);
|
|
20198
|
-
}
|
|
20199
|
-
options.type = value;
|
|
20200
|
-
break;
|
|
20201
|
-
}
|
|
20202
|
-
case "--rename":
|
|
20203
|
-
options.rename = value;
|
|
20204
|
-
break;
|
|
20205
20118
|
case "--aspect":
|
|
20206
20119
|
options.aspect = value;
|
|
20207
20120
|
break;
|
|
@@ -20382,12 +20295,6 @@ async function main() {
|
|
|
20382
20295
|
case "make-remixable":
|
|
20383
20296
|
await runMakeRemixable(parsed.options);
|
|
20384
20297
|
break;
|
|
20385
|
-
case "shop":
|
|
20386
|
-
await runShop(parsed.options);
|
|
20387
|
-
break;
|
|
20388
|
-
case "domain":
|
|
20389
|
-
await runDomain(parsed.options);
|
|
20390
|
-
break;
|
|
20391
20298
|
case "controller":
|
|
20392
20299
|
await runController({ ...parsed.options, kind: parsed.options.name });
|
|
20393
20300
|
break;
|
|
@@ -20435,6 +20342,14 @@ async function main() {
|
|
|
20435
20342
|
await runAnimationsSearch(parsed.options);
|
|
20436
20343
|
}
|
|
20437
20344
|
break;
|
|
20345
|
+
case "asset":
|
|
20346
|
+
if (parsed.options.name !== "new") {
|
|
20347
|
+
log.error("Unknown asset command. Use `genex asset new <slug>`.");
|
|
20348
|
+
process.exitCode = 1;
|
|
20349
|
+
} else {
|
|
20350
|
+
process.exitCode = await runAssetNew(parsed.options);
|
|
20351
|
+
}
|
|
20352
|
+
break;
|
|
20438
20353
|
case "preview":
|
|
20439
20354
|
await runPreview(parsed.options);
|
|
20440
20355
|
break;
|