@genex-ai/cli-demo 1.25.1-dev.619 → 1.26.1-dev.621
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/{blender-mcp-PWOUSXK4.js → blender-mcp-Z4ENPVVE.js} +1 -1
- package/dist/{chunk-HEDRWUVP.js → chunk-CE3B24E2.js} +10 -1
- package/dist/index.js +93 -81
- package/package.json +1 -1
- package/templates/skills/genex-blender-scene/SKILL.md +16 -5
- package/templates/skills/genex-game-director/SKILL.md +7 -0
- package/templates/skills/genex-tool-publish/SKILL.md +100 -0
- package/templates/skills/genex-tool-workflow/SKILL.md +4 -3
|
@@ -704,6 +704,7 @@ async function touchCliSeat() {
|
|
|
704
704
|
}
|
|
705
705
|
}
|
|
706
706
|
async function blenderCall(base, route, body) {
|
|
707
|
+
const cwd = process.cwd();
|
|
707
708
|
const seat = readSeatGrant();
|
|
708
709
|
const ctl = new AbortController();
|
|
709
710
|
const timer = setTimeout(() => ctl.abort(), BLENDER_TIMEOUT_MS);
|
|
@@ -734,6 +735,15 @@ async function blenderCall(base, route, body) {
|
|
|
734
735
|
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
735
736
|
signal: ctl.signal
|
|
736
737
|
});
|
|
738
|
+
if (res.status === 401 && seat?.token) {
|
|
739
|
+
const current = readSeatGrant(cwd);
|
|
740
|
+
if (current?.url === seat.url && current.token === seat.token) deleteSeatGrant(cwd);
|
|
741
|
+
await res.body?.cancel().catch(() => {
|
|
742
|
+
});
|
|
743
|
+
throw new Error(
|
|
744
|
+
`${route} refused the seat token (401) \u2014 ${seat.kind === "cli" ? 'run "genex blender seat" to acquire a new one' : "run the command again to acquire a new one"}`
|
|
745
|
+
);
|
|
746
|
+
}
|
|
737
747
|
const text = await res.text();
|
|
738
748
|
let json;
|
|
739
749
|
try {
|
|
@@ -903,7 +913,6 @@ export {
|
|
|
903
913
|
apiFetch,
|
|
904
914
|
fetchSignedInEmail,
|
|
905
915
|
restrictFilePermissions,
|
|
906
|
-
getWorkspacePath,
|
|
907
916
|
readWorkspace,
|
|
908
917
|
writeWorkspace,
|
|
909
918
|
writeUserToken,
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
cloneSource,
|
|
15
15
|
fetchCloneGrant,
|
|
16
16
|
fetchSignedInEmail,
|
|
17
|
-
getWorkspacePath,
|
|
18
17
|
inHostedSession,
|
|
19
18
|
isRenderMode,
|
|
20
19
|
isStale,
|
|
@@ -42,7 +41,7 @@ import {
|
|
|
42
41
|
writeProject,
|
|
43
42
|
writeUserToken,
|
|
44
43
|
writeWorkspace
|
|
45
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-CE3B24E2.js";
|
|
46
45
|
import {
|
|
47
46
|
CLI_CHANNEL,
|
|
48
47
|
DEFAULT_API_URL,
|
|
@@ -747,18 +746,28 @@ Important note: put soul into your creations, with many details and love. Aim to
|
|
|
747
746
|
22. **What the player sees must read as the thing it is**, in this game's own style: a building as that building, a person as a person, a prop as that prop, a surface as its material \u2014 and a coloured box, a capsule, or a flat grey block standing in for one of them is never its finished version. Generation is the default route to that bar wherever code will not honestly reach it: the player's character, whatever the request names, whatever the player walks up to, enters, or interacts with, and the music and sound underneath \u2014 reach for the belt on your own judgment, without stopping to ask; a status line saying what you queued is enough, and \`--no-wait\` keeps you building while it lands. Procedural code is a first-class engine for what is structural, repeated, distant, or parametric \u2014 terrain, sky, fences, paving, modular kits, filler \u2014 and for anything else only when the result meets the same bar and you have looked at it in a capture before its row says \`landed\`. Decide per object, not per category, and write the route on each Assets row in \`DESIGN.md\` \u2014 the paid flow for generated pieces, the procedural flow with its local path for code-built ones. An Assets table with no generation in it is a decision, not a default: record it as \`Generation: none \u2014 <why code alone reaches the bar here>\` or generate.
|
|
748
747
|
${CONTRACT_END}
|
|
749
748
|
`;
|
|
750
|
-
var
|
|
751
|
-
# Genex Tools (always in effect in this folder)
|
|
749
|
+
var TOOLS_CONTRACT_HEAD = `# Genex Tools (always in effect in this folder)
|
|
752
750
|
|
|
753
|
-
This folder uses **Genex Tools**: AI asset generation for games, from your terminal. All via \`npx genex \u2026\` \u2014 \`model "<prompt>"\` (plus \`model --image <file>\` from a reference photo, \`model segment <id>\` into named parts, \`model rig <id>\` across 7 body plans, \`model animate <rig-id> --preset walk\`) \xB7 \`image\` (\`--transparent\` \xB7 \`--edit\` \xB7 \`--inpaint\` \xB7 \`--remove-bg\` \xB7 \`--upscale\`) \xB7 \`video\` \xB7 \`texture\` \xB7 \`sfx\` \xB7 \`music\` \xB7 \`voice\` \xB7 rigged \`character\` / \`creature\` and \`character animate <id> "<verb>"\` \xB7 \`animations search\` \xB7 \`wait <id>\` / \`wait --all\` \xB7 \`doctor\`. Full options with costs: \`npx genex --help\`. Each tool has a skill card (\`genex-tool-\u2026\`) \u2014 load the one for the lane you are using. The project and code are the user's own \u2014 Genex generates the assets, and can put the finished game live on the web when they want it
|
|
754
|
-
|
|
755
|
-
1. **NEVER delete, empty, move, rename, or overwrite anything you did not create yourself.** This is someone's real project: it holds their code, their reference images, their notes, an earlier attempt \u2014 files that may 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 prompt, and every tool's offer to empty a directory (\`--force\`, \`--overwrite\`, "Remove existing files"). 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.
|
|
751
|
+
This folder uses **Genex Tools**: AI asset generation for games, from your terminal. All via \`npx genex \u2026\` \u2014 \`model "<prompt>"\` (plus \`model --image <file>\` from a reference photo, \`model segment <id>\` into named parts, \`model rig <id>\` across 7 body plans, \`model animate <rig-id> --preset walk\`) \xB7 \`image\` (\`--transparent\` \xB7 \`--edit\` \xB7 \`--inpaint\` \xB7 \`--remove-bg\` \xB7 \`--upscale\`) \xB7 \`video\` \xB7 \`texture\` \xB7 \`sfx\` \xB7 \`music\` \xB7 \`voice\` \xB7 rigged \`character\` / \`creature\` and \`character animate <id> "<verb>"\` \xB7 \`animations search\` \xB7 \`wait <id>\` / \`wait --all\` \xB7 \`doctor\`. Full options with costs: \`npx genex --help\`. Each tool has a skill card (\`genex-tool-\u2026\`) \u2014 load the one for the lane you are using. The project and code are the user's own \u2014 Genex generates the assets, and can put the finished game live on the web when they want it.`;
|
|
752
|
+
var TOOLS_CONTRACT_LAWS_1_TO_4 = `1. **NEVER delete, empty, move, rename, or overwrite anything you did not create yourself.** This is someone's real project: it holds their code, their reference images, their notes, an earlier attempt \u2014 files that may 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 prompt, and every tool's offer to empty a directory (\`--force\`, \`--overwrite\`, "Remove existing files"). 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.
|
|
756
753
|
2. **Generated assets are local files the user owns.** Every generation downloads into \`./assets/\` and the command prints the local path \u2014 wire THAT path into the code, exactly like an asset they drew themselves. The URL printed beside it is provenance, not hosting: never fetch it at runtime, never ship a build that depends on it. Assets are theirs to move, rename, commit, or process further.
|
|
757
754
|
3. **Run \`npx genex doctor\` when a command fails, and before promising what you can generate.** It reports sign-in, credit balance, and which generation lanes are actually live on this account \u2014 the only live source for all three. Treat every genex warning line as work, not noise, and never re-run a generation command to "retry" one that is still running: that bills a second asset. Attach to the first with \`npx genex wait <id>\`.
|
|
758
|
-
4. **Real assets beat placeholders.** When anything you build will be seen or heard by a player, reach for the belt first \u2014 a generated model instead of a colored box, a texture instead of a flat material, music and sound instead of silence, a rigged character instead of a capsule. Put soul into the details; never limit your imagination to what is easy to code. You know the tools and their costs (\`npx genex --help\`) \u2014 decide and generate on your own judgment, without stopping to ask permission; a status line saying what you queued is enough. Queue with \`--no-wait\` and keep building while generations run
|
|
759
|
-
5. **When the game is built and playable \u2014 never mid-build \u2014 offer once, in one line: "Want to make your game live on the web with its own URL?"** Offer only if the game runs in the browser and builds to static files; otherwise never bring it up. A no means drop it for good. On a yes, run \`npx genex init --convert\` and follow what it prints \u2014 it
|
|
755
|
+
4. **Real assets beat placeholders.** When anything you build will be seen or heard by a player, reach for the belt first \u2014 a generated model instead of a colored box, a texture instead of a flat material, music and sound instead of silence, a rigged character instead of a capsule. Put soul into the details; never limit your imagination to what is easy to code. You know the tools and their costs (\`npx genex --help\`) \u2014 decide and generate on your own judgment, without stopping to ask permission; a status line saying what you queued is enough. Queue with \`--no-wait\` and keep building while generations run.`;
|
|
756
|
+
var TOOLS_CONTRACT_OFFER = `5. **When the game is built and playable \u2014 never mid-build \u2014 offer once, in one line: "Want to make your game live on the web with its own URL?"** Offer only if the game runs in the browser and builds to static files; otherwise never bring it up. A no means drop it for good. On a yes, run \`npx genex init --convert\` and follow what it prints \u2014 it connects this folder to a hosted Genex game: the code and this toolkit stay exactly as they are, the source goes to a private repo, and the game gets a draft page plus a public URL when they choose to publish.`;
|
|
757
|
+
var TOOLS_CONTRACT_HOSTED_LAWS = `5. **This game is hosted on Genex now, and publishing is glue \u2014 never a rebuild.** The game ships exactly as the user built it: \`initEmbed()\` first in the boot code (the \`genex-threejs-embed-auth\` card has the call; the renderer draws before any \`await waitForPlayer()\`), a static build into \`dist/\` with relative asset paths, then \`npx genex preview\`. Never restructure, reformat or "improve" the game to ship it, never scaffold a new app around it, and never start a design document or a build plan for it \u2014 the user's own process is theirs. Load the \`genex-tool-publish\` card before the first preview; it owns the vocabulary, the links and the limits.
|
|
758
|
+
6. **Ship first, then report.** \`preview\` prints a preflight \u2014 phone memory, a missing volume slider, an asset nobody wired, a viewport line. It never blocks a deploy, so push the build as it is, hand over the link, THEN relay each preflight line to the user in one plain sentence with an offer to fix it, and fix one only on their yes. The preflight is a report for the user, not a to-do list for you.
|
|
759
|
+
7. **The link is the game's page, and there are two versions.** After every preview give the user \`<dashboard>/draft/<slug>\` \u2014 \`dashboardOrigins[0]\` and \`slug\` from \`.genex/project.json\` \u2014 never localhost, a file path or the bare play origin. \`preview\` updates the draft and never touches what players are on; the first release is \`npx genex publish\` (it lists the game); after that "publish it", "update it" and "yes" all mean \`npx genex promote\` \u2014 the exact draft build, no rebuild. Ask once per round of work, in one line, and keep working while you wait.`;
|
|
760
|
+
function toolsContractBlock(opts) {
|
|
761
|
+
return `${CONTRACT_BEGIN}
|
|
762
|
+
${TOOLS_CONTRACT_HEAD}${opts.hosted ? " This game is also hosted on Genex: `preview` \xB7 `promote` \xB7 `publish` \xB7 `rollback` \xB7 `rename` work here (the `genex-tool-publish` card owns them)." : ""}
|
|
763
|
+
|
|
764
|
+
${TOOLS_CONTRACT_LAWS_1_TO_4}
|
|
765
|
+
${opts.hosted ? TOOLS_CONTRACT_HOSTED_LAWS : TOOLS_CONTRACT_OFFER}
|
|
760
766
|
${CONTRACT_END}
|
|
761
767
|
`;
|
|
768
|
+
}
|
|
769
|
+
var GENEX_TOOLS_CONTRACT_BLOCK = toolsContractBlock({ hosted: false });
|
|
770
|
+
var GENEX_TOOLS_HOSTED_CONTRACT_BLOCK = toolsContractBlock({ hosted: true });
|
|
762
771
|
var CLAUDE_IMPORT_LINE = "@AGENTS.md";
|
|
763
772
|
var CONTRACT_REGION = /<!-- genex:contract:begin[^\n]*-->[\s\S]*?<!-- genex:contract:end -->/;
|
|
764
773
|
function mergeContractBlock(existing, contractBlock = GENEX_CONTRACT_BLOCK) {
|
|
@@ -814,8 +823,8 @@ ${CLAUDE_IMPORT_LINE}
|
|
|
814
823
|
}
|
|
815
824
|
return changed;
|
|
816
825
|
}
|
|
817
|
-
async function writeToolsContract(projectDir) {
|
|
818
|
-
return writeAgentsContract(projectDir,
|
|
826
|
+
async function writeToolsContract(projectDir, opts = {}) {
|
|
827
|
+
return writeAgentsContract(projectDir, toolsContractBlock({ hosted: opts.hosted === true }));
|
|
819
828
|
}
|
|
820
829
|
async function healAncestorContracts(projectDir, stopDir = os3.homedir()) {
|
|
821
830
|
const findings = [];
|
|
@@ -894,21 +903,23 @@ import path4 from "path";
|
|
|
894
903
|
// src/lib/workspace.ts
|
|
895
904
|
async function resolveWorkspace(cwd = process.cwd()) {
|
|
896
905
|
const project = await readProject(cwd);
|
|
897
|
-
|
|
898
|
-
const slug = typeof project.slug === "string" && project.slug.trim() ? project.slug.trim() : null;
|
|
899
|
-
return { mode: "game", slug };
|
|
900
|
-
}
|
|
906
|
+
const slug = project && typeof project.slug === "string" && project.slug.trim() ? project.slug.trim() : null;
|
|
901
907
|
const ws = await readWorkspace(cwd);
|
|
902
|
-
|
|
908
|
+
if (ws?.mode === "tools") return { mode: "tools", slug, hosted: Boolean(project) };
|
|
909
|
+
if (project) return { mode: "game", slug, hosted: true };
|
|
910
|
+
return { mode: null, slug: null, hosted: false };
|
|
903
911
|
}
|
|
904
912
|
async function workspaceMode(cwd = process.cwd()) {
|
|
905
913
|
return (await resolveWorkspace(cwd)).mode;
|
|
906
914
|
}
|
|
907
915
|
function workspaceHeaderLabel(ws) {
|
|
916
|
+
if (ws.hosted) return ws.slug ? { game: ws.slug } : ws.mode === "tools" ? "tools" : null;
|
|
908
917
|
if (ws.mode === "tools") return "tools";
|
|
909
|
-
if (ws.mode === "game" && ws.slug) return { game: ws.slug };
|
|
910
918
|
return null;
|
|
911
919
|
}
|
|
920
|
+
function platformRefusedIn(ws, command) {
|
|
921
|
+
return ws.mode === "tools" && !ws.hosted && refusedInTools(command);
|
|
922
|
+
}
|
|
912
923
|
var REFUSED_IN_TOOLS = /* @__PURE__ */ new Set([
|
|
913
924
|
"link",
|
|
914
925
|
"pull",
|
|
@@ -982,19 +993,12 @@ async function hasGenexSkills(skillsDir) {
|
|
|
982
993
|
}
|
|
983
994
|
}
|
|
984
995
|
var TOOL_SKILL_PREFIX = "genex-tool-";
|
|
985
|
-
var
|
|
986
|
-
"genex-tool-audio",
|
|
987
|
-
"genex-tool-character",
|
|
988
|
-
"genex-tool-image",
|
|
989
|
-
"genex-tool-model",
|
|
990
|
-
"genex-tool-texture",
|
|
991
|
-
"genex-tool-video",
|
|
992
|
-
"genex-tool-workflow"
|
|
993
|
-
];
|
|
996
|
+
var HOSTED_TOOL_SKILLS = ["genex-tool-publish", "genex-threejs-embed-auth"];
|
|
994
997
|
function skillFamilyOf(name) {
|
|
995
998
|
return name.startsWith(TOOL_SKILL_PREFIX) ? "tools" : "game";
|
|
996
999
|
}
|
|
997
|
-
function skillFamilyFilter(mode) {
|
|
1000
|
+
function skillFamilyFilter(mode, opts = {}) {
|
|
1001
|
+
const hostedExtras = new Set(HOSTED_TOOL_SKILLS);
|
|
998
1002
|
return (rel) => {
|
|
999
1003
|
const segments = rel.split(/[\\/]/);
|
|
1000
1004
|
const top = segments[0] ?? "";
|
|
@@ -1003,6 +1007,7 @@ function skillFamilyFilter(mode) {
|
|
|
1003
1007
|
}
|
|
1004
1008
|
const skill = segments[1];
|
|
1005
1009
|
if (skill === void 0) return true;
|
|
1010
|
+
if (mode === "tools" && hostedExtras.has(skill)) return opts.hosted === true;
|
|
1006
1011
|
return skillFamilyOf(skill) === mode;
|
|
1007
1012
|
};
|
|
1008
1013
|
}
|
|
@@ -1057,38 +1062,13 @@ async function pruneRemovedSkills(skillsDir, log) {
|
|
|
1057
1062
|
}
|
|
1058
1063
|
return removed.length > 0;
|
|
1059
1064
|
}
|
|
1060
|
-
async function
|
|
1061
|
-
let removedAny = false;
|
|
1062
|
-
for (const target of resolveAgentTargets(overrides ?? {})) {
|
|
1063
|
-
const skillsDir = path4.join(target.baseDir, "skills");
|
|
1064
|
-
for (const name of TOOL_SKILLS) {
|
|
1065
|
-
const dir = path4.join(skillsDir, name);
|
|
1066
|
-
try {
|
|
1067
|
-
await fs4.access(dir);
|
|
1068
|
-
} catch {
|
|
1069
|
-
continue;
|
|
1070
|
-
}
|
|
1071
|
-
try {
|
|
1072
|
-
await fs4.rm(dir, { recursive: true });
|
|
1073
|
-
removedAny = true;
|
|
1074
|
-
} catch {
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
if (removedAny) {
|
|
1079
|
-
log?.plain(
|
|
1080
|
-
"\u{1F9F9} Removed the Genex Tools cards \u2014 this is a game workspace now, and the game skills cover every lane."
|
|
1081
|
-
);
|
|
1082
|
-
}
|
|
1083
|
-
return removedAny;
|
|
1084
|
-
}
|
|
1085
|
-
async function syncSkillsForTarget(target, templatesDir = getTemplatesDir(), version = getCliVersion(), log, family = "game") {
|
|
1065
|
+
async function syncSkillsForTarget(target, templatesDir = getTemplatesDir(), version = getCliVersion(), log, family = "game", hosted = false) {
|
|
1086
1066
|
const skillsDir = path4.join(target.baseDir, "skills");
|
|
1087
1067
|
if (!await hasGenexSkills(skillsDir)) return false;
|
|
1088
1068
|
if (await readSkillsMarker(skillsDir) === version) return false;
|
|
1089
1069
|
const src = target.full ? templatesDir : path4.join(templatesDir, "skills");
|
|
1090
1070
|
const dest = target.full ? target.baseDir : skillsDir;
|
|
1091
|
-
const familyFilter = skillFamilyFilter(family);
|
|
1071
|
+
const familyFilter = skillFamilyFilter(family, { hosted });
|
|
1092
1072
|
const filter = target.full ? familyFilter : (rel) => familyFilter(`skills/${rel}`);
|
|
1093
1073
|
await copyTemplates(src, dest, {
|
|
1094
1074
|
exclude: ["controllers", "motion", "asset-viewer", "blender-service"],
|
|
@@ -1142,17 +1122,18 @@ async function cleanupLegacyGlobalSkills(log) {
|
|
|
1142
1122
|
return false;
|
|
1143
1123
|
}
|
|
1144
1124
|
}
|
|
1145
|
-
async function syncSkills(log,
|
|
1125
|
+
async function syncSkills(log, workspace) {
|
|
1146
1126
|
try {
|
|
1147
1127
|
await cleanupLegacyGlobalSkills(log);
|
|
1148
1128
|
const version = getCliVersion();
|
|
1149
1129
|
const templatesDir = getTemplatesDir();
|
|
1150
|
-
const
|
|
1130
|
+
const ws = workspace !== void 0 ? workspace : await resolveWorkspace();
|
|
1131
|
+
const resolved = ws.mode;
|
|
1151
1132
|
const family = resolved === "tools" ? "tools" : "game";
|
|
1152
1133
|
let refreshed = false;
|
|
1153
1134
|
for (const target of resolveAgentTargets()) {
|
|
1154
1135
|
try {
|
|
1155
|
-
refreshed = await syncSkillsForTarget(target, templatesDir, version, log, family) || refreshed;
|
|
1136
|
+
refreshed = await syncSkillsForTarget(target, templatesDir, version, log, family, ws.hosted) || refreshed;
|
|
1156
1137
|
} catch {
|
|
1157
1138
|
}
|
|
1158
1139
|
}
|
|
@@ -1163,7 +1144,7 @@ async function syncSkills(log, mode) {
|
|
|
1163
1144
|
log.plain("\u{1F504} Genex build contract refreshed (AGENTS.md managed block)");
|
|
1164
1145
|
}
|
|
1165
1146
|
} else if (resolved === "tools") {
|
|
1166
|
-
if (await writeToolsContract(process.cwd())) {
|
|
1147
|
+
if (await writeToolsContract(process.cwd(), { hosted: ws.hosted })) {
|
|
1167
1148
|
log.plain("\u{1F504} Genex Tools workspace rules refreshed (AGENTS.md managed block)");
|
|
1168
1149
|
}
|
|
1169
1150
|
} else {
|
|
@@ -1612,15 +1593,28 @@ function warnPreexisting(log, entries) {
|
|
|
1612
1593
|
async function runInit(opts) {
|
|
1613
1594
|
const log = createLogger({ quiet: opts.quiet });
|
|
1614
1595
|
const ws = await readWorkspace();
|
|
1596
|
+
const project = ws?.mode === "tools" ? await readProject() : null;
|
|
1597
|
+
if (ws?.mode === "tools" && project) {
|
|
1598
|
+
log.success(
|
|
1599
|
+
`This folder is already connected to the Genex game ${c.cyan(project.slug)} \u2014 nothing to convert.`
|
|
1600
|
+
);
|
|
1601
|
+
log.plain(
|
|
1602
|
+
[
|
|
1603
|
+
` ${c.cyan("\u2192")} ${c.cyan("npx genex preview")} builds the game and puts it on its draft page.`,
|
|
1604
|
+
` ${c.cyan("\u2192")} ${c.cyan("npx genex doctor")} checks sign-in, credits and the lanes.`
|
|
1605
|
+
].join("\n")
|
|
1606
|
+
);
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1615
1609
|
if (ws?.mode === "tools" && !opts.convert) {
|
|
1616
1610
|
log.error(
|
|
1617
|
-
"This folder is a Genex Tools workspace \u2014
|
|
1611
|
+
"This folder is a Genex Tools workspace \u2014 putting its game on Genex is the user's call."
|
|
1618
1612
|
);
|
|
1619
1613
|
log.plain(
|
|
1620
1614
|
[
|
|
1621
1615
|
" Converting means: a Genex project is created for this folder, the source is pushed to a",
|
|
1622
1616
|
" private Genex-managed repo, and the game gets its own play URL and draft page. The code",
|
|
1623
|
-
" stays here and stays theirs; nothing is moved or rewritten.",
|
|
1617
|
+
" stays here and stays theirs, this toolkit stays as it is; nothing is moved or rewritten.",
|
|
1624
1618
|
` ${c.cyan("\u2192")} Ask the user first. If they say yes: ${c.cyan("npx genex init --convert")}`,
|
|
1625
1619
|
` ${c.cyan("\u2192")} Just want the tools working? ${c.cyan("npx genex doctor")} says what needs fixing.`
|
|
1626
1620
|
].join("\n")
|
|
@@ -1646,10 +1640,11 @@ async function runInit(opts) {
|
|
|
1646
1640
|
for (const t of targets) {
|
|
1647
1641
|
const src = t.full ? templatesDir : path7.join(templatesDir, "skills");
|
|
1648
1642
|
const dest = t.full ? t.baseDir : path7.join(t.baseDir, "skills");
|
|
1643
|
+
const family = converting ? skillFamilyFilter("tools", { hosted: true }) : skillFamilyFilter("game");
|
|
1649
1644
|
const { copied, updated } = await copyTemplates(src, dest, {
|
|
1650
1645
|
force: opts.force,
|
|
1651
1646
|
exclude: ["controllers", "motion", "asset-viewer", "blender-service"],
|
|
1652
|
-
filter: t.full ?
|
|
1647
|
+
filter: t.full ? family : (rel) => family(`skills/${rel}`)
|
|
1653
1648
|
});
|
|
1654
1649
|
await pruneRemovedSkills(path7.join(t.baseDir, "skills"), log);
|
|
1655
1650
|
await writeSkillsMarker(path7.join(t.baseDir, "skills"));
|
|
@@ -1693,7 +1688,7 @@ async function runInit(opts) {
|
|
|
1693
1688
|
savedToken = false;
|
|
1694
1689
|
}
|
|
1695
1690
|
await writeGitignore(process.cwd(), log);
|
|
1696
|
-
if (await writeAgentsContract(process.cwd())) {
|
|
1691
|
+
if (!converting && await writeAgentsContract(process.cwd())) {
|
|
1697
1692
|
log.dim(" wrote the Genex build contract into AGENTS.md (managed block) + CLAUDE.md stub");
|
|
1698
1693
|
}
|
|
1699
1694
|
await healAncestorContractsAndReport(log, process.cwd());
|
|
@@ -1751,9 +1746,9 @@ async function runInit(opts) {
|
|
|
1751
1746
|
log.dim(` saved ${c.cyan(metaPath)}`);
|
|
1752
1747
|
await writeGameConfigFiles(meta, log);
|
|
1753
1748
|
if (converting) {
|
|
1754
|
-
await
|
|
1755
|
-
|
|
1756
|
-
|
|
1749
|
+
if (await writeToolsContract(process.cwd(), { hosted: true })) {
|
|
1750
|
+
log.dim(" wrote the Genex Tools publishing rules into AGENTS.md (managed block)");
|
|
1751
|
+
}
|
|
1757
1752
|
await ensureEnvVar(path7.join(process.cwd(), ".env"), "VITE_GENEX_SLUG", meta.slug, log);
|
|
1758
1753
|
await reportCliEvent(apiUrl, token, "tools_converted");
|
|
1759
1754
|
}
|
|
@@ -1762,17 +1757,19 @@ async function runInit(opts) {
|
|
|
1762
1757
|
log.success("All set. \u{1F680}");
|
|
1763
1758
|
if (converting) {
|
|
1764
1759
|
log.plain("");
|
|
1765
|
-
log.plain(c.bold("
|
|
1760
|
+
log.plain(c.bold("Connected: this game now has a home on Genex. Publish it as it is \u2014 nothing else changes."));
|
|
1766
1761
|
log.plain(
|
|
1767
1762
|
[
|
|
1768
|
-
"
|
|
1769
|
-
|
|
1763
|
+
" This is still a Genex Tools workspace (same cards, same rules, assets in ./assets); it gained",
|
|
1764
|
+
` the publishing commands, the ${c.cyan("genex-tool-publish")} card and three rules about shipping.`,
|
|
1765
|
+
" Do NOT start a design document, a build plan or a rework \u2014 the user asked for it online.",
|
|
1770
1766
|
" Next, to get the game on its draft page:",
|
|
1771
1767
|
` 1. Make sure the game builds to static files with ${c.cyan("npm run build")} into ${c.cyan("dist/")} (or an`,
|
|
1772
1768
|
` index.html at the root) and asset paths are relative (Vite: ${c.cyan("base: './'")}).`,
|
|
1773
|
-
` 2. Load the ${c.cyan("genex-threejs-embed-auth")}
|
|
1769
|
+
` 2. Load the ${c.cyan("genex-threejs-embed-auth")} card and add the one ${c.cyan("initEmbed()")} call at the`,
|
|
1774
1770
|
" top of the game's boot code.",
|
|
1775
|
-
` 3. ${c.cyan("npx genex preview")} \u2014 then send the user their draft page link
|
|
1771
|
+
` 3. ${c.cyan("npx genex preview")} \u2014 then send the user their draft page link. Its preflight lines`,
|
|
1772
|
+
" are a report to relay in one sentence each, not work to do before the link."
|
|
1776
1773
|
].join("\n")
|
|
1777
1774
|
);
|
|
1778
1775
|
}
|
|
@@ -4832,7 +4829,7 @@ async function deployGame(ctx, opts, log) {
|
|
|
4832
4829
|
}
|
|
4833
4830
|
printMobilePreflight(files, log);
|
|
4834
4831
|
await printUiAuditPreflight(log);
|
|
4835
|
-
await printPipelineStatePreflight(log);
|
|
4832
|
+
if (await workspaceMode(cwd) !== "tools") await printPipelineStatePreflight(log);
|
|
4836
4833
|
const commit = contentCommit(files);
|
|
4837
4834
|
log.step("Requesting an upload token\u2026");
|
|
4838
4835
|
const grant = await getUploadToken(ctx, commit, log);
|
|
@@ -8487,7 +8484,8 @@ async function runWaitAll(opts) {
|
|
|
8487
8484
|
rows.push(await toRow(e, v, cwd));
|
|
8488
8485
|
}
|
|
8489
8486
|
const delivered = [];
|
|
8490
|
-
|
|
8487
|
+
const mode = await workspaceMode(cwd);
|
|
8488
|
+
if (!opts.noDownload && mode === "tools") {
|
|
8491
8489
|
for (const e of ledger) {
|
|
8492
8490
|
const v = views.get(e.id);
|
|
8493
8491
|
if (v?.status !== "completed" || !v.files?.length) continue;
|
|
@@ -8558,7 +8556,7 @@ async function runWaitAll(opts) {
|
|
|
8558
8556
|
}
|
|
8559
8557
|
log.plain("");
|
|
8560
8558
|
log.dim(
|
|
8561
|
-
" \u{1F4DC} Resumed or compacted? Re-read AGENTS.md + DESIGN.md (continue from its Now: line) before building on."
|
|
8559
|
+
mode === "tools" ? " \u{1F4DC} Resumed or compacted? Re-read AGENTS.md before building on." : " \u{1F4DC} Resumed or compacted? Re-read AGENTS.md + DESIGN.md (continue from its Now: line) before building on."
|
|
8562
8560
|
);
|
|
8563
8561
|
}
|
|
8564
8562
|
async function toRow(e, v, cwd) {
|
|
@@ -18614,7 +18612,8 @@ async function runCharacterAdopt(opts) {
|
|
|
18614
18612
|
fail2(opts, "Not authorized. Run `genex init` first to sign in.", "character");
|
|
18615
18613
|
return;
|
|
18616
18614
|
}
|
|
18617
|
-
|
|
18615
|
+
const ws = await resolveWorkspace();
|
|
18616
|
+
if (ws.mode === "tools" && !ws.hosted) {
|
|
18618
18617
|
reportPlatformRefused(log, "character adopt");
|
|
18619
18618
|
process.exitCode = 1;
|
|
18620
18619
|
return;
|
|
@@ -21105,7 +21104,7 @@ async function runBlender(opts) {
|
|
|
21105
21104
|
return serveLocalBlender({ port, log });
|
|
21106
21105
|
}
|
|
21107
21106
|
if (sub === "mcp") {
|
|
21108
|
-
const { runBlenderMcp } = await import("./blender-mcp-
|
|
21107
|
+
const { runBlenderMcp } = await import("./blender-mcp-Z4ENPVVE.js");
|
|
21109
21108
|
return runBlenderMcp();
|
|
21110
21109
|
}
|
|
21111
21110
|
if (sub === "seat") {
|
|
@@ -22061,6 +22060,18 @@ async function latestPublished() {
|
|
|
22061
22060
|
async function runTools(opts) {
|
|
22062
22061
|
const log = createLogger({ quiet: opts.quiet });
|
|
22063
22062
|
const project = await readProject();
|
|
22063
|
+
if (project && (await readWorkspace())?.mode === "tools") {
|
|
22064
|
+
log.success(
|
|
22065
|
+
`This folder is already a Genex Tools workspace, connected to the game ${c.cyan(project.slug)}.`
|
|
22066
|
+
);
|
|
22067
|
+
log.plain(
|
|
22068
|
+
[
|
|
22069
|
+
" The tool cards refresh on every command; nothing to set up.",
|
|
22070
|
+
` ${c.cyan("\u2192")} ${c.cyan("npx genex doctor")} checks sign-in, credits and the lanes.`
|
|
22071
|
+
].join("\n")
|
|
22072
|
+
);
|
|
22073
|
+
return;
|
|
22074
|
+
}
|
|
22064
22075
|
if (project) {
|
|
22065
22076
|
log.error("This folder is already a Genex game project.");
|
|
22066
22077
|
log.plain(
|
|
@@ -22473,9 +22484,10 @@ ${c.bold("Options for `init`")}
|
|
|
22473
22484
|
--auth-url <url> Override the auth site (default: ${DEFAULT_AUTH_URL}).
|
|
22474
22485
|
--api-url <url> Override the API base URL (default: ${DEFAULT_API_URL}).
|
|
22475
22486
|
--no-auth Only scaffold templates; skip authorization.
|
|
22476
|
-
--convert In a Genex Tools workspace: with the user's explicit yes,
|
|
22477
|
-
the folder
|
|
22478
|
-
|
|
22487
|
+
--convert In a Genex Tools workspace: with the user's explicit yes, connect
|
|
22488
|
+
the folder to a hosted Genex game in place \u2014 the toolkit stays,
|
|
22489
|
+
the publishing commands open (init is otherwise refused there).
|
|
22490
|
+
Ask the user before passing this.
|
|
22479
22491
|
--force Overwrite existing files (default: never overwrite).
|
|
22480
22492
|
--timeout <seconds> How long to wait inline for sign-in approval before handing
|
|
22481
22493
|
off to 'genex auth' (default: 100). The code itself stays
|
|
@@ -23456,14 +23468,14 @@ async function main() {
|
|
|
23456
23468
|
setWorkspaceHeader(workspaceHeaderLabel(workspace));
|
|
23457
23469
|
if (parsed.help || parsed.command === void 0) {
|
|
23458
23470
|
const perCommand = parsed.command === void 0 ? void 0 : COMMAND_HELP[parsed.command];
|
|
23459
|
-
log.plain(perCommand ?? (mode === "tools" ? HELP_TOOLS : HELP));
|
|
23471
|
+
log.plain(perCommand ?? (mode === "tools" && !workspace.hosted ? HELP_TOOLS : HELP));
|
|
23460
23472
|
return;
|
|
23461
23473
|
}
|
|
23462
23474
|
const isBlenderMcp = parsed.command === "blender" && parsed.options.name === "mcp";
|
|
23463
23475
|
const quietUpdates = Boolean(parsed.options.quiet || parsed.options.json || isBlenderMcp);
|
|
23464
23476
|
const updateLog = createLogger({ quiet: quietUpdates });
|
|
23465
23477
|
if (parsed.command !== "init" && parsed.command !== "tools") {
|
|
23466
|
-
await syncSkills(updateLog,
|
|
23478
|
+
await syncSkills(updateLog, workspace);
|
|
23467
23479
|
}
|
|
23468
23480
|
const updateCheck = startUpdateCheck();
|
|
23469
23481
|
Sentry2.setTag("command", parsed.command);
|
|
@@ -23478,7 +23490,7 @@ async function main() {
|
|
|
23478
23490
|
ci: !!process.env.CI
|
|
23479
23491
|
});
|
|
23480
23492
|
try {
|
|
23481
|
-
if (
|
|
23493
|
+
if (platformRefusedIn(workspace, parsed.command)) {
|
|
23482
23494
|
reportPlatformRefused(log, parsed.command);
|
|
23483
23495
|
process.exitCode = 1;
|
|
23484
23496
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/cli-demo",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.26.1-dev.621",
|
|
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": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: genex-blender-scene
|
|
3
|
-
description: Compose a level, arena, interior, building or map — a SPACE of rooms, lanes, walls and sites that must relate — in a live Blender with `npx genex blender`, then export one GLB the game walks through. Use
|
|
3
|
+
description: Compose a level, arena, interior, building or map — a SPACE of rooms, lanes, walls and sites that must relate — in a live Blender with `npx genex blender`, then export one GLB the game walks through. Use for places to move through, floor plans, or explicit Blender authoring including standalone assets. Probe the lane first; code is the fallback only when Blender was not explicitly required.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Genex Blender · Scene
|
|
@@ -12,13 +12,22 @@ level grows the way a level is actually built: shell, then zones, then dressing.
|
|
|
12
12
|
|
|
13
13
|
## When this lane owns the work
|
|
14
14
|
|
|
15
|
+
- An explicit request to author or edit **in Blender**, even one standalone
|
|
16
|
+
asset. This overrides the default single-object route below. Keep Python
|
|
17
|
+
source under `blender/`, export the requested GLB and show it in a simple
|
|
18
|
+
browser preview; add gameplay only when requested. In hosted creation use
|
|
19
|
+
the CLI through Bash and open contact sheets with Read: the Blender MCP
|
|
20
|
+
server is not registered in the hosted harness.
|
|
21
|
+
If Blender is unavailable, report that limitation and keep the authored
|
|
22
|
+
scripts; the code fallback below is for requests that did not require Blender.
|
|
23
|
+
|
|
15
24
|
- A **space**: a level, arena, interior, building, map, dungeon, station — several
|
|
16
25
|
rooms, lanes, walls or sites that must relate to each other, and a player who
|
|
17
26
|
moves through them.
|
|
18
27
|
- A **floor plan or reference** to trace (a sketch, an ASCII plan, a photo of a room).
|
|
19
28
|
- A **blockout**: the playable shape first, dressing later.
|
|
20
29
|
|
|
21
|
-
|
|
30
|
+
Default routes when the player did not explicitly request Blender:
|
|
22
31
|
|
|
23
32
|
- One recognisable object (a barrel, a cart, a statue) → `$genex-ai-model`.
|
|
24
33
|
- One parameterized object placed many times with variation, or a stylized shader
|
|
@@ -224,9 +233,11 @@ when one is available — the measurements favour it, and it costs nothing.
|
|
|
224
233
|
- `payload_too_large` on export → fewer cylinder segments, delete faces the player
|
|
225
234
|
never sees, or export two GLBs (`--out public/assets/level-a.glb`).
|
|
226
235
|
- The sheet is grey → `material.diffuse_color` was not set (see above).
|
|
227
|
-
- `refused the seat token (401)` → the
|
|
228
|
-
|
|
229
|
-
|
|
236
|
+
- `refused the seat token (401)` → the rejected cached grant has been removed.
|
|
237
|
+
In hosted creation, run `npx genex blender scene` to acquire a new seat and
|
|
238
|
+
inspect the restored scene before deciding whether to rerun the failed script.
|
|
239
|
+
At a terminal, run `npx genex blender seat` first. Recovery never automatically
|
|
240
|
+
replays scene changes; a replaced pod restores the latest saved checkpoint.
|
|
230
241
|
- `seat_not_ready` → the pod is still booting; run again shortly.
|
|
231
242
|
- The numbers are right but the top view is wrong → coordinates: `y` in Blender is
|
|
232
243
|
depth, and `scale` on a 2-unit cube is the half-size.
|
|
@@ -41,6 +41,13 @@ paths. Building, previewing, promoting and publishing use `genex preview` /
|
|
|
41
41
|
|
|
42
42
|
Use the request in front of you, not the oldest description of the project.
|
|
43
43
|
|
|
44
|
+
- **Explicit Blender authoring**, including a standalone prop, owns the
|
|
45
|
+
`$genex-blender-scene` lane. Keep the requested scope: Python scripts, an
|
|
46
|
+
exported GLB and a browser preview for an asset request. Do not invent
|
|
47
|
+
gameplay, a character controller or a game concept to justify the hosted
|
|
48
|
+
workspace. Blender in this sentence names the authoring tool; a kitchen
|
|
49
|
+
blender requested as a prop follows the ordinary asset rows.
|
|
50
|
+
|
|
44
51
|
- **Clear focused request**—“work on the UI,” “fix movement,” “add one enemy,”
|
|
45
52
|
“make this prop,” “publish this”—read the touched implementation and
|
|
46
53
|
`DESIGN.md`, load only the owning skills, update `Now:`, and start. Do not
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: genex-tool-publish
|
|
3
|
+
description: Put the game in this folder on the web with Genex — the draft page, `npx genex preview`, then `promote` / `publish`; the link you hand the user, the size limits, and what to do with the preflight lines. Installed once the folder is connected to a hosted Genex game (`npx genex init --convert`). Load it before the first preview.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Genex Tools · Publish
|
|
7
|
+
|
|
8
|
+
The user built this game themselves and asked for it online. Your job is to
|
|
9
|
+
put it there **as it is** — the code, the structure and the assets stay
|
|
10
|
+
exactly what they are. Publishing adds glue and nothing else; it never starts
|
|
11
|
+
a rework, a design document or a build plan.
|
|
12
|
+
|
|
13
|
+
## The glue (all of it)
|
|
14
|
+
|
|
15
|
+
1. **A static build.** The game must build to files with its own build
|
|
16
|
+
command — `npm run build` into `dist/`, or an `index.html` at the root
|
|
17
|
+
with no build step. Asset paths must work from the game's own hosted URL,
|
|
18
|
+
so prefer relative ones (Vite: `base: './'`). Files the game loads at
|
|
19
|
+
runtime — everything in `./assets` — must end up inside the build (Vite:
|
|
20
|
+
a `public/` folder, or copy `assets/` into `dist/` in the build).
|
|
21
|
+
2. **Player identity.** Load `$genex-threejs-embed-auth` and add the one
|
|
22
|
+
`initEmbed(...)` call at the very top of the boot code. Create the
|
|
23
|
+
renderer and draw a frame BEFORE any `await waitForPlayer()` — on a hosted
|
|
24
|
+
page identity takes seconds, and a game that awaits it first ships black.
|
|
25
|
+
3. **The CLI as a dev dependency** so plain `npx genex` keeps resolving here
|
|
26
|
+
(setup already did this; check `package.json` if in doubt).
|
|
27
|
+
|
|
28
|
+
Commit the glue as its own small commit when the folder is a git repo, so the
|
|
29
|
+
line between the user's game and what publishing added stays visible.
|
|
30
|
+
|
|
31
|
+
## Preview
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx genex preview
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Builds, uploads and puts the build on the game's **draft page** — unlisted,
|
|
38
|
+
only the user can open it. When it reports live, open the hosted build once
|
|
39
|
+
yourself and confirm it loads with no missing files (the dev server forgives
|
|
40
|
+
paths the hosted build will not), then hand over the link.
|
|
41
|
+
|
|
42
|
+
**The link is always the game's page on Genex, never the raw play address,
|
|
43
|
+
never localhost, never a file path:**
|
|
44
|
+
|
|
45
|
+
- draft: `<dashboard>/draft/<slug>`
|
|
46
|
+
- public, once published: `<dashboard>/world/<slug>`
|
|
47
|
+
|
|
48
|
+
`<dashboard>` is the first `dashboardOrigins` entry in `.genex/project.json`
|
|
49
|
+
and `<slug>` is the `slug` there. `preview` prints the exact page link as
|
|
50
|
+
"your game's page" — use that line verbatim.
|
|
51
|
+
|
|
52
|
+
## The preflight is a report, not a to-do list
|
|
53
|
+
|
|
54
|
+
`preview` prints warning lines before it uploads — an estimated phone GPU
|
|
55
|
+
budget, a music track with no volume slider, a paid asset nobody wired, a
|
|
56
|
+
viewport meta line. None of them blocks the deploy, and none of them was
|
|
57
|
+
asked for. So: **ship first**, hand over the link, then relay each line to
|
|
58
|
+
the user in one plain sentence with an offer ("phones will struggle with the
|
|
59
|
+
textures — want me to shrink them?"). Fix one only on the user's yes. Never
|
|
60
|
+
turn the report into a milestone before the link.
|
|
61
|
+
|
|
62
|
+
## Draft and public version
|
|
63
|
+
|
|
64
|
+
`preview` updates the **draft** and never touches what players are on. The
|
|
65
|
+
first release lists the game:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npx genex publish --categories games
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Pick 1–3 categories that fit (`games`, `assets`, `physics`, `terrain`,
|
|
72
|
+
`lighting`, `vfx`); `games` when unsure. Ask before the first publish — "This
|
|
73
|
+
is a private draft only you can open. Say the word and I'll publish it for
|
|
74
|
+
anyone to play." — and celebrate the public page link when it lands.
|
|
75
|
+
|
|
76
|
+
After that first release the game is two versions, and the user only ever
|
|
77
|
+
hears these two words for them: the **draft** (updated by `preview`) and the
|
|
78
|
+
**public version** (what everyone plays). "Publish it", "update it" and "yes"
|
|
79
|
+
after the first release ALL mean:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npx genex promote # the exact draft build goes public — no rebuild
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Never run `publish` a second time (it would ship an untried rebuild). If
|
|
86
|
+
anything changed since the last `preview`, preview again before promoting.
|
|
87
|
+
Ask once per round of work, in one line, and keep working while you wait.
|
|
88
|
+
|
|
89
|
+
## Limits and refusals
|
|
90
|
+
|
|
91
|
+
- **Upload size**: ~95 MB per file, ~500 MB in total. Over that, tell the user
|
|
92
|
+
plainly which part is too large (usually a video, model or audio file) and by
|
|
93
|
+
roughly how much; ask before compressing or removing anything.
|
|
94
|
+
- **Terms refusal**: the CLI prints a link, waits for the user's click and
|
|
95
|
+
re-sends by itself — hand the link over and let it finish.
|
|
96
|
+
- **`preview` says the folder is behind another machine** (`stale_source`):
|
|
97
|
+
`npx genex pull` takes the newer draft, then redo the change — never
|
|
98
|
+
`--force` over somebody else's work.
|
|
99
|
+
- **Out of credits or a lane down**: `npx genex doctor` says which; publishing
|
|
100
|
+
itself costs nothing.
|
|
@@ -92,6 +92,7 @@ tiling floor and three paid assets nobody loaded.
|
|
|
92
92
|
Hosting, publishing, multiplayer, remixing and custom domains are the Genex
|
|
93
93
|
platform, not this toolkit — those commands are refused in this folder by
|
|
94
94
|
design, and the refusal says where they live. This workspace generates assets
|
|
95
|
-
for a game you build and ship yourself
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
for a game you build and ship yourself. When the user wants that game live on
|
|
96
|
+
Genex with its own URL, the AGENTS.md rules say how to offer it; on a yes the
|
|
97
|
+
folder is connected to a hosted game in place — same cards, same rules — and
|
|
98
|
+
the `$genex-tool-publish` card arrives with the publishing commands.
|