@genex-ai/cli-demo 1.11.3-dev.547 → 1.13.0-dev.550
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-OXFN3RVI.js → blender-mcp-LLN4FPA4.js} +34 -6
- package/dist/blender-serve-BF4FZ55Z.js +244 -0
- package/dist/{chunk-ZHIP7LCA.js → chunk-HYCSNWYX.js} +15 -67
- package/dist/chunk-WXHTNSFB.js +441 -0
- package/dist/index.js +460 -679
- package/package.json +1 -1
- package/templates/blender-service/demo/castle.py +117 -0
- package/templates/blender-service/gpu_witness.py +245 -0
- package/templates/blender-service/ops.py +225 -0
- package/templates/blender-service/pool.py +910 -0
- package/templates/blender-service/server.py +603 -0
- package/templates/blender-service/supervisor.py +221 -0
- package/templates/blender-service/views.py +281 -0
package/dist/index.js
CHANGED
|
@@ -1,15 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
BLENDER_SETUP_HINT,
|
|
4
|
+
RENDER_MODES,
|
|
5
|
+
SHEET_FORMATS,
|
|
6
|
+
acquireSeat,
|
|
7
|
+
apiFetch,
|
|
8
|
+
blenderCall,
|
|
9
|
+
blenderEndpoint,
|
|
10
|
+
fetchSignedInEmail,
|
|
11
|
+
getWorkspacePath,
|
|
12
|
+
isRenderMode,
|
|
13
|
+
printedStructuredError,
|
|
14
|
+
readProject,
|
|
15
|
+
readUserToken,
|
|
16
|
+
readWorkspace,
|
|
17
|
+
restrictFilePermissions,
|
|
18
|
+
rotateRejectedEnv,
|
|
19
|
+
setWorkspaceHeader,
|
|
20
|
+
sheetExt,
|
|
21
|
+
sheetOf,
|
|
22
|
+
writeProject,
|
|
23
|
+
writeUserToken,
|
|
24
|
+
writeWorkspace
|
|
25
|
+
} from "./chunk-WXHTNSFB.js";
|
|
26
|
+
import {
|
|
4
27
|
CLI_CHANNEL,
|
|
5
28
|
DEFAULT_API_URL,
|
|
6
29
|
DEFAULT_AUTH_URL,
|
|
7
|
-
ENV_FILE_ENV,
|
|
8
30
|
ENV_TOKEN_KEY,
|
|
9
|
-
RENDER_MODES,
|
|
10
31
|
STANDS,
|
|
11
|
-
|
|
12
|
-
blenderEndpoint,
|
|
32
|
+
c,
|
|
13
33
|
getAnimsBase,
|
|
14
34
|
getAnimsCacheDir,
|
|
15
35
|
getApiUrl,
|
|
@@ -18,9 +38,8 @@ import {
|
|
|
18
38
|
getGenexDir,
|
|
19
39
|
getGenexEnvPath,
|
|
20
40
|
getTemplatesDir,
|
|
21
|
-
isRenderMode,
|
|
22
41
|
resolveAgentTargets
|
|
23
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-HYCSNWYX.js";
|
|
24
43
|
|
|
25
44
|
// src/instrument.ts
|
|
26
45
|
import * as Sentry from "@sentry/node";
|
|
@@ -128,204 +147,18 @@ import http from "http";
|
|
|
128
147
|
import crypto from "crypto";
|
|
129
148
|
import os2 from "os";
|
|
130
149
|
import readline from "readline";
|
|
131
|
-
import { spawn
|
|
150
|
+
import { spawn } from "child_process";
|
|
132
151
|
import { URL as URL2 } from "url";
|
|
133
152
|
|
|
134
|
-
// src/utils/colors.ts
|
|
135
|
-
var useColor = Boolean(process.stdout.isTTY) && process.env.NO_COLOR === void 0 && process.env.TERM !== "dumb";
|
|
136
|
-
var ESC = String.fromCharCode(27);
|
|
137
|
-
var code = (open, close) => (s) => useColor ? `${ESC}[${open}m${s}${ESC}[${close}m` : s;
|
|
138
|
-
var c = {
|
|
139
|
-
bold: code(1, 22),
|
|
140
|
-
dim: code(2, 22),
|
|
141
|
-
red: code(31, 39),
|
|
142
|
-
green: code(32, 39),
|
|
143
|
-
yellow: code(33, 39),
|
|
144
|
-
blue: code(34, 39),
|
|
145
|
-
cyan: code(36, 39),
|
|
146
|
-
gray: code(90, 39)
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
// src/lib/api.ts
|
|
150
|
-
var CLI_VERSION_HEADER = "x-genex-cli-version";
|
|
151
|
-
var WORKSPACE_HEADER = "x-genex-workspace";
|
|
152
|
-
var workspaceLabel = null;
|
|
153
|
-
function setWorkspaceHeader(label) {
|
|
154
|
-
workspaceLabel = label;
|
|
155
|
-
}
|
|
156
|
-
function formatUpdateRequired(body) {
|
|
157
|
-
const action = body.action ?? `npm i -D @genex-ai/cli-demo@${CLI_CHANNEL}`;
|
|
158
|
-
const message = body.message ?? `Genex CLI ${body.clientVersion ?? getCliVersion()} is below the minimum supported version${body.minVersion ? ` ${body.minVersion}` : ""}.`;
|
|
159
|
-
return [`${c.red("\u2717")} ${message}`, ` Update now \u2014 run: ${action} (then re-run this command)`];
|
|
160
|
-
}
|
|
161
|
-
function shortDate(iso) {
|
|
162
|
-
const d = new Date(iso);
|
|
163
|
-
if (Number.isNaN(d.getTime())) return iso;
|
|
164
|
-
return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
165
|
-
}
|
|
166
|
-
function formatInsufficientCredits(body) {
|
|
167
|
-
const message = body.message ?? `this generation costs ${body.price ?? "?"} credits; your balance is ${body.balance ?? 0}.`;
|
|
168
|
-
const lines = [`${c.red("\u2717")} Out of credits \u2014 ${lowerFirst(message)}`];
|
|
169
|
-
if (body.refillAt && body.refillTo) {
|
|
170
|
-
lines.push(` Credits refill to ${body.refillTo} on ${shortDate(body.refillAt)}.`);
|
|
171
|
-
}
|
|
172
|
-
if (body.url) lines.push(` Get more or check your balance: ${body.url}`);
|
|
173
|
-
return lines;
|
|
174
|
-
}
|
|
175
|
-
function formatVerificationRequired(body) {
|
|
176
|
-
const lines = [
|
|
177
|
-
`${c.red("\u2717")} Email not verified \u2014 verify your email to unlock your free generation credits.`
|
|
178
|
-
];
|
|
179
|
-
if (body.url) lines.push(` Verify here: ${body.url} (then re-run this command)`);
|
|
180
|
-
return lines;
|
|
181
|
-
}
|
|
182
|
-
function lowerFirst(s) {
|
|
183
|
-
return s ? s[0].toLowerCase() + s.slice(1) : s;
|
|
184
|
-
}
|
|
185
|
-
var structuredPrinted = /* @__PURE__ */ new WeakSet();
|
|
186
|
-
function printedStructuredError(res) {
|
|
187
|
-
return structuredPrinted.has(res);
|
|
188
|
-
}
|
|
189
|
-
async function apiFetch(url, init2 = {}) {
|
|
190
|
-
const headers = new Headers(init2.headers);
|
|
191
|
-
if (!headers.has(CLI_VERSION_HEADER)) headers.set(CLI_VERSION_HEADER, getCliVersion());
|
|
192
|
-
if (workspaceLabel && !headers.has(WORKSPACE_HEADER)) {
|
|
193
|
-
headers.set(WORKSPACE_HEADER, workspaceLabel);
|
|
194
|
-
}
|
|
195
|
-
const res = await fetch(url, { ...init2, headers });
|
|
196
|
-
if (res.status === 426) {
|
|
197
|
-
try {
|
|
198
|
-
const body = await res.clone().json();
|
|
199
|
-
if (body?.error === "cli_update_required") {
|
|
200
|
-
for (const line of formatUpdateRequired(body)) process.stderr.write(line + "\n");
|
|
201
|
-
}
|
|
202
|
-
} catch {
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
if (res.status === 402) {
|
|
206
|
-
try {
|
|
207
|
-
const body = await res.clone().json();
|
|
208
|
-
if (body?.error === "insufficient_credits") {
|
|
209
|
-
for (const line of formatInsufficientCredits(body)) process.stderr.write(line + "\n");
|
|
210
|
-
structuredPrinted.add(res);
|
|
211
|
-
}
|
|
212
|
-
} catch {
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
if (res.status === 403) {
|
|
216
|
-
try {
|
|
217
|
-
const body = await res.clone().json();
|
|
218
|
-
if (body?.error === "email_verification_required") {
|
|
219
|
-
for (const line of formatVerificationRequired(body)) process.stderr.write(line + "\n");
|
|
220
|
-
structuredPrinted.add(res);
|
|
221
|
-
}
|
|
222
|
-
} catch {
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
if (res.status === 503) {
|
|
226
|
-
try {
|
|
227
|
-
const body = await res.clone().json();
|
|
228
|
-
if (body?.error === "generation_paused") {
|
|
229
|
-
process.stderr.write(
|
|
230
|
-
`${c.red("\u2717")} ${body.message ?? "Generation is temporarily paused platform-wide. Try again later."}
|
|
231
|
-
`
|
|
232
|
-
);
|
|
233
|
-
structuredPrinted.add(res);
|
|
234
|
-
}
|
|
235
|
-
} catch {
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
return res;
|
|
239
|
-
}
|
|
240
|
-
async function fetchSignedInEmail(apiUrl, token) {
|
|
241
|
-
try {
|
|
242
|
-
const res = await apiFetch(`${apiUrl}/api/auth/get-session`, {
|
|
243
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
244
|
-
});
|
|
245
|
-
if (!res.ok) return null;
|
|
246
|
-
const data = await res.json().catch(() => null);
|
|
247
|
-
return data?.user?.email ?? null;
|
|
248
|
-
} catch {
|
|
249
|
-
return null;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
153
|
// src/lib/pending-auth.ts
|
|
254
|
-
import fs2 from "fs/promises";
|
|
255
|
-
import path2 from "path";
|
|
256
|
-
|
|
257
|
-
// src/lib/env.ts
|
|
258
154
|
import fs from "fs/promises";
|
|
259
155
|
import path from "path";
|
|
260
|
-
import { spawn } from "child_process";
|
|
261
|
-
async function writeEnvVar(envPath, key, value) {
|
|
262
|
-
let content = "";
|
|
263
|
-
let existed = false;
|
|
264
|
-
try {
|
|
265
|
-
content = await fs.readFile(envPath, "utf8");
|
|
266
|
-
existed = true;
|
|
267
|
-
} catch {
|
|
268
|
-
}
|
|
269
|
-
const assignment = `${key}=${formatValue(value)}`;
|
|
270
|
-
const keyPattern = new RegExp(
|
|
271
|
-
`^(\\s*export\\s+)?${escapeRegExp(key)}=.*$`,
|
|
272
|
-
"gm"
|
|
273
|
-
);
|
|
274
|
-
let next;
|
|
275
|
-
let mode;
|
|
276
|
-
if (keyPattern.test(content)) {
|
|
277
|
-
next = content.replace(keyPattern, assignment);
|
|
278
|
-
mode = "updated";
|
|
279
|
-
} else {
|
|
280
|
-
let prefix = content;
|
|
281
|
-
if (prefix.length > 0 && !prefix.endsWith("\n")) prefix += "\n";
|
|
282
|
-
next = prefix + assignment + "\n";
|
|
283
|
-
mode = existed ? "appended" : "created";
|
|
284
|
-
}
|
|
285
|
-
await fs.mkdir(path.dirname(envPath), { recursive: true });
|
|
286
|
-
await fs.writeFile(envPath, next, { mode: 384 });
|
|
287
|
-
await restrictFilePermissions(envPath);
|
|
288
|
-
return { mode, path: envPath };
|
|
289
|
-
}
|
|
290
|
-
async function restrictFilePermissions(filePath) {
|
|
291
|
-
if (process.platform !== "win32") {
|
|
292
|
-
await fs.chmod(filePath, 384).catch(() => {
|
|
293
|
-
});
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
const user = process.env.USERNAME ?? process.env.USER;
|
|
297
|
-
if (!user) return;
|
|
298
|
-
await new Promise((resolve) => {
|
|
299
|
-
try {
|
|
300
|
-
const child = spawn(
|
|
301
|
-
"icacls",
|
|
302
|
-
[filePath, "/inheritance:r", "/grant:r", `${user}:F`],
|
|
303
|
-
{ stdio: "ignore" }
|
|
304
|
-
);
|
|
305
|
-
child.on("error", () => resolve());
|
|
306
|
-
child.on("close", () => resolve());
|
|
307
|
-
} catch {
|
|
308
|
-
resolve();
|
|
309
|
-
}
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
function formatValue(value) {
|
|
313
|
-
if (/[\s#"'$`\\]/.test(value)) {
|
|
314
|
-
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
315
|
-
}
|
|
316
|
-
return value;
|
|
317
|
-
}
|
|
318
|
-
function escapeRegExp(s) {
|
|
319
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// src/lib/pending-auth.ts
|
|
323
156
|
function pendingAuthPath() {
|
|
324
|
-
return
|
|
157
|
+
return path.join(getGenexDir(), "pending-auth.json");
|
|
325
158
|
}
|
|
326
159
|
async function readPendingAuth(apiUrl) {
|
|
327
160
|
try {
|
|
328
|
-
const raw = JSON.parse(await
|
|
161
|
+
const raw = JSON.parse(await fs.readFile(pendingAuthPath(), "utf8"));
|
|
329
162
|
if (!raw?.deviceCode || !raw.userCode || raw.apiUrl !== apiUrl) return null;
|
|
330
163
|
if (Date.now() >= raw.expiresAt) return null;
|
|
331
164
|
return raw;
|
|
@@ -335,12 +168,12 @@ async function readPendingAuth(apiUrl) {
|
|
|
335
168
|
}
|
|
336
169
|
async function writePendingAuth(pending) {
|
|
337
170
|
const file = pendingAuthPath();
|
|
338
|
-
await
|
|
339
|
-
await
|
|
171
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
172
|
+
await fs.writeFile(file, JSON.stringify(pending, null, 2), { mode: 384 });
|
|
340
173
|
await restrictFilePermissions(file);
|
|
341
174
|
}
|
|
342
175
|
async function clearPendingAuth() {
|
|
343
|
-
await
|
|
176
|
+
await fs.rm(pendingAuthPath(), { force: true }).catch(() => {
|
|
344
177
|
});
|
|
345
178
|
}
|
|
346
179
|
|
|
@@ -360,8 +193,8 @@ var AuthPendingError = class extends Error {
|
|
|
360
193
|
this.verifyUrl = verifyUrl;
|
|
361
194
|
}
|
|
362
195
|
};
|
|
363
|
-
function formatUserCode(
|
|
364
|
-
return
|
|
196
|
+
function formatUserCode(code) {
|
|
197
|
+
return code.length === 8 ? `${code.slice(0, 4)}-${code.slice(4)}` : code;
|
|
365
198
|
}
|
|
366
199
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
367
200
|
async function authorize(apiBaseUrl, authBaseUrl, options) {
|
|
@@ -709,7 +542,7 @@ function openBrowser(url, onError = () => {
|
|
|
709
542
|
}
|
|
710
543
|
}
|
|
711
544
|
try {
|
|
712
|
-
const child =
|
|
545
|
+
const child = spawn(command, args, {
|
|
713
546
|
stdio: "ignore",
|
|
714
547
|
detached: true
|
|
715
548
|
});
|
|
@@ -747,88 +580,6 @@ function tokenizeCommand(input) {
|
|
|
747
580
|
return tokens2;
|
|
748
581
|
}
|
|
749
582
|
|
|
750
|
-
// src/lib/store.ts
|
|
751
|
-
import fs3 from "fs/promises";
|
|
752
|
-
import path3 from "path";
|
|
753
|
-
function getProjectMetadataPath(cwd = process.cwd()) {
|
|
754
|
-
return path3.join(cwd, ".genex", "project.json");
|
|
755
|
-
}
|
|
756
|
-
function getWorkspacePath(cwd = process.cwd()) {
|
|
757
|
-
return path3.join(cwd, ".genex", "workspace.json");
|
|
758
|
-
}
|
|
759
|
-
async function readWorkspace(cwd = process.cwd()) {
|
|
760
|
-
try {
|
|
761
|
-
const raw = await fs3.readFile(getWorkspacePath(cwd), "utf8");
|
|
762
|
-
return JSON.parse(raw);
|
|
763
|
-
} catch {
|
|
764
|
-
return null;
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
async function writeWorkspace(meta, cwd = process.cwd()) {
|
|
768
|
-
const file = getWorkspacePath(cwd);
|
|
769
|
-
await fs3.mkdir(path3.dirname(file), { recursive: true });
|
|
770
|
-
await fs3.writeFile(file, JSON.stringify(meta, null, 2) + "\n", { mode: 384 });
|
|
771
|
-
await fs3.chmod(file, 384).catch(() => {
|
|
772
|
-
});
|
|
773
|
-
return { path: file };
|
|
774
|
-
}
|
|
775
|
-
async function writeUserToken(token, envPath) {
|
|
776
|
-
const { path: written } = await writeEnvVar(getGenexEnvPath(envPath), ENV_TOKEN_KEY, token);
|
|
777
|
-
return { path: written };
|
|
778
|
-
}
|
|
779
|
-
async function rotateRejectedEnv(envPath) {
|
|
780
|
-
const file = getGenexEnvPath(envPath);
|
|
781
|
-
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
782
|
-
const aside = `${file}.rejected-${stamp}`;
|
|
783
|
-
try {
|
|
784
|
-
await fs3.rename(file, aside);
|
|
785
|
-
return aside;
|
|
786
|
-
} catch {
|
|
787
|
-
return null;
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
async function readUserToken(envPath) {
|
|
791
|
-
const fromGenex = await readTokenFromFile(getGenexEnvPath(envPath));
|
|
792
|
-
if (fromGenex) return fromGenex;
|
|
793
|
-
if (!envPath && !process.env[ENV_FILE_ENV]) {
|
|
794
|
-
return readTokenFromFile(path3.join(process.cwd(), ".env"));
|
|
795
|
-
}
|
|
796
|
-
return null;
|
|
797
|
-
}
|
|
798
|
-
async function readTokenFromFile(file) {
|
|
799
|
-
let content;
|
|
800
|
-
try {
|
|
801
|
-
content = await fs3.readFile(file, "utf8");
|
|
802
|
-
} catch {
|
|
803
|
-
return null;
|
|
804
|
-
}
|
|
805
|
-
const m = content.match(/^\s*(?:export\s+)?GENEX_TOKEN=(.*)$/m);
|
|
806
|
-
if (!m) return null;
|
|
807
|
-
return stripQuotes(m[1].trim()) || null;
|
|
808
|
-
}
|
|
809
|
-
function stripQuotes(v) {
|
|
810
|
-
if (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'")) {
|
|
811
|
-
return v.slice(1, -1);
|
|
812
|
-
}
|
|
813
|
-
return v;
|
|
814
|
-
}
|
|
815
|
-
async function readProject(cwd = process.cwd()) {
|
|
816
|
-
try {
|
|
817
|
-
const raw = await fs3.readFile(getProjectMetadataPath(cwd), "utf8");
|
|
818
|
-
return JSON.parse(raw);
|
|
819
|
-
} catch {
|
|
820
|
-
return null;
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
async function writeProject(meta, cwd = process.cwd()) {
|
|
824
|
-
const file = getProjectMetadataPath(cwd);
|
|
825
|
-
await fs3.mkdir(path3.dirname(file), { recursive: true });
|
|
826
|
-
await fs3.writeFile(file, JSON.stringify(meta, null, 2) + "\n", { mode: 384 });
|
|
827
|
-
await fs3.chmod(file, 384).catch(() => {
|
|
828
|
-
});
|
|
829
|
-
return { path: file };
|
|
830
|
-
}
|
|
831
|
-
|
|
832
583
|
// src/utils/logger.ts
|
|
833
584
|
function createLogger(opts = {}) {
|
|
834
585
|
const out = (s) => {
|
|
@@ -889,14 +640,14 @@ async function runAuth(opts) {
|
|
|
889
640
|
}
|
|
890
641
|
|
|
891
642
|
// src/commands/init.ts
|
|
892
|
-
import
|
|
893
|
-
import
|
|
643
|
+
import fs7 from "fs/promises";
|
|
644
|
+
import path7 from "path";
|
|
894
645
|
|
|
895
646
|
// src/lib/copy-templates.ts
|
|
896
|
-
import
|
|
897
|
-
import
|
|
647
|
+
import fs2 from "fs/promises";
|
|
648
|
+
import path2 from "path";
|
|
898
649
|
function isGenexManaged(rel) {
|
|
899
|
-
return rel.split(
|
|
650
|
+
return rel.split(path2.sep).some((seg) => seg.startsWith("genex"));
|
|
900
651
|
}
|
|
901
652
|
async function copyTemplates(srcDir, destDir, opts = {}) {
|
|
902
653
|
const result = { copied: [], updated: [], skipped: [] };
|
|
@@ -904,15 +655,15 @@ async function copyTemplates(srcDir, destDir, opts = {}) {
|
|
|
904
655
|
return result;
|
|
905
656
|
}
|
|
906
657
|
async function walk(rootSrc, src, dest, opts, result) {
|
|
907
|
-
const entries = await
|
|
658
|
+
const entries = await fs2.readdir(src, { withFileTypes: true });
|
|
908
659
|
for (const entry of entries) {
|
|
909
|
-
const srcPath =
|
|
910
|
-
const destPath =
|
|
911
|
-
const rel =
|
|
660
|
+
const srcPath = path2.join(src, entry.name);
|
|
661
|
+
const destPath = path2.join(dest, entry.name);
|
|
662
|
+
const rel = path2.relative(rootSrc, srcPath);
|
|
912
663
|
if (opts.exclude?.includes(rel)) continue;
|
|
913
664
|
if (opts.filter && !opts.filter(rel)) continue;
|
|
914
665
|
if (entry.isDirectory()) {
|
|
915
|
-
await
|
|
666
|
+
await fs2.mkdir(destPath, { recursive: true });
|
|
916
667
|
await walk(rootSrc, srcPath, destPath, opts, result);
|
|
917
668
|
continue;
|
|
918
669
|
}
|
|
@@ -925,15 +676,15 @@ async function walk(rootSrc, src, dest, opts, result) {
|
|
|
925
676
|
result.skipped.push(rel);
|
|
926
677
|
continue;
|
|
927
678
|
}
|
|
928
|
-
await
|
|
929
|
-
await
|
|
679
|
+
await fs2.mkdir(path2.dirname(destPath), { recursive: true });
|
|
680
|
+
await fs2.copyFile(srcPath, destPath);
|
|
930
681
|
result.copied.push(rel);
|
|
931
682
|
if (present) result.updated.push(rel);
|
|
932
683
|
}
|
|
933
684
|
}
|
|
934
685
|
async function exists(p) {
|
|
935
686
|
try {
|
|
936
|
-
await
|
|
687
|
+
await fs2.access(p);
|
|
937
688
|
return true;
|
|
938
689
|
} catch {
|
|
939
690
|
return false;
|
|
@@ -941,9 +692,9 @@ async function exists(p) {
|
|
|
941
692
|
}
|
|
942
693
|
|
|
943
694
|
// src/lib/agents-contract.ts
|
|
944
|
-
import
|
|
695
|
+
import fs3 from "fs/promises";
|
|
945
696
|
import os3 from "os";
|
|
946
|
-
import
|
|
697
|
+
import path3 from "path";
|
|
947
698
|
var CONTRACT_BEGIN = "<!-- genex:contract:begin (managed by genex \u2014 edits inside this block are overwritten on sync) -->";
|
|
948
699
|
var CONTRACT_END = "<!-- genex:contract:end -->";
|
|
949
700
|
var GENEX_CONTRACT_BLOCK = `${CONTRACT_BEGIN}
|
|
@@ -1008,32 +759,32 @@ ${block}
|
|
|
1008
759
|
async function writeAgentsContract(projectDir, contractBlock = GENEX_CONTRACT_BLOCK) {
|
|
1009
760
|
let changed = false;
|
|
1010
761
|
try {
|
|
1011
|
-
const agentsPath =
|
|
762
|
+
const agentsPath = path3.join(projectDir, "AGENTS.md");
|
|
1012
763
|
let existing = null;
|
|
1013
764
|
try {
|
|
1014
|
-
existing = await
|
|
765
|
+
existing = await fs3.readFile(agentsPath, "utf8");
|
|
1015
766
|
} catch {
|
|
1016
767
|
existing = null;
|
|
1017
768
|
}
|
|
1018
769
|
const next = mergeContractBlock(existing, contractBlock);
|
|
1019
770
|
if (next !== existing) {
|
|
1020
|
-
await
|
|
771
|
+
await fs3.writeFile(agentsPath, next, "utf8");
|
|
1021
772
|
changed = true;
|
|
1022
773
|
}
|
|
1023
|
-
const claudePath =
|
|
774
|
+
const claudePath = path3.join(projectDir, "CLAUDE.md");
|
|
1024
775
|
let claude = null;
|
|
1025
776
|
try {
|
|
1026
|
-
claude = await
|
|
777
|
+
claude = await fs3.readFile(claudePath, "utf8");
|
|
1027
778
|
} catch {
|
|
1028
779
|
claude = null;
|
|
1029
780
|
}
|
|
1030
781
|
if (claude === null) {
|
|
1031
|
-
await
|
|
782
|
+
await fs3.writeFile(claudePath, `${CLAUDE_IMPORT_LINE}
|
|
1032
783
|
`, "utf8");
|
|
1033
784
|
changed = true;
|
|
1034
785
|
} else if (!claude.split("\n").some((line) => line.trim() === CLAUDE_IMPORT_LINE)) {
|
|
1035
786
|
const sep = claude.endsWith("\n") ? "" : "\n";
|
|
1036
|
-
await
|
|
787
|
+
await fs3.writeFile(claudePath, `${claude}${sep}
|
|
1037
788
|
${CLAUDE_IMPORT_LINE}
|
|
1038
789
|
`, "utf8");
|
|
1039
790
|
changed = true;
|
|
@@ -1047,24 +798,24 @@ async function writeToolsContract(projectDir) {
|
|
|
1047
798
|
}
|
|
1048
799
|
async function healAncestorContracts(projectDir, stopDir = os3.homedir()) {
|
|
1049
800
|
const findings = [];
|
|
1050
|
-
const stop =
|
|
1051
|
-
let dir =
|
|
801
|
+
const stop = path3.resolve(stopDir);
|
|
802
|
+
let dir = path3.dirname(path3.resolve(projectDir));
|
|
1052
803
|
for (let depth = 0; depth < 20; depth++) {
|
|
1053
804
|
try {
|
|
1054
805
|
let isWorkspace = false;
|
|
1055
806
|
for (const marker of ["project.json", "workspace.json"]) {
|
|
1056
807
|
try {
|
|
1057
|
-
await
|
|
808
|
+
await fs3.access(path3.join(dir, ".genex", marker));
|
|
1058
809
|
isWorkspace = true;
|
|
1059
810
|
break;
|
|
1060
811
|
} catch {
|
|
1061
812
|
}
|
|
1062
813
|
}
|
|
1063
814
|
if (!isWorkspace) {
|
|
1064
|
-
const agentsPath =
|
|
815
|
+
const agentsPath = path3.join(dir, "AGENTS.md");
|
|
1065
816
|
let content = null;
|
|
1066
817
|
try {
|
|
1067
|
-
content = await
|
|
818
|
+
content = await fs3.readFile(agentsPath, "utf8");
|
|
1068
819
|
} catch {
|
|
1069
820
|
}
|
|
1070
821
|
if (content !== null && CONTRACT_REGION.test(content)) {
|
|
@@ -1073,11 +824,11 @@ async function healAncestorContracts(projectDir, stopDir = os3.homedir()) {
|
|
|
1073
824
|
remainder = remainder.replace(CONTRACT_REGION, "");
|
|
1074
825
|
}
|
|
1075
826
|
if (remainder.trim() === "") {
|
|
1076
|
-
await
|
|
827
|
+
await fs3.unlink(agentsPath);
|
|
1077
828
|
try {
|
|
1078
|
-
const claudePath =
|
|
1079
|
-
if ((await
|
|
1080
|
-
await
|
|
829
|
+
const claudePath = path3.join(dir, "CLAUDE.md");
|
|
830
|
+
if ((await fs3.readFile(claudePath, "utf8")).trim() === CLAUDE_IMPORT_LINE) {
|
|
831
|
+
await fs3.unlink(claudePath);
|
|
1081
832
|
}
|
|
1082
833
|
} catch {
|
|
1083
834
|
}
|
|
@@ -1090,7 +841,7 @@ async function healAncestorContracts(projectDir, stopDir = os3.homedir()) {
|
|
|
1090
841
|
} catch {
|
|
1091
842
|
}
|
|
1092
843
|
if (dir === stop) break;
|
|
1093
|
-
const parent =
|
|
844
|
+
const parent = path3.dirname(dir);
|
|
1094
845
|
if (parent === dir) break;
|
|
1095
846
|
dir = parent;
|
|
1096
847
|
}
|
|
@@ -1099,7 +850,7 @@ async function healAncestorContracts(projectDir, stopDir = os3.homedir()) {
|
|
|
1099
850
|
async function healAncestorContractsAndReport(log, projectDir) {
|
|
1100
851
|
try {
|
|
1101
852
|
for (const f of await healAncestorContracts(projectDir)) {
|
|
1102
|
-
const file =
|
|
853
|
+
const file = path3.join(f.dir, "AGENTS.md");
|
|
1103
854
|
if (f.removed) {
|
|
1104
855
|
log.plain(
|
|
1105
856
|
`\u{1F9F9} Removed a stray genex build contract from ${file} \u2014 it belongs in each game's folder, and a stale copy above the project misleads agents.`
|
|
@@ -1115,9 +866,9 @@ async function healAncestorContractsAndReport(log, projectDir) {
|
|
|
1115
866
|
}
|
|
1116
867
|
|
|
1117
868
|
// src/lib/updates.ts
|
|
1118
|
-
import
|
|
869
|
+
import fs4 from "fs/promises";
|
|
1119
870
|
import os4 from "os";
|
|
1120
|
-
import
|
|
871
|
+
import path4 from "path";
|
|
1121
872
|
|
|
1122
873
|
// src/lib/workspace.ts
|
|
1123
874
|
async function workspaceMode(cwd = process.cwd()) {
|
|
@@ -1175,7 +926,7 @@ function isNewerVersion(a, b) {
|
|
|
1175
926
|
var SKILLS_VERSION_MARKER = "genex-skills-version.json";
|
|
1176
927
|
async function readSkillsMarker(skillsDir) {
|
|
1177
928
|
try {
|
|
1178
|
-
const raw = await
|
|
929
|
+
const raw = await fs4.readFile(path4.join(skillsDir, SKILLS_VERSION_MARKER), "utf8");
|
|
1179
930
|
const parsed = JSON.parse(raw);
|
|
1180
931
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
1181
932
|
} catch {
|
|
@@ -1183,15 +934,15 @@ async function readSkillsMarker(skillsDir) {
|
|
|
1183
934
|
}
|
|
1184
935
|
}
|
|
1185
936
|
async function writeSkillsMarker(skillsDir, version = getCliVersion()) {
|
|
1186
|
-
await
|
|
1187
|
-
await
|
|
1188
|
-
|
|
937
|
+
await fs4.mkdir(skillsDir, { recursive: true });
|
|
938
|
+
await fs4.writeFile(
|
|
939
|
+
path4.join(skillsDir, SKILLS_VERSION_MARKER),
|
|
1189
940
|
JSON.stringify({ version, syncedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n"
|
|
1190
941
|
);
|
|
1191
942
|
}
|
|
1192
943
|
async function hasGenexSkills(skillsDir) {
|
|
1193
944
|
try {
|
|
1194
|
-
const entries = await
|
|
945
|
+
const entries = await fs4.readdir(skillsDir);
|
|
1195
946
|
return entries.some((name) => name.startsWith("genex-"));
|
|
1196
947
|
} catch {
|
|
1197
948
|
return false;
|
|
@@ -1254,14 +1005,14 @@ var REMOVED_SKILLS = [
|
|
|
1254
1005
|
async function pruneRemovedSkills(skillsDir, log) {
|
|
1255
1006
|
const removed = [];
|
|
1256
1007
|
for (const name of REMOVED_SKILLS) {
|
|
1257
|
-
const target =
|
|
1008
|
+
const target = path4.join(skillsDir, name);
|
|
1258
1009
|
try {
|
|
1259
|
-
await
|
|
1010
|
+
await fs4.access(target);
|
|
1260
1011
|
} catch {
|
|
1261
1012
|
continue;
|
|
1262
1013
|
}
|
|
1263
1014
|
try {
|
|
1264
|
-
await
|
|
1015
|
+
await fs4.rm(target, { recursive: true });
|
|
1265
1016
|
removed.push(name);
|
|
1266
1017
|
} catch {
|
|
1267
1018
|
}
|
|
@@ -1276,16 +1027,16 @@ async function pruneRemovedSkills(skillsDir, log) {
|
|
|
1276
1027
|
async function pruneToolCards(log, overrides) {
|
|
1277
1028
|
let removedAny = false;
|
|
1278
1029
|
for (const target of resolveAgentTargets(overrides ?? {})) {
|
|
1279
|
-
const skillsDir =
|
|
1030
|
+
const skillsDir = path4.join(target.baseDir, "skills");
|
|
1280
1031
|
for (const name of TOOL_SKILLS) {
|
|
1281
|
-
const dir =
|
|
1032
|
+
const dir = path4.join(skillsDir, name);
|
|
1282
1033
|
try {
|
|
1283
|
-
await
|
|
1034
|
+
await fs4.access(dir);
|
|
1284
1035
|
} catch {
|
|
1285
1036
|
continue;
|
|
1286
1037
|
}
|
|
1287
1038
|
try {
|
|
1288
|
-
await
|
|
1039
|
+
await fs4.rm(dir, { recursive: true });
|
|
1289
1040
|
removedAny = true;
|
|
1290
1041
|
} catch {
|
|
1291
1042
|
}
|
|
@@ -1299,15 +1050,15 @@ async function pruneToolCards(log, overrides) {
|
|
|
1299
1050
|
return removedAny;
|
|
1300
1051
|
}
|
|
1301
1052
|
async function syncSkillsForTarget(target, templatesDir = getTemplatesDir(), version = getCliVersion(), log, family = "game") {
|
|
1302
|
-
const skillsDir =
|
|
1053
|
+
const skillsDir = path4.join(target.baseDir, "skills");
|
|
1303
1054
|
if (!await hasGenexSkills(skillsDir)) return false;
|
|
1304
1055
|
if (await readSkillsMarker(skillsDir) === version) return false;
|
|
1305
|
-
const src = target.full ? templatesDir :
|
|
1056
|
+
const src = target.full ? templatesDir : path4.join(templatesDir, "skills");
|
|
1306
1057
|
const dest = target.full ? target.baseDir : skillsDir;
|
|
1307
1058
|
const familyFilter = skillFamilyFilter(family);
|
|
1308
1059
|
const filter = target.full ? familyFilter : (rel) => familyFilter(`skills/${rel}`);
|
|
1309
1060
|
await copyTemplates(src, dest, {
|
|
1310
|
-
exclude: ["controllers", "motion", "asset-viewer"],
|
|
1061
|
+
exclude: ["controllers", "motion", "asset-viewer", "blender-service"],
|
|
1311
1062
|
filter
|
|
1312
1063
|
});
|
|
1313
1064
|
await pruneRemovedSkills(skillsDir, log);
|
|
@@ -1318,34 +1069,34 @@ async function cleanupLegacyGlobalSkills(log) {
|
|
|
1318
1069
|
try {
|
|
1319
1070
|
const home = os4.homedir();
|
|
1320
1071
|
const [realCwd, realHome] = await Promise.all([
|
|
1321
|
-
|
|
1322
|
-
|
|
1072
|
+
fs4.realpath(process.cwd()).catch(() => path4.resolve(process.cwd())),
|
|
1073
|
+
fs4.realpath(home).catch(() => path4.resolve(home))
|
|
1323
1074
|
]);
|
|
1324
1075
|
if (realCwd === realHome) return false;
|
|
1325
1076
|
let removed = false;
|
|
1326
1077
|
for (const dirName of [".claude", ".codex", ".cursor"]) {
|
|
1327
|
-
const skillsDir =
|
|
1078
|
+
const skillsDir = path4.join(home, dirName, "skills");
|
|
1328
1079
|
let entries = [];
|
|
1329
1080
|
try {
|
|
1330
|
-
entries = await
|
|
1081
|
+
entries = await fs4.readdir(skillsDir);
|
|
1331
1082
|
} catch {
|
|
1332
1083
|
continue;
|
|
1333
1084
|
}
|
|
1334
1085
|
for (const name of entries) {
|
|
1335
1086
|
if (!name.startsWith("genex-")) continue;
|
|
1336
1087
|
try {
|
|
1337
|
-
await
|
|
1088
|
+
await fs4.rm(path4.join(skillsDir, name), { recursive: true });
|
|
1338
1089
|
removed = true;
|
|
1339
1090
|
} catch {
|
|
1340
1091
|
}
|
|
1341
1092
|
}
|
|
1342
1093
|
}
|
|
1343
1094
|
for (const rel of [
|
|
1344
|
-
|
|
1345
|
-
|
|
1095
|
+
path4.join("agents", "genex-helper.md"),
|
|
1096
|
+
path4.join("commands", "genex-status.md")
|
|
1346
1097
|
]) {
|
|
1347
1098
|
try {
|
|
1348
|
-
await
|
|
1099
|
+
await fs4.rm(path4.join(home, ".claude", rel));
|
|
1349
1100
|
removed = true;
|
|
1350
1101
|
} catch {
|
|
1351
1102
|
}
|
|
@@ -1403,7 +1154,7 @@ var PUBLISHED_PACKAGES = [
|
|
|
1403
1154
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1404
1155
|
var REGISTRY_TIMEOUT_MS = 1500;
|
|
1405
1156
|
function getUpdateCachePath() {
|
|
1406
|
-
return
|
|
1157
|
+
return path4.join(getGenexDir(), "update-check.json");
|
|
1407
1158
|
}
|
|
1408
1159
|
function isCacheFresh(cache, nowMs) {
|
|
1409
1160
|
const at = Date.parse(cache.checkedAt);
|
|
@@ -1416,7 +1167,7 @@ function wasRecentlyNotified(cache, name, latest, nowMs) {
|
|
|
1416
1167
|
}
|
|
1417
1168
|
async function readUpdateCache() {
|
|
1418
1169
|
try {
|
|
1419
|
-
const raw = await
|
|
1170
|
+
const raw = await fs4.readFile(getUpdateCachePath(), "utf8");
|
|
1420
1171
|
const parsed = JSON.parse(raw);
|
|
1421
1172
|
if (typeof parsed.checkedAt !== "string" || typeof parsed.latest !== "object") return null;
|
|
1422
1173
|
return parsed;
|
|
@@ -1455,8 +1206,8 @@ function startUpdateCheck() {
|
|
|
1455
1206
|
...cached?.notified ? { notified: cached.notified } : {},
|
|
1456
1207
|
...cached?.notifiedAt ? { notifiedAt: cached.notifiedAt } : {}
|
|
1457
1208
|
};
|
|
1458
|
-
await
|
|
1459
|
-
await
|
|
1209
|
+
await fs4.mkdir(getGenexDir(), { recursive: true });
|
|
1210
|
+
await fs4.writeFile(getUpdateCachePath(), JSON.stringify(next, null, 2) + "\n");
|
|
1460
1211
|
return next;
|
|
1461
1212
|
} catch {
|
|
1462
1213
|
return null;
|
|
@@ -1465,7 +1216,7 @@ function startUpdateCheck() {
|
|
|
1465
1216
|
}
|
|
1466
1217
|
async function installedPackageVersion(cwd, name) {
|
|
1467
1218
|
try {
|
|
1468
|
-
const raw = await
|
|
1219
|
+
const raw = await fs4.readFile(path4.join(cwd, "node_modules", name, "package.json"), "utf8");
|
|
1469
1220
|
const pkg = JSON.parse(raw);
|
|
1470
1221
|
return typeof pkg.version === "string" ? pkg.version : null;
|
|
1471
1222
|
} catch {
|
|
@@ -1512,8 +1263,8 @@ async function recordNotified(cache, announced, cachePath) {
|
|
|
1512
1263
|
notified: { ...cache.notified ?? {}, ...announced },
|
|
1513
1264
|
notifiedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1514
1265
|
};
|
|
1515
|
-
await
|
|
1516
|
-
await
|
|
1266
|
+
await fs4.mkdir(path4.dirname(cachePath), { recursive: true });
|
|
1267
|
+
await fs4.writeFile(cachePath, JSON.stringify(next, null, 2) + "\n");
|
|
1517
1268
|
} catch {
|
|
1518
1269
|
}
|
|
1519
1270
|
}
|
|
@@ -1612,13 +1363,13 @@ async function fetchProjectStatus(apiUrl, token, slug) {
|
|
|
1612
1363
|
}
|
|
1613
1364
|
|
|
1614
1365
|
// src/lib/ssh.ts
|
|
1615
|
-
import
|
|
1616
|
-
import
|
|
1366
|
+
import fs5 from "fs/promises";
|
|
1367
|
+
import path5 from "path";
|
|
1617
1368
|
async function writeGitignore(dir, log) {
|
|
1618
|
-
const file =
|
|
1369
|
+
const file = path5.join(dir, ".gitignore");
|
|
1619
1370
|
let content = "";
|
|
1620
1371
|
try {
|
|
1621
|
-
content = await
|
|
1372
|
+
content = await fs5.readFile(file, "utf8");
|
|
1622
1373
|
} catch {
|
|
1623
1374
|
}
|
|
1624
1375
|
const present = new Set(content.split("\n").map((l) => l.trim()));
|
|
@@ -1628,7 +1379,7 @@ async function writeGitignore(dir, log) {
|
|
|
1628
1379
|
if (next.length > 0 && !next.endsWith("\n")) next += "\n";
|
|
1629
1380
|
if (!content.trim()) next += "# genex local metadata + secrets \u2014 never publish\n";
|
|
1630
1381
|
next += toAdd.join("\n") + "\n";
|
|
1631
|
-
await
|
|
1382
|
+
await fs5.writeFile(file, next);
|
|
1632
1383
|
log.dim(`Updated .gitignore (${toAdd.join(", ")}).`);
|
|
1633
1384
|
}
|
|
1634
1385
|
var LFS_PATTERNS = [
|
|
@@ -1658,10 +1409,10 @@ var LFS_PATTERNS = [
|
|
|
1658
1409
|
"*.webm"
|
|
1659
1410
|
];
|
|
1660
1411
|
async function writeGitattributes(dir, log) {
|
|
1661
|
-
const file =
|
|
1412
|
+
const file = path5.join(dir, ".gitattributes");
|
|
1662
1413
|
let content = "";
|
|
1663
1414
|
try {
|
|
1664
|
-
content = await
|
|
1415
|
+
content = await fs5.readFile(file, "utf8");
|
|
1665
1416
|
} catch {
|
|
1666
1417
|
}
|
|
1667
1418
|
const present = new Set(content.split("\n").map((l) => l.trim().split(/\s+/)[0]));
|
|
@@ -1671,13 +1422,13 @@ async function writeGitattributes(dir, log) {
|
|
|
1671
1422
|
if (next.length > 0 && !next.endsWith("\n")) next += "\n";
|
|
1672
1423
|
if (!content.trim()) next += "# Binary game assets go to Git LFS (R2) \u2014 keeps the source push small\n";
|
|
1673
1424
|
next += toAdd.map((p) => `${p} filter=lfs diff=lfs merge=lfs -text`).join("\n") + "\n";
|
|
1674
|
-
await
|
|
1425
|
+
await fs5.writeFile(file, next);
|
|
1675
1426
|
log.dim(`Updated .gitattributes (${toAdd.length} binary globs \u2192 Git LFS).`);
|
|
1676
1427
|
}
|
|
1677
1428
|
|
|
1678
1429
|
// src/lib/game-config.ts
|
|
1679
|
-
import
|
|
1680
|
-
import
|
|
1430
|
+
import fs6 from "fs/promises";
|
|
1431
|
+
import path6 from "path";
|
|
1681
1432
|
var DEFAULT_DASHBOARD_ORIGIN = new URL(DEFAULT_AUTH_URL).origin;
|
|
1682
1433
|
function renderGenexConfig(slug) {
|
|
1683
1434
|
return `// src/genex.config.ts \u2014 written by \`genex init\`. DO NOT hardcode URLs here.
|
|
@@ -1734,7 +1485,7 @@ ${overrides.join("\n")}
|
|
|
1734
1485
|
}
|
|
1735
1486
|
async function writeIfAbsent(file, content, log) {
|
|
1736
1487
|
try {
|
|
1737
|
-
await
|
|
1488
|
+
await fs6.writeFile(file, content, { flag: "wx" });
|
|
1738
1489
|
log.dim(` wrote ${c.cyan(file)}`);
|
|
1739
1490
|
return true;
|
|
1740
1491
|
} catch (err) {
|
|
@@ -1748,7 +1499,7 @@ async function writeIfAbsent(file, content, log) {
|
|
|
1748
1499
|
async function ensureEnvVar(file, key, value, log) {
|
|
1749
1500
|
let existing = null;
|
|
1750
1501
|
try {
|
|
1751
|
-
existing = await
|
|
1502
|
+
existing = await fs6.readFile(file, "utf8");
|
|
1752
1503
|
} catch {
|
|
1753
1504
|
existing = null;
|
|
1754
1505
|
}
|
|
@@ -1759,23 +1510,23 @@ async function ensureEnvVar(file, key, value, log) {
|
|
|
1759
1510
|
return false;
|
|
1760
1511
|
}
|
|
1761
1512
|
const sep = existing.endsWith("\n") || existing === "" ? "" : "\n";
|
|
1762
|
-
await
|
|
1513
|
+
await fs6.writeFile(file, `${existing}${sep}${key}=${value}
|
|
1763
1514
|
`, "utf8");
|
|
1764
1515
|
log.dim(` appended ${c.cyan(`${key}=${value}`)} to ${c.cyan(file)}`);
|
|
1765
1516
|
return true;
|
|
1766
1517
|
}
|
|
1767
|
-
await
|
|
1518
|
+
await fs6.writeFile(file, `${key}=${value}
|
|
1768
1519
|
`, "utf8");
|
|
1769
1520
|
log.dim(` wrote ${c.cyan(file)} (${key})`);
|
|
1770
1521
|
return true;
|
|
1771
1522
|
}
|
|
1772
1523
|
async function writeGameConfigFiles(meta, log, cwd = process.cwd()) {
|
|
1773
|
-
await
|
|
1774
|
-
await writeIfAbsent(
|
|
1775
|
-
await writeIfAbsent(
|
|
1524
|
+
await fs6.mkdir(path6.join(cwd, "src"), { recursive: true });
|
|
1525
|
+
await writeIfAbsent(path6.join(cwd, "src", "genex.config.ts"), renderGenexConfig(meta.slug), log);
|
|
1526
|
+
await writeIfAbsent(path6.join(cwd, ".env"), renderSlugEnv(meta.slug), log);
|
|
1776
1527
|
const overrides = renderDevOverrides(meta);
|
|
1777
1528
|
if (overrides) {
|
|
1778
|
-
await writeIfAbsent(
|
|
1529
|
+
await writeIfAbsent(path6.join(cwd, ".env.development.local"), overrides, log);
|
|
1779
1530
|
}
|
|
1780
1531
|
}
|
|
1781
1532
|
|
|
@@ -1808,7 +1559,7 @@ var TOOLING_ENTRIES = /* @__PURE__ */ new Set([
|
|
|
1808
1559
|
]);
|
|
1809
1560
|
async function listPreexistingEntries(dir) {
|
|
1810
1561
|
try {
|
|
1811
|
-
return (await
|
|
1562
|
+
return (await fs7.readdir(dir)).filter((name) => !TOOLING_ENTRIES.has(name)).sort();
|
|
1812
1563
|
} catch {
|
|
1813
1564
|
return [];
|
|
1814
1565
|
}
|
|
@@ -1860,15 +1611,15 @@ async function runInit(opts) {
|
|
|
1860
1611
|
let totalNew = 0;
|
|
1861
1612
|
let totalUpdated = 0;
|
|
1862
1613
|
for (const t of targets) {
|
|
1863
|
-
const src = t.full ? templatesDir :
|
|
1864
|
-
const dest = t.full ? t.baseDir :
|
|
1614
|
+
const src = t.full ? templatesDir : path7.join(templatesDir, "skills");
|
|
1615
|
+
const dest = t.full ? t.baseDir : path7.join(t.baseDir, "skills");
|
|
1865
1616
|
const { copied, updated } = await copyTemplates(src, dest, {
|
|
1866
1617
|
force: opts.force,
|
|
1867
|
-
exclude: ["controllers", "motion", "asset-viewer"],
|
|
1618
|
+
exclude: ["controllers", "motion", "asset-viewer", "blender-service"],
|
|
1868
1619
|
filter: t.full ? skillFamilyFilter("game") : (rel) => skillFamilyFilter("game")(`skills/${rel}`)
|
|
1869
1620
|
});
|
|
1870
|
-
await pruneRemovedSkills(
|
|
1871
|
-
await writeSkillsMarker(
|
|
1621
|
+
await pruneRemovedSkills(path7.join(t.baseDir, "skills"), log);
|
|
1622
|
+
await writeSkillsMarker(path7.join(t.baseDir, "skills"));
|
|
1872
1623
|
const added = copied.length - updated.length;
|
|
1873
1624
|
totalNew += added;
|
|
1874
1625
|
totalUpdated += updated.length;
|
|
@@ -1919,7 +1670,7 @@ async function runInit(opts) {
|
|
|
1919
1670
|
if (email) log.plain(` signed in as ${c.cyan(email)}`);
|
|
1920
1671
|
};
|
|
1921
1672
|
await echoIdentity();
|
|
1922
|
-
const projectName = opts.name?.trim() ||
|
|
1673
|
+
const projectName = opts.name?.trim() || path7.basename(process.cwd());
|
|
1923
1674
|
const create = (bearer) => createDraftProject({
|
|
1924
1675
|
apiUrl,
|
|
1925
1676
|
token: bearer,
|
|
@@ -1967,10 +1718,10 @@ async function runInit(opts) {
|
|
|
1967
1718
|
log.dim(` saved ${c.cyan(metaPath)}`);
|
|
1968
1719
|
await writeGameConfigFiles(meta, log);
|
|
1969
1720
|
if (converting) {
|
|
1970
|
-
await
|
|
1721
|
+
await fs7.rm(getWorkspacePath(process.cwd()), { force: true }).catch(() => {
|
|
1971
1722
|
});
|
|
1972
1723
|
await pruneToolCards(log, { dir: opts.dir, agents: opts.agents });
|
|
1973
|
-
await ensureEnvVar(
|
|
1724
|
+
await ensureEnvVar(path7.join(process.cwd(), ".env"), "VITE_GENEX_SLUG", meta.slug, log);
|
|
1974
1725
|
await reportCliEvent(apiUrl, token, "tools_converted");
|
|
1975
1726
|
}
|
|
1976
1727
|
warnPreexisting(log, preexisting);
|
|
@@ -1995,24 +1746,24 @@ async function runInit(opts) {
|
|
|
1995
1746
|
}
|
|
1996
1747
|
|
|
1997
1748
|
// src/commands/link.ts
|
|
1998
|
-
import
|
|
1749
|
+
import fs9 from "fs/promises";
|
|
1999
1750
|
import os6 from "os";
|
|
2000
|
-
import
|
|
1751
|
+
import path9 from "path";
|
|
2001
1752
|
|
|
2002
1753
|
// src/lib/source-sync.ts
|
|
2003
|
-
import
|
|
2004
|
-
import
|
|
1754
|
+
import fs8 from "fs/promises";
|
|
1755
|
+
import path8 from "path";
|
|
2005
1756
|
import os5 from "os";
|
|
2006
1757
|
|
|
2007
1758
|
// src/utils/run.ts
|
|
2008
|
-
import { spawn as
|
|
1759
|
+
import { spawn as spawn2 } from "child_process";
|
|
2009
1760
|
var WIN_SHELL_COMMANDS = /* @__PURE__ */ new Set(["npm", "npx"]);
|
|
2010
1761
|
function run(cmd, args, env) {
|
|
2011
1762
|
const shell = process.platform === "win32" && WIN_SHELL_COMMANDS.has(cmd);
|
|
2012
1763
|
return new Promise((resolve) => {
|
|
2013
1764
|
let child;
|
|
2014
1765
|
try {
|
|
2015
|
-
child =
|
|
1766
|
+
child = spawn2(cmd, args, { env: env ? { ...process.env, ...env } : process.env, shell });
|
|
2016
1767
|
} catch {
|
|
2017
1768
|
resolve({ code: -1, out: "", err: `${cmd} not found` });
|
|
2018
1769
|
return;
|
|
@@ -2022,7 +1773,7 @@ function run(cmd, args, env) {
|
|
|
2022
1773
|
child.stdout?.on("data", (d) => out += String(d));
|
|
2023
1774
|
child.stderr?.on("data", (d) => err += String(d));
|
|
2024
1775
|
child.on("error", () => resolve({ code: -1, out, err: `${cmd} not found` }));
|
|
2025
|
-
child.on("close", (
|
|
1776
|
+
child.on("close", (code) => resolve({ code: code ?? -1, out, err }));
|
|
2026
1777
|
});
|
|
2027
1778
|
}
|
|
2028
1779
|
|
|
@@ -2074,12 +1825,12 @@ function reportStale(log, slug, local, remote) {
|
|
|
2074
1825
|
log.dim(` ${c.cyan("npx genex preview --force")} \u2014 keep yours and replace theirs`);
|
|
2075
1826
|
}
|
|
2076
1827
|
async function sourceTreeHash(cwd) {
|
|
2077
|
-
const gitDir = await
|
|
1828
|
+
const gitDir = await fs8.mkdtemp(path8.join(os5.tmpdir(), "genex-tree-"));
|
|
2078
1829
|
const base = { GIT_DIR: gitDir };
|
|
2079
1830
|
try {
|
|
2080
1831
|
if ((await run("git", ["init", "-q"], base)).code !== 0) return null;
|
|
2081
|
-
await
|
|
2082
|
-
|
|
1832
|
+
await fs8.writeFile(
|
|
1833
|
+
path8.join(gitDir, "info", "exclude"),
|
|
2083
1834
|
["node_modules/", "dist/", ".git/", ".genex/", ".env", ".env.*", "!.env.example", ""].join("\n")
|
|
2084
1835
|
);
|
|
2085
1836
|
if ((await run("git", ["lfs", "version"], base)).code === 0) {
|
|
@@ -2091,14 +1842,14 @@ async function sourceTreeHash(cwd) {
|
|
|
2091
1842
|
];
|
|
2092
1843
|
for (const [key, value] of filters) await run("git", ["config", key, value], base);
|
|
2093
1844
|
}
|
|
2094
|
-
const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE:
|
|
1845
|
+
const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path8.join(gitDir, "index-tree") };
|
|
2095
1846
|
if ((await run("git", ["add", "-A"], env)).code !== 0) return null;
|
|
2096
1847
|
const tree = (await run("git", ["write-tree"], env)).out.trim();
|
|
2097
1848
|
return /^[0-9a-f]{40}$/.test(tree) ? tree : null;
|
|
2098
1849
|
} catch {
|
|
2099
1850
|
return null;
|
|
2100
1851
|
} finally {
|
|
2101
|
-
await
|
|
1852
|
+
await fs8.rm(gitDir, { recursive: true, force: true }).catch(() => {
|
|
2102
1853
|
});
|
|
2103
1854
|
}
|
|
2104
1855
|
}
|
|
@@ -2149,7 +1900,7 @@ async function cloneSource(grant, dest, log) {
|
|
|
2149
1900
|
log.dim(` git clone exited ${cloned.code}`);
|
|
2150
1901
|
return false;
|
|
2151
1902
|
}
|
|
2152
|
-
const repo = { ...env, GIT_DIR:
|
|
1903
|
+
const repo = { ...env, GIT_DIR: path8.join(dest, ".git"), GIT_WORK_TREE: dest };
|
|
2153
1904
|
const filters = [
|
|
2154
1905
|
["filter.lfs.clean", "git-lfs clean -- %f"],
|
|
2155
1906
|
["filter.lfs.smudge", "git-lfs smudge -- %f"],
|
|
@@ -2245,7 +1996,7 @@ async function runLink(opts) {
|
|
|
2245
1996
|
}
|
|
2246
1997
|
async function isEmptyDir(cwd) {
|
|
2247
1998
|
try {
|
|
2248
|
-
const entries = await
|
|
1999
|
+
const entries = await fs9.readdir(cwd);
|
|
2249
2000
|
return entries.every((e) => e === ".git" || e === ".genex" || e === ".DS_Store");
|
|
2250
2001
|
} catch {
|
|
2251
2002
|
return false;
|
|
@@ -2259,13 +2010,13 @@ async function downloadSource(apiUrl, token, project, log) {
|
|
|
2259
2010
|
return false;
|
|
2260
2011
|
}
|
|
2261
2012
|
log.step(`Downloading the ${grant.sourceRef === "preview" ? "draft" : "published"} source\u2026`);
|
|
2262
|
-
const staging = await
|
|
2263
|
-
const fresh =
|
|
2013
|
+
const staging = await fs9.mkdtemp(path9.join(os6.tmpdir(), "genex-link-"));
|
|
2014
|
+
const fresh = path9.join(staging, "source");
|
|
2264
2015
|
try {
|
|
2265
2016
|
if (!await cloneSource(grant, fresh, log)) return false;
|
|
2266
|
-
await
|
|
2267
|
-
for (const entry of await
|
|
2268
|
-
await
|
|
2017
|
+
await fs9.rm(path9.join(fresh, ".git"), { recursive: true, force: true });
|
|
2018
|
+
for (const entry of await fs9.readdir(fresh)) {
|
|
2019
|
+
await fs9.cp(path9.join(fresh, entry), path9.join(process.cwd(), entry), {
|
|
2269
2020
|
recursive: true,
|
|
2270
2021
|
force: true
|
|
2271
2022
|
});
|
|
@@ -2273,28 +2024,28 @@ async function downloadSource(apiUrl, token, project, log) {
|
|
|
2273
2024
|
log.success("Downloaded.");
|
|
2274
2025
|
return true;
|
|
2275
2026
|
} finally {
|
|
2276
|
-
await
|
|
2027
|
+
await fs9.rm(staging, { recursive: true, force: true }).catch(() => {
|
|
2277
2028
|
});
|
|
2278
2029
|
}
|
|
2279
2030
|
}
|
|
2280
2031
|
async function ensureSlugEnv(slug, log, cwd = process.cwd()) {
|
|
2281
|
-
const file =
|
|
2032
|
+
const file = path9.join(cwd, ".env");
|
|
2282
2033
|
let content;
|
|
2283
2034
|
try {
|
|
2284
|
-
content = await
|
|
2035
|
+
content = await fs9.readFile(file, "utf8");
|
|
2285
2036
|
} catch {
|
|
2286
2037
|
return;
|
|
2287
2038
|
}
|
|
2288
2039
|
const re = /^(\s*VITE_GENEX_SLUG=)(.*)$/m;
|
|
2289
2040
|
const m = content.match(re);
|
|
2290
2041
|
if (!m) {
|
|
2291
|
-
await
|
|
2042
|
+
await fs9.appendFile(file, `${content.endsWith("\n") ? "" : "\n"}VITE_GENEX_SLUG=${slug}
|
|
2292
2043
|
`);
|
|
2293
2044
|
log.dim(` added VITE_GENEX_SLUG=${slug} to .env`);
|
|
2294
2045
|
return;
|
|
2295
2046
|
}
|
|
2296
2047
|
if (m[2].trim() === slug) return;
|
|
2297
|
-
await
|
|
2048
|
+
await fs9.writeFile(file, content.replace(re, `$1${slug}`));
|
|
2298
2049
|
log.dim(` updated .env: VITE_GENEX_SLUG=${slug} (was ${m[2].trim()})`);
|
|
2299
2050
|
}
|
|
2300
2051
|
async function fetchOwnProject(apiUrl, token, slug, log) {
|
|
@@ -2348,8 +2099,8 @@ async function listOwnSlugs(apiUrl, token, log) {
|
|
|
2348
2099
|
}
|
|
2349
2100
|
|
|
2350
2101
|
// src/commands/pull.ts
|
|
2351
|
-
import
|
|
2352
|
-
import
|
|
2102
|
+
import fs10 from "fs/promises";
|
|
2103
|
+
import path10 from "path";
|
|
2353
2104
|
import os7 from "os";
|
|
2354
2105
|
function isMachineLocal(entry) {
|
|
2355
2106
|
if (entry === ".genex" || entry === "node_modules" || entry === ".git") return true;
|
|
@@ -2409,23 +2160,23 @@ async function runPull(opts) {
|
|
|
2409
2160
|
process.exitCode = 1;
|
|
2410
2161
|
return;
|
|
2411
2162
|
}
|
|
2412
|
-
const staging = await
|
|
2413
|
-
const fresh =
|
|
2163
|
+
const staging = await fs10.mkdtemp(path10.join(os7.tmpdir(), "genex-pull-"));
|
|
2164
|
+
const fresh = path10.join(staging, "source");
|
|
2414
2165
|
try {
|
|
2415
2166
|
if (!await cloneSource(grant, fresh, log)) {
|
|
2416
2167
|
process.exitCode = 1;
|
|
2417
2168
|
return;
|
|
2418
2169
|
}
|
|
2419
|
-
await
|
|
2170
|
+
await fs10.rm(path10.join(fresh, ".git"), { recursive: true, force: true });
|
|
2420
2171
|
const kept = await keepReplaced(cwd, log);
|
|
2421
2172
|
await replaceTree(cwd, fresh);
|
|
2422
2173
|
if (kept) {
|
|
2423
2174
|
log.plain("");
|
|
2424
|
-
log.info(`What was here is kept at ${c.cyan(
|
|
2175
|
+
log.info(`What was here is kept at ${c.cyan(path10.relative(cwd, kept) || kept)}`);
|
|
2425
2176
|
log.dim(" Nothing was thrown away \u2014 re-apply from there, or delete it when you are done.");
|
|
2426
2177
|
}
|
|
2427
2178
|
} finally {
|
|
2428
|
-
await
|
|
2179
|
+
await fs10.rm(staging, { recursive: true, force: true }).catch(() => {
|
|
2429
2180
|
});
|
|
2430
2181
|
}
|
|
2431
2182
|
const remote = await readRemoteSource(apiUrl, token, meta.id);
|
|
@@ -2444,13 +2195,13 @@ async function runPull(opts) {
|
|
|
2444
2195
|
}
|
|
2445
2196
|
async function keepReplaced(cwd, log) {
|
|
2446
2197
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2447
|
-
const dest =
|
|
2198
|
+
const dest = path10.join(cwd, ".genex", `replaced-${stamp}`);
|
|
2448
2199
|
try {
|
|
2449
|
-
const entries = (await
|
|
2200
|
+
const entries = (await fs10.readdir(cwd)).filter((e) => !isMachineLocal(e));
|
|
2450
2201
|
if (entries.length === 0) return null;
|
|
2451
|
-
await
|
|
2202
|
+
await fs10.mkdir(dest, { recursive: true });
|
|
2452
2203
|
for (const entry of entries) {
|
|
2453
|
-
await
|
|
2204
|
+
await fs10.cp(path10.join(cwd, entry), path10.join(dest, entry), { recursive: true });
|
|
2454
2205
|
}
|
|
2455
2206
|
return dest;
|
|
2456
2207
|
} catch (err) {
|
|
@@ -2460,19 +2211,19 @@ async function keepReplaced(cwd, log) {
|
|
|
2460
2211
|
}
|
|
2461
2212
|
}
|
|
2462
2213
|
async function replaceTree(dest, src) {
|
|
2463
|
-
for (const entry of await
|
|
2214
|
+
for (const entry of await fs10.readdir(dest)) {
|
|
2464
2215
|
if (isMachineLocal(entry)) continue;
|
|
2465
|
-
await
|
|
2216
|
+
await fs10.rm(path10.join(dest, entry), { recursive: true, force: true });
|
|
2466
2217
|
}
|
|
2467
|
-
for (const entry of await
|
|
2218
|
+
for (const entry of await fs10.readdir(src)) {
|
|
2468
2219
|
if (isMachineLocal(entry)) continue;
|
|
2469
|
-
await
|
|
2220
|
+
await fs10.cp(path10.join(src, entry), path10.join(dest, entry), { recursive: true });
|
|
2470
2221
|
}
|
|
2471
2222
|
}
|
|
2472
2223
|
|
|
2473
2224
|
// src/commands/rename.ts
|
|
2474
|
-
import
|
|
2475
|
-
import
|
|
2225
|
+
import fs11 from "fs/promises";
|
|
2226
|
+
import path11 from "path";
|
|
2476
2227
|
async function runRename(opts) {
|
|
2477
2228
|
const log = createLogger({ quiet: opts.quiet });
|
|
2478
2229
|
log.plain(c.bold("genex rename"));
|
|
@@ -2560,23 +2311,23 @@ async function runRename(opts) {
|
|
|
2560
2311
|
log.info("Run `genex preview` (or `publish`) to rebuild \u2014 the new slug is baked into the bundle.");
|
|
2561
2312
|
}
|
|
2562
2313
|
async function rewriteSlugEnv(from, to, log, cwd = process.cwd()) {
|
|
2563
|
-
const file =
|
|
2314
|
+
const file = path11.join(cwd, ".env");
|
|
2564
2315
|
let content;
|
|
2565
2316
|
try {
|
|
2566
|
-
content = await
|
|
2317
|
+
content = await fs11.readFile(file, "utf8");
|
|
2567
2318
|
} catch {
|
|
2568
2319
|
return;
|
|
2569
2320
|
}
|
|
2570
2321
|
const re = /^(\s*VITE_GENEX_SLUG=)(.*)$/m;
|
|
2571
2322
|
if (!re.test(content)) return;
|
|
2572
|
-
await
|
|
2323
|
+
await fs11.writeFile(file, content.replace(re, `$1${to}`));
|
|
2573
2324
|
log.dim(` .env: VITE_GENEX_SLUG=${to} (was ${from})`);
|
|
2574
2325
|
}
|
|
2575
2326
|
async function rewriteBakedSlug(from, to, log, cwd = process.cwd()) {
|
|
2576
|
-
const file =
|
|
2327
|
+
const file = path11.join(cwd, "src", "genex.config.ts");
|
|
2577
2328
|
let content;
|
|
2578
2329
|
try {
|
|
2579
|
-
content = await
|
|
2330
|
+
content = await fs11.readFile(file, "utf8");
|
|
2580
2331
|
} catch {
|
|
2581
2332
|
return;
|
|
2582
2333
|
}
|
|
@@ -2586,7 +2337,7 @@ async function rewriteBakedSlug(from, to, log, cwd = process.cwd()) {
|
|
|
2586
2337
|
log.warn(` src/genex.config.ts has no "${from}" literal \u2014 check its slug by hand.`);
|
|
2587
2338
|
return;
|
|
2588
2339
|
}
|
|
2589
|
-
await
|
|
2340
|
+
await fs11.writeFile(file, content.replace(quoted, `"${to}"`));
|
|
2590
2341
|
log.dim(` src/genex.config.ts: baked slug -> ${to}`);
|
|
2591
2342
|
}
|
|
2592
2343
|
|
|
@@ -2716,9 +2467,9 @@ function relTime(iso) {
|
|
|
2716
2467
|
// src/lib/deploy.ts
|
|
2717
2468
|
import "child_process";
|
|
2718
2469
|
import crypto3 from "crypto";
|
|
2719
|
-
import
|
|
2470
|
+
import fs14 from "fs/promises";
|
|
2720
2471
|
import os8 from "os";
|
|
2721
|
-
import
|
|
2472
|
+
import path13 from "path";
|
|
2722
2473
|
|
|
2723
2474
|
// ../../packages/mobile-scan/src/image-dims.ts
|
|
2724
2475
|
function u32be(b, o) {
|
|
@@ -2982,12 +2733,12 @@ function tierFor(estVramMb) {
|
|
|
2982
2733
|
}
|
|
2983
2734
|
|
|
2984
2735
|
// src/commands/ui.ts
|
|
2985
|
-
import
|
|
2986
|
-
import
|
|
2736
|
+
import fs13 from "fs/promises";
|
|
2737
|
+
import path12 from "path";
|
|
2987
2738
|
import { PNG as PNG2 } from "pngjs";
|
|
2988
2739
|
|
|
2989
2740
|
// src/lib/png-tools.ts
|
|
2990
|
-
import
|
|
2741
|
+
import fs12 from "fs/promises";
|
|
2991
2742
|
import { PNG } from "pngjs";
|
|
2992
2743
|
var ALPHA_TRANSPARENT_MAX = 16;
|
|
2993
2744
|
var isHttpUrl = (s) => /^https?:\/\//i.test(s);
|
|
@@ -2998,12 +2749,12 @@ async function loadPng(input) {
|
|
|
2998
2749
|
if (!res.ok) throw new Error(`Couldn't fetch ${input} (HTTP ${res.status}).`);
|
|
2999
2750
|
buf = Buffer.from(await res.arrayBuffer());
|
|
3000
2751
|
} else {
|
|
3001
|
-
buf = await
|
|
2752
|
+
buf = await fs12.readFile(input);
|
|
3002
2753
|
}
|
|
3003
2754
|
return PNG.sync.read(buf);
|
|
3004
2755
|
}
|
|
3005
2756
|
async function writePng(file, png) {
|
|
3006
|
-
await
|
|
2757
|
+
await fs12.writeFile(file, PNG.sync.write(png));
|
|
3007
2758
|
}
|
|
3008
2759
|
function cropPng(image, box) {
|
|
3009
2760
|
const out = new PNG({ width: box.w, height: box.h });
|
|
@@ -3303,7 +3054,7 @@ async function uiExtract(opts, log) {
|
|
|
3303
3054
|
const dilatePx = opts.dilate ?? 0;
|
|
3304
3055
|
const sheet = await loadPng(input);
|
|
3305
3056
|
const { width: W, height: H, data } = sheet;
|
|
3306
|
-
await
|
|
3057
|
+
await fs13.mkdir(outDir, { recursive: true });
|
|
3307
3058
|
log.plain(c.bold("genex ui extract"));
|
|
3308
3059
|
log.dim(` ${input} (${W}x${H}) \u2192 ${outDir}, ${names.length} names`);
|
|
3309
3060
|
let hasTransparency = false;
|
|
@@ -3532,7 +3283,7 @@ async function uiExtract(opts, log) {
|
|
|
3532
3283
|
rimPixels: speckle.sampled
|
|
3533
3284
|
});
|
|
3534
3285
|
}
|
|
3535
|
-
const outPath =
|
|
3286
|
+
const outPath = path12.join(outDir, `${name}.png`);
|
|
3536
3287
|
await writePng(outPath, out);
|
|
3537
3288
|
const sidecar = {
|
|
3538
3289
|
name,
|
|
@@ -3551,7 +3302,7 @@ async function uiExtract(opts, log) {
|
|
|
3551
3302
|
defringed
|
|
3552
3303
|
};
|
|
3553
3304
|
const { name: _n, out: _o, ...sidecarBody } = sidecar;
|
|
3554
|
-
await
|
|
3305
|
+
await fs13.writeFile(
|
|
3555
3306
|
outPath.replace(/\.png$/i, "") + ".bbox.json",
|
|
3556
3307
|
JSON.stringify(sidecarBody, null, 2)
|
|
3557
3308
|
);
|
|
@@ -3560,8 +3311,8 @@ async function uiExtract(opts, log) {
|
|
|
3560
3311
|
`${name}.png ${cropW}x${cropH} (ar ${sidecar.aspectRatio}) at sheet ${padX0},${padY0}`
|
|
3561
3312
|
);
|
|
3562
3313
|
}
|
|
3563
|
-
const debugPath =
|
|
3564
|
-
await
|
|
3314
|
+
const debugPath = path12.join(outDir, "extract-debug.json");
|
|
3315
|
+
await fs13.writeFile(
|
|
3565
3316
|
debugPath,
|
|
3566
3317
|
JSON.stringify(
|
|
3567
3318
|
{
|
|
@@ -4023,7 +3774,7 @@ async function uiMasks(opts, log) {
|
|
|
4023
3774
|
const registrationTolerance = opts.registrationTolerance ?? 0.04;
|
|
4024
3775
|
const edgeFlushMax = opts.edgeFlushMax ?? 0.04;
|
|
4025
3776
|
const loosened = registrationTolerance > 0.04 || edgeFlushMax > 0.04 || minCoverage < 0.01 || maxCoverage > 0.85;
|
|
4026
|
-
await
|
|
3777
|
+
await fs13.mkdir(outDir, { recursive: true });
|
|
4027
3778
|
log.plain(c.bold("genex ui masks"));
|
|
4028
3779
|
log.dim(` ${input} (${image.width}x${image.height}), ${pairs.length} pair(s) \u2192 ${outDir}`);
|
|
4029
3780
|
const sheetComponents = detectSheetComponents(image, opts.minPixels ?? 2e3);
|
|
@@ -4076,11 +3827,11 @@ async function uiMasks(opts, log) {
|
|
|
4076
3827
|
});
|
|
4077
3828
|
}
|
|
4078
3829
|
const overlay = makeOverlay(clean2, converted.png);
|
|
4079
|
-
const framePath =
|
|
4080
|
-
const maskPath =
|
|
4081
|
-
const annotatedPath =
|
|
4082
|
-
const overlayPath =
|
|
4083
|
-
const metaPath =
|
|
3830
|
+
const framePath = path12.join(outDir, `${pair.name}-frame.png`);
|
|
3831
|
+
const maskPath = path12.join(outDir, `${pair.name}-mask.png`);
|
|
3832
|
+
const annotatedPath = path12.join(outDir, `${pair.name}-annotated-source.png`);
|
|
3833
|
+
const overlayPath = path12.join(outDir, `${pair.name}-overlay.png`);
|
|
3834
|
+
const metaPath = path12.join(outDir, `${pair.name}.annotated-progress.json`);
|
|
4084
3835
|
await writePng(framePath, clean2);
|
|
4085
3836
|
await writePng(maskPath, converted.png);
|
|
4086
3837
|
await writePng(annotatedPath, annotated);
|
|
@@ -4127,7 +3878,7 @@ async function uiMasks(opts, log) {
|
|
|
4127
3878
|
},
|
|
4128
3879
|
overlay: overlayPath
|
|
4129
3880
|
};
|
|
4130
|
-
await
|
|
3881
|
+
await fs13.writeFile(metaPath, `${JSON.stringify(meta, null, 2)}
|
|
4131
3882
|
`);
|
|
4132
3883
|
results.push(meta);
|
|
4133
3884
|
const fb = converted.bbox;
|
|
@@ -4143,8 +3894,8 @@ async function uiMasks(opts, log) {
|
|
|
4143
3894
|
);
|
|
4144
3895
|
}
|
|
4145
3896
|
}
|
|
4146
|
-
const indexPath =
|
|
4147
|
-
await
|
|
3897
|
+
const indexPath = path12.join(outDir, "annotated-progress.json");
|
|
3898
|
+
await fs13.writeFile(indexPath, `${JSON.stringify({ input, loosened, pairs: results }, null, 2)}
|
|
4148
3899
|
`);
|
|
4149
3900
|
log.plain("");
|
|
4150
3901
|
log.success(`Wrote ${pairs.length} frame+mask pair(s) \u2192 ${outDir} (index: ${indexPath}).`);
|
|
@@ -4270,7 +4021,7 @@ async function uiTextColor(opts, log) {
|
|
|
4270
4021
|
};
|
|
4271
4022
|
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
4272
4023
|
`);
|
|
4273
|
-
if (opts.out) await
|
|
4024
|
+
if (opts.out) await fs13.writeFile(opts.out, `${JSON.stringify(result, null, 2)}
|
|
4274
4025
|
`);
|
|
4275
4026
|
if (opts.cropPath) log.dim(` crop \u2192 ${opts.cropPath}`);
|
|
4276
4027
|
}
|
|
@@ -4302,7 +4053,7 @@ async function uiTrim(opts, log) {
|
|
|
4302
4053
|
const sidecar = computeBBoxes(trimmed);
|
|
4303
4054
|
const speckleAllowed = !!(speckle && speckle.ratio > SPECKLE_MAX_RATIO);
|
|
4304
4055
|
const sidecarPath = outPath.replace(/\.png$/i, "") + ".bbox.json";
|
|
4305
|
-
await
|
|
4056
|
+
await fs13.writeFile(
|
|
4306
4057
|
sidecarPath,
|
|
4307
4058
|
JSON.stringify(speckleAllowed ? { ...sidecar, speckleAllowed: true } : sidecar, null, 2)
|
|
4308
4059
|
);
|
|
@@ -4409,7 +4160,7 @@ async function uiPlate(opts, log) {
|
|
|
4409
4160
|
fail("No interior found \u2014 the image is fully transparent (or erode ate everything). Check --in / lower --erode.");
|
|
4410
4161
|
}
|
|
4411
4162
|
await writePng(outPath, out);
|
|
4412
|
-
const name =
|
|
4163
|
+
const name = path12.basename(outPath);
|
|
4413
4164
|
log.plain(c.bold("genex ui plate"));
|
|
4414
4165
|
log.success(`${outPath} ${W}x${H}, interior ${(count / (W * H) * 100).toFixed(1)}% (erode ${erode}px)`);
|
|
4415
4166
|
log.dim(" Wire it as the plate's silhouette (same box as the frame <img>, plate UNDER the art):");
|
|
@@ -4574,13 +4325,13 @@ async function walkFiles(dir) {
|
|
|
4574
4325
|
const out = [];
|
|
4575
4326
|
let entries;
|
|
4576
4327
|
try {
|
|
4577
|
-
entries = await
|
|
4328
|
+
entries = await fs13.readdir(dir, { withFileTypes: true });
|
|
4578
4329
|
} catch {
|
|
4579
4330
|
return out;
|
|
4580
4331
|
}
|
|
4581
4332
|
for (const entry of entries) {
|
|
4582
4333
|
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
4583
|
-
const p =
|
|
4334
|
+
const p = path12.join(dir, entry.name);
|
|
4584
4335
|
if (entry.isDirectory()) out.push(...await walkFiles(p));
|
|
4585
4336
|
else out.push(p);
|
|
4586
4337
|
}
|
|
@@ -4589,7 +4340,7 @@ async function walkFiles(dir) {
|
|
|
4589
4340
|
async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
4590
4341
|
const viewportFindings = [];
|
|
4591
4342
|
try {
|
|
4592
|
-
const indexHtml = await
|
|
4343
|
+
const indexHtml = await fs13.readFile(path12.join(cwd, "index.html"), "utf8");
|
|
4593
4344
|
if (!/<meta[^>]+name=["']viewport["']/i.test(indexHtml)) {
|
|
4594
4345
|
viewportFindings.push({
|
|
4595
4346
|
kind: "viewport-meta",
|
|
@@ -4603,28 +4354,28 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4603
4354
|
}
|
|
4604
4355
|
} catch {
|
|
4605
4356
|
}
|
|
4606
|
-
const absAssets =
|
|
4357
|
+
const absAssets = path12.resolve(cwd, assetDir);
|
|
4607
4358
|
try {
|
|
4608
|
-
if (!(await
|
|
4359
|
+
if (!(await fs13.stat(absAssets)).isDirectory()) {
|
|
4609
4360
|
return viewportFindings.length > 0 ? viewportFindings : null;
|
|
4610
4361
|
}
|
|
4611
4362
|
} catch {
|
|
4612
4363
|
return viewportFindings.length > 0 ? viewportFindings : null;
|
|
4613
4364
|
}
|
|
4614
|
-
const srcFiles = (await walkFiles(
|
|
4615
|
-
(p) => AUDIT_SRC_EXTS.has(
|
|
4365
|
+
const srcFiles = (await walkFiles(path12.resolve(cwd, srcDir))).filter(
|
|
4366
|
+
(p) => AUDIT_SRC_EXTS.has(path12.extname(p).toLowerCase())
|
|
4616
4367
|
);
|
|
4617
4368
|
try {
|
|
4618
|
-
for (const name of await
|
|
4619
|
-
const ext =
|
|
4620
|
-
if (ext === ".html" || ext === ".css") srcFiles.push(
|
|
4369
|
+
for (const name of await fs13.readdir(cwd)) {
|
|
4370
|
+
const ext = path12.extname(name).toLowerCase();
|
|
4371
|
+
if (ext === ".html" || ext === ".css") srcFiles.push(path12.join(cwd, name));
|
|
4621
4372
|
}
|
|
4622
4373
|
} catch {
|
|
4623
4374
|
}
|
|
4624
4375
|
const sources = [];
|
|
4625
4376
|
for (const p of srcFiles) {
|
|
4626
4377
|
try {
|
|
4627
|
-
sources.push({ rel:
|
|
4378
|
+
sources.push({ rel: path12.relative(cwd, p), text: await fs13.readFile(p, "utf8") });
|
|
4628
4379
|
} catch {
|
|
4629
4380
|
}
|
|
4630
4381
|
}
|
|
@@ -4634,11 +4385,11 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4634
4385
|
const metaByName = /* @__PURE__ */ new Map();
|
|
4635
4386
|
const bboxByPng = /* @__PURE__ */ new Map();
|
|
4636
4387
|
for (const p of assetFiles) {
|
|
4637
|
-
const base =
|
|
4388
|
+
const base = path12.basename(p);
|
|
4638
4389
|
const metaMatch = /^(.+)\.annotated-progress\.json$/.exec(base);
|
|
4639
4390
|
if (metaMatch) {
|
|
4640
4391
|
try {
|
|
4641
|
-
const meta = JSON.parse(await
|
|
4392
|
+
const meta = JSON.parse(await fs13.readFile(p, "utf8"));
|
|
4642
4393
|
metaByName.set(metaMatch[1], {
|
|
4643
4394
|
cleanCrop: meta.clean?.crop ?? null,
|
|
4644
4395
|
loosened: meta.loosened === true
|
|
@@ -4649,7 +4400,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4649
4400
|
}
|
|
4650
4401
|
if (base.endsWith(".bbox.json")) {
|
|
4651
4402
|
try {
|
|
4652
|
-
const sidecar = JSON.parse(await
|
|
4403
|
+
const sidecar = JSON.parse(await fs13.readFile(p, "utf8"));
|
|
4653
4404
|
if (sidecar.sheetBBox) bboxByPng.set(base.replace(/\.bbox\.json$/, ".png"), sidecar.sheetBBox);
|
|
4654
4405
|
} catch {
|
|
4655
4406
|
}
|
|
@@ -4658,7 +4409,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4658
4409
|
for (const [name, meta] of metaByName) {
|
|
4659
4410
|
if (!meta.cleanCrop || !referenced(`${name}-mask.png`)) continue;
|
|
4660
4411
|
for (const p of assetFiles) {
|
|
4661
|
-
const base =
|
|
4412
|
+
const base = path12.basename(p);
|
|
4662
4413
|
if (!base.toLowerCase().endsWith(".png")) continue;
|
|
4663
4414
|
if (base !== `${name}.png` && !base.startsWith(`${name}-`)) continue;
|
|
4664
4415
|
if (/-mask\.png$|-frame\.png$|-overlay\.png$|-annotated-source\.png$/.test(base)) continue;
|
|
@@ -4682,7 +4433,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4682
4433
|
}
|
|
4683
4434
|
const pngByBase = /* @__PURE__ */ new Map();
|
|
4684
4435
|
for (const p of assetFiles) {
|
|
4685
|
-
const base =
|
|
4436
|
+
const base = path12.basename(p);
|
|
4686
4437
|
if (base.toLowerCase().endsWith(".png")) pngByBase.set(base, p);
|
|
4687
4438
|
}
|
|
4688
4439
|
for (const [maskBase, maskPath] of pngByBase) {
|
|
@@ -4695,8 +4446,8 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4695
4446
|
let frame;
|
|
4696
4447
|
let mask;
|
|
4697
4448
|
try {
|
|
4698
|
-
frame = PNG2.sync.read(await
|
|
4699
|
-
mask = PNG2.sync.read(await
|
|
4449
|
+
frame = PNG2.sync.read(await fs13.readFile(framePath));
|
|
4450
|
+
mask = PNG2.sync.read(await fs13.readFile(maskPath));
|
|
4700
4451
|
} catch {
|
|
4701
4452
|
continue;
|
|
4702
4453
|
}
|
|
@@ -4715,7 +4466,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4715
4466
|
if (!referenced(base)) continue;
|
|
4716
4467
|
let png;
|
|
4717
4468
|
try {
|
|
4718
|
-
png = PNG2.sync.read(await
|
|
4469
|
+
png = PNG2.sync.read(await fs13.readFile(p));
|
|
4719
4470
|
} catch {
|
|
4720
4471
|
continue;
|
|
4721
4472
|
}
|
|
@@ -4735,7 +4486,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4735
4486
|
}
|
|
4736
4487
|
const maskReported = /* @__PURE__ */ new Set();
|
|
4737
4488
|
for (const p of assetFiles) {
|
|
4738
|
-
const m = /^(.+)\.annotated-progress\.json$/.exec(
|
|
4489
|
+
const m = /^(.+)\.annotated-progress\.json$/.exec(path12.basename(p));
|
|
4739
4490
|
if (!m) continue;
|
|
4740
4491
|
const maskBase = `${m[1]}-mask.png`;
|
|
4741
4492
|
if (!referenced(maskBase)) {
|
|
@@ -4747,14 +4498,14 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4747
4498
|
}
|
|
4748
4499
|
}
|
|
4749
4500
|
for (const p of assetFiles) {
|
|
4750
|
-
const base =
|
|
4501
|
+
const base = path12.basename(p);
|
|
4751
4502
|
if (!base.toLowerCase().endsWith(".png")) continue;
|
|
4752
4503
|
if (/-annotated-source\.png$|-overlay\.png$/.test(base)) continue;
|
|
4753
4504
|
if (maskReported.has(base)) continue;
|
|
4754
4505
|
if (!referenced(base)) {
|
|
4755
4506
|
findings.push({
|
|
4756
4507
|
kind: "unwired-sprite",
|
|
4757
|
-
message: `${
|
|
4508
|
+
message: `${path12.relative(cwd, p)} is on disk but never referenced in ${srcDir}/index.html/CSS \u2014 wire it, or record the one-line reason it was cut. (Computed-string references are invisible here \u2014 check.)`
|
|
4758
4509
|
});
|
|
4759
4510
|
}
|
|
4760
4511
|
}
|
|
@@ -4941,7 +4692,7 @@ async function printUiAuditPreflight(log) {
|
|
|
4941
4692
|
}
|
|
4942
4693
|
async function printPipelineStatePreflight(log, cwd = process.cwd()) {
|
|
4943
4694
|
try {
|
|
4944
|
-
const design = await
|
|
4695
|
+
const design = await fs14.readFile(path13.join(cwd, "DESIGN.md"), "utf8").catch(() => "");
|
|
4945
4696
|
const warnings = [];
|
|
4946
4697
|
if (design.length > 0 && !/##\s*build plan & status/i.test(design)) {
|
|
4947
4698
|
warnings.push(
|
|
@@ -4949,7 +4700,7 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
|
|
|
4949
4700
|
);
|
|
4950
4701
|
}
|
|
4951
4702
|
if (!/player character:/i.test(design)) {
|
|
4952
|
-
const hasCharacter = await
|
|
4703
|
+
const hasCharacter = await fs14.access(path13.join(cwd, "public", "assets", "meshy-character.json")).then(() => true, () => false);
|
|
4953
4704
|
if (!hasCharacter && await loadsPlayerBody(cwd)) {
|
|
4954
4705
|
warnings.push(
|
|
4955
4706
|
`Player character is the stock avatar \u2014 no generated character is wired. The game's own generated character is the player's body wherever a human body appears on screen, first-person included (genex-ai-character). Generate it, or record "Player character: VRM \u2014 <reason>" in DESIGN.md (no human body in this game / out of credits / player declined).`
|
|
@@ -4963,12 +4714,12 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
|
|
|
4963
4714
|
async function loadsPlayerBody(cwd) {
|
|
4964
4715
|
const BODY_LOADERS = /\bloadPlayerCharacter\s*\(|\bloadVrm(?:Clone)?\s*\(/;
|
|
4965
4716
|
try {
|
|
4966
|
-
const entries = await
|
|
4717
|
+
const entries = await fs14.readdir(path13.join(cwd, "src"), { recursive: true });
|
|
4967
4718
|
for (const rel of entries) {
|
|
4968
4719
|
if (rel.includes("node_modules")) continue;
|
|
4969
|
-
if (rel.split(
|
|
4720
|
+
if (rel.split(path13.sep)[0] === "controllers" || rel.startsWith("controllers/")) continue;
|
|
4970
4721
|
if (!/\.(ts|tsx|js|jsx)$/.test(rel)) continue;
|
|
4971
|
-
const text = await
|
|
4722
|
+
const text = await fs14.readFile(path13.join(cwd, "src", rel), "utf8").catch(() => "");
|
|
4972
4723
|
if (BODY_LOADERS.test(text)) return true;
|
|
4973
4724
|
}
|
|
4974
4725
|
} catch {
|
|
@@ -5005,9 +4756,9 @@ async function deployGame(ctx, opts, log) {
|
|
|
5005
4756
|
}
|
|
5006
4757
|
log.success("Built.");
|
|
5007
4758
|
}
|
|
5008
|
-
const distDir =
|
|
4759
|
+
const distDir = path13.join(cwd, "dist");
|
|
5009
4760
|
const siteDir = await isDir(distDir) ? distDir : cwd;
|
|
5010
|
-
const rel =
|
|
4761
|
+
const rel = path13.relative(cwd, siteDir) || ".";
|
|
5011
4762
|
if (siteDir === cwd) await writeGitignore(cwd, log);
|
|
5012
4763
|
const files = await collectFiles(siteDir);
|
|
5013
4764
|
if (files.length === 0) {
|
|
@@ -5096,7 +4847,7 @@ async function deployGame(ctx, opts, log) {
|
|
|
5096
4847
|
}
|
|
5097
4848
|
async function hasBuildScript(cwd) {
|
|
5098
4849
|
try {
|
|
5099
|
-
const pkg = JSON.parse(await
|
|
4850
|
+
const pkg = JSON.parse(await fs14.readFile(path13.join(cwd, "package.json"), "utf8"));
|
|
5100
4851
|
return Boolean(pkg.scripts?.build);
|
|
5101
4852
|
} catch {
|
|
5102
4853
|
return false;
|
|
@@ -5105,12 +4856,12 @@ async function hasBuildScript(cwd) {
|
|
|
5105
4856
|
async function collectFiles(root) {
|
|
5106
4857
|
const out = [];
|
|
5107
4858
|
const walk2 = async (dir, prefix) => {
|
|
5108
|
-
for (const e of await
|
|
4859
|
+
for (const e of await fs14.readdir(dir, { withFileTypes: true })) {
|
|
5109
4860
|
const relPath = prefix ? `${prefix}/${e.name}` : e.name;
|
|
5110
4861
|
if (e.isDirectory()) {
|
|
5111
|
-
if (!EXCLUDE_DIRS.has(e.name)) await walk2(
|
|
4862
|
+
if (!EXCLUDE_DIRS.has(e.name)) await walk2(path13.join(dir, e.name), relPath);
|
|
5112
4863
|
} else if (e.isFile() && !isSecretEnvFile(e.name)) {
|
|
5113
|
-
out.push({ relPath, bytes: await
|
|
4864
|
+
out.push({ relPath, bytes: await fs14.readFile(path13.join(dir, e.name)) });
|
|
5114
4865
|
}
|
|
5115
4866
|
}
|
|
5116
4867
|
};
|
|
@@ -5352,7 +5103,7 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
|
|
|
5352
5103
|
log.error("Couldn't save your game's source \u2014 please try again.");
|
|
5353
5104
|
return false;
|
|
5354
5105
|
};
|
|
5355
|
-
const gitDir = await
|
|
5106
|
+
const gitDir = await fs14.mkdtemp(path13.join(os8.tmpdir(), "genex-source-"));
|
|
5356
5107
|
const base = { GIT_DIR: gitDir };
|
|
5357
5108
|
if (urlHasEmbeddedCredentials(pushUrl)) {
|
|
5358
5109
|
base.GIT_CONFIG_COUNT = "1";
|
|
@@ -5367,12 +5118,12 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
|
|
|
5367
5118
|
};
|
|
5368
5119
|
try {
|
|
5369
5120
|
if ((await run("git", ["init", "-q"], base)).code !== 0) return failed();
|
|
5370
|
-
await
|
|
5371
|
-
|
|
5121
|
+
await fs14.writeFile(
|
|
5122
|
+
path13.join(gitDir, "info", "exclude"),
|
|
5372
5123
|
// .env* are secrets — never publish them; `!` keeps the non-secret template.
|
|
5373
5124
|
["node_modules/", "dist/", ".git/", ".genex/", ".env", ".env.*", "!.env.example", ""].join("\n")
|
|
5374
5125
|
);
|
|
5375
|
-
const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE:
|
|
5126
|
+
const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path13.join(gitDir, "index-source") };
|
|
5376
5127
|
let lfs = (await run("git", ["lfs", "version"], base)).code !== 0 ? false : true;
|
|
5377
5128
|
if (!lfs) {
|
|
5378
5129
|
log.step("Installing git-lfs (keeps large binary assets out of the source push)\u2026");
|
|
@@ -5424,7 +5175,7 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
|
|
|
5424
5175
|
} catch {
|
|
5425
5176
|
return failed();
|
|
5426
5177
|
} finally {
|
|
5427
|
-
await
|
|
5178
|
+
await fs14.rm(gitDir, { recursive: true, force: true }).catch(() => {
|
|
5428
5179
|
});
|
|
5429
5180
|
}
|
|
5430
5181
|
}
|
|
@@ -5476,7 +5227,7 @@ async function fetchPushUrl(ctx, log, expectStagingCommit) {
|
|
|
5476
5227
|
}
|
|
5477
5228
|
async function isDir(p) {
|
|
5478
5229
|
try {
|
|
5479
|
-
return (await
|
|
5230
|
+
return (await fs14.stat(p)).isDirectory();
|
|
5480
5231
|
} catch {
|
|
5481
5232
|
return false;
|
|
5482
5233
|
}
|
|
@@ -5958,17 +5709,17 @@ async function promoteBuild(apiUrl, projectId, token, log) {
|
|
|
5958
5709
|
}
|
|
5959
5710
|
|
|
5960
5711
|
// src/lib/detect-features.ts
|
|
5961
|
-
import
|
|
5962
|
-
import
|
|
5712
|
+
import fs16 from "fs/promises";
|
|
5713
|
+
import path15 from "path";
|
|
5963
5714
|
|
|
5964
5715
|
// src/lib/generation-ledger.ts
|
|
5965
|
-
import
|
|
5966
|
-
import
|
|
5967
|
-
var ledgerPath = (cwd) =>
|
|
5716
|
+
import fs15 from "fs/promises";
|
|
5717
|
+
import path14 from "path";
|
|
5718
|
+
var ledgerPath = (cwd) => path14.join(cwd, ".genex", "generations.ndjson");
|
|
5968
5719
|
async function append(cwd, event) {
|
|
5969
5720
|
try {
|
|
5970
|
-
await
|
|
5971
|
-
await
|
|
5721
|
+
await fs15.access(path14.join(cwd, ".genex"));
|
|
5722
|
+
await fs15.appendFile(ledgerPath(cwd), `${JSON.stringify(event)}
|
|
5972
5723
|
`, "utf8");
|
|
5973
5724
|
} catch {
|
|
5974
5725
|
}
|
|
@@ -5976,7 +5727,7 @@ async function append(cwd, event) {
|
|
|
5976
5727
|
async function readLedger(cwd = process.cwd()) {
|
|
5977
5728
|
let raw;
|
|
5978
5729
|
try {
|
|
5979
|
-
raw = await
|
|
5730
|
+
raw = await fs15.readFile(ledgerPath(cwd), "utf8");
|
|
5980
5731
|
} catch {
|
|
5981
5732
|
return [];
|
|
5982
5733
|
}
|
|
@@ -6037,7 +5788,7 @@ async function countOutcomes(kind, cwd = process.cwd()) {
|
|
|
6037
5788
|
// src/lib/detect-features.ts
|
|
6038
5789
|
async function detectEmbedSdkVersion(cwd = process.cwd()) {
|
|
6039
5790
|
try {
|
|
6040
|
-
const raw = await
|
|
5791
|
+
const raw = await fs16.readFile(path15.join(cwd, "package.json"), "utf8");
|
|
6041
5792
|
const pkg = JSON.parse(raw);
|
|
6042
5793
|
const version = pkg.dependencies?.["@genex-ai/embed-sdk"] ?? pkg.devDependencies?.["@genex-ai/embed-sdk"];
|
|
6043
5794
|
return typeof version === "string" && version ? version : null;
|
|
@@ -6047,7 +5798,7 @@ async function detectEmbedSdkVersion(cwd = process.cwd()) {
|
|
|
6047
5798
|
}
|
|
6048
5799
|
async function detectMultiplayer(cwd = process.cwd()) {
|
|
6049
5800
|
try {
|
|
6050
|
-
const raw = await
|
|
5801
|
+
const raw = await fs16.readFile(path15.join(cwd, "package.json"), "utf8");
|
|
6051
5802
|
const pkg = JSON.parse(raw);
|
|
6052
5803
|
return Boolean(
|
|
6053
5804
|
pkg.dependencies?.["@genex-ai/multiplayer"] ?? pkg.devDependencies?.["@genex-ai/multiplayer"]
|
|
@@ -6059,7 +5810,7 @@ async function detectMultiplayer(cwd = process.cwd()) {
|
|
|
6059
5810
|
async function detectMatchmaking(log, cwd = process.cwd()) {
|
|
6060
5811
|
let pkg;
|
|
6061
5812
|
try {
|
|
6062
|
-
pkg = JSON.parse(await
|
|
5813
|
+
pkg = JSON.parse(await fs16.readFile(path15.join(cwd, "package.json"), "utf8"));
|
|
6063
5814
|
} catch (err) {
|
|
6064
5815
|
log.dim(` (skipping matchmaking \u2014 couldn't read package.json: ${String(err)})`);
|
|
6065
5816
|
return null;
|
|
@@ -6077,15 +5828,15 @@ async function detectMatchmaking(log, cwd = process.cwd()) {
|
|
|
6077
5828
|
var TOUCH_KIT_MARKERS = /controllers\/touch\/|controllers\/character\/touch-joystick|TouchJoystick|VirtualButton|DragZone|RotateOverlay/;
|
|
6078
5829
|
async function detectMobileControls(cwd = process.cwd()) {
|
|
6079
5830
|
try {
|
|
6080
|
-
const raw = await
|
|
5831
|
+
const raw = await fs16.readFile(path15.join(cwd, "package.json"), "utf8");
|
|
6081
5832
|
const pkg = JSON.parse(raw);
|
|
6082
5833
|
if (pkg.genex?.mobileControls === true) return true;
|
|
6083
5834
|
} catch {
|
|
6084
5835
|
}
|
|
6085
|
-
const srcDir =
|
|
5836
|
+
const srcDir = path15.join(cwd, "src");
|
|
6086
5837
|
let entries;
|
|
6087
5838
|
try {
|
|
6088
|
-
entries = await
|
|
5839
|
+
entries = await fs16.readdir(srcDir, { recursive: true });
|
|
6089
5840
|
} catch {
|
|
6090
5841
|
return false;
|
|
6091
5842
|
}
|
|
@@ -6093,7 +5844,7 @@ async function detectMobileControls(cwd = process.cwd()) {
|
|
|
6093
5844
|
if (rel.includes("node_modules")) continue;
|
|
6094
5845
|
if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(rel)) continue;
|
|
6095
5846
|
try {
|
|
6096
|
-
const content = await
|
|
5847
|
+
const content = await fs16.readFile(path15.join(srcDir, rel), "utf8");
|
|
6097
5848
|
if (TOUCH_KIT_MARKERS.test(content)) return true;
|
|
6098
5849
|
} catch {
|
|
6099
5850
|
}
|
|
@@ -6102,10 +5853,10 @@ async function detectMobileControls(cwd = process.cwd()) {
|
|
|
6102
5853
|
}
|
|
6103
5854
|
var GAME_STATE_CALLS = /savePlayerState|saveWorldState|submitScore/;
|
|
6104
5855
|
async function detectGameStateUsage(cwd = process.cwd()) {
|
|
6105
|
-
const srcDir =
|
|
5856
|
+
const srcDir = path15.join(cwd, "src");
|
|
6106
5857
|
let entries;
|
|
6107
5858
|
try {
|
|
6108
|
-
entries = await
|
|
5859
|
+
entries = await fs16.readdir(srcDir, { recursive: true });
|
|
6109
5860
|
} catch {
|
|
6110
5861
|
return false;
|
|
6111
5862
|
}
|
|
@@ -6113,7 +5864,7 @@ async function detectGameStateUsage(cwd = process.cwd()) {
|
|
|
6113
5864
|
if (rel.includes("node_modules")) continue;
|
|
6114
5865
|
if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(rel)) continue;
|
|
6115
5866
|
try {
|
|
6116
|
-
const content = await
|
|
5867
|
+
const content = await fs16.readFile(path15.join(srcDir, rel), "utf8");
|
|
6117
5868
|
if (GAME_STATE_CALLS.test(content)) return true;
|
|
6118
5869
|
} catch {
|
|
6119
5870
|
}
|
|
@@ -6164,19 +5915,19 @@ async function detectSurfaceScan(cwd = process.cwd()) {
|
|
|
6164
5915
|
deferredAudioContext: [],
|
|
6165
5916
|
usesThree: false
|
|
6166
5917
|
};
|
|
6167
|
-
const srcDir =
|
|
5918
|
+
const srcDir = path15.join(cwd, "src");
|
|
6168
5919
|
let entries;
|
|
6169
5920
|
try {
|
|
6170
|
-
entries = await
|
|
5921
|
+
entries = await fs16.readdir(srcDir, { recursive: true });
|
|
6171
5922
|
} catch {
|
|
6172
5923
|
return found;
|
|
6173
5924
|
}
|
|
6174
5925
|
for (const nativeRel of entries) {
|
|
6175
5926
|
if (nativeRel.includes("node_modules")) continue;
|
|
6176
5927
|
if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(nativeRel)) continue;
|
|
6177
|
-
const raw = await
|
|
5928
|
+
const raw = await fs16.readFile(path15.join(srcDir, nativeRel), "utf8").catch(() => "");
|
|
6178
5929
|
if (!raw) continue;
|
|
6179
|
-
const rel = nativeRel.split(
|
|
5930
|
+
const rel = nativeRel.split(path15.sep).join("/");
|
|
6180
5931
|
const content = raw.replace(/\/\*[\s\S]*?\*\//g, (m2) => m2.replace(/[^\n]/g, " ")).replace(/(^|[^:])\/\/[^\n]*/gm, (m2, p1) => p1 + " ".repeat(m2.length - p1.length));
|
|
6181
5932
|
const repeatRe = /\.repeat\.set\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\)/g;
|
|
6182
5933
|
let m;
|
|
@@ -6257,25 +6008,25 @@ async function detectGenerationAudit(cwd = process.cwd()) {
|
|
|
6257
6008
|
let haystack = "";
|
|
6258
6009
|
const read = async (file) => {
|
|
6259
6010
|
try {
|
|
6260
|
-
haystack += await
|
|
6011
|
+
haystack += await fs16.readFile(file, "utf8");
|
|
6261
6012
|
} catch {
|
|
6262
6013
|
}
|
|
6263
6014
|
};
|
|
6264
6015
|
try {
|
|
6265
|
-
for (const entry of await
|
|
6016
|
+
for (const entry of await fs16.readdir(cwd, { withFileTypes: true })) {
|
|
6266
6017
|
if (entry.isFile() && /\.(ts|tsx|js|jsx|css|html|json)$/.test(entry.name)) {
|
|
6267
|
-
await read(
|
|
6018
|
+
await read(path15.join(cwd, entry.name));
|
|
6268
6019
|
}
|
|
6269
6020
|
}
|
|
6270
6021
|
} catch {
|
|
6271
6022
|
}
|
|
6272
6023
|
for (const sub of ["src", "public"]) {
|
|
6273
6024
|
try {
|
|
6274
|
-
const entries = await
|
|
6025
|
+
const entries = await fs16.readdir(path15.join(cwd, sub), { recursive: true });
|
|
6275
6026
|
for (const rel of entries) {
|
|
6276
6027
|
if (rel.includes("node_modules")) continue;
|
|
6277
6028
|
if (!/\.(ts|tsx|js|jsx|css|html|json|txt)$/.test(rel)) continue;
|
|
6278
|
-
await read(
|
|
6029
|
+
await read(path15.join(cwd, sub, rel));
|
|
6279
6030
|
}
|
|
6280
6031
|
} catch {
|
|
6281
6032
|
}
|
|
@@ -6422,7 +6173,7 @@ async function borrowEvidence(meta, cwd) {
|
|
|
6422
6173
|
} catch {
|
|
6423
6174
|
return true;
|
|
6424
6175
|
}
|
|
6425
|
-
const gitConfig = await
|
|
6176
|
+
const gitConfig = await fs16.readFile(path15.join(cwd, ".git", "config"), "utf8").catch(() => "");
|
|
6426
6177
|
for (const m of gitConfig.matchAll(/url\s*=\s*(\S+)/g)) {
|
|
6427
6178
|
try {
|
|
6428
6179
|
const u = new URL(m[1]);
|
|
@@ -6430,7 +6181,7 @@ async function borrowEvidence(meta, cwd) {
|
|
|
6430
6181
|
} catch {
|
|
6431
6182
|
}
|
|
6432
6183
|
}
|
|
6433
|
-
const readme = await
|
|
6184
|
+
const readme = await fs16.readFile(path15.join(cwd, "README.md"), "utf8").catch(() => "");
|
|
6434
6185
|
return readme.includes(host) || /\b(upstream|originally by|ported from|borrowed from)\b/i.test(readme);
|
|
6435
6186
|
}
|
|
6436
6187
|
|
|
@@ -6910,8 +6661,8 @@ async function runRollback(opts) {
|
|
|
6910
6661
|
}
|
|
6911
6662
|
|
|
6912
6663
|
// src/commands/generate.ts
|
|
6913
|
-
import
|
|
6914
|
-
import
|
|
6664
|
+
import fs18 from "fs/promises";
|
|
6665
|
+
import path17 from "path";
|
|
6915
6666
|
import { PNG as PNG4 } from "pngjs";
|
|
6916
6667
|
|
|
6917
6668
|
// src/lib/glass.ts
|
|
@@ -6992,8 +6743,8 @@ function solveMagentaGlass(source) {
|
|
|
6992
6743
|
}
|
|
6993
6744
|
|
|
6994
6745
|
// src/lib/download-assets.ts
|
|
6995
|
-
import
|
|
6996
|
-
import
|
|
6746
|
+
import fs17 from "fs/promises";
|
|
6747
|
+
import path16 from "path";
|
|
6997
6748
|
var RUNG_ROLE = /@\d+$/;
|
|
6998
6749
|
function isPrimaryRole(role) {
|
|
6999
6750
|
return !RUNG_ROLE.test(role);
|
|
@@ -7013,7 +6764,7 @@ async function downloadAssets(files, opts) {
|
|
|
7013
6764
|
const result = { saved: [], failures: [] };
|
|
7014
6765
|
if (wanted.length === 0) return result;
|
|
7015
6766
|
try {
|
|
7016
|
-
await
|
|
6767
|
+
await fs17.mkdir(opts.outDir, { recursive: true });
|
|
7017
6768
|
} catch (err) {
|
|
7018
6769
|
result.failures.push(
|
|
7019
6770
|
`couldn't create ${opts.outDir} (${err instanceof Error ? err.message : String(err)})`
|
|
@@ -7022,12 +6773,12 @@ async function downloadAssets(files, opts) {
|
|
|
7022
6773
|
}
|
|
7023
6774
|
const multi = wanted.length > 1;
|
|
7024
6775
|
for (const file of wanted) {
|
|
7025
|
-
const dest =
|
|
6776
|
+
const dest = path16.join(opts.outDir, localAssetName(opts.kind, opts.prompt, opts.id, file, multi));
|
|
7026
6777
|
try {
|
|
7027
6778
|
const res = await fetch(file.url);
|
|
7028
6779
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
7029
6780
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
7030
|
-
await
|
|
6781
|
+
await fs17.writeFile(dest, buf);
|
|
7031
6782
|
result.saved.push({ role: file.role, path: dest, url: file.url, bytes: buf.byteLength });
|
|
7032
6783
|
} catch (err) {
|
|
7033
6784
|
result.failures.push(
|
|
@@ -7082,7 +6833,7 @@ function mockLaneRefusal(kind, provider) {
|
|
|
7082
6833
|
}
|
|
7083
6834
|
|
|
7084
6835
|
// src/lib/open.ts
|
|
7085
|
-
import { spawn as
|
|
6836
|
+
import { spawn as spawn4 } from "child_process";
|
|
7086
6837
|
function tokenize(cmd) {
|
|
7087
6838
|
return cmd.trim().split(/\s+/).filter(Boolean);
|
|
7088
6839
|
}
|
|
@@ -7111,7 +6862,7 @@ function openUrl(url) {
|
|
|
7111
6862
|
}
|
|
7112
6863
|
}
|
|
7113
6864
|
try {
|
|
7114
|
-
const child =
|
|
6865
|
+
const child = spawn4(command, args, { stdio: "ignore", detached: true });
|
|
7115
6866
|
child.on("error", () => {
|
|
7116
6867
|
});
|
|
7117
6868
|
child.unref();
|
|
@@ -7171,7 +6922,7 @@ var isRemoteRef = (value) => /^(https?:\/\/|data:)/i.test(value);
|
|
|
7171
6922
|
async function inlineLocalImage(filePath, flag) {
|
|
7172
6923
|
let bytes;
|
|
7173
6924
|
try {
|
|
7174
|
-
bytes = await
|
|
6925
|
+
bytes = await fs18.readFile(filePath);
|
|
7175
6926
|
} catch {
|
|
7176
6927
|
return { ok: false, error: `Couldn't read the ${flag} file at ${filePath}.` };
|
|
7177
6928
|
}
|
|
@@ -7181,7 +6932,7 @@ async function inlineLocalImage(filePath, flag) {
|
|
|
7181
6932
|
error: `${flag} file is ${(bytes.length / 1048576).toFixed(1)} MB \u2014 over the ~4 MB inline limit. Downscale/compress it first, or pass an asset URL instead.`
|
|
7182
6933
|
};
|
|
7183
6934
|
}
|
|
7184
|
-
const mime = IMAGE_MIME_BY_EXT[
|
|
6935
|
+
const mime = IMAGE_MIME_BY_EXT[path17.extname(filePath).toLowerCase()] ?? "image/png";
|
|
7185
6936
|
return { ok: true, dataUri: `data:${mime};base64,${bytes.toString("base64")}` };
|
|
7186
6937
|
}
|
|
7187
6938
|
var SKYBOX_ENVIRONMENT_SUFFIX = ". The image contains ONLY sky: cloud, atmosphere, light, weather and distant haze at the horizon. Every structure, object, plant and ground surface is outside the frame.";
|
|
@@ -7339,7 +7090,7 @@ async function runGenerate(kind, opts) {
|
|
|
7339
7090
|
let typedPrompt = opts.prompt?.trim();
|
|
7340
7091
|
if (!typedPrompt && kind === "model" && opts.imageUrl) {
|
|
7341
7092
|
const ref = opts.imageUrl.startsWith("data:") ? "local image" : opts.imageUrl;
|
|
7342
|
-
typedPrompt = `from image: ${
|
|
7093
|
+
typedPrompt = `from image: ${path17.basename(ref).slice(0, 120)}`;
|
|
7343
7094
|
}
|
|
7344
7095
|
if (!typedPrompt) {
|
|
7345
7096
|
log.error(`Missing prompt. Usage: ${c.cyan(`genex ${kind} "<prompt>"`)}`);
|
|
@@ -7383,7 +7134,7 @@ async function runGenerate(kind, opts) {
|
|
|
7383
7134
|
return;
|
|
7384
7135
|
}
|
|
7385
7136
|
try {
|
|
7386
|
-
const bytes = await
|
|
7137
|
+
const bytes = await fs18.readFile(opts.inpaintUrl);
|
|
7387
7138
|
opts = { ...opts, inpaintUrl: `data:image/png;base64,${bytes.toString("base64")}` };
|
|
7388
7139
|
} catch {
|
|
7389
7140
|
log.error(`Couldn't read the --inpaint mask at ${opts.inpaintUrl}.`);
|
|
@@ -7568,7 +7319,7 @@ async function reportGlassTerminal(view, outDir, log, json) {
|
|
|
7568
7319
|
return;
|
|
7569
7320
|
}
|
|
7570
7321
|
await recordTerminal(view.id, "completed", files.map((f) => f.url));
|
|
7571
|
-
await
|
|
7322
|
+
await fs18.mkdir(outDir, { recursive: true });
|
|
7572
7323
|
const solved = [];
|
|
7573
7324
|
for (let i = 0; i < files.length; i++) {
|
|
7574
7325
|
const f = files[i];
|
|
@@ -7600,8 +7351,8 @@ async function reportGlassTerminal(view, outDir, log, json) {
|
|
|
7600
7351
|
});
|
|
7601
7352
|
continue;
|
|
7602
7353
|
}
|
|
7603
|
-
const outPath =
|
|
7604
|
-
await
|
|
7354
|
+
const outPath = path17.join(outDir, `glass-${i + 1}.png`);
|
|
7355
|
+
await fs18.writeFile(outPath, PNG4.sync.write(r.png));
|
|
7605
7356
|
solved.push({
|
|
7606
7357
|
path: outPath,
|
|
7607
7358
|
url: f.url,
|
|
@@ -8367,8 +8118,8 @@ async function toRow(e, v, cwd) {
|
|
|
8367
8118
|
}
|
|
8368
8119
|
|
|
8369
8120
|
// src/commands/controller.ts
|
|
8370
|
-
import
|
|
8371
|
-
import
|
|
8121
|
+
import fs20 from "fs/promises";
|
|
8122
|
+
import path19 from "path";
|
|
8372
8123
|
|
|
8373
8124
|
// ../../packages/meshy-animation-catalog/src/index.ts
|
|
8374
8125
|
import { createHash } from "crypto";
|
|
@@ -17498,9 +17249,9 @@ function searchMeshyAnimations(query, options = {}) {
|
|
|
17498
17249
|
}
|
|
17499
17250
|
|
|
17500
17251
|
// src/lib/anims.ts
|
|
17501
|
-
import
|
|
17502
|
-
import
|
|
17503
|
-
var ANIMS_DEST =
|
|
17252
|
+
import fs19 from "fs/promises";
|
|
17253
|
+
import path18 from "path";
|
|
17254
|
+
var ANIMS_DEST = path18.join("public", "assets", "anims");
|
|
17504
17255
|
var HIDDEN_TAG = "reference";
|
|
17505
17256
|
async function runAnims(opts) {
|
|
17506
17257
|
const log = createLogger({ quiet: opts.quiet });
|
|
@@ -17516,7 +17267,7 @@ async function runAnims(opts) {
|
|
|
17516
17267
|
printCatalog(log, manifest, selectors);
|
|
17517
17268
|
return;
|
|
17518
17269
|
}
|
|
17519
|
-
const controllerMarker =
|
|
17270
|
+
const controllerMarker = path18.join(root, "src", "controllers", "character");
|
|
17520
17271
|
if (!await exists2(controllerMarker)) {
|
|
17521
17272
|
log.error(
|
|
17522
17273
|
`No character controller in this game (missing ${c.cyan("src/controllers/character/")}).`
|
|
@@ -17525,11 +17276,11 @@ async function runAnims(opts) {
|
|
|
17525
17276
|
process.exitCode = 1;
|
|
17526
17277
|
return;
|
|
17527
17278
|
}
|
|
17528
|
-
const destDir =
|
|
17529
|
-
const gameManifestPath =
|
|
17279
|
+
const destDir = path18.join(root, ANIMS_DEST);
|
|
17280
|
+
const gameManifestPath = path18.join(destDir, "manifest.json");
|
|
17530
17281
|
if (opts.reset) {
|
|
17531
|
-
await
|
|
17532
|
-
log.step(`Cleared ${c.cyan(ANIMS_DEST +
|
|
17282
|
+
await fs19.rm(destDir, { recursive: true, force: true });
|
|
17283
|
+
log.step(`Cleared ${c.cyan(ANIMS_DEST + path18.sep)} (--reset)`);
|
|
17533
17284
|
}
|
|
17534
17285
|
if (selectors.length === 0) {
|
|
17535
17286
|
const installed = await readGameManifest(gameManifestPath);
|
|
@@ -17567,35 +17318,35 @@ async function runAnims(opts) {
|
|
|
17567
17318
|
}
|
|
17568
17319
|
}
|
|
17569
17320
|
const wanted = [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
17570
|
-
const cacheDir =
|
|
17321
|
+
const cacheDir = path18.join(
|
|
17571
17322
|
opts.cacheDir ?? getAnimsCacheDir(),
|
|
17572
17323
|
`${manifest.library}-v${manifest.version}`
|
|
17573
17324
|
);
|
|
17574
|
-
await
|
|
17575
|
-
await
|
|
17325
|
+
await fs19.mkdir(cacheDir, { recursive: true });
|
|
17326
|
+
await fs19.mkdir(destDir, { recursive: true });
|
|
17576
17327
|
const base = getAnimsBase(opts.animsBase);
|
|
17577
17328
|
let installedCount = 0;
|
|
17578
17329
|
let presentCount = 0;
|
|
17579
17330
|
let addedBytes = 0;
|
|
17580
17331
|
const failures = [];
|
|
17581
17332
|
for (const entry of wanted) {
|
|
17582
|
-
const dest =
|
|
17333
|
+
const dest = path18.join(destDir, entry.file);
|
|
17583
17334
|
if (await hasSize(dest, entry.bytes)) {
|
|
17584
17335
|
presentCount++;
|
|
17585
17336
|
continue;
|
|
17586
17337
|
}
|
|
17587
17338
|
try {
|
|
17588
|
-
const cached =
|
|
17339
|
+
const cached = path18.join(cacheDir, entry.file);
|
|
17589
17340
|
if (!await hasSize(cached, entry.bytes)) {
|
|
17590
17341
|
const res = await fetch(base + entry.file);
|
|
17591
17342
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
17592
17343
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
17593
|
-
await
|
|
17344
|
+
await fs19.writeFile(cached, buf);
|
|
17594
17345
|
}
|
|
17595
|
-
await
|
|
17346
|
+
await fs19.copyFile(cached, dest);
|
|
17596
17347
|
installedCount++;
|
|
17597
17348
|
addedBytes += entry.bytes;
|
|
17598
|
-
log.dim(` ${
|
|
17349
|
+
log.dim(` ${path18.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
|
|
17599
17350
|
} catch (err) {
|
|
17600
17351
|
failures.push(`${entry.name} (${err instanceof Error ? err.message : String(err)})`);
|
|
17601
17352
|
}
|
|
@@ -17611,13 +17362,13 @@ async function runAnims(opts) {
|
|
|
17611
17362
|
version: manifest.version,
|
|
17612
17363
|
clips: [...union].sort((a, b) => a.localeCompare(b))
|
|
17613
17364
|
};
|
|
17614
|
-
await
|
|
17365
|
+
await fs19.writeFile(gameManifestPath, JSON.stringify(gameManifest, null, 2) + "\n");
|
|
17615
17366
|
log.plain("");
|
|
17616
17367
|
const parts = [`${installedCount} clip${installedCount === 1 ? "" : "s"} installed`];
|
|
17617
17368
|
if (presentCount > 0) parts.push(`${presentCount} already present`);
|
|
17618
17369
|
if (bundledSkips > 0) parts.push(`${bundledSkips} already bundled in animation-library.glb`);
|
|
17619
17370
|
log.success(
|
|
17620
|
-
`${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST +
|
|
17371
|
+
`${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path18.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
|
|
17621
17372
|
);
|
|
17622
17373
|
for (const [selector, entries] of resolved) {
|
|
17623
17374
|
const names = entries.filter((e) => !coreNames.has(e.name)).map((e) => e.name);
|
|
@@ -17649,8 +17400,8 @@ async function loadManifest(baseOverride) {
|
|
|
17649
17400
|
}
|
|
17650
17401
|
} catch {
|
|
17651
17402
|
}
|
|
17652
|
-
const snapshotPath =
|
|
17653
|
-
const manifest = JSON.parse(await
|
|
17403
|
+
const snapshotPath = path18.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
|
|
17404
|
+
const manifest = JSON.parse(await fs19.readFile(snapshotPath, "utf8"));
|
|
17654
17405
|
return { manifest, source: "snapshot" };
|
|
17655
17406
|
}
|
|
17656
17407
|
function resolveSelectors(manifest, selectors) {
|
|
@@ -17768,21 +17519,21 @@ function printCatalog(log, manifest, selectors) {
|
|
|
17768
17519
|
}
|
|
17769
17520
|
async function readGameManifest(file) {
|
|
17770
17521
|
try {
|
|
17771
|
-
return JSON.parse(await
|
|
17522
|
+
return JSON.parse(await fs19.readFile(file, "utf8"));
|
|
17772
17523
|
} catch {
|
|
17773
17524
|
return null;
|
|
17774
17525
|
}
|
|
17775
17526
|
}
|
|
17776
17527
|
async function hasSize(file, bytes) {
|
|
17777
17528
|
try {
|
|
17778
|
-
return (await
|
|
17529
|
+
return (await fs19.stat(file)).size === bytes;
|
|
17779
17530
|
} catch {
|
|
17780
17531
|
return false;
|
|
17781
17532
|
}
|
|
17782
17533
|
}
|
|
17783
17534
|
async function exists2(p) {
|
|
17784
17535
|
try {
|
|
17785
|
-
await
|
|
17536
|
+
await fs19.access(p);
|
|
17786
17537
|
return true;
|
|
17787
17538
|
} catch {
|
|
17788
17539
|
return false;
|
|
@@ -17976,8 +17727,8 @@ var CONTROLLER_FILE_SETS = {
|
|
|
17976
17727
|
]
|
|
17977
17728
|
}
|
|
17978
17729
|
};
|
|
17979
|
-
var CODE_DEST =
|
|
17980
|
-
var ASSETS_DEST =
|
|
17730
|
+
var CODE_DEST = path19.join("src", "controllers");
|
|
17731
|
+
var ASSETS_DEST = path19.join("public", "assets");
|
|
17981
17732
|
async function runController(opts) {
|
|
17982
17733
|
const log = createLogger({ quiet: opts.quiet });
|
|
17983
17734
|
if (opts.kind?.trim() === "anims") {
|
|
@@ -17994,31 +17745,31 @@ async function runController(opts) {
|
|
|
17994
17745
|
process.exitCode = 1;
|
|
17995
17746
|
return;
|
|
17996
17747
|
}
|
|
17997
|
-
const srcDir =
|
|
17748
|
+
const srcDir = path19.join(getTemplatesDir(), "controllers");
|
|
17998
17749
|
const root = opts.cwd ?? process.cwd();
|
|
17999
17750
|
const set = CONTROLLER_FILE_SETS[kind];
|
|
18000
17751
|
log.plain(c.bold(`genex controller ${kind}`));
|
|
18001
17752
|
log.plain("");
|
|
18002
|
-
log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST +
|
|
17753
|
+
log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path19.sep)}`);
|
|
18003
17754
|
const plan = [
|
|
18004
|
-
...set.code.map((rel) => ({ from: rel, rel:
|
|
17755
|
+
...set.code.map((rel) => ({ from: rel, rel: path19.join(CODE_DEST, rel) })),
|
|
18005
17756
|
...set.assets.map((rel) => ({
|
|
18006
17757
|
from: rel,
|
|
18007
|
-
rel:
|
|
17758
|
+
rel: path19.join(ASSETS_DEST, path19.basename(rel))
|
|
18008
17759
|
}))
|
|
18009
17760
|
];
|
|
18010
17761
|
let copied = 0;
|
|
18011
17762
|
let skipped = 0;
|
|
18012
17763
|
try {
|
|
18013
17764
|
for (const file of plan) {
|
|
18014
|
-
const dest =
|
|
17765
|
+
const dest = path19.join(root, file.rel);
|
|
18015
17766
|
if (!opts.force && await exists3(dest)) {
|
|
18016
17767
|
skipped++;
|
|
18017
17768
|
log.dim(` skipped ${file.rel} (exists \u2014 use --force to overwrite)`);
|
|
18018
17769
|
continue;
|
|
18019
17770
|
}
|
|
18020
|
-
await
|
|
18021
|
-
await
|
|
17771
|
+
await fs20.mkdir(path19.dirname(dest), { recursive: true });
|
|
17772
|
+
await fs20.copyFile(path19.join(srcDir, file.from), dest);
|
|
18022
17773
|
copied++;
|
|
18023
17774
|
log.dim(` ${file.rel}`);
|
|
18024
17775
|
}
|
|
@@ -18071,7 +17822,7 @@ async function runController(opts) {
|
|
|
18071
17822
|
for (const line of set.sketch) {
|
|
18072
17823
|
log.dim(` ${line}`);
|
|
18073
17824
|
}
|
|
18074
|
-
if (kind === "character" && !await exists3(
|
|
17825
|
+
if (kind === "character" && !await exists3(path19.join(root, ASSETS_DEST, "meshy-character.json"))) {
|
|
18075
17826
|
log.plain("");
|
|
18076
17827
|
log.plain(
|
|
18077
17828
|
` ${stepOffset + 3}. This game has no generated character yet \u2014 ${c.cyan(
|
|
@@ -18103,9 +17854,9 @@ async function installMeshyCharacterManifest(args) {
|
|
|
18103
17854
|
throw new Error("The API returned an invalid Meshy character manifest.");
|
|
18104
17855
|
}
|
|
18105
17856
|
assertCompleteMeshyControllerPack(manifest);
|
|
18106
|
-
const destination =
|
|
18107
|
-
await
|
|
18108
|
-
await
|
|
17857
|
+
const destination = path19.join(args.root, ASSETS_DEST, "meshy-character.json");
|
|
17858
|
+
await fs20.mkdir(path19.dirname(destination), { recursive: true });
|
|
17859
|
+
await fs20.writeFile(
|
|
18109
17860
|
destination,
|
|
18110
17861
|
`${JSON.stringify(manifest, null, 2)}
|
|
18111
17862
|
`
|
|
@@ -18243,14 +17994,14 @@ function assertCompleteMeshyControllerPack(manifest) {
|
|
|
18243
17994
|
}
|
|
18244
17995
|
async function installFallbackAvatar(args) {
|
|
18245
17996
|
const { root, srcDir, log } = args;
|
|
18246
|
-
const dest =
|
|
18247
|
-
await
|
|
18248
|
-
await
|
|
17997
|
+
const dest = path19.join(root, ASSETS_DEST, "avatar.vrm");
|
|
17998
|
+
await fs20.mkdir(path19.dirname(dest), { recursive: true });
|
|
17999
|
+
await fs20.copyFile(path19.join(srcDir, "assets", "default-avatar.vrm"), dest);
|
|
18249
18000
|
log.dim(" public/assets/avatar.vrm (fallback avatar \u2014 bundled CC0 default)");
|
|
18250
18001
|
}
|
|
18251
18002
|
async function exists3(p) {
|
|
18252
18003
|
try {
|
|
18253
|
-
await
|
|
18004
|
+
await fs20.access(p);
|
|
18254
18005
|
return true;
|
|
18255
18006
|
} catch {
|
|
18256
18007
|
return false;
|
|
@@ -18258,8 +18009,8 @@ async function exists3(p) {
|
|
|
18258
18009
|
}
|
|
18259
18010
|
|
|
18260
18011
|
// src/commands/character.ts
|
|
18261
|
-
import
|
|
18262
|
-
import
|
|
18012
|
+
import fs21 from "fs/promises";
|
|
18013
|
+
import path20 from "path";
|
|
18263
18014
|
function exactAnimation(selector) {
|
|
18264
18015
|
const trimmed = selector.trim();
|
|
18265
18016
|
if (/^-?\d+$/.test(trimmed)) return animationById(Number(trimmed));
|
|
@@ -18339,7 +18090,7 @@ function showAmbiguity(selector, candidates, log, json = false) {
|
|
|
18339
18090
|
}
|
|
18340
18091
|
process.exitCode = 1;
|
|
18341
18092
|
}
|
|
18342
|
-
var INSTALLED_MANIFEST =
|
|
18093
|
+
var INSTALLED_MANIFEST = path20.join("public", "assets", "meshy-character.json");
|
|
18343
18094
|
async function resolveAdoptTarget(selector) {
|
|
18344
18095
|
const trimmed = selector?.trim();
|
|
18345
18096
|
if (trimmed && !trimmed.endsWith(".json")) {
|
|
@@ -18348,7 +18099,7 @@ async function resolveAdoptTarget(selector) {
|
|
|
18348
18099
|
const file = trimmed ?? INSTALLED_MANIFEST;
|
|
18349
18100
|
let raw;
|
|
18350
18101
|
try {
|
|
18351
|
-
raw = await
|
|
18102
|
+
raw = await fs21.readFile(file, "utf8");
|
|
18352
18103
|
} catch {
|
|
18353
18104
|
return {
|
|
18354
18105
|
ok: false,
|
|
@@ -18831,22 +18582,22 @@ async function context2(opts) {
|
|
|
18831
18582
|
const project = await readProject();
|
|
18832
18583
|
return { token, apiUrl: getApiUrl(opts.apiUrl ?? project?.apiUrl) };
|
|
18833
18584
|
}
|
|
18834
|
-
async function readVideo(
|
|
18585
|
+
async function readVideo(path27, log) {
|
|
18835
18586
|
let bytes;
|
|
18836
18587
|
try {
|
|
18837
|
-
bytes = await readFile(
|
|
18588
|
+
bytes = await readFile(path27);
|
|
18838
18589
|
} catch {
|
|
18839
|
-
log.error(`Can't read ${
|
|
18590
|
+
log.error(`Can't read ${path27}.`);
|
|
18840
18591
|
return null;
|
|
18841
18592
|
}
|
|
18842
18593
|
if (bytes.byteLength > MAX_VIDEO_BYTES) {
|
|
18843
|
-
log.error(`${basename(
|
|
18594
|
+
log.error(`${basename(path27)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
|
|
18844
18595
|
return null;
|
|
18845
18596
|
}
|
|
18846
18597
|
return bytes;
|
|
18847
18598
|
}
|
|
18848
|
-
async function uploadVideo(apiUrl, token, characterId,
|
|
18849
|
-
const contentType = /\.mov$/i.test(
|
|
18599
|
+
async function uploadVideo(apiUrl, token, characterId, path27, bytes, log) {
|
|
18600
|
+
const contentType = /\.mov$/i.test(path27) ? "video/quicktime" : "video/mp4";
|
|
18850
18601
|
const minted = await apiFetch(
|
|
18851
18602
|
`${apiUrl}/api/characters/${encodeURIComponent(characterId)}/motions/video-url`,
|
|
18852
18603
|
{
|
|
@@ -18861,7 +18612,7 @@ async function uploadVideo(apiUrl, token, characterId, path29, bytes, log) {
|
|
|
18861
18612
|
return null;
|
|
18862
18613
|
}
|
|
18863
18614
|
const { uploadUrl, videoUrl } = await minted.json();
|
|
18864
|
-
log.dim(` uploading ${basename(
|
|
18615
|
+
log.dim(` uploading ${basename(path27)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
|
|
18865
18616
|
const put = await fetch(uploadUrl, {
|
|
18866
18617
|
method: "PUT",
|
|
18867
18618
|
headers: { "Content-Type": contentType, "Content-Length": String(bytes.byteLength) },
|
|
@@ -19177,8 +18928,8 @@ function rank(items, query) {
|
|
|
19177
18928
|
}
|
|
19178
18929
|
|
|
19179
18930
|
// src/commands/motion.ts
|
|
19180
|
-
import
|
|
19181
|
-
import
|
|
18931
|
+
import fs22 from "fs/promises";
|
|
18932
|
+
import path21 from "path";
|
|
19182
18933
|
|
|
19183
18934
|
// src/lib/motion/npz.ts
|
|
19184
18935
|
import zlib from "zlib";
|
|
@@ -20429,7 +20180,7 @@ async function motionGen(opts, log) {
|
|
|
20429
20180
|
}
|
|
20430
20181
|
if (opts.constraintsPath !== void 0) {
|
|
20431
20182
|
try {
|
|
20432
|
-
const raw = await
|
|
20183
|
+
const raw = await fs22.readFile(opts.constraintsPath, "utf8");
|
|
20433
20184
|
generationOptions.constraints = JSON.parse(raw);
|
|
20434
20185
|
} catch {
|
|
20435
20186
|
log.error(`Couldn't read the --constraints JSON at ${opts.constraintsPath}.`);
|
|
@@ -20453,10 +20204,10 @@ async function motionGen(opts, log) {
|
|
|
20453
20204
|
async function expandTakes(selectors) {
|
|
20454
20205
|
const out = [];
|
|
20455
20206
|
for (const sel of selectors) {
|
|
20456
|
-
const st = await
|
|
20207
|
+
const st = await fs22.stat(sel).catch(() => null);
|
|
20457
20208
|
if (st?.isDirectory()) {
|
|
20458
|
-
const names = await
|
|
20459
|
-
for (const n of names.sort()) if (n.endsWith(".npz")) out.push(
|
|
20209
|
+
const names = await fs22.readdir(sel);
|
|
20210
|
+
for (const n of names.sort()) if (n.endsWith(".npz")) out.push(path21.join(sel, n));
|
|
20460
20211
|
} else if (st?.isFile()) {
|
|
20461
20212
|
out.push(sel);
|
|
20462
20213
|
} else {
|
|
@@ -20491,7 +20242,7 @@ async function motionVerify(opts, log) {
|
|
|
20491
20242
|
let gates = DEFAULT_GATES;
|
|
20492
20243
|
if (opts.gatesPath) {
|
|
20493
20244
|
try {
|
|
20494
|
-
gates = mergeGates(DEFAULT_GATES, JSON.parse(await
|
|
20245
|
+
gates = mergeGates(DEFAULT_GATES, JSON.parse(await fs22.readFile(opts.gatesPath, "utf8")));
|
|
20495
20246
|
} catch {
|
|
20496
20247
|
log.error(`Couldn't read the --gates JSON at ${opts.gatesPath}.`);
|
|
20497
20248
|
process.exitCode = 1;
|
|
@@ -20513,9 +20264,9 @@ async function motionVerify(opts, log) {
|
|
|
20513
20264
|
}
|
|
20514
20265
|
const reports = [];
|
|
20515
20266
|
for (const file of files) {
|
|
20516
|
-
const stem =
|
|
20267
|
+
const stem = path21.basename(file).replace(/\.npz$/, "");
|
|
20517
20268
|
try {
|
|
20518
|
-
reports.push(analyzeTake(stem, await
|
|
20269
|
+
reports.push(analyzeTake(stem, await fs22.readFile(file), gates));
|
|
20519
20270
|
} catch (err) {
|
|
20520
20271
|
reports.push({
|
|
20521
20272
|
take: stem,
|
|
@@ -20553,7 +20304,7 @@ async function motionCompile(opts, log) {
|
|
|
20553
20304
|
let cfg = DEFAULT_MOTION_CONFIG;
|
|
20554
20305
|
if (opts.configPath) {
|
|
20555
20306
|
try {
|
|
20556
|
-
const patch = JSON.parse(await
|
|
20307
|
+
const patch = JSON.parse(await fs22.readFile(opts.configPath, "utf8"));
|
|
20557
20308
|
cfg = { ...DEFAULT_MOTION_CONFIG, ...patch, idleLoop: { ...DEFAULT_MOTION_CONFIG.idleLoop, ...patch.idleLoop } };
|
|
20558
20309
|
} catch {
|
|
20559
20310
|
log.error(`Couldn't read the --config JSON at ${opts.configPath}.`);
|
|
@@ -20571,16 +20322,16 @@ async function motionCompile(opts, log) {
|
|
|
20571
20322
|
}
|
|
20572
20323
|
const inputs = [];
|
|
20573
20324
|
for (const file of files) {
|
|
20574
|
-
const stem =
|
|
20325
|
+
const stem = path21.basename(file).replace(/\.npz$/, "");
|
|
20575
20326
|
try {
|
|
20576
|
-
inputs.push({ stem, take: loadTake(await
|
|
20327
|
+
inputs.push({ stem, take: loadTake(await fs22.readFile(file)) });
|
|
20577
20328
|
} catch (err) {
|
|
20578
20329
|
log.error(`${stem}: ${err instanceof Error ? err.message : String(err)}`);
|
|
20579
20330
|
process.exitCode = 1;
|
|
20580
20331
|
return;
|
|
20581
20332
|
}
|
|
20582
20333
|
}
|
|
20583
|
-
const setName = opts.set ??
|
|
20334
|
+
const setName = opts.set ?? path21.basename(opts.out).replace(/\.json$/, "");
|
|
20584
20335
|
let result;
|
|
20585
20336
|
try {
|
|
20586
20337
|
result = compileSet(inputs, setName, cfg);
|
|
@@ -20595,9 +20346,9 @@ async function motionCompile(opts, log) {
|
|
|
20595
20346
|
process.exitCode = 1;
|
|
20596
20347
|
return;
|
|
20597
20348
|
}
|
|
20598
|
-
await
|
|
20349
|
+
await fs22.mkdir(path21.dirname(path21.resolve(opts.out)), { recursive: true });
|
|
20599
20350
|
const json = JSON.stringify(result.data);
|
|
20600
|
-
await
|
|
20351
|
+
await fs22.writeFile(opts.out, json);
|
|
20601
20352
|
if (opts.json) {
|
|
20602
20353
|
writeJson({ out: opts.out, set: setName, tracks: Object.keys(result.data.gaits), bytes: json.length });
|
|
20603
20354
|
return;
|
|
@@ -20615,9 +20366,9 @@ var MOTION_RUNTIME_FILES = [
|
|
|
20615
20366
|
var MOTION_PRESETS = {
|
|
20616
20367
|
rifle: ["sets/rifle.json", "sets/jumps.json"]
|
|
20617
20368
|
};
|
|
20618
|
-
var MOTION_DEST =
|
|
20369
|
+
var MOTION_DEST = path21.join("src", "motion");
|
|
20619
20370
|
async function motionInstall(opts, log) {
|
|
20620
|
-
const srcDir =
|
|
20371
|
+
const srcDir = path21.join(getTemplatesDir(), "motion");
|
|
20621
20372
|
const root = opts.cwd ?? process.cwd();
|
|
20622
20373
|
const preset = opts.set;
|
|
20623
20374
|
if (preset !== void 0 && !MOTION_PRESETS[preset]) {
|
|
@@ -20628,21 +20379,21 @@ async function motionInstall(opts, log) {
|
|
|
20628
20379
|
const files = [...MOTION_RUNTIME_FILES, ...preset ? MOTION_PRESETS[preset] : []];
|
|
20629
20380
|
log.plain(c.bold(`genex motion install${preset ? ` --set ${preset}` : ""}`));
|
|
20630
20381
|
log.plain("");
|
|
20631
|
-
log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST +
|
|
20382
|
+
log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST + path21.sep)}`);
|
|
20632
20383
|
let copied = 0, skipped = 0;
|
|
20633
20384
|
try {
|
|
20634
20385
|
for (const rel of files) {
|
|
20635
|
-
const dest =
|
|
20636
|
-
const exists5 = await
|
|
20386
|
+
const dest = path21.join(root, MOTION_DEST, rel);
|
|
20387
|
+
const exists5 = await fs22.access(dest).then(() => true, () => false);
|
|
20637
20388
|
if (!opts.force && exists5) {
|
|
20638
20389
|
skipped++;
|
|
20639
|
-
log.dim(` skipped ${
|
|
20390
|
+
log.dim(` skipped ${path21.join(MOTION_DEST, rel)} (exists \u2014 use --force to overwrite)`);
|
|
20640
20391
|
continue;
|
|
20641
20392
|
}
|
|
20642
|
-
await
|
|
20643
|
-
await
|
|
20393
|
+
await fs22.mkdir(path21.dirname(dest), { recursive: true });
|
|
20394
|
+
await fs22.copyFile(path21.join(srcDir, rel), dest);
|
|
20644
20395
|
copied++;
|
|
20645
|
-
log.dim(` ${
|
|
20396
|
+
log.dim(` ${path21.join(MOTION_DEST, rel)}`);
|
|
20646
20397
|
}
|
|
20647
20398
|
} catch (err) {
|
|
20648
20399
|
log.error(`Copy failed: ${String(err)}`);
|
|
@@ -20683,7 +20434,7 @@ async function motionConstraints(opts, log) {
|
|
|
20683
20434
|
}
|
|
20684
20435
|
const doc = directionConstraint(dir, speed, duration);
|
|
20685
20436
|
const out = opts.out ?? "constraints.json";
|
|
20686
|
-
await
|
|
20437
|
+
await fs22.writeFile(out, JSON.stringify(doc));
|
|
20687
20438
|
if (opts.json) {
|
|
20688
20439
|
writeJson({ out, dir: dirName, speed, duration, waypoints: doc[0].frame_indices.length });
|
|
20689
20440
|
return;
|
|
@@ -20720,14 +20471,14 @@ async function runMotion(opts) {
|
|
|
20720
20471
|
}
|
|
20721
20472
|
|
|
20722
20473
|
// src/commands/blender.ts
|
|
20723
|
-
import
|
|
20724
|
-
import
|
|
20725
|
-
var SUBS2 = ["demo", "exec", "snap", "scene", "export", "reset", "mcp"];
|
|
20474
|
+
import fs23 from "fs/promises";
|
|
20475
|
+
import path22 from "path";
|
|
20476
|
+
var SUBS2 = ["demo", "exec", "snap", "scene", "export", "reset", "mcp", "serve"];
|
|
20726
20477
|
var DEFAULT_OUT_DIR = "assets/blender";
|
|
20727
20478
|
async function writeB64(dir, name, b64) {
|
|
20728
|
-
await
|
|
20729
|
-
const p =
|
|
20730
|
-
await
|
|
20479
|
+
await fs23.mkdir(dir, { recursive: true });
|
|
20480
|
+
const p = path22.join(dir, name);
|
|
20481
|
+
await fs23.writeFile(p, Buffer.from(b64, "base64"));
|
|
20731
20482
|
return p;
|
|
20732
20483
|
}
|
|
20733
20484
|
function reportScene(log, s) {
|
|
@@ -20743,18 +20494,37 @@ async function runBlender(opts) {
|
|
|
20743
20494
|
log.error(`Usage: genex blender <${SUBS2.join("|")}>`);
|
|
20744
20495
|
return 1;
|
|
20745
20496
|
}
|
|
20497
|
+
if (sub === "serve") {
|
|
20498
|
+
const { serveLocalBlender } = await import("./blender-serve-BF4FZ55Z.js");
|
|
20499
|
+
const port = Number(process.env.GENEX_BLENDER_PORT ?? 8088);
|
|
20500
|
+
log.step(`Starting a local Blender service on port ${port}`);
|
|
20501
|
+
log.plain(
|
|
20502
|
+
` ${c.dim("Then, in another terminal:")} ${c.cyan(`export GENEX_BLENDER_URL=http://localhost:${port}`)}`
|
|
20503
|
+
);
|
|
20504
|
+
return serveLocalBlender({ port, log });
|
|
20505
|
+
}
|
|
20746
20506
|
if (sub === "mcp") {
|
|
20747
|
-
const { runBlenderMcp } = await import("./blender-mcp-
|
|
20507
|
+
const { runBlenderMcp } = await import("./blender-mcp-LLN4FPA4.js");
|
|
20748
20508
|
return runBlenderMcp();
|
|
20749
20509
|
}
|
|
20750
|
-
|
|
20510
|
+
let base = blenderEndpoint();
|
|
20511
|
+
const seat = await acquireSeat({ log });
|
|
20512
|
+
if (seat.kind === "granted") {
|
|
20513
|
+
base = seat.grant.url;
|
|
20514
|
+
} else if (seat.kind === "warming") {
|
|
20515
|
+
log.warn("The Blender seat is still warming up. Run the same command again in a minute.");
|
|
20516
|
+
return 0;
|
|
20517
|
+
} else if (seat.kind === "refused") {
|
|
20518
|
+
log.error(`No Blender seat: ${seat.reason}`);
|
|
20519
|
+
return 1;
|
|
20520
|
+
}
|
|
20751
20521
|
if (!base) {
|
|
20752
20522
|
const [first, ...rest] = BLENDER_SETUP_HINT.split("\n");
|
|
20753
20523
|
log.error(first ?? "GENEX_BLENDER_URL is not set");
|
|
20754
20524
|
log.plain(rest.join("\n"));
|
|
20755
20525
|
return 1;
|
|
20756
20526
|
}
|
|
20757
|
-
const outDir =
|
|
20527
|
+
const outDir = path22.resolve(opts.outDir ?? DEFAULT_OUT_DIR);
|
|
20758
20528
|
const mode = opts.mode;
|
|
20759
20529
|
if (mode !== void 0 && !isRenderMode(mode)) {
|
|
20760
20530
|
log.error(`Unknown --mode ${mode}. Use one of: ${RENDER_MODES.join(", ")}.`);
|
|
@@ -20781,18 +20551,23 @@ async function runBlender(opts) {
|
|
|
20781
20551
|
return 0;
|
|
20782
20552
|
}
|
|
20783
20553
|
case "snap": {
|
|
20784
|
-
const r = await blenderCall(base, "/render", mode ? { mode } : {});
|
|
20554
|
+
const r = await blenderCall(base, "/render", { ...mode ? { mode } : {}, sheet: { formats: SHEET_FORMATS } });
|
|
20785
20555
|
const used = String(r.mode ?? mode ?? "default");
|
|
20786
|
-
const
|
|
20787
|
-
|
|
20556
|
+
const sheet = sheetOf(r);
|
|
20557
|
+
if (!sheet) {
|
|
20558
|
+
log.error(`no sheet returned${r.contactSheetTooLarge ? ` \u2014 ${r.contactSheetTooLarge.detail}` : ""}`);
|
|
20559
|
+
return 1;
|
|
20560
|
+
}
|
|
20561
|
+
const p = await writeB64(outDir, `snap-${used}.${sheetExt(sheet.mime)}`, sheet.b64);
|
|
20562
|
+
log.success(`${used} sheet in ${r.ms ?? "?"}ms ${c.dim(`(${sheet.mime})`)}`);
|
|
20788
20563
|
log.plain(` ${c.cyan(p)}`);
|
|
20789
20564
|
return 0;
|
|
20790
20565
|
}
|
|
20791
20566
|
case "export": {
|
|
20792
|
-
const target = opts.out ??
|
|
20567
|
+
const target = opts.out ?? path22.join(outDir, "scene.glb");
|
|
20793
20568
|
const r = await blenderCall(base, "/export", { path: "/tmp/genex-export.glb" });
|
|
20794
|
-
await
|
|
20795
|
-
await
|
|
20569
|
+
await fs23.mkdir(path22.dirname(target), { recursive: true });
|
|
20570
|
+
await fs23.writeFile(target, Buffer.from(r.glbBase64 ?? "", "base64"));
|
|
20796
20571
|
log.success(`Exported ${r.bytes ?? 0} bytes`);
|
|
20797
20572
|
log.plain(` ${c.cyan(target)}`);
|
|
20798
20573
|
return 0;
|
|
@@ -20810,21 +20585,22 @@ async function runBlender(opts) {
|
|
|
20810
20585
|
return 1;
|
|
20811
20586
|
}
|
|
20812
20587
|
try {
|
|
20813
|
-
script = await
|
|
20588
|
+
script = await fs23.readFile(opts.input, "utf8");
|
|
20814
20589
|
} catch {
|
|
20815
20590
|
log.error(`Can't read ${opts.input}`);
|
|
20816
20591
|
return 1;
|
|
20817
20592
|
}
|
|
20818
|
-
label =
|
|
20593
|
+
label = path22.basename(opts.input);
|
|
20819
20594
|
}
|
|
20820
|
-
const r = await blenderCall(base, "/exec", mode ? {
|
|
20595
|
+
const r = await blenderCall(base, "/exec", { script, ...mode ? { mode } : {}, sheet: { formats: SHEET_FORMATS } });
|
|
20821
20596
|
if (r.stdout?.trim()) log.plain(r.stdout.trimEnd());
|
|
20822
20597
|
if (r.stderr?.trim()) log.dim(r.stderr.trimEnd());
|
|
20823
20598
|
if (r.error) {
|
|
20824
20599
|
log.error(`${label} raised:`);
|
|
20825
20600
|
log.plain(r.error.trimEnd());
|
|
20826
|
-
|
|
20827
|
-
|
|
20601
|
+
const failSheet = sheetOf(r);
|
|
20602
|
+
if (failSheet) {
|
|
20603
|
+
const p = await writeB64(outDir, `contact-sheet.${sheetExt(failSheet.mime)}`, failSheet.b64);
|
|
20828
20604
|
log.plain(` ${c.dim("scene at failure:")} ${c.cyan(p)}`);
|
|
20829
20605
|
}
|
|
20830
20606
|
return 1;
|
|
@@ -20837,8 +20613,13 @@ async function runBlender(opts) {
|
|
|
20837
20613
|
}
|
|
20838
20614
|
reportScene(log, r.scene);
|
|
20839
20615
|
const written = [];
|
|
20840
|
-
|
|
20841
|
-
|
|
20616
|
+
const sheet = sheetOf(r);
|
|
20617
|
+
if (sheet) {
|
|
20618
|
+
written.push(await writeB64(outDir, `contact-sheet.${sheetExt(sheet.mime)}`, sheet.b64));
|
|
20619
|
+
} else if (r.contactSheetUnchanged) {
|
|
20620
|
+
log.dim(" scene unchanged \u2014 no new sheet rendered");
|
|
20621
|
+
} else if (r.contactSheetTooLarge) {
|
|
20622
|
+
log.warn(`sheet too large to inline (${r.contactSheetTooLarge.b64Bytes} b64 bytes)`);
|
|
20842
20623
|
}
|
|
20843
20624
|
if (sub === "demo") {
|
|
20844
20625
|
const glb = await blenderCall(base, "/export", { path: "/tmp/genex-export.glb" });
|
|
@@ -20928,9 +20709,9 @@ print(f"castle: {n} objects")
|
|
|
20928
20709
|
`;
|
|
20929
20710
|
|
|
20930
20711
|
// src/commands/asset-new.ts
|
|
20931
|
-
import
|
|
20712
|
+
import fs24 from "fs";
|
|
20932
20713
|
import fsp from "fs/promises";
|
|
20933
|
-
import
|
|
20714
|
+
import path23 from "path";
|
|
20934
20715
|
import { pathToFileURL } from "url";
|
|
20935
20716
|
var EXTRA_FILES = [
|
|
20936
20717
|
"genex-asset.example.json",
|
|
@@ -21024,7 +20805,7 @@ Every milestone ends with \`npm run verify\`, then \`npx genex preview --no-buil
|
|
|
21024
20805
|
}
|
|
21025
20806
|
async function runAssetNew(options) {
|
|
21026
20807
|
const log = createLogger();
|
|
21027
|
-
const cwd = options.dir ?
|
|
20808
|
+
const cwd = options.dir ? path23.resolve(options.dir) : process.cwd();
|
|
21028
20809
|
const slug = options.assetSlug;
|
|
21029
20810
|
if (!slug) {
|
|
21030
20811
|
log.error('Usage: genex asset new <slug> [--title "<Name>"] [--dims WxDxH] [--tri-band LOW-HIGH] [--holder "<your name>"] [--out <dir>]');
|
|
@@ -21034,14 +20815,14 @@ async function runAssetNew(options) {
|
|
|
21034
20815
|
log.error(`The slug must be kebab-case (letters, digits, dashes), got: ${slug}`);
|
|
21035
20816
|
return 1;
|
|
21036
20817
|
}
|
|
21037
|
-
const templateDir =
|
|
21038
|
-
if (!
|
|
20818
|
+
const templateDir = path23.join(getTemplatesDir(), "asset-viewer");
|
|
20819
|
+
if (!fs24.existsSync(templateDir)) {
|
|
21039
20820
|
log.error(`Vendored asset-viewer template not found at ${templateDir} \u2014 reinstall the CLI.`);
|
|
21040
20821
|
return 1;
|
|
21041
20822
|
}
|
|
21042
|
-
const manifestTools = await import(pathToFileURL(
|
|
20823
|
+
const manifestTools = await import(pathToFileURL(path23.join(templateDir, "tools", "emit-manifest.mjs")).href);
|
|
21043
20824
|
const { SHARED_FILES, PARITY_FILENAME, hashSharedFiles, pascalCase } = manifestTools;
|
|
21044
|
-
const lockPath =
|
|
20825
|
+
const lockPath = path23.join(templateDir, "shared-files.sha256.json");
|
|
21045
20826
|
const lock = JSON.parse(await fsp.readFile(lockPath, "utf8"));
|
|
21046
20827
|
const actual = hashSharedFiles(templateDir);
|
|
21047
20828
|
const drifted = SHARED_FILES.filter((rel) => lock.files[rel] !== actual[rel]);
|
|
@@ -21054,8 +20835,8 @@ async function runAssetNew(options) {
|
|
|
21054
20835
|
const triBand = parseBand(options.triBand ?? "500-8000");
|
|
21055
20836
|
const holder = options.holder?.trim() || HOLDER_PLACEHOLDER;
|
|
21056
20837
|
const year = (/* @__PURE__ */ new Date()).getUTCFullYear();
|
|
21057
|
-
const outDir =
|
|
21058
|
-
if (
|
|
20838
|
+
const outDir = path23.resolve(cwd, options.out ?? slug);
|
|
20839
|
+
if (fs24.existsSync(outDir) && fs24.readdirSync(outDir).length > 0 && !options.force) {
|
|
21059
20840
|
log.error(`${outDir} already holds files. Nothing was touched \u2014 pass --force to write into it anyway.`);
|
|
21060
20841
|
return 1;
|
|
21061
20842
|
}
|
|
@@ -21083,24 +20864,24 @@ async function runAssetNew(options) {
|
|
|
21083
20864
|
};
|
|
21084
20865
|
await fsp.mkdir(outDir, { recursive: true });
|
|
21085
20866
|
for (const rel of [...SHARED_FILES, ...EXTRA_FILES]) {
|
|
21086
|
-
const to =
|
|
21087
|
-
await fsp.mkdir(
|
|
21088
|
-
await fsp.copyFile(
|
|
20867
|
+
const to = path23.join(outDir, rel);
|
|
20868
|
+
await fsp.mkdir(path23.dirname(to), { recursive: true });
|
|
20869
|
+
await fsp.copyFile(path23.join(templateDir, rel), to);
|
|
21089
20870
|
}
|
|
21090
|
-
const pkg = fillTemplate(await fsp.readFile(
|
|
20871
|
+
const pkg = fillTemplate(await fsp.readFile(path23.join(templateDir, "package.json"), "utf8"), {
|
|
21091
20872
|
slug,
|
|
21092
20873
|
name,
|
|
21093
20874
|
version
|
|
21094
20875
|
});
|
|
21095
|
-
await fsp.writeFile(
|
|
21096
|
-
await fsp.writeFile(
|
|
21097
|
-
await fsp.writeFile(
|
|
20876
|
+
await fsp.writeFile(path23.join(outDir, "package.json"), pkg, "utf8");
|
|
20877
|
+
await fsp.writeFile(path23.join(outDir, "asset.config.json"), JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
20878
|
+
await fsp.writeFile(path23.join(outDir, ".gitignore"), GITIGNORE, "utf8");
|
|
21098
20879
|
await fsp.writeFile(
|
|
21099
|
-
|
|
20880
|
+
path23.join(outDir, "DESIGN.md"),
|
|
21100
20881
|
designDoc({ name, slug, sizeMeters, triBand, holder }),
|
|
21101
20882
|
"utf8"
|
|
21102
20883
|
);
|
|
21103
|
-
const placeholder = await fsp.readFile(
|
|
20884
|
+
const placeholder = await fsp.readFile(path23.join(templateDir, "src", "asset", "PLACEHOLDER.ts"), "utf8");
|
|
21104
20885
|
const seeded = seedAssetSource(placeholder, {
|
|
21105
20886
|
slug,
|
|
21106
20887
|
name,
|
|
@@ -21111,8 +20892,8 @@ async function runAssetNew(options) {
|
|
|
21111
20892
|
pascalCase
|
|
21112
20893
|
});
|
|
21113
20894
|
const assetFilename = `${slug.replace(/-([a-z0-9])/g, (_, ch) => ch.toUpperCase())}.ts`;
|
|
21114
|
-
await fsp.mkdir(
|
|
21115
|
-
await fsp.writeFile(
|
|
20895
|
+
await fsp.mkdir(path23.join(outDir, "src", "asset"), { recursive: true });
|
|
20896
|
+
await fsp.writeFile(path23.join(outDir, "src", "asset", assetFilename), seeded, "utf8");
|
|
21116
20897
|
const copied = hashSharedFiles(outDir);
|
|
21117
20898
|
const mismatched = SHARED_FILES.filter((rel) => copied[rel] !== lock.files[rel]);
|
|
21118
20899
|
if (mismatched.length) {
|
|
@@ -21120,7 +20901,7 @@ async function runAssetNew(options) {
|
|
|
21120
20901
|
return 1;
|
|
21121
20902
|
}
|
|
21122
20903
|
await fsp.writeFile(
|
|
21123
|
-
|
|
20904
|
+
path23.join(outDir, PARITY_FILENAME),
|
|
21124
20905
|
JSON.stringify(
|
|
21125
20906
|
{
|
|
21126
20907
|
note: "Recorded by `genex asset new`. Gate G13 recomputes these; do not hand-edit the viewer.",
|
|
@@ -21164,12 +20945,12 @@ async function runAssetNew(options) {
|
|
|
21164
20945
|
}
|
|
21165
20946
|
|
|
21166
20947
|
// src/commands/tools.ts
|
|
21167
|
-
import
|
|
20948
|
+
import path26 from "path";
|
|
21168
20949
|
|
|
21169
20950
|
// src/lib/local-install.ts
|
|
21170
|
-
import
|
|
21171
|
-
import
|
|
21172
|
-
import { spawn as
|
|
20951
|
+
import fs25 from "fs/promises";
|
|
20952
|
+
import path24 from "path";
|
|
20953
|
+
import { spawn as spawn5 } from "child_process";
|
|
21173
20954
|
var CLI_PACKAGE = "@genex-ai/cli-demo";
|
|
21174
20955
|
var FULL_NAME_FALLBACK = `npx ${CLI_PACKAGE}@${CLI_CHANNEL}`;
|
|
21175
20956
|
var LOCKFILES = [
|
|
@@ -21181,17 +20962,17 @@ var LOCKFILES = [
|
|
|
21181
20962
|
];
|
|
21182
20963
|
async function exists4(p) {
|
|
21183
20964
|
try {
|
|
21184
|
-
await
|
|
20965
|
+
await fs25.access(p);
|
|
21185
20966
|
return true;
|
|
21186
20967
|
} catch {
|
|
21187
20968
|
return false;
|
|
21188
20969
|
}
|
|
21189
20970
|
}
|
|
21190
20971
|
async function detectPackageManager(cwd) {
|
|
21191
|
-
let dir =
|
|
20972
|
+
let dir = path24.resolve(cwd);
|
|
21192
20973
|
for (; ; ) {
|
|
21193
20974
|
try {
|
|
21194
|
-
const raw = await
|
|
20975
|
+
const raw = await fs25.readFile(path24.join(dir, "package.json"), "utf8");
|
|
21195
20976
|
const pm = JSON.parse(raw).packageManager;
|
|
21196
20977
|
if (typeof pm === "string") {
|
|
21197
20978
|
const name = pm.split("@")[0];
|
|
@@ -21200,18 +20981,18 @@ async function detectPackageManager(cwd) {
|
|
|
21200
20981
|
} catch {
|
|
21201
20982
|
}
|
|
21202
20983
|
for (const [file, pm] of LOCKFILES) {
|
|
21203
|
-
if (await exists4(
|
|
20984
|
+
if (await exists4(path24.join(dir, file))) return pm;
|
|
21204
20985
|
}
|
|
21205
|
-
const parent =
|
|
20986
|
+
const parent = path24.dirname(dir);
|
|
21206
20987
|
if (parent === dir) return "npm";
|
|
21207
20988
|
dir = parent;
|
|
21208
20989
|
}
|
|
21209
20990
|
}
|
|
21210
20991
|
async function findLocalCli(cwd) {
|
|
21211
|
-
let dir =
|
|
20992
|
+
let dir = path24.resolve(cwd);
|
|
21212
20993
|
for (; ; ) {
|
|
21213
|
-
if (await exists4(
|
|
21214
|
-
const parent =
|
|
20994
|
+
if (await exists4(path24.join(dir, "node_modules", CLI_PACKAGE, "package.json"))) return dir;
|
|
20995
|
+
const parent = path24.dirname(dir);
|
|
21215
20996
|
if (parent === dir) return null;
|
|
21216
20997
|
dir = parent;
|
|
21217
20998
|
}
|
|
@@ -21229,7 +21010,7 @@ function installArgs(pm, spec) {
|
|
|
21229
21010
|
}
|
|
21230
21011
|
}
|
|
21231
21012
|
function manifestName(cwd) {
|
|
21232
|
-
const slug =
|
|
21013
|
+
const slug = path24.basename(path24.resolve(cwd)).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 100);
|
|
21233
21014
|
return slug || "genex-tools-workspace";
|
|
21234
21015
|
}
|
|
21235
21016
|
function isSourceRun(moduleUrl = import.meta.url) {
|
|
@@ -21246,7 +21027,7 @@ async function spawnInstall(command, args, cwd) {
|
|
|
21246
21027
|
resolve({ ok, detail });
|
|
21247
21028
|
};
|
|
21248
21029
|
try {
|
|
21249
|
-
const child =
|
|
21030
|
+
const child = spawn5(command, args, {
|
|
21250
21031
|
cwd,
|
|
21251
21032
|
// npm/pnpm/yarn are .cmd shims on Windows; the args carry no user input.
|
|
21252
21033
|
shell: process.platform === "win32",
|
|
@@ -21262,9 +21043,9 @@ async function spawnInstall(command, args, cwd) {
|
|
|
21262
21043
|
clearTimeout(timer);
|
|
21263
21044
|
done(false, err.message);
|
|
21264
21045
|
});
|
|
21265
|
-
child.on("close", (
|
|
21046
|
+
child.on("close", (code) => {
|
|
21266
21047
|
clearTimeout(timer);
|
|
21267
|
-
if (
|
|
21048
|
+
if (code === 0) done(true);
|
|
21268
21049
|
else done(false, output.trim().split("\n").slice(-3).join("\n"));
|
|
21269
21050
|
});
|
|
21270
21051
|
} catch (err) {
|
|
@@ -21287,10 +21068,10 @@ async function ensureLocalCli(log, cwd = process.cwd(), opts = {}) {
|
|
|
21287
21068
|
return;
|
|
21288
21069
|
}
|
|
21289
21070
|
const pm = await detectPackageManager(cwd);
|
|
21290
|
-
const hadManifest = await exists4(
|
|
21071
|
+
const hadManifest = await exists4(path24.join(cwd, "package.json"));
|
|
21291
21072
|
if (!hadManifest) {
|
|
21292
|
-
await
|
|
21293
|
-
|
|
21073
|
+
await fs25.writeFile(
|
|
21074
|
+
path24.join(cwd, "package.json"),
|
|
21294
21075
|
JSON.stringify({ name: manifestName(cwd), private: true }, null, 2) + "\n"
|
|
21295
21076
|
);
|
|
21296
21077
|
await ensureIgnored(cwd, "node_modules/");
|
|
@@ -21310,20 +21091,20 @@ async function ensureLocalCli(log, cwd = process.cwd(), opts = {}) {
|
|
|
21310
21091
|
}
|
|
21311
21092
|
}
|
|
21312
21093
|
async function ensureIgnored(dir, entry) {
|
|
21313
|
-
const file =
|
|
21094
|
+
const file = path24.join(dir, ".gitignore");
|
|
21314
21095
|
let content = "";
|
|
21315
21096
|
try {
|
|
21316
|
-
content = await
|
|
21097
|
+
content = await fs25.readFile(file, "utf8");
|
|
21317
21098
|
} catch {
|
|
21318
21099
|
}
|
|
21319
21100
|
if (content.split("\n").some((l) => l.trim() === entry)) return;
|
|
21320
21101
|
let next = content;
|
|
21321
21102
|
if (next.length > 0 && !next.endsWith("\n")) next += "\n";
|
|
21322
|
-
await
|
|
21103
|
+
await fs25.writeFile(file, next + entry + "\n");
|
|
21323
21104
|
}
|
|
21324
21105
|
|
|
21325
21106
|
// src/commands/doctor.ts
|
|
21326
|
-
import
|
|
21107
|
+
import path25 from "path";
|
|
21327
21108
|
var LANE_ORDER = [
|
|
21328
21109
|
"model",
|
|
21329
21110
|
"image",
|
|
@@ -21510,7 +21291,7 @@ function creditsRow(state, info) {
|
|
|
21510
21291
|
fix: "Verify your email to unlock your free credits \u2014 then re-run."
|
|
21511
21292
|
};
|
|
21512
21293
|
}
|
|
21513
|
-
const refill = info.refillAt ? ` \xB7 refills to ${info.refillTo} on ${
|
|
21294
|
+
const refill = info.refillAt ? ` \xB7 refills to ${info.refillTo} on ${shortDate(info.refillAt)}` : "";
|
|
21514
21295
|
return {
|
|
21515
21296
|
label: "Credits",
|
|
21516
21297
|
value: `${info.balance}${refill}`,
|
|
@@ -21518,7 +21299,7 @@ function creditsRow(state, info) {
|
|
|
21518
21299
|
fix: info.balance <= 0 ? "Out of credits \u2014 generation is refused until the refill or a top-up." : void 0
|
|
21519
21300
|
};
|
|
21520
21301
|
}
|
|
21521
|
-
function
|
|
21302
|
+
function shortDate(iso) {
|
|
21522
21303
|
const d = new Date(iso);
|
|
21523
21304
|
return Number.isNaN(d.getTime()) ? iso : d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
21524
21305
|
}
|
|
@@ -21551,7 +21332,7 @@ async function fetchCredits(apiUrl, token) {
|
|
|
21551
21332
|
}
|
|
21552
21333
|
async function firstSkillsMarker() {
|
|
21553
21334
|
for (const target of resolveAgentTargets()) {
|
|
21554
|
-
const marker = await readSkillsMarker(
|
|
21335
|
+
const marker = await readSkillsMarker(path25.join(target.baseDir, "skills"));
|
|
21555
21336
|
if (marker) return marker;
|
|
21556
21337
|
}
|
|
21557
21338
|
return null;
|
|
@@ -21590,8 +21371,8 @@ async function runTools(opts) {
|
|
|
21590
21371
|
let totalNew = 0;
|
|
21591
21372
|
let totalUpdated = 0;
|
|
21592
21373
|
for (const t of targets) {
|
|
21593
|
-
const dest =
|
|
21594
|
-
const { copied, updated } = await copyTemplates(
|
|
21374
|
+
const dest = path26.join(t.baseDir, "skills");
|
|
21375
|
+
const { copied, updated } = await copyTemplates(path26.join(templatesDir, "skills"), dest, {
|
|
21595
21376
|
filter: (rel) => skillFamilyFilter("tools")(`skills/${rel}`)
|
|
21596
21377
|
});
|
|
21597
21378
|
await pruneRemovedSkills(dest, log);
|