@genex-ai/cli-demo 1.15.0-dev.577 → 1.15.1-dev.579
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -2,27 +2,42 @@
|
|
|
2
2
|
import {
|
|
3
3
|
RENDER_MODES,
|
|
4
4
|
SHEET_FORMATS,
|
|
5
|
+
acceptUrl,
|
|
5
6
|
acquireSeat,
|
|
6
7
|
apiFetch,
|
|
7
8
|
blenderCall,
|
|
8
9
|
blenderEndpoint,
|
|
9
10
|
blenderSetupHint,
|
|
11
|
+
cloneSource,
|
|
12
|
+
fetchCloneGrant,
|
|
10
13
|
fetchSignedInEmail,
|
|
11
14
|
getWorkspacePath,
|
|
15
|
+
inHostedSession,
|
|
12
16
|
isRenderMode,
|
|
17
|
+
isStale,
|
|
18
|
+
isTermsRefusal,
|
|
19
|
+
mayForceOverAnotherDevice,
|
|
13
20
|
printedStructuredError,
|
|
14
21
|
readProject,
|
|
22
|
+
readRemoteSource,
|
|
15
23
|
readUserToken,
|
|
16
24
|
readWorkspace,
|
|
25
|
+
reportForceRefused,
|
|
26
|
+
reportStale,
|
|
27
|
+
reportTermsRefusal,
|
|
17
28
|
restrictFilePermissions,
|
|
18
29
|
rotateRejectedEnv,
|
|
30
|
+
run,
|
|
31
|
+
runAccept,
|
|
19
32
|
setWorkspaceHeader,
|
|
20
33
|
sheetExt,
|
|
21
34
|
sheetOf,
|
|
35
|
+
sourceTreeHash,
|
|
36
|
+
urlHasEmbeddedCredentials,
|
|
22
37
|
writeProject,
|
|
23
38
|
writeUserToken,
|
|
24
39
|
writeWorkspace
|
|
25
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-JGX6YRC7.js";
|
|
26
41
|
import {
|
|
27
42
|
CLI_CHANNEL,
|
|
28
43
|
DEFAULT_API_URL,
|
|
@@ -1760,177 +1775,9 @@ async function runInit(opts) {
|
|
|
1760
1775
|
}
|
|
1761
1776
|
|
|
1762
1777
|
// src/commands/link.ts
|
|
1763
|
-
import fs9 from "fs/promises";
|
|
1764
|
-
import os6 from "os";
|
|
1765
|
-
import path9 from "path";
|
|
1766
|
-
|
|
1767
|
-
// src/lib/source-sync.ts
|
|
1768
1778
|
import fs8 from "fs/promises";
|
|
1769
|
-
import path8 from "path";
|
|
1770
1779
|
import os5 from "os";
|
|
1771
|
-
|
|
1772
|
-
// src/utils/run.ts
|
|
1773
|
-
import { spawn as spawn2 } from "child_process";
|
|
1774
|
-
var WIN_SHELL_COMMANDS = /* @__PURE__ */ new Set(["npm", "npx"]);
|
|
1775
|
-
function run(cmd, args, env) {
|
|
1776
|
-
const shell = process.platform === "win32" && WIN_SHELL_COMMANDS.has(cmd);
|
|
1777
|
-
return new Promise((resolve) => {
|
|
1778
|
-
let child;
|
|
1779
|
-
try {
|
|
1780
|
-
child = spawn2(cmd, args, { env: env ? { ...process.env, ...env } : process.env, shell });
|
|
1781
|
-
} catch {
|
|
1782
|
-
resolve({ code: -1, out: "", err: `${cmd} not found` });
|
|
1783
|
-
return;
|
|
1784
|
-
}
|
|
1785
|
-
let out = "";
|
|
1786
|
-
let err = "";
|
|
1787
|
-
child.stdout?.on("data", (d) => out += String(d));
|
|
1788
|
-
child.stderr?.on("data", (d) => err += String(d));
|
|
1789
|
-
child.on("error", () => resolve({ code: -1, out, err: `${cmd} not found` }));
|
|
1790
|
-
child.on("close", (code) => resolve({ code: code ?? -1, out, err }));
|
|
1791
|
-
});
|
|
1792
|
-
}
|
|
1793
|
-
|
|
1794
|
-
// src/lib/source-sync.ts
|
|
1795
|
-
async function readRemoteSource(apiUrl, token, projectId) {
|
|
1796
|
-
try {
|
|
1797
|
-
const res = await apiFetch(`${apiUrl}/api/projects/${projectId}`, {
|
|
1798
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
1799
|
-
});
|
|
1800
|
-
if (!res.ok) return null;
|
|
1801
|
-
const data = await res.json().catch(() => null);
|
|
1802
|
-
if (!data?.project) return null;
|
|
1803
|
-
return {
|
|
1804
|
-
stagingCommit: data.project.stagingCommitSha ?? null,
|
|
1805
|
-
commit: data.project.commitSha ?? null
|
|
1806
|
-
};
|
|
1807
|
-
} catch {
|
|
1808
|
-
return null;
|
|
1809
|
-
}
|
|
1810
|
-
}
|
|
1811
|
-
function isStale(local, remote) {
|
|
1812
|
-
if (!local || !remote) return false;
|
|
1813
|
-
return local !== remote;
|
|
1814
|
-
}
|
|
1815
|
-
function inHostedSession() {
|
|
1816
|
-
return process.env.GENEX_HOSTED_SESSION === "1";
|
|
1817
|
-
}
|
|
1818
|
-
function mayForceOverAnotherDevice() {
|
|
1819
|
-
return !inHostedSession();
|
|
1820
|
-
}
|
|
1821
|
-
function reportForceRefused(log) {
|
|
1822
|
-
log.error("`--force` is not available inside a Genex chat session.");
|
|
1823
|
-
log.dim(" It would replace work shipped from another machine, and that is not yours to discard here.");
|
|
1824
|
-
log.dim(` Do this instead \u2014 it keeps both sides:`);
|
|
1825
|
-
log.dim(` 1. ${c.cyan("npx genex pull --force")} takes the other machine's work (a copy of this folder is kept under .genex/replaced-*)`);
|
|
1826
|
-
log.dim(` 2. redo your change on top of it \u2014 you know what you just changed`);
|
|
1827
|
-
log.dim(` 3. ${c.cyan("npx genex preview")}`);
|
|
1828
|
-
log.dim(" If the user explicitly wants their chat version to win, ask them to run `npx genex preview --force` themselves.");
|
|
1829
|
-
}
|
|
1830
|
-
function reportStale(log, slug, local, remote) {
|
|
1831
|
-
log.error(`${c.cyan(slug)} was updated from another device \u2014 nothing was deployed.`);
|
|
1832
|
-
if (remote && local) {
|
|
1833
|
-
const short = remote.slice(0, 7) !== local.slice(0, 7);
|
|
1834
|
-
const r = short ? remote.slice(0, 7) : remote;
|
|
1835
|
-
const l = short ? local.slice(0, 7) : local;
|
|
1836
|
-
log.dim(` the draft is on ${r}, this folder last shipped ${l}`);
|
|
1837
|
-
}
|
|
1838
|
-
log.dim(` ${c.cyan("npx genex pull")} \u2014 take the other device's work (refuses if you have unshipped changes)`);
|
|
1839
|
-
log.dim(` ${c.cyan("npx genex preview --force")} \u2014 keep yours and replace theirs`);
|
|
1840
|
-
}
|
|
1841
|
-
async function sourceTreeHash(cwd) {
|
|
1842
|
-
const gitDir = await fs8.mkdtemp(path8.join(os5.tmpdir(), "genex-tree-"));
|
|
1843
|
-
const base = { GIT_DIR: gitDir };
|
|
1844
|
-
try {
|
|
1845
|
-
if ((await run("git", ["init", "-q"], base)).code !== 0) return null;
|
|
1846
|
-
await fs8.writeFile(
|
|
1847
|
-
path8.join(gitDir, "info", "exclude"),
|
|
1848
|
-
["node_modules/", "dist/", ".git/", ".genex/", ".env", ".env.*", "!.env.example", ""].join("\n")
|
|
1849
|
-
);
|
|
1850
|
-
if ((await run("git", ["lfs", "version"], base)).code === 0) {
|
|
1851
|
-
const filters = [
|
|
1852
|
-
["filter.lfs.clean", "git-lfs clean -- %f"],
|
|
1853
|
-
["filter.lfs.smudge", "git-lfs smudge -- %f"],
|
|
1854
|
-
["filter.lfs.process", "git-lfs filter-process"],
|
|
1855
|
-
["filter.lfs.required", "true"]
|
|
1856
|
-
];
|
|
1857
|
-
for (const [key, value] of filters) await run("git", ["config", key, value], base);
|
|
1858
|
-
}
|
|
1859
|
-
const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path8.join(gitDir, "index-tree") };
|
|
1860
|
-
if ((await run("git", ["add", "-A"], env)).code !== 0) return null;
|
|
1861
|
-
const tree = (await run("git", ["write-tree"], env)).out.trim();
|
|
1862
|
-
return /^[0-9a-f]{40}$/.test(tree) ? tree : null;
|
|
1863
|
-
} catch {
|
|
1864
|
-
return null;
|
|
1865
|
-
} finally {
|
|
1866
|
-
await fs8.rm(gitDir, { recursive: true, force: true }).catch(() => {
|
|
1867
|
-
});
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
1870
|
-
function urlHasEmbeddedCredentials(url) {
|
|
1871
|
-
return /^[a-z][a-z0-9+.-]*:\/\/[^/@]+@/i.test(url);
|
|
1872
|
-
}
|
|
1873
|
-
function credentialHelperOff(url) {
|
|
1874
|
-
if (!urlHasEmbeddedCredentials(url)) return {};
|
|
1875
|
-
return { GIT_CONFIG_COUNT: "1", GIT_CONFIG_KEY_0: "credential.helper", GIT_CONFIG_VALUE_0: "" };
|
|
1876
|
-
}
|
|
1877
|
-
async function fetchCloneGrant(apiUrl, token, projectId, log) {
|
|
1878
|
-
let res;
|
|
1879
|
-
try {
|
|
1880
|
-
res = await apiFetch(`${apiUrl}/api/projects/${projectId}/push-token`, {
|
|
1881
|
-
method: "POST",
|
|
1882
|
-
headers: { Authorization: `Bearer ${token}`, "X-Genex-Source-Intent": "read" }
|
|
1883
|
-
});
|
|
1884
|
-
} catch (err) {
|
|
1885
|
-
log.error(`Couldn't reach the API to authorize the source read: ${String(err)}`);
|
|
1886
|
-
return null;
|
|
1887
|
-
}
|
|
1888
|
-
if (res.status === 401) {
|
|
1889
|
-
log.error("Not authorized \u2014 your token may have expired. Re-run `genex auth`.");
|
|
1890
|
-
return null;
|
|
1891
|
-
}
|
|
1892
|
-
if (!res.ok) {
|
|
1893
|
-
log.error(`Couldn't authorize the source read (HTTP ${res.status}).`);
|
|
1894
|
-
return null;
|
|
1895
|
-
}
|
|
1896
|
-
const data = await res.json().catch(() => null);
|
|
1897
|
-
const url = data?.pushUrl ?? data?.cloneUrl;
|
|
1898
|
-
if (!url) {
|
|
1899
|
-
log.error("The API didn't return a source URL.");
|
|
1900
|
-
return null;
|
|
1901
|
-
}
|
|
1902
|
-
return { cloneUrl: url, sourceRef: data?.sourceRef ?? null };
|
|
1903
|
-
}
|
|
1904
|
-
async function cloneSource(grant, dest, log) {
|
|
1905
|
-
const args = grant.sourceRef ? ["clone", "--branch", grant.sourceRef, grant.cloneUrl, dest] : ["clone", grant.cloneUrl, dest];
|
|
1906
|
-
const env = {
|
|
1907
|
-
GIT_LFS_SKIP_SMUDGE: "1",
|
|
1908
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
1909
|
-
...credentialHelperOff(grant.cloneUrl)
|
|
1910
|
-
};
|
|
1911
|
-
const cloned = await run("git", args, env);
|
|
1912
|
-
if (cloned.code !== 0) {
|
|
1913
|
-
log.error("Couldn't download the game's source.");
|
|
1914
|
-
log.dim(` git clone exited ${cloned.code}`);
|
|
1915
|
-
return false;
|
|
1916
|
-
}
|
|
1917
|
-
const repo = { ...env, GIT_DIR: path8.join(dest, ".git"), GIT_WORK_TREE: dest };
|
|
1918
|
-
const filters = [
|
|
1919
|
-
["filter.lfs.clean", "git-lfs clean -- %f"],
|
|
1920
|
-
["filter.lfs.smudge", "git-lfs smudge -- %f"],
|
|
1921
|
-
["filter.lfs.process", "git-lfs filter-process"],
|
|
1922
|
-
["filter.lfs.required", "true"]
|
|
1923
|
-
];
|
|
1924
|
-
for (const [key, value] of filters) await run("git", ["config", key, value], repo);
|
|
1925
|
-
const pulled = await run("git", ["lfs", "pull"], repo);
|
|
1926
|
-
if (pulled.code !== 0) {
|
|
1927
|
-
log.warn("Binary assets (models, textures, audio) are still pointer files \u2014 `git lfs pull` failed.");
|
|
1928
|
-
log.dim(" The source is there; run `git lfs pull` in this folder once git-lfs works.");
|
|
1929
|
-
}
|
|
1930
|
-
return true;
|
|
1931
|
-
}
|
|
1932
|
-
|
|
1933
|
-
// src/commands/link.ts
|
|
1780
|
+
import path8 from "path";
|
|
1934
1781
|
async function runLink(opts) {
|
|
1935
1782
|
const log = createLogger({ quiet: opts.quiet });
|
|
1936
1783
|
log.plain(c.bold("genex link"));
|
|
@@ -2010,7 +1857,7 @@ async function runLink(opts) {
|
|
|
2010
1857
|
}
|
|
2011
1858
|
async function isEmptyDir(cwd) {
|
|
2012
1859
|
try {
|
|
2013
|
-
const entries = await
|
|
1860
|
+
const entries = await fs8.readdir(cwd);
|
|
2014
1861
|
return entries.every((e) => e === ".git" || e === ".genex" || e === ".DS_Store");
|
|
2015
1862
|
} catch {
|
|
2016
1863
|
return false;
|
|
@@ -2024,13 +1871,13 @@ async function downloadSource(apiUrl, token, project, log) {
|
|
|
2024
1871
|
return false;
|
|
2025
1872
|
}
|
|
2026
1873
|
log.step(`Downloading the ${grant.sourceRef === "preview" ? "draft" : "published"} source\u2026`);
|
|
2027
|
-
const staging = await
|
|
2028
|
-
const fresh =
|
|
1874
|
+
const staging = await fs8.mkdtemp(path8.join(os5.tmpdir(), "genex-link-"));
|
|
1875
|
+
const fresh = path8.join(staging, "source");
|
|
2029
1876
|
try {
|
|
2030
1877
|
if (!await cloneSource(grant, fresh, log)) return false;
|
|
2031
|
-
await
|
|
2032
|
-
for (const entry of await
|
|
2033
|
-
await
|
|
1878
|
+
await fs8.rm(path8.join(fresh, ".git"), { recursive: true, force: true });
|
|
1879
|
+
for (const entry of await fs8.readdir(fresh)) {
|
|
1880
|
+
await fs8.cp(path8.join(fresh, entry), path8.join(process.cwd(), entry), {
|
|
2034
1881
|
recursive: true,
|
|
2035
1882
|
force: true
|
|
2036
1883
|
});
|
|
@@ -2038,28 +1885,28 @@ async function downloadSource(apiUrl, token, project, log) {
|
|
|
2038
1885
|
log.success("Downloaded.");
|
|
2039
1886
|
return true;
|
|
2040
1887
|
} finally {
|
|
2041
|
-
await
|
|
1888
|
+
await fs8.rm(staging, { recursive: true, force: true }).catch(() => {
|
|
2042
1889
|
});
|
|
2043
1890
|
}
|
|
2044
1891
|
}
|
|
2045
1892
|
async function ensureSlugEnv(slug, log, cwd = process.cwd()) {
|
|
2046
|
-
const file =
|
|
1893
|
+
const file = path8.join(cwd, ".env");
|
|
2047
1894
|
let content;
|
|
2048
1895
|
try {
|
|
2049
|
-
content = await
|
|
1896
|
+
content = await fs8.readFile(file, "utf8");
|
|
2050
1897
|
} catch {
|
|
2051
1898
|
return;
|
|
2052
1899
|
}
|
|
2053
1900
|
const re = /^(\s*VITE_GENEX_SLUG=)(.*)$/m;
|
|
2054
1901
|
const m = content.match(re);
|
|
2055
1902
|
if (!m) {
|
|
2056
|
-
await
|
|
1903
|
+
await fs8.appendFile(file, `${content.endsWith("\n") ? "" : "\n"}VITE_GENEX_SLUG=${slug}
|
|
2057
1904
|
`);
|
|
2058
1905
|
log.dim(` added VITE_GENEX_SLUG=${slug} to .env`);
|
|
2059
1906
|
return;
|
|
2060
1907
|
}
|
|
2061
1908
|
if (m[2].trim() === slug) return;
|
|
2062
|
-
await
|
|
1909
|
+
await fs8.writeFile(file, content.replace(re, `$1${slug}`));
|
|
2063
1910
|
log.dim(` updated .env: VITE_GENEX_SLUG=${slug} (was ${m[2].trim()})`);
|
|
2064
1911
|
}
|
|
2065
1912
|
async function fetchOwnProject(apiUrl, token, slug, log) {
|
|
@@ -2113,9 +1960,9 @@ async function listOwnSlugs(apiUrl, token, log) {
|
|
|
2113
1960
|
}
|
|
2114
1961
|
|
|
2115
1962
|
// src/commands/pull.ts
|
|
2116
|
-
import
|
|
2117
|
-
import
|
|
2118
|
-
import
|
|
1963
|
+
import fs9 from "fs/promises";
|
|
1964
|
+
import path9 from "path";
|
|
1965
|
+
import os6 from "os";
|
|
2119
1966
|
function isMachineLocal(entry) {
|
|
2120
1967
|
if (entry === ".genex" || entry === "node_modules" || entry === ".git") return true;
|
|
2121
1968
|
if (entry === ".env.example") return false;
|
|
@@ -2174,23 +2021,23 @@ async function runPull(opts) {
|
|
|
2174
2021
|
process.exitCode = 1;
|
|
2175
2022
|
return;
|
|
2176
2023
|
}
|
|
2177
|
-
const staging = await
|
|
2178
|
-
const fresh =
|
|
2024
|
+
const staging = await fs9.mkdtemp(path9.join(os6.tmpdir(), "genex-pull-"));
|
|
2025
|
+
const fresh = path9.join(staging, "source");
|
|
2179
2026
|
try {
|
|
2180
2027
|
if (!await cloneSource(grant, fresh, log)) {
|
|
2181
2028
|
process.exitCode = 1;
|
|
2182
2029
|
return;
|
|
2183
2030
|
}
|
|
2184
|
-
await
|
|
2031
|
+
await fs9.rm(path9.join(fresh, ".git"), { recursive: true, force: true });
|
|
2185
2032
|
const kept = await keepReplaced(cwd, log);
|
|
2186
2033
|
await replaceTree(cwd, fresh);
|
|
2187
2034
|
if (kept) {
|
|
2188
2035
|
log.plain("");
|
|
2189
|
-
log.info(`What was here is kept at ${c.cyan(
|
|
2036
|
+
log.info(`What was here is kept at ${c.cyan(path9.relative(cwd, kept) || kept)}`);
|
|
2190
2037
|
log.dim(" Nothing was thrown away \u2014 re-apply from there, or delete it when you are done.");
|
|
2191
2038
|
}
|
|
2192
2039
|
} finally {
|
|
2193
|
-
await
|
|
2040
|
+
await fs9.rm(staging, { recursive: true, force: true }).catch(() => {
|
|
2194
2041
|
});
|
|
2195
2042
|
}
|
|
2196
2043
|
const remote = await readRemoteSource(apiUrl, token, meta.id);
|
|
@@ -2209,13 +2056,13 @@ async function runPull(opts) {
|
|
|
2209
2056
|
}
|
|
2210
2057
|
async function keepReplaced(cwd, log) {
|
|
2211
2058
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2212
|
-
const dest =
|
|
2059
|
+
const dest = path9.join(cwd, ".genex", `replaced-${stamp}`);
|
|
2213
2060
|
try {
|
|
2214
|
-
const entries = (await
|
|
2061
|
+
const entries = (await fs9.readdir(cwd)).filter((e) => !isMachineLocal(e));
|
|
2215
2062
|
if (entries.length === 0) return null;
|
|
2216
|
-
await
|
|
2063
|
+
await fs9.mkdir(dest, { recursive: true });
|
|
2217
2064
|
for (const entry of entries) {
|
|
2218
|
-
await
|
|
2065
|
+
await fs9.cp(path9.join(cwd, entry), path9.join(dest, entry), { recursive: true });
|
|
2219
2066
|
}
|
|
2220
2067
|
return dest;
|
|
2221
2068
|
} catch (err) {
|
|
@@ -2225,19 +2072,19 @@ async function keepReplaced(cwd, log) {
|
|
|
2225
2072
|
}
|
|
2226
2073
|
}
|
|
2227
2074
|
async function replaceTree(dest, src) {
|
|
2228
|
-
for (const entry of await
|
|
2075
|
+
for (const entry of await fs9.readdir(dest)) {
|
|
2229
2076
|
if (isMachineLocal(entry)) continue;
|
|
2230
|
-
await
|
|
2077
|
+
await fs9.rm(path9.join(dest, entry), { recursive: true, force: true });
|
|
2231
2078
|
}
|
|
2232
|
-
for (const entry of await
|
|
2079
|
+
for (const entry of await fs9.readdir(src)) {
|
|
2233
2080
|
if (isMachineLocal(entry)) continue;
|
|
2234
|
-
await
|
|
2081
|
+
await fs9.cp(path9.join(src, entry), path9.join(dest, entry), { recursive: true });
|
|
2235
2082
|
}
|
|
2236
2083
|
}
|
|
2237
2084
|
|
|
2238
2085
|
// src/commands/rename.ts
|
|
2239
|
-
import
|
|
2240
|
-
import
|
|
2086
|
+
import fs10 from "fs/promises";
|
|
2087
|
+
import path10 from "path";
|
|
2241
2088
|
async function runRename(opts) {
|
|
2242
2089
|
const log = createLogger({ quiet: opts.quiet });
|
|
2243
2090
|
log.plain(c.bold("genex rename"));
|
|
@@ -2325,23 +2172,23 @@ async function runRename(opts) {
|
|
|
2325
2172
|
log.info("Run `genex preview` (or `publish`) to rebuild \u2014 the new slug is baked into the bundle.");
|
|
2326
2173
|
}
|
|
2327
2174
|
async function rewriteSlugEnv(from, to, log, cwd = process.cwd()) {
|
|
2328
|
-
const file =
|
|
2175
|
+
const file = path10.join(cwd, ".env");
|
|
2329
2176
|
let content;
|
|
2330
2177
|
try {
|
|
2331
|
-
content = await
|
|
2178
|
+
content = await fs10.readFile(file, "utf8");
|
|
2332
2179
|
} catch {
|
|
2333
2180
|
return;
|
|
2334
2181
|
}
|
|
2335
2182
|
const re = /^(\s*VITE_GENEX_SLUG=)(.*)$/m;
|
|
2336
2183
|
if (!re.test(content)) return;
|
|
2337
|
-
await
|
|
2184
|
+
await fs10.writeFile(file, content.replace(re, `$1${to}`));
|
|
2338
2185
|
log.dim(` .env: VITE_GENEX_SLUG=${to} (was ${from})`);
|
|
2339
2186
|
}
|
|
2340
2187
|
async function rewriteBakedSlug(from, to, log, cwd = process.cwd()) {
|
|
2341
|
-
const file =
|
|
2188
|
+
const file = path10.join(cwd, "src", "genex.config.ts");
|
|
2342
2189
|
let content;
|
|
2343
2190
|
try {
|
|
2344
|
-
content = await
|
|
2191
|
+
content = await fs10.readFile(file, "utf8");
|
|
2345
2192
|
} catch {
|
|
2346
2193
|
return;
|
|
2347
2194
|
}
|
|
@@ -2351,7 +2198,7 @@ async function rewriteBakedSlug(from, to, log, cwd = process.cwd()) {
|
|
|
2351
2198
|
log.warn(` src/genex.config.ts has no "${from}" literal \u2014 check its slug by hand.`);
|
|
2352
2199
|
return;
|
|
2353
2200
|
}
|
|
2354
|
-
await
|
|
2201
|
+
await fs10.writeFile(file, content.replace(quoted, `"${to}"`));
|
|
2355
2202
|
log.dim(` src/genex.config.ts: baked slug -> ${to}`);
|
|
2356
2203
|
}
|
|
2357
2204
|
|
|
@@ -2482,13 +2329,13 @@ function relTime(iso) {
|
|
|
2482
2329
|
import "child_process";
|
|
2483
2330
|
|
|
2484
2331
|
// src/lib/generation-ledger.ts
|
|
2485
|
-
import
|
|
2486
|
-
import
|
|
2487
|
-
var ledgerPath = (cwd) =>
|
|
2332
|
+
import fs11 from "fs/promises";
|
|
2333
|
+
import path11 from "path";
|
|
2334
|
+
var ledgerPath = (cwd) => path11.join(cwd, ".genex", "generations.ndjson");
|
|
2488
2335
|
async function append(cwd, event) {
|
|
2489
2336
|
try {
|
|
2490
|
-
await
|
|
2491
|
-
await
|
|
2337
|
+
await fs11.access(path11.join(cwd, ".genex"));
|
|
2338
|
+
await fs11.appendFile(ledgerPath(cwd), `${JSON.stringify(event)}
|
|
2492
2339
|
`, "utf8");
|
|
2493
2340
|
} catch {
|
|
2494
2341
|
}
|
|
@@ -2496,7 +2343,7 @@ async function append(cwd, event) {
|
|
|
2496
2343
|
async function readLedger(cwd = process.cwd()) {
|
|
2497
2344
|
let raw;
|
|
2498
2345
|
try {
|
|
2499
|
-
raw = await
|
|
2346
|
+
raw = await fs11.readFile(ledgerPath(cwd), "utf8");
|
|
2500
2347
|
} catch {
|
|
2501
2348
|
return [];
|
|
2502
2349
|
}
|
|
@@ -2701,9 +2548,9 @@ function auditGenerationPlan(input) {
|
|
|
2701
2548
|
|
|
2702
2549
|
// src/lib/deploy.ts
|
|
2703
2550
|
import crypto3 from "crypto";
|
|
2704
|
-
import
|
|
2705
|
-
import
|
|
2706
|
-
import
|
|
2551
|
+
import fs14 from "fs/promises";
|
|
2552
|
+
import os7 from "os";
|
|
2553
|
+
import path13 from "path";
|
|
2707
2554
|
|
|
2708
2555
|
// ../../packages/mobile-scan/src/image-dims.ts
|
|
2709
2556
|
function u32be(b, o) {
|
|
@@ -2967,12 +2814,12 @@ function tierFor(estVramMb) {
|
|
|
2967
2814
|
}
|
|
2968
2815
|
|
|
2969
2816
|
// src/commands/ui.ts
|
|
2970
|
-
import
|
|
2971
|
-
import
|
|
2817
|
+
import fs13 from "fs/promises";
|
|
2818
|
+
import path12 from "path";
|
|
2972
2819
|
import { PNG as PNG2 } from "pngjs";
|
|
2973
2820
|
|
|
2974
2821
|
// src/lib/png-tools.ts
|
|
2975
|
-
import
|
|
2822
|
+
import fs12 from "fs/promises";
|
|
2976
2823
|
import { PNG } from "pngjs";
|
|
2977
2824
|
var ALPHA_TRANSPARENT_MAX = 16;
|
|
2978
2825
|
var isHttpUrl = (s) => /^https?:\/\//i.test(s);
|
|
@@ -2983,12 +2830,12 @@ async function loadPng(input) {
|
|
|
2983
2830
|
if (!res.ok) throw new Error(`Couldn't fetch ${input} (HTTP ${res.status}).`);
|
|
2984
2831
|
buf = Buffer.from(await res.arrayBuffer());
|
|
2985
2832
|
} else {
|
|
2986
|
-
buf = await
|
|
2833
|
+
buf = await fs12.readFile(input);
|
|
2987
2834
|
}
|
|
2988
2835
|
return PNG.sync.read(buf);
|
|
2989
2836
|
}
|
|
2990
2837
|
async function writePng(file, png) {
|
|
2991
|
-
await
|
|
2838
|
+
await fs12.writeFile(file, PNG.sync.write(png));
|
|
2992
2839
|
}
|
|
2993
2840
|
function cropPng(image, box) {
|
|
2994
2841
|
const out = new PNG({ width: box.w, height: box.h });
|
|
@@ -3288,7 +3135,7 @@ async function uiExtract(opts, log) {
|
|
|
3288
3135
|
const dilatePx = opts.dilate ?? 0;
|
|
3289
3136
|
const sheet = await loadPng(input);
|
|
3290
3137
|
const { width: W, height: H, data } = sheet;
|
|
3291
|
-
await
|
|
3138
|
+
await fs13.mkdir(outDir, { recursive: true });
|
|
3292
3139
|
log.plain(c.bold("genex ui extract"));
|
|
3293
3140
|
log.dim(` ${input} (${W}x${H}) \u2192 ${outDir}, ${names.length} names`);
|
|
3294
3141
|
let hasTransparency = false;
|
|
@@ -3517,7 +3364,7 @@ async function uiExtract(opts, log) {
|
|
|
3517
3364
|
rimPixels: speckle.sampled
|
|
3518
3365
|
});
|
|
3519
3366
|
}
|
|
3520
|
-
const outPath =
|
|
3367
|
+
const outPath = path12.join(outDir, `${name}.png`);
|
|
3521
3368
|
await writePng(outPath, out);
|
|
3522
3369
|
const sidecar = {
|
|
3523
3370
|
name,
|
|
@@ -3536,7 +3383,7 @@ async function uiExtract(opts, log) {
|
|
|
3536
3383
|
defringed
|
|
3537
3384
|
};
|
|
3538
3385
|
const { name: _n, out: _o, ...sidecarBody } = sidecar;
|
|
3539
|
-
await
|
|
3386
|
+
await fs13.writeFile(
|
|
3540
3387
|
outPath.replace(/\.png$/i, "") + ".bbox.json",
|
|
3541
3388
|
JSON.stringify(sidecarBody, null, 2)
|
|
3542
3389
|
);
|
|
@@ -3545,8 +3392,8 @@ async function uiExtract(opts, log) {
|
|
|
3545
3392
|
`${name}.png ${cropW}x${cropH} (ar ${sidecar.aspectRatio}) at sheet ${padX0},${padY0}`
|
|
3546
3393
|
);
|
|
3547
3394
|
}
|
|
3548
|
-
const debugPath =
|
|
3549
|
-
await
|
|
3395
|
+
const debugPath = path12.join(outDir, "extract-debug.json");
|
|
3396
|
+
await fs13.writeFile(
|
|
3550
3397
|
debugPath,
|
|
3551
3398
|
JSON.stringify(
|
|
3552
3399
|
{
|
|
@@ -4008,7 +3855,7 @@ async function uiMasks(opts, log) {
|
|
|
4008
3855
|
const registrationTolerance = opts.registrationTolerance ?? 0.04;
|
|
4009
3856
|
const edgeFlushMax = opts.edgeFlushMax ?? 0.04;
|
|
4010
3857
|
const loosened = registrationTolerance > 0.04 || edgeFlushMax > 0.04 || minCoverage < 0.01 || maxCoverage > 0.85;
|
|
4011
|
-
await
|
|
3858
|
+
await fs13.mkdir(outDir, { recursive: true });
|
|
4012
3859
|
log.plain(c.bold("genex ui masks"));
|
|
4013
3860
|
log.dim(` ${input} (${image.width}x${image.height}), ${pairs.length} pair(s) \u2192 ${outDir}`);
|
|
4014
3861
|
const sheetComponents = detectSheetComponents(image, opts.minPixels ?? 2e3);
|
|
@@ -4061,11 +3908,11 @@ async function uiMasks(opts, log) {
|
|
|
4061
3908
|
});
|
|
4062
3909
|
}
|
|
4063
3910
|
const overlay = makeOverlay(clean2, converted.png);
|
|
4064
|
-
const framePath =
|
|
4065
|
-
const maskPath =
|
|
4066
|
-
const annotatedPath =
|
|
4067
|
-
const overlayPath =
|
|
4068
|
-
const metaPath =
|
|
3911
|
+
const framePath = path12.join(outDir, `${pair.name}-frame.png`);
|
|
3912
|
+
const maskPath = path12.join(outDir, `${pair.name}-mask.png`);
|
|
3913
|
+
const annotatedPath = path12.join(outDir, `${pair.name}-annotated-source.png`);
|
|
3914
|
+
const overlayPath = path12.join(outDir, `${pair.name}-overlay.png`);
|
|
3915
|
+
const metaPath = path12.join(outDir, `${pair.name}.annotated-progress.json`);
|
|
4069
3916
|
await writePng(framePath, clean2);
|
|
4070
3917
|
await writePng(maskPath, converted.png);
|
|
4071
3918
|
await writePng(annotatedPath, annotated);
|
|
@@ -4112,7 +3959,7 @@ async function uiMasks(opts, log) {
|
|
|
4112
3959
|
},
|
|
4113
3960
|
overlay: overlayPath
|
|
4114
3961
|
};
|
|
4115
|
-
await
|
|
3962
|
+
await fs13.writeFile(metaPath, `${JSON.stringify(meta, null, 2)}
|
|
4116
3963
|
`);
|
|
4117
3964
|
results.push(meta);
|
|
4118
3965
|
const fb = converted.bbox;
|
|
@@ -4128,8 +3975,8 @@ async function uiMasks(opts, log) {
|
|
|
4128
3975
|
);
|
|
4129
3976
|
}
|
|
4130
3977
|
}
|
|
4131
|
-
const indexPath =
|
|
4132
|
-
await
|
|
3978
|
+
const indexPath = path12.join(outDir, "annotated-progress.json");
|
|
3979
|
+
await fs13.writeFile(indexPath, `${JSON.stringify({ input, loosened, pairs: results }, null, 2)}
|
|
4133
3980
|
`);
|
|
4134
3981
|
log.plain("");
|
|
4135
3982
|
log.success(`Wrote ${pairs.length} frame+mask pair(s) \u2192 ${outDir} (index: ${indexPath}).`);
|
|
@@ -4255,7 +4102,7 @@ async function uiTextColor(opts, log) {
|
|
|
4255
4102
|
};
|
|
4256
4103
|
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
4257
4104
|
`);
|
|
4258
|
-
if (opts.out) await
|
|
4105
|
+
if (opts.out) await fs13.writeFile(opts.out, `${JSON.stringify(result, null, 2)}
|
|
4259
4106
|
`);
|
|
4260
4107
|
if (opts.cropPath) log.dim(` crop \u2192 ${opts.cropPath}`);
|
|
4261
4108
|
}
|
|
@@ -4287,7 +4134,7 @@ async function uiTrim(opts, log) {
|
|
|
4287
4134
|
const sidecar = computeBBoxes(trimmed);
|
|
4288
4135
|
const speckleAllowed = !!(speckle && speckle.ratio > SPECKLE_MAX_RATIO);
|
|
4289
4136
|
const sidecarPath = outPath.replace(/\.png$/i, "") + ".bbox.json";
|
|
4290
|
-
await
|
|
4137
|
+
await fs13.writeFile(
|
|
4291
4138
|
sidecarPath,
|
|
4292
4139
|
JSON.stringify(speckleAllowed ? { ...sidecar, speckleAllowed: true } : sidecar, null, 2)
|
|
4293
4140
|
);
|
|
@@ -4394,7 +4241,7 @@ async function uiPlate(opts, log) {
|
|
|
4394
4241
|
fail("No interior found \u2014 the image is fully transparent (or erode ate everything). Check --in / lower --erode.");
|
|
4395
4242
|
}
|
|
4396
4243
|
await writePng(outPath, out);
|
|
4397
|
-
const name =
|
|
4244
|
+
const name = path12.basename(outPath);
|
|
4398
4245
|
log.plain(c.bold("genex ui plate"));
|
|
4399
4246
|
log.success(`${outPath} ${W}x${H}, interior ${(count / (W * H) * 100).toFixed(1)}% (erode ${erode}px)`);
|
|
4400
4247
|
log.dim(" Wire it as the plate's silhouette (same box as the frame <img>, plate UNDER the art):");
|
|
@@ -4559,13 +4406,13 @@ async function walkFiles(dir) {
|
|
|
4559
4406
|
const out = [];
|
|
4560
4407
|
let entries;
|
|
4561
4408
|
try {
|
|
4562
|
-
entries = await
|
|
4409
|
+
entries = await fs13.readdir(dir, { withFileTypes: true });
|
|
4563
4410
|
} catch {
|
|
4564
4411
|
return out;
|
|
4565
4412
|
}
|
|
4566
4413
|
for (const entry of entries) {
|
|
4567
4414
|
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
4568
|
-
const p =
|
|
4415
|
+
const p = path12.join(dir, entry.name);
|
|
4569
4416
|
if (entry.isDirectory()) out.push(...await walkFiles(p));
|
|
4570
4417
|
else out.push(p);
|
|
4571
4418
|
}
|
|
@@ -4574,7 +4421,7 @@ async function walkFiles(dir) {
|
|
|
4574
4421
|
async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
4575
4422
|
const viewportFindings = [];
|
|
4576
4423
|
try {
|
|
4577
|
-
const indexHtml = await
|
|
4424
|
+
const indexHtml = await fs13.readFile(path12.join(cwd, "index.html"), "utf8");
|
|
4578
4425
|
if (!/<meta[^>]+name=["']viewport["']/i.test(indexHtml)) {
|
|
4579
4426
|
viewportFindings.push({
|
|
4580
4427
|
kind: "viewport-meta",
|
|
@@ -4588,28 +4435,28 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4588
4435
|
}
|
|
4589
4436
|
} catch {
|
|
4590
4437
|
}
|
|
4591
|
-
const absAssets =
|
|
4438
|
+
const absAssets = path12.resolve(cwd, assetDir);
|
|
4592
4439
|
try {
|
|
4593
|
-
if (!(await
|
|
4440
|
+
if (!(await fs13.stat(absAssets)).isDirectory()) {
|
|
4594
4441
|
return viewportFindings.length > 0 ? viewportFindings : null;
|
|
4595
4442
|
}
|
|
4596
4443
|
} catch {
|
|
4597
4444
|
return viewportFindings.length > 0 ? viewportFindings : null;
|
|
4598
4445
|
}
|
|
4599
|
-
const srcFiles = (await walkFiles(
|
|
4600
|
-
(p) => AUDIT_SRC_EXTS.has(
|
|
4446
|
+
const srcFiles = (await walkFiles(path12.resolve(cwd, srcDir))).filter(
|
|
4447
|
+
(p) => AUDIT_SRC_EXTS.has(path12.extname(p).toLowerCase())
|
|
4601
4448
|
);
|
|
4602
4449
|
try {
|
|
4603
|
-
for (const name of await
|
|
4604
|
-
const ext =
|
|
4605
|
-
if (ext === ".html" || ext === ".css") srcFiles.push(
|
|
4450
|
+
for (const name of await fs13.readdir(cwd)) {
|
|
4451
|
+
const ext = path12.extname(name).toLowerCase();
|
|
4452
|
+
if (ext === ".html" || ext === ".css") srcFiles.push(path12.join(cwd, name));
|
|
4606
4453
|
}
|
|
4607
4454
|
} catch {
|
|
4608
4455
|
}
|
|
4609
4456
|
const sources = [];
|
|
4610
4457
|
for (const p of srcFiles) {
|
|
4611
4458
|
try {
|
|
4612
|
-
sources.push({ rel:
|
|
4459
|
+
sources.push({ rel: path12.relative(cwd, p), text: await fs13.readFile(p, "utf8") });
|
|
4613
4460
|
} catch {
|
|
4614
4461
|
}
|
|
4615
4462
|
}
|
|
@@ -4619,11 +4466,11 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4619
4466
|
const metaByName = /* @__PURE__ */ new Map();
|
|
4620
4467
|
const bboxByPng = /* @__PURE__ */ new Map();
|
|
4621
4468
|
for (const p of assetFiles) {
|
|
4622
|
-
const base =
|
|
4469
|
+
const base = path12.basename(p);
|
|
4623
4470
|
const metaMatch = /^(.+)\.annotated-progress\.json$/.exec(base);
|
|
4624
4471
|
if (metaMatch) {
|
|
4625
4472
|
try {
|
|
4626
|
-
const meta = JSON.parse(await
|
|
4473
|
+
const meta = JSON.parse(await fs13.readFile(p, "utf8"));
|
|
4627
4474
|
metaByName.set(metaMatch[1], {
|
|
4628
4475
|
cleanCrop: meta.clean?.crop ?? null,
|
|
4629
4476
|
loosened: meta.loosened === true
|
|
@@ -4634,7 +4481,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4634
4481
|
}
|
|
4635
4482
|
if (base.endsWith(".bbox.json")) {
|
|
4636
4483
|
try {
|
|
4637
|
-
const sidecar = JSON.parse(await
|
|
4484
|
+
const sidecar = JSON.parse(await fs13.readFile(p, "utf8"));
|
|
4638
4485
|
if (sidecar.sheetBBox) bboxByPng.set(base.replace(/\.bbox\.json$/, ".png"), sidecar.sheetBBox);
|
|
4639
4486
|
} catch {
|
|
4640
4487
|
}
|
|
@@ -4643,7 +4490,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4643
4490
|
for (const [name, meta] of metaByName) {
|
|
4644
4491
|
if (!meta.cleanCrop || !referenced(`${name}-mask.png`)) continue;
|
|
4645
4492
|
for (const p of assetFiles) {
|
|
4646
|
-
const base =
|
|
4493
|
+
const base = path12.basename(p);
|
|
4647
4494
|
if (!base.toLowerCase().endsWith(".png")) continue;
|
|
4648
4495
|
if (base !== `${name}.png` && !base.startsWith(`${name}-`)) continue;
|
|
4649
4496
|
if (/-mask\.png$|-frame\.png$|-overlay\.png$|-annotated-source\.png$/.test(base)) continue;
|
|
@@ -4667,7 +4514,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4667
4514
|
}
|
|
4668
4515
|
const pngByBase = /* @__PURE__ */ new Map();
|
|
4669
4516
|
for (const p of assetFiles) {
|
|
4670
|
-
const base =
|
|
4517
|
+
const base = path12.basename(p);
|
|
4671
4518
|
if (base.toLowerCase().endsWith(".png")) pngByBase.set(base, p);
|
|
4672
4519
|
}
|
|
4673
4520
|
for (const [maskBase, maskPath] of pngByBase) {
|
|
@@ -4680,8 +4527,8 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4680
4527
|
let frame;
|
|
4681
4528
|
let mask;
|
|
4682
4529
|
try {
|
|
4683
|
-
frame = PNG2.sync.read(await
|
|
4684
|
-
mask = PNG2.sync.read(await
|
|
4530
|
+
frame = PNG2.sync.read(await fs13.readFile(framePath));
|
|
4531
|
+
mask = PNG2.sync.read(await fs13.readFile(maskPath));
|
|
4685
4532
|
} catch {
|
|
4686
4533
|
continue;
|
|
4687
4534
|
}
|
|
@@ -4700,7 +4547,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4700
4547
|
if (!referenced(base)) continue;
|
|
4701
4548
|
let png;
|
|
4702
4549
|
try {
|
|
4703
|
-
png = PNG2.sync.read(await
|
|
4550
|
+
png = PNG2.sync.read(await fs13.readFile(p));
|
|
4704
4551
|
} catch {
|
|
4705
4552
|
continue;
|
|
4706
4553
|
}
|
|
@@ -4720,7 +4567,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4720
4567
|
}
|
|
4721
4568
|
const maskReported = /* @__PURE__ */ new Set();
|
|
4722
4569
|
for (const p of assetFiles) {
|
|
4723
|
-
const m = /^(.+)\.annotated-progress\.json$/.exec(
|
|
4570
|
+
const m = /^(.+)\.annotated-progress\.json$/.exec(path12.basename(p));
|
|
4724
4571
|
if (!m) continue;
|
|
4725
4572
|
const maskBase = `${m[1]}-mask.png`;
|
|
4726
4573
|
if (!referenced(maskBase)) {
|
|
@@ -4732,14 +4579,14 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4732
4579
|
}
|
|
4733
4580
|
}
|
|
4734
4581
|
for (const p of assetFiles) {
|
|
4735
|
-
const base =
|
|
4582
|
+
const base = path12.basename(p);
|
|
4736
4583
|
if (!base.toLowerCase().endsWith(".png")) continue;
|
|
4737
4584
|
if (/-annotated-source\.png$|-overlay\.png$/.test(base)) continue;
|
|
4738
4585
|
if (maskReported.has(base)) continue;
|
|
4739
4586
|
if (!referenced(base)) {
|
|
4740
4587
|
findings.push({
|
|
4741
4588
|
kind: "unwired-sprite",
|
|
4742
|
-
message: `${
|
|
4589
|
+
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.)`
|
|
4743
4590
|
});
|
|
4744
4591
|
}
|
|
4745
4592
|
}
|
|
@@ -4791,78 +4638,6 @@ async function uiAudit(opts, log) {
|
|
|
4791
4638
|
if (opts.strict || hard.length > 0) process.exitCode = 1;
|
|
4792
4639
|
}
|
|
4793
4640
|
|
|
4794
|
-
// src/lib/terms.ts
|
|
4795
|
-
import readline2 from "readline";
|
|
4796
|
-
var TERMS_ERROR_CODE = "terms_acceptance_required";
|
|
4797
|
-
async function isTermsRefusal(res) {
|
|
4798
|
-
if (res.status !== 403) return false;
|
|
4799
|
-
try {
|
|
4800
|
-
const body = await res.clone().json();
|
|
4801
|
-
return body?.error === TERMS_ERROR_CODE;
|
|
4802
|
-
} catch {
|
|
4803
|
-
return false;
|
|
4804
|
-
}
|
|
4805
|
-
}
|
|
4806
|
-
function reportTermsRefusal(log, authUrl) {
|
|
4807
|
-
log.error("Your account needs to accept the current Terms before publishing.");
|
|
4808
|
-
log.dim(" Nothing was deployed, and nothing on your game changed.");
|
|
4809
|
-
if (!inHostedSession()) {
|
|
4810
|
-
log.dim(` Run ${c.cyan("npx genex accept")} to read them and agree here.`);
|
|
4811
|
-
}
|
|
4812
|
-
log.dim(` Open ${c.cyan(`${getAuthUrl(authUrl)}/terms`)} and agree, then try again.`);
|
|
4813
|
-
}
|
|
4814
|
-
function askOnStdin(question) {
|
|
4815
|
-
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
4816
|
-
return new Promise((resolve) => {
|
|
4817
|
-
rl.question(question, (answer) => {
|
|
4818
|
-
rl.close();
|
|
4819
|
-
resolve(answer);
|
|
4820
|
-
});
|
|
4821
|
-
});
|
|
4822
|
-
}
|
|
4823
|
-
async function runAccept(opts) {
|
|
4824
|
-
const { token, log } = opts;
|
|
4825
|
-
const interactive = opts.interactive ?? Boolean(process.stdin.isTTY);
|
|
4826
|
-
const apiUrl = getApiUrl(opts.apiUrl);
|
|
4827
|
-
const web = getAuthUrl();
|
|
4828
|
-
const status = await apiFetch(`${apiUrl}/api/legal/status`, {
|
|
4829
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
4830
|
-
}).catch(() => null);
|
|
4831
|
-
if (status?.ok) {
|
|
4832
|
-
const body = await status.json().catch(() => null);
|
|
4833
|
-
if (body?.accepted) {
|
|
4834
|
-
log.success("Already accepted \u2014 nothing to do.");
|
|
4835
|
-
return true;
|
|
4836
|
-
}
|
|
4837
|
-
log.plain("Before publishing, please read and agree to:");
|
|
4838
|
-
for (const doc of body?.documents ?? []) log.plain(` ${doc.title} ${c.cyan(doc.url)}`);
|
|
4839
|
-
log.plain("");
|
|
4840
|
-
}
|
|
4841
|
-
if (!interactive) {
|
|
4842
|
-
log.error("Accepting the Terms needs a person, so this cannot run unattended.");
|
|
4843
|
-
log.dim(" Agreeing is a contract, and an agent cannot do it on your behalf.");
|
|
4844
|
-
log.dim(` Open ${c.cyan(`${web}/terms`)} and agree there, then re-run your command.`);
|
|
4845
|
-
return false;
|
|
4846
|
-
}
|
|
4847
|
-
const ask = opts.ask ?? askOnStdin;
|
|
4848
|
-
const answer = (await ask("Type 'agree' to accept: ")).trim().toLowerCase();
|
|
4849
|
-
if (answer !== "agree") {
|
|
4850
|
-
log.error("Not accepted \u2014 nothing was recorded.");
|
|
4851
|
-
return false;
|
|
4852
|
-
}
|
|
4853
|
-
const res = await apiFetch(`${apiUrl}/api/legal/accept`, {
|
|
4854
|
-
method: "POST",
|
|
4855
|
-
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
4856
|
-
body: JSON.stringify({ surface: "cli" })
|
|
4857
|
-
}).catch(() => null);
|
|
4858
|
-
if (!res?.ok) {
|
|
4859
|
-
log.error(`Couldn't record your acceptance (HTTP ${res?.status ?? "no response"}).`);
|
|
4860
|
-
return false;
|
|
4861
|
-
}
|
|
4862
|
-
log.success("Accepted. Re-run your command.");
|
|
4863
|
-
return true;
|
|
4864
|
-
}
|
|
4865
|
-
|
|
4866
4641
|
// src/lib/deploy.ts
|
|
4867
4642
|
function printMobilePreflight(files, log) {
|
|
4868
4643
|
try {
|
|
@@ -4926,7 +4701,7 @@ async function printUiAuditPreflight(log) {
|
|
|
4926
4701
|
}
|
|
4927
4702
|
async function printPipelineStatePreflight(log, cwd = process.cwd()) {
|
|
4928
4703
|
try {
|
|
4929
|
-
const design = await
|
|
4704
|
+
const design = await fs14.readFile(path13.join(cwd, "DESIGN.md"), "utf8").catch(() => "");
|
|
4930
4705
|
const warnings = [];
|
|
4931
4706
|
if (design.length > 0 && !/##\s*build plan & status/i.test(design)) {
|
|
4932
4707
|
warnings.push(
|
|
@@ -4934,7 +4709,7 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
|
|
|
4934
4709
|
);
|
|
4935
4710
|
}
|
|
4936
4711
|
if (!/player character:/i.test(design)) {
|
|
4937
|
-
const hasCharacter = await
|
|
4712
|
+
const hasCharacter = await fs14.access(path13.join(cwd, "public", "assets", "meshy-character.json")).then(() => true, () => false);
|
|
4938
4713
|
if (!hasCharacter && await loadsPlayerBody(cwd)) {
|
|
4939
4714
|
warnings.push(
|
|
4940
4715
|
`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).`
|
|
@@ -4949,12 +4724,12 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
|
|
|
4949
4724
|
async function loadsPlayerBody(cwd) {
|
|
4950
4725
|
const BODY_LOADERS = /\bloadPlayerCharacter\s*\(|\bloadVrm(?:Clone)?\s*\(/;
|
|
4951
4726
|
try {
|
|
4952
|
-
const entries = await
|
|
4727
|
+
const entries = await fs14.readdir(path13.join(cwd, "src"), { recursive: true });
|
|
4953
4728
|
for (const rel of entries) {
|
|
4954
4729
|
if (rel.includes("node_modules")) continue;
|
|
4955
|
-
if (rel.split(
|
|
4730
|
+
if (rel.split(path13.sep)[0] === "controllers" || rel.startsWith("controllers/")) continue;
|
|
4956
4731
|
if (!/\.(ts|tsx|js|jsx)$/.test(rel)) continue;
|
|
4957
|
-
const text = await
|
|
4732
|
+
const text = await fs14.readFile(path13.join(cwd, "src", rel), "utf8").catch(() => "");
|
|
4958
4733
|
if (BODY_LOADERS.test(text)) return true;
|
|
4959
4734
|
}
|
|
4960
4735
|
} catch {
|
|
@@ -4991,9 +4766,9 @@ async function deployGame(ctx, opts, log) {
|
|
|
4991
4766
|
}
|
|
4992
4767
|
log.success("Built.");
|
|
4993
4768
|
}
|
|
4994
|
-
const distDir =
|
|
4769
|
+
const distDir = path13.join(cwd, "dist");
|
|
4995
4770
|
const siteDir = await isDir(distDir) ? distDir : cwd;
|
|
4996
|
-
const rel =
|
|
4771
|
+
const rel = path13.relative(cwd, siteDir) || ".";
|
|
4997
4772
|
if (siteDir === cwd) await writeGitignore(cwd, log);
|
|
4998
4773
|
const files = await collectFiles(siteDir);
|
|
4999
4774
|
if (files.length === 0) {
|
|
@@ -5082,7 +4857,7 @@ async function deployGame(ctx, opts, log) {
|
|
|
5082
4857
|
}
|
|
5083
4858
|
async function hasBuildScript(cwd) {
|
|
5084
4859
|
try {
|
|
5085
|
-
const pkg = JSON.parse(await
|
|
4860
|
+
const pkg = JSON.parse(await fs14.readFile(path13.join(cwd, "package.json"), "utf8"));
|
|
5086
4861
|
return Boolean(pkg.scripts?.build);
|
|
5087
4862
|
} catch {
|
|
5088
4863
|
return false;
|
|
@@ -5091,12 +4866,12 @@ async function hasBuildScript(cwd) {
|
|
|
5091
4866
|
async function collectFiles(root) {
|
|
5092
4867
|
const out = [];
|
|
5093
4868
|
const walk2 = async (dir, prefix) => {
|
|
5094
|
-
for (const e of await
|
|
4869
|
+
for (const e of await fs14.readdir(dir, { withFileTypes: true })) {
|
|
5095
4870
|
const relPath = prefix ? `${prefix}/${e.name}` : e.name;
|
|
5096
4871
|
if (e.isDirectory()) {
|
|
5097
|
-
if (!EXCLUDE_DIRS.has(e.name)) await walk2(
|
|
4872
|
+
if (!EXCLUDE_DIRS.has(e.name)) await walk2(path13.join(dir, e.name), relPath);
|
|
5098
4873
|
} else if (e.isFile() && !isSecretEnvFile(e.name)) {
|
|
5099
|
-
out.push({ relPath, bytes: await
|
|
4874
|
+
out.push({ relPath, bytes: await fs14.readFile(path13.join(dir, e.name)) });
|
|
5100
4875
|
}
|
|
5101
4876
|
}
|
|
5102
4877
|
};
|
|
@@ -5349,7 +5124,7 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
|
|
|
5349
5124
|
log.error("Couldn't save your game's source \u2014 please try again.");
|
|
5350
5125
|
return false;
|
|
5351
5126
|
};
|
|
5352
|
-
const gitDir = await
|
|
5127
|
+
const gitDir = await fs14.mkdtemp(path13.join(os7.tmpdir(), "genex-source-"));
|
|
5353
5128
|
const base = { GIT_DIR: gitDir };
|
|
5354
5129
|
if (urlHasEmbeddedCredentials(pushUrl)) {
|
|
5355
5130
|
base.GIT_CONFIG_COUNT = "1";
|
|
@@ -5364,12 +5139,12 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
|
|
|
5364
5139
|
};
|
|
5365
5140
|
try {
|
|
5366
5141
|
if ((await run("git", ["init", "-q"], base)).code !== 0) return failed();
|
|
5367
|
-
await
|
|
5368
|
-
|
|
5142
|
+
await fs14.writeFile(
|
|
5143
|
+
path13.join(gitDir, "info", "exclude"),
|
|
5369
5144
|
// .env* are secrets — never publish them; `!` keeps the non-secret template.
|
|
5370
5145
|
["node_modules/", "dist/", ".git/", ".genex/", ".env", ".env.*", "!.env.example", ""].join("\n")
|
|
5371
5146
|
);
|
|
5372
|
-
const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE:
|
|
5147
|
+
const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path13.join(gitDir, "index-source") };
|
|
5373
5148
|
let lfs = (await run("git", ["lfs", "version"], base)).code !== 0 ? false : true;
|
|
5374
5149
|
if (!lfs) {
|
|
5375
5150
|
log.step("Installing git-lfs (keeps large binary assets out of the source push)\u2026");
|
|
@@ -5421,7 +5196,7 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
|
|
|
5421
5196
|
} catch {
|
|
5422
5197
|
return failed();
|
|
5423
5198
|
} finally {
|
|
5424
|
-
await
|
|
5199
|
+
await fs14.rm(gitDir, { recursive: true, force: true }).catch(() => {
|
|
5425
5200
|
});
|
|
5426
5201
|
}
|
|
5427
5202
|
}
|
|
@@ -5457,7 +5232,7 @@ async function fetchPushUrl(ctx, log, expectStagingCommit) {
|
|
|
5457
5232
|
return null;
|
|
5458
5233
|
}
|
|
5459
5234
|
if (await isTermsRefusal(res)) {
|
|
5460
|
-
reportTermsRefusal(log);
|
|
5235
|
+
if (!printedStructuredError(res)) reportTermsRefusal(log);
|
|
5461
5236
|
return null;
|
|
5462
5237
|
}
|
|
5463
5238
|
if (!res.ok) {
|
|
@@ -5475,7 +5250,7 @@ async function fetchPushUrl(ctx, log, expectStagingCommit) {
|
|
|
5475
5250
|
}
|
|
5476
5251
|
async function isDir(p) {
|
|
5477
5252
|
try {
|
|
5478
|
-
return (await
|
|
5253
|
+
return (await fs14.stat(p)).isDirectory();
|
|
5479
5254
|
} catch {
|
|
5480
5255
|
return false;
|
|
5481
5256
|
}
|
|
@@ -5957,11 +5732,11 @@ async function promoteBuild(apiUrl, projectId, token, log) {
|
|
|
5957
5732
|
}
|
|
5958
5733
|
|
|
5959
5734
|
// src/lib/detect-features.ts
|
|
5960
|
-
import
|
|
5961
|
-
import
|
|
5735
|
+
import fs15 from "fs/promises";
|
|
5736
|
+
import path14 from "path";
|
|
5962
5737
|
async function detectEmbedSdkVersion(cwd = process.cwd()) {
|
|
5963
5738
|
try {
|
|
5964
|
-
const raw = await
|
|
5739
|
+
const raw = await fs15.readFile(path14.join(cwd, "package.json"), "utf8");
|
|
5965
5740
|
const pkg = JSON.parse(raw);
|
|
5966
5741
|
const version = pkg.dependencies?.["@genex-ai/embed-sdk"] ?? pkg.devDependencies?.["@genex-ai/embed-sdk"];
|
|
5967
5742
|
return typeof version === "string" && version ? version : null;
|
|
@@ -5971,7 +5746,7 @@ async function detectEmbedSdkVersion(cwd = process.cwd()) {
|
|
|
5971
5746
|
}
|
|
5972
5747
|
async function detectMultiplayer(cwd = process.cwd()) {
|
|
5973
5748
|
try {
|
|
5974
|
-
const raw = await
|
|
5749
|
+
const raw = await fs15.readFile(path14.join(cwd, "package.json"), "utf8");
|
|
5975
5750
|
const pkg = JSON.parse(raw);
|
|
5976
5751
|
return Boolean(
|
|
5977
5752
|
pkg.dependencies?.["@genex-ai/multiplayer"] ?? pkg.devDependencies?.["@genex-ai/multiplayer"]
|
|
@@ -5983,7 +5758,7 @@ async function detectMultiplayer(cwd = process.cwd()) {
|
|
|
5983
5758
|
async function detectMatchmaking(log, cwd = process.cwd()) {
|
|
5984
5759
|
let pkg;
|
|
5985
5760
|
try {
|
|
5986
|
-
pkg = JSON.parse(await
|
|
5761
|
+
pkg = JSON.parse(await fs15.readFile(path14.join(cwd, "package.json"), "utf8"));
|
|
5987
5762
|
} catch (err) {
|
|
5988
5763
|
log.dim(` (skipping matchmaking \u2014 couldn't read package.json: ${String(err)})`);
|
|
5989
5764
|
return null;
|
|
@@ -6001,15 +5776,15 @@ async function detectMatchmaking(log, cwd = process.cwd()) {
|
|
|
6001
5776
|
var TOUCH_KIT_MARKERS = /controllers\/touch\/|controllers\/character\/touch-joystick|TouchJoystick|VirtualButton|DragZone|RotateOverlay/;
|
|
6002
5777
|
async function detectMobileControls(cwd = process.cwd()) {
|
|
6003
5778
|
try {
|
|
6004
|
-
const raw = await
|
|
5779
|
+
const raw = await fs15.readFile(path14.join(cwd, "package.json"), "utf8");
|
|
6005
5780
|
const pkg = JSON.parse(raw);
|
|
6006
5781
|
if (pkg.genex?.mobileControls === true) return true;
|
|
6007
5782
|
} catch {
|
|
6008
5783
|
}
|
|
6009
|
-
const srcDir =
|
|
5784
|
+
const srcDir = path14.join(cwd, "src");
|
|
6010
5785
|
let entries;
|
|
6011
5786
|
try {
|
|
6012
|
-
entries = await
|
|
5787
|
+
entries = await fs15.readdir(srcDir, { recursive: true });
|
|
6013
5788
|
} catch {
|
|
6014
5789
|
return false;
|
|
6015
5790
|
}
|
|
@@ -6017,7 +5792,7 @@ async function detectMobileControls(cwd = process.cwd()) {
|
|
|
6017
5792
|
if (rel.includes("node_modules")) continue;
|
|
6018
5793
|
if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(rel)) continue;
|
|
6019
5794
|
try {
|
|
6020
|
-
const content = await
|
|
5795
|
+
const content = await fs15.readFile(path14.join(srcDir, rel), "utf8");
|
|
6021
5796
|
if (TOUCH_KIT_MARKERS.test(content)) return true;
|
|
6022
5797
|
} catch {
|
|
6023
5798
|
}
|
|
@@ -6026,10 +5801,10 @@ async function detectMobileControls(cwd = process.cwd()) {
|
|
|
6026
5801
|
}
|
|
6027
5802
|
var GAME_STATE_CALLS = /savePlayerState|saveWorldState|submitScore/;
|
|
6028
5803
|
async function detectGameStateUsage(cwd = process.cwd()) {
|
|
6029
|
-
const srcDir =
|
|
5804
|
+
const srcDir = path14.join(cwd, "src");
|
|
6030
5805
|
let entries;
|
|
6031
5806
|
try {
|
|
6032
|
-
entries = await
|
|
5807
|
+
entries = await fs15.readdir(srcDir, { recursive: true });
|
|
6033
5808
|
} catch {
|
|
6034
5809
|
return false;
|
|
6035
5810
|
}
|
|
@@ -6037,7 +5812,7 @@ async function detectGameStateUsage(cwd = process.cwd()) {
|
|
|
6037
5812
|
if (rel.includes("node_modules")) continue;
|
|
6038
5813
|
if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(rel)) continue;
|
|
6039
5814
|
try {
|
|
6040
|
-
const content = await
|
|
5815
|
+
const content = await fs15.readFile(path14.join(srcDir, rel), "utf8");
|
|
6041
5816
|
if (GAME_STATE_CALLS.test(content)) return true;
|
|
6042
5817
|
} catch {
|
|
6043
5818
|
}
|
|
@@ -6088,19 +5863,19 @@ async function detectSurfaceScan(cwd = process.cwd()) {
|
|
|
6088
5863
|
deferredAudioContext: [],
|
|
6089
5864
|
usesThree: false
|
|
6090
5865
|
};
|
|
6091
|
-
const srcDir =
|
|
5866
|
+
const srcDir = path14.join(cwd, "src");
|
|
6092
5867
|
let entries;
|
|
6093
5868
|
try {
|
|
6094
|
-
entries = await
|
|
5869
|
+
entries = await fs15.readdir(srcDir, { recursive: true });
|
|
6095
5870
|
} catch {
|
|
6096
5871
|
return found;
|
|
6097
5872
|
}
|
|
6098
5873
|
for (const nativeRel of entries) {
|
|
6099
5874
|
if (nativeRel.includes("node_modules")) continue;
|
|
6100
5875
|
if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(nativeRel)) continue;
|
|
6101
|
-
const raw = await
|
|
5876
|
+
const raw = await fs15.readFile(path14.join(srcDir, nativeRel), "utf8").catch(() => "");
|
|
6102
5877
|
if (!raw) continue;
|
|
6103
|
-
const rel = nativeRel.split(
|
|
5878
|
+
const rel = nativeRel.split(path14.sep).join("/");
|
|
6104
5879
|
const content = raw.replace(/\/\*[\s\S]*?\*\//g, (m2) => m2.replace(/[^\n]/g, " ")).replace(/(^|[^:])\/\/[^\n]*/gm, (m2, p1) => p1 + " ".repeat(m2.length - p1.length));
|
|
6105
5880
|
const repeatRe = /\.repeat\.set\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\)/g;
|
|
6106
5881
|
let m;
|
|
@@ -6181,25 +5956,25 @@ async function detectGenerationAudit(cwd = process.cwd()) {
|
|
|
6181
5956
|
let haystack = "";
|
|
6182
5957
|
const read = async (file) => {
|
|
6183
5958
|
try {
|
|
6184
|
-
haystack += await
|
|
5959
|
+
haystack += await fs15.readFile(file, "utf8");
|
|
6185
5960
|
} catch {
|
|
6186
5961
|
}
|
|
6187
5962
|
};
|
|
6188
5963
|
try {
|
|
6189
|
-
for (const entry of await
|
|
5964
|
+
for (const entry of await fs15.readdir(cwd, { withFileTypes: true })) {
|
|
6190
5965
|
if (entry.isFile() && /\.(ts|tsx|js|jsx|css|html|json)$/.test(entry.name)) {
|
|
6191
|
-
await read(
|
|
5966
|
+
await read(path14.join(cwd, entry.name));
|
|
6192
5967
|
}
|
|
6193
5968
|
}
|
|
6194
5969
|
} catch {
|
|
6195
5970
|
}
|
|
6196
5971
|
for (const sub of ["src", "public"]) {
|
|
6197
5972
|
try {
|
|
6198
|
-
const entries = await
|
|
5973
|
+
const entries = await fs15.readdir(path14.join(cwd, sub), { recursive: true });
|
|
6199
5974
|
for (const rel of entries) {
|
|
6200
5975
|
if (rel.includes("node_modules")) continue;
|
|
6201
5976
|
if (!/\.(ts|tsx|js|jsx|css|html|json|txt)$/.test(rel)) continue;
|
|
6202
|
-
await read(
|
|
5977
|
+
await read(path14.join(cwd, sub, rel));
|
|
6203
5978
|
}
|
|
6204
5979
|
} catch {
|
|
6205
5980
|
}
|
|
@@ -6346,7 +6121,7 @@ async function borrowEvidence(meta, cwd) {
|
|
|
6346
6121
|
} catch {
|
|
6347
6122
|
return true;
|
|
6348
6123
|
}
|
|
6349
|
-
const gitConfig = await
|
|
6124
|
+
const gitConfig = await fs15.readFile(path14.join(cwd, ".git", "config"), "utf8").catch(() => "");
|
|
6350
6125
|
for (const m of gitConfig.matchAll(/url\s*=\s*(\S+)/g)) {
|
|
6351
6126
|
try {
|
|
6352
6127
|
const u = new URL(m[1]);
|
|
@@ -6354,7 +6129,7 @@ async function borrowEvidence(meta, cwd) {
|
|
|
6354
6129
|
} catch {
|
|
6355
6130
|
}
|
|
6356
6131
|
}
|
|
6357
|
-
const readme = await
|
|
6132
|
+
const readme = await fs15.readFile(path14.join(cwd, "README.md"), "utf8").catch(() => "");
|
|
6358
6133
|
return readme.includes(host) || /\b(upstream|originally by|ported from|borrowed from)\b/i.test(readme);
|
|
6359
6134
|
}
|
|
6360
6135
|
|
|
@@ -6616,11 +6391,12 @@ async function runAcceptCommand(opts) {
|
|
|
6616
6391
|
process.exitCode = 1;
|
|
6617
6392
|
return;
|
|
6618
6393
|
}
|
|
6619
|
-
const meta = await readProject();
|
|
6394
|
+
const [meta, workspace] = await Promise.all([readProject(), readWorkspace()]);
|
|
6395
|
+
const apiUrl = meta?.apiUrl ?? workspace?.apiUrl;
|
|
6620
6396
|
const ok = await runAccept({
|
|
6621
6397
|
token,
|
|
6622
6398
|
log,
|
|
6623
|
-
...
|
|
6399
|
+
...apiUrl ? { apiUrl } : {}
|
|
6624
6400
|
});
|
|
6625
6401
|
if (!ok) process.exitCode = 1;
|
|
6626
6402
|
}
|
|
@@ -6834,8 +6610,8 @@ async function runRollback(opts) {
|
|
|
6834
6610
|
}
|
|
6835
6611
|
|
|
6836
6612
|
// src/commands/generate.ts
|
|
6837
|
-
import
|
|
6838
|
-
import
|
|
6613
|
+
import fs17 from "fs/promises";
|
|
6614
|
+
import path16 from "path";
|
|
6839
6615
|
import { PNG as PNG4 } from "pngjs";
|
|
6840
6616
|
|
|
6841
6617
|
// src/lib/glass.ts
|
|
@@ -6916,8 +6692,8 @@ function solveMagentaGlass(source) {
|
|
|
6916
6692
|
}
|
|
6917
6693
|
|
|
6918
6694
|
// src/lib/download-assets.ts
|
|
6919
|
-
import
|
|
6920
|
-
import
|
|
6695
|
+
import fs16 from "fs/promises";
|
|
6696
|
+
import path15 from "path";
|
|
6921
6697
|
var RUNG_ROLE = /@\d+$/;
|
|
6922
6698
|
function isPrimaryRole(role) {
|
|
6923
6699
|
return !RUNG_ROLE.test(role);
|
|
@@ -6937,7 +6713,7 @@ async function downloadAssets(files, opts) {
|
|
|
6937
6713
|
const result = { saved: [], failures: [] };
|
|
6938
6714
|
if (wanted.length === 0) return result;
|
|
6939
6715
|
try {
|
|
6940
|
-
await
|
|
6716
|
+
await fs16.mkdir(opts.outDir, { recursive: true });
|
|
6941
6717
|
} catch (err) {
|
|
6942
6718
|
result.failures.push(
|
|
6943
6719
|
`couldn't create ${opts.outDir} (${err instanceof Error ? err.message : String(err)})`
|
|
@@ -6946,12 +6722,12 @@ async function downloadAssets(files, opts) {
|
|
|
6946
6722
|
}
|
|
6947
6723
|
const multi = wanted.length > 1;
|
|
6948
6724
|
for (const file of wanted) {
|
|
6949
|
-
const dest =
|
|
6725
|
+
const dest = path15.join(opts.outDir, localAssetName(opts.kind, opts.prompt, opts.id, file, multi));
|
|
6950
6726
|
try {
|
|
6951
6727
|
const res = await fetch(file.url);
|
|
6952
6728
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
6953
6729
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
6954
|
-
await
|
|
6730
|
+
await fs16.writeFile(dest, buf);
|
|
6955
6731
|
result.saved.push({ role: file.role, path: dest, url: file.url, bytes: buf.byteLength });
|
|
6956
6732
|
} catch (err) {
|
|
6957
6733
|
result.failures.push(
|
|
@@ -7006,7 +6782,7 @@ function mockLaneRefusal(kind, provider) {
|
|
|
7006
6782
|
}
|
|
7007
6783
|
|
|
7008
6784
|
// src/lib/open.ts
|
|
7009
|
-
import { spawn as
|
|
6785
|
+
import { spawn as spawn3 } from "child_process";
|
|
7010
6786
|
function tokenize(cmd) {
|
|
7011
6787
|
return cmd.trim().split(/\s+/).filter(Boolean);
|
|
7012
6788
|
}
|
|
@@ -7035,7 +6811,7 @@ function openUrl(url) {
|
|
|
7035
6811
|
}
|
|
7036
6812
|
}
|
|
7037
6813
|
try {
|
|
7038
|
-
const child =
|
|
6814
|
+
const child = spawn3(command, args, { stdio: "ignore", detached: true });
|
|
7039
6815
|
child.on("error", () => {
|
|
7040
6816
|
});
|
|
7041
6817
|
child.unref();
|
|
@@ -7095,7 +6871,7 @@ var isRemoteRef = (value) => /^(https?:\/\/|data:)/i.test(value);
|
|
|
7095
6871
|
async function inlineLocalImage(filePath, flag) {
|
|
7096
6872
|
let bytes;
|
|
7097
6873
|
try {
|
|
7098
|
-
bytes = await
|
|
6874
|
+
bytes = await fs17.readFile(filePath);
|
|
7099
6875
|
} catch {
|
|
7100
6876
|
return { ok: false, error: `Couldn't read the ${flag} file at ${filePath}.` };
|
|
7101
6877
|
}
|
|
@@ -7105,7 +6881,7 @@ async function inlineLocalImage(filePath, flag) {
|
|
|
7105
6881
|
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.`
|
|
7106
6882
|
};
|
|
7107
6883
|
}
|
|
7108
|
-
const mime = IMAGE_MIME_BY_EXT[
|
|
6884
|
+
const mime = IMAGE_MIME_BY_EXT[path16.extname(filePath).toLowerCase()] ?? "image/png";
|
|
7109
6885
|
return { ok: true, dataUri: `data:${mime};base64,${bytes.toString("base64")}` };
|
|
7110
6886
|
}
|
|
7111
6887
|
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.";
|
|
@@ -7263,7 +7039,7 @@ async function runGenerate(kind, opts) {
|
|
|
7263
7039
|
let typedPrompt = opts.prompt?.trim();
|
|
7264
7040
|
if (!typedPrompt && kind === "model" && opts.imageUrl) {
|
|
7265
7041
|
const ref = opts.imageUrl.startsWith("data:") ? "local image" : opts.imageUrl;
|
|
7266
|
-
typedPrompt = `from image: ${
|
|
7042
|
+
typedPrompt = `from image: ${path16.basename(ref).slice(0, 120)}`;
|
|
7267
7043
|
}
|
|
7268
7044
|
if (!typedPrompt) {
|
|
7269
7045
|
log.error(`Missing prompt. Usage: ${c.cyan(`genex ${kind} "<prompt>"`)}`);
|
|
@@ -7307,7 +7083,7 @@ async function runGenerate(kind, opts) {
|
|
|
7307
7083
|
return;
|
|
7308
7084
|
}
|
|
7309
7085
|
try {
|
|
7310
|
-
const bytes = await
|
|
7086
|
+
const bytes = await fs17.readFile(opts.inpaintUrl);
|
|
7311
7087
|
opts = { ...opts, inpaintUrl: `data:image/png;base64,${bytes.toString("base64")}` };
|
|
7312
7088
|
} catch {
|
|
7313
7089
|
log.error(`Couldn't read the --inpaint mask at ${opts.inpaintUrl}.`);
|
|
@@ -7492,7 +7268,7 @@ async function reportGlassTerminal(view, outDir, log, json) {
|
|
|
7492
7268
|
return;
|
|
7493
7269
|
}
|
|
7494
7270
|
await recordTerminal(view.id, "completed", files.map((f) => f.url));
|
|
7495
|
-
await
|
|
7271
|
+
await fs17.mkdir(outDir, { recursive: true });
|
|
7496
7272
|
const solved = [];
|
|
7497
7273
|
for (let i = 0; i < files.length; i++) {
|
|
7498
7274
|
const f = files[i];
|
|
@@ -7524,8 +7300,8 @@ async function reportGlassTerminal(view, outDir, log, json) {
|
|
|
7524
7300
|
});
|
|
7525
7301
|
continue;
|
|
7526
7302
|
}
|
|
7527
|
-
const outPath =
|
|
7528
|
-
await
|
|
7303
|
+
const outPath = path16.join(outDir, `glass-${i + 1}.png`);
|
|
7304
|
+
await fs17.writeFile(outPath, PNG4.sync.write(r.png));
|
|
7529
7305
|
solved.push({
|
|
7530
7306
|
path: outPath,
|
|
7531
7307
|
url: f.url,
|
|
@@ -8291,8 +8067,8 @@ async function toRow(e, v, cwd) {
|
|
|
8291
8067
|
}
|
|
8292
8068
|
|
|
8293
8069
|
// src/commands/controller.ts
|
|
8294
|
-
import
|
|
8295
|
-
import
|
|
8070
|
+
import fs19 from "fs/promises";
|
|
8071
|
+
import path18 from "path";
|
|
8296
8072
|
|
|
8297
8073
|
// ../../packages/meshy-animation-catalog/src/index.ts
|
|
8298
8074
|
import { createHash } from "crypto";
|
|
@@ -17422,9 +17198,9 @@ function searchMeshyAnimations(query, options = {}) {
|
|
|
17422
17198
|
}
|
|
17423
17199
|
|
|
17424
17200
|
// src/lib/anims.ts
|
|
17425
|
-
import
|
|
17426
|
-
import
|
|
17427
|
-
var ANIMS_DEST =
|
|
17201
|
+
import fs18 from "fs/promises";
|
|
17202
|
+
import path17 from "path";
|
|
17203
|
+
var ANIMS_DEST = path17.join("public", "assets", "anims");
|
|
17428
17204
|
var HIDDEN_TAG = "reference";
|
|
17429
17205
|
async function runAnims(opts) {
|
|
17430
17206
|
const log = createLogger({ quiet: opts.quiet });
|
|
@@ -17440,7 +17216,7 @@ async function runAnims(opts) {
|
|
|
17440
17216
|
printCatalog(log, manifest, selectors);
|
|
17441
17217
|
return;
|
|
17442
17218
|
}
|
|
17443
|
-
const controllerMarker =
|
|
17219
|
+
const controllerMarker = path17.join(root, "src", "controllers", "character");
|
|
17444
17220
|
if (!await exists2(controllerMarker)) {
|
|
17445
17221
|
log.error(
|
|
17446
17222
|
`No character controller in this game (missing ${c.cyan("src/controllers/character/")}).`
|
|
@@ -17449,11 +17225,11 @@ async function runAnims(opts) {
|
|
|
17449
17225
|
process.exitCode = 1;
|
|
17450
17226
|
return;
|
|
17451
17227
|
}
|
|
17452
|
-
const destDir =
|
|
17453
|
-
const gameManifestPath =
|
|
17228
|
+
const destDir = path17.join(root, ANIMS_DEST);
|
|
17229
|
+
const gameManifestPath = path17.join(destDir, "manifest.json");
|
|
17454
17230
|
if (opts.reset) {
|
|
17455
|
-
await
|
|
17456
|
-
log.step(`Cleared ${c.cyan(ANIMS_DEST +
|
|
17231
|
+
await fs18.rm(destDir, { recursive: true, force: true });
|
|
17232
|
+
log.step(`Cleared ${c.cyan(ANIMS_DEST + path17.sep)} (--reset)`);
|
|
17457
17233
|
}
|
|
17458
17234
|
if (selectors.length === 0) {
|
|
17459
17235
|
const installed = await readGameManifest(gameManifestPath);
|
|
@@ -17491,35 +17267,35 @@ async function runAnims(opts) {
|
|
|
17491
17267
|
}
|
|
17492
17268
|
}
|
|
17493
17269
|
const wanted = [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
17494
|
-
const cacheDir =
|
|
17270
|
+
const cacheDir = path17.join(
|
|
17495
17271
|
opts.cacheDir ?? getAnimsCacheDir(),
|
|
17496
17272
|
`${manifest.library}-v${manifest.version}`
|
|
17497
17273
|
);
|
|
17498
|
-
await
|
|
17499
|
-
await
|
|
17274
|
+
await fs18.mkdir(cacheDir, { recursive: true });
|
|
17275
|
+
await fs18.mkdir(destDir, { recursive: true });
|
|
17500
17276
|
const base = getAnimsBase(opts.animsBase);
|
|
17501
17277
|
let installedCount = 0;
|
|
17502
17278
|
let presentCount = 0;
|
|
17503
17279
|
let addedBytes = 0;
|
|
17504
17280
|
const failures = [];
|
|
17505
17281
|
for (const entry of wanted) {
|
|
17506
|
-
const dest =
|
|
17282
|
+
const dest = path17.join(destDir, entry.file);
|
|
17507
17283
|
if (await hasSize(dest, entry.bytes)) {
|
|
17508
17284
|
presentCount++;
|
|
17509
17285
|
continue;
|
|
17510
17286
|
}
|
|
17511
17287
|
try {
|
|
17512
|
-
const cached =
|
|
17288
|
+
const cached = path17.join(cacheDir, entry.file);
|
|
17513
17289
|
if (!await hasSize(cached, entry.bytes)) {
|
|
17514
17290
|
const res = await fetch(base + entry.file);
|
|
17515
17291
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
17516
17292
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
17517
|
-
await
|
|
17293
|
+
await fs18.writeFile(cached, buf);
|
|
17518
17294
|
}
|
|
17519
|
-
await
|
|
17295
|
+
await fs18.copyFile(cached, dest);
|
|
17520
17296
|
installedCount++;
|
|
17521
17297
|
addedBytes += entry.bytes;
|
|
17522
|
-
log.dim(` ${
|
|
17298
|
+
log.dim(` ${path17.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
|
|
17523
17299
|
} catch (err) {
|
|
17524
17300
|
failures.push(`${entry.name} (${err instanceof Error ? err.message : String(err)})`);
|
|
17525
17301
|
}
|
|
@@ -17535,13 +17311,13 @@ async function runAnims(opts) {
|
|
|
17535
17311
|
version: manifest.version,
|
|
17536
17312
|
clips: [...union].sort((a, b) => a.localeCompare(b))
|
|
17537
17313
|
};
|
|
17538
|
-
await
|
|
17314
|
+
await fs18.writeFile(gameManifestPath, JSON.stringify(gameManifest, null, 2) + "\n");
|
|
17539
17315
|
log.plain("");
|
|
17540
17316
|
const parts = [`${installedCount} clip${installedCount === 1 ? "" : "s"} installed`];
|
|
17541
17317
|
if (presentCount > 0) parts.push(`${presentCount} already present`);
|
|
17542
17318
|
if (bundledSkips > 0) parts.push(`${bundledSkips} already bundled in animation-library.glb`);
|
|
17543
17319
|
log.success(
|
|
17544
|
-
`${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST +
|
|
17320
|
+
`${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path17.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
|
|
17545
17321
|
);
|
|
17546
17322
|
for (const [selector, entries] of resolved) {
|
|
17547
17323
|
const names = entries.filter((e) => !coreNames.has(e.name)).map((e) => e.name);
|
|
@@ -17573,8 +17349,8 @@ async function loadManifest(baseOverride) {
|
|
|
17573
17349
|
}
|
|
17574
17350
|
} catch {
|
|
17575
17351
|
}
|
|
17576
|
-
const snapshotPath =
|
|
17577
|
-
const manifest = JSON.parse(await
|
|
17352
|
+
const snapshotPath = path17.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
|
|
17353
|
+
const manifest = JSON.parse(await fs18.readFile(snapshotPath, "utf8"));
|
|
17578
17354
|
return { manifest, source: "snapshot" };
|
|
17579
17355
|
}
|
|
17580
17356
|
function resolveSelectors(manifest, selectors) {
|
|
@@ -17692,21 +17468,21 @@ function printCatalog(log, manifest, selectors) {
|
|
|
17692
17468
|
}
|
|
17693
17469
|
async function readGameManifest(file) {
|
|
17694
17470
|
try {
|
|
17695
|
-
return JSON.parse(await
|
|
17471
|
+
return JSON.parse(await fs18.readFile(file, "utf8"));
|
|
17696
17472
|
} catch {
|
|
17697
17473
|
return null;
|
|
17698
17474
|
}
|
|
17699
17475
|
}
|
|
17700
17476
|
async function hasSize(file, bytes) {
|
|
17701
17477
|
try {
|
|
17702
|
-
return (await
|
|
17478
|
+
return (await fs18.stat(file)).size === bytes;
|
|
17703
17479
|
} catch {
|
|
17704
17480
|
return false;
|
|
17705
17481
|
}
|
|
17706
17482
|
}
|
|
17707
17483
|
async function exists2(p) {
|
|
17708
17484
|
try {
|
|
17709
|
-
await
|
|
17485
|
+
await fs18.access(p);
|
|
17710
17486
|
return true;
|
|
17711
17487
|
} catch {
|
|
17712
17488
|
return false;
|
|
@@ -17900,8 +17676,8 @@ var CONTROLLER_FILE_SETS = {
|
|
|
17900
17676
|
]
|
|
17901
17677
|
}
|
|
17902
17678
|
};
|
|
17903
|
-
var CODE_DEST =
|
|
17904
|
-
var ASSETS_DEST =
|
|
17679
|
+
var CODE_DEST = path18.join("src", "controllers");
|
|
17680
|
+
var ASSETS_DEST = path18.join("public", "assets");
|
|
17905
17681
|
async function runController(opts) {
|
|
17906
17682
|
const log = createLogger({ quiet: opts.quiet });
|
|
17907
17683
|
if (opts.kind?.trim() === "anims") {
|
|
@@ -17918,31 +17694,31 @@ async function runController(opts) {
|
|
|
17918
17694
|
process.exitCode = 1;
|
|
17919
17695
|
return;
|
|
17920
17696
|
}
|
|
17921
|
-
const srcDir =
|
|
17697
|
+
const srcDir = path18.join(getTemplatesDir(), "controllers");
|
|
17922
17698
|
const root = opts.cwd ?? process.cwd();
|
|
17923
17699
|
const set = CONTROLLER_FILE_SETS[kind];
|
|
17924
17700
|
log.plain(c.bold(`genex controller ${kind}`));
|
|
17925
17701
|
log.plain("");
|
|
17926
|
-
log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST +
|
|
17702
|
+
log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path18.sep)}`);
|
|
17927
17703
|
const plan = [
|
|
17928
|
-
...set.code.map((rel) => ({ from: rel, rel:
|
|
17704
|
+
...set.code.map((rel) => ({ from: rel, rel: path18.join(CODE_DEST, rel) })),
|
|
17929
17705
|
...set.assets.map((rel) => ({
|
|
17930
17706
|
from: rel,
|
|
17931
|
-
rel:
|
|
17707
|
+
rel: path18.join(ASSETS_DEST, path18.basename(rel))
|
|
17932
17708
|
}))
|
|
17933
17709
|
];
|
|
17934
17710
|
let copied = 0;
|
|
17935
17711
|
let skipped = 0;
|
|
17936
17712
|
try {
|
|
17937
17713
|
for (const file of plan) {
|
|
17938
|
-
const dest =
|
|
17714
|
+
const dest = path18.join(root, file.rel);
|
|
17939
17715
|
if (!opts.force && await exists3(dest)) {
|
|
17940
17716
|
skipped++;
|
|
17941
17717
|
log.dim(` skipped ${file.rel} (exists \u2014 use --force to overwrite)`);
|
|
17942
17718
|
continue;
|
|
17943
17719
|
}
|
|
17944
|
-
await
|
|
17945
|
-
await
|
|
17720
|
+
await fs19.mkdir(path18.dirname(dest), { recursive: true });
|
|
17721
|
+
await fs19.copyFile(path18.join(srcDir, file.from), dest);
|
|
17946
17722
|
copied++;
|
|
17947
17723
|
log.dim(` ${file.rel}`);
|
|
17948
17724
|
}
|
|
@@ -17995,7 +17771,7 @@ async function runController(opts) {
|
|
|
17995
17771
|
for (const line of set.sketch) {
|
|
17996
17772
|
log.dim(` ${line}`);
|
|
17997
17773
|
}
|
|
17998
|
-
if (kind === "character" && !await exists3(
|
|
17774
|
+
if (kind === "character" && !await exists3(path18.join(root, ASSETS_DEST, "meshy-character.json"))) {
|
|
17999
17775
|
log.plain("");
|
|
18000
17776
|
log.plain(
|
|
18001
17777
|
` ${stepOffset + 3}. This game has no generated character yet \u2014 ${c.cyan(
|
|
@@ -18027,9 +17803,9 @@ async function installMeshyCharacterManifest(args) {
|
|
|
18027
17803
|
throw new Error("The API returned an invalid Meshy character manifest.");
|
|
18028
17804
|
}
|
|
18029
17805
|
assertCompleteMeshyControllerPack(manifest);
|
|
18030
|
-
const destination =
|
|
18031
|
-
await
|
|
18032
|
-
await
|
|
17806
|
+
const destination = path18.join(args.root, ASSETS_DEST, "meshy-character.json");
|
|
17807
|
+
await fs19.mkdir(path18.dirname(destination), { recursive: true });
|
|
17808
|
+
await fs19.writeFile(
|
|
18033
17809
|
destination,
|
|
18034
17810
|
`${JSON.stringify(manifest, null, 2)}
|
|
18035
17811
|
`
|
|
@@ -18167,14 +17943,14 @@ function assertCompleteMeshyControllerPack(manifest) {
|
|
|
18167
17943
|
}
|
|
18168
17944
|
async function installFallbackAvatar(args) {
|
|
18169
17945
|
const { root, srcDir, log } = args;
|
|
18170
|
-
const dest =
|
|
18171
|
-
await
|
|
18172
|
-
await
|
|
17946
|
+
const dest = path18.join(root, ASSETS_DEST, "avatar.vrm");
|
|
17947
|
+
await fs19.mkdir(path18.dirname(dest), { recursive: true });
|
|
17948
|
+
await fs19.copyFile(path18.join(srcDir, "assets", "default-avatar.vrm"), dest);
|
|
18173
17949
|
log.dim(" public/assets/avatar.vrm (fallback avatar \u2014 bundled CC0 default)");
|
|
18174
17950
|
}
|
|
18175
17951
|
async function exists3(p) {
|
|
18176
17952
|
try {
|
|
18177
|
-
await
|
|
17953
|
+
await fs19.access(p);
|
|
18178
17954
|
return true;
|
|
18179
17955
|
} catch {
|
|
18180
17956
|
return false;
|
|
@@ -18182,8 +17958,8 @@ async function exists3(p) {
|
|
|
18182
17958
|
}
|
|
18183
17959
|
|
|
18184
17960
|
// src/commands/character.ts
|
|
18185
|
-
import
|
|
18186
|
-
import
|
|
17961
|
+
import fs20 from "fs/promises";
|
|
17962
|
+
import path19 from "path";
|
|
18187
17963
|
function exactAnimation(selector) {
|
|
18188
17964
|
const trimmed = selector.trim();
|
|
18189
17965
|
if (/^-?\d+$/.test(trimmed)) return animationById(Number(trimmed));
|
|
@@ -18263,7 +18039,7 @@ function showAmbiguity(selector, candidates, log, json = false) {
|
|
|
18263
18039
|
}
|
|
18264
18040
|
process.exitCode = 1;
|
|
18265
18041
|
}
|
|
18266
|
-
var INSTALLED_MANIFEST =
|
|
18042
|
+
var INSTALLED_MANIFEST = path19.join("public", "assets", "meshy-character.json");
|
|
18267
18043
|
async function resolveAdoptTarget(selector) {
|
|
18268
18044
|
const trimmed = selector?.trim();
|
|
18269
18045
|
if (trimmed && !trimmed.endsWith(".json")) {
|
|
@@ -18272,7 +18048,7 @@ async function resolveAdoptTarget(selector) {
|
|
|
18272
18048
|
const file = trimmed ?? INSTALLED_MANIFEST;
|
|
18273
18049
|
let raw;
|
|
18274
18050
|
try {
|
|
18275
|
-
raw = await
|
|
18051
|
+
raw = await fs20.readFile(file, "utf8");
|
|
18276
18052
|
} catch {
|
|
18277
18053
|
return {
|
|
18278
18054
|
ok: false,
|
|
@@ -18755,22 +18531,22 @@ async function context2(opts) {
|
|
|
18755
18531
|
const project = await readProject();
|
|
18756
18532
|
return { token, apiUrl: getApiUrl(opts.apiUrl ?? project?.apiUrl) };
|
|
18757
18533
|
}
|
|
18758
|
-
async function readVideo(
|
|
18534
|
+
async function readVideo(path26, log) {
|
|
18759
18535
|
let bytes;
|
|
18760
18536
|
try {
|
|
18761
|
-
bytes = await readFile(
|
|
18537
|
+
bytes = await readFile(path26);
|
|
18762
18538
|
} catch {
|
|
18763
|
-
log.error(`Can't read ${
|
|
18539
|
+
log.error(`Can't read ${path26}.`);
|
|
18764
18540
|
return null;
|
|
18765
18541
|
}
|
|
18766
18542
|
if (bytes.byteLength > MAX_VIDEO_BYTES) {
|
|
18767
|
-
log.error(`${basename(
|
|
18543
|
+
log.error(`${basename(path26)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
|
|
18768
18544
|
return null;
|
|
18769
18545
|
}
|
|
18770
18546
|
return bytes;
|
|
18771
18547
|
}
|
|
18772
|
-
async function uploadVideo(apiUrl, token, characterId,
|
|
18773
|
-
const contentType = /\.mov$/i.test(
|
|
18548
|
+
async function uploadVideo(apiUrl, token, characterId, path26, bytes, log) {
|
|
18549
|
+
const contentType = /\.mov$/i.test(path26) ? "video/quicktime" : "video/mp4";
|
|
18774
18550
|
const minted = await apiFetch(
|
|
18775
18551
|
`${apiUrl}/api/characters/${encodeURIComponent(characterId)}/motions/video-url`,
|
|
18776
18552
|
{
|
|
@@ -18785,7 +18561,7 @@ async function uploadVideo(apiUrl, token, characterId, path27, bytes, log) {
|
|
|
18785
18561
|
return null;
|
|
18786
18562
|
}
|
|
18787
18563
|
const { uploadUrl, videoUrl } = await minted.json();
|
|
18788
|
-
log.dim(` uploading ${basename(
|
|
18564
|
+
log.dim(` uploading ${basename(path26)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
|
|
18789
18565
|
const put = await fetch(uploadUrl, {
|
|
18790
18566
|
method: "PUT",
|
|
18791
18567
|
headers: { "Content-Type": contentType, "Content-Length": String(bytes.byteLength) },
|
|
@@ -19101,8 +18877,8 @@ function rank(items, query) {
|
|
|
19101
18877
|
}
|
|
19102
18878
|
|
|
19103
18879
|
// src/commands/motion.ts
|
|
19104
|
-
import
|
|
19105
|
-
import
|
|
18880
|
+
import fs21 from "fs/promises";
|
|
18881
|
+
import path20 from "path";
|
|
19106
18882
|
|
|
19107
18883
|
// src/lib/motion/npz.ts
|
|
19108
18884
|
import zlib from "zlib";
|
|
@@ -20353,7 +20129,7 @@ async function motionGen(opts, log) {
|
|
|
20353
20129
|
}
|
|
20354
20130
|
if (opts.constraintsPath !== void 0) {
|
|
20355
20131
|
try {
|
|
20356
|
-
const raw = await
|
|
20132
|
+
const raw = await fs21.readFile(opts.constraintsPath, "utf8");
|
|
20357
20133
|
generationOptions.constraints = JSON.parse(raw);
|
|
20358
20134
|
} catch {
|
|
20359
20135
|
log.error(`Couldn't read the --constraints JSON at ${opts.constraintsPath}.`);
|
|
@@ -20377,10 +20153,10 @@ async function motionGen(opts, log) {
|
|
|
20377
20153
|
async function expandTakes(selectors) {
|
|
20378
20154
|
const out = [];
|
|
20379
20155
|
for (const sel of selectors) {
|
|
20380
|
-
const st = await
|
|
20156
|
+
const st = await fs21.stat(sel).catch(() => null);
|
|
20381
20157
|
if (st?.isDirectory()) {
|
|
20382
|
-
const names = await
|
|
20383
|
-
for (const n of names.sort()) if (n.endsWith(".npz")) out.push(
|
|
20158
|
+
const names = await fs21.readdir(sel);
|
|
20159
|
+
for (const n of names.sort()) if (n.endsWith(".npz")) out.push(path20.join(sel, n));
|
|
20384
20160
|
} else if (st?.isFile()) {
|
|
20385
20161
|
out.push(sel);
|
|
20386
20162
|
} else {
|
|
@@ -20415,7 +20191,7 @@ async function motionVerify(opts, log) {
|
|
|
20415
20191
|
let gates = DEFAULT_GATES;
|
|
20416
20192
|
if (opts.gatesPath) {
|
|
20417
20193
|
try {
|
|
20418
|
-
gates = mergeGates(DEFAULT_GATES, JSON.parse(await
|
|
20194
|
+
gates = mergeGates(DEFAULT_GATES, JSON.parse(await fs21.readFile(opts.gatesPath, "utf8")));
|
|
20419
20195
|
} catch {
|
|
20420
20196
|
log.error(`Couldn't read the --gates JSON at ${opts.gatesPath}.`);
|
|
20421
20197
|
process.exitCode = 1;
|
|
@@ -20437,9 +20213,9 @@ async function motionVerify(opts, log) {
|
|
|
20437
20213
|
}
|
|
20438
20214
|
const reports = [];
|
|
20439
20215
|
for (const file of files) {
|
|
20440
|
-
const stem =
|
|
20216
|
+
const stem = path20.basename(file).replace(/\.npz$/, "");
|
|
20441
20217
|
try {
|
|
20442
|
-
reports.push(analyzeTake(stem, await
|
|
20218
|
+
reports.push(analyzeTake(stem, await fs21.readFile(file), gates));
|
|
20443
20219
|
} catch (err) {
|
|
20444
20220
|
reports.push({
|
|
20445
20221
|
take: stem,
|
|
@@ -20477,7 +20253,7 @@ async function motionCompile(opts, log) {
|
|
|
20477
20253
|
let cfg = DEFAULT_MOTION_CONFIG;
|
|
20478
20254
|
if (opts.configPath) {
|
|
20479
20255
|
try {
|
|
20480
|
-
const patch = JSON.parse(await
|
|
20256
|
+
const patch = JSON.parse(await fs21.readFile(opts.configPath, "utf8"));
|
|
20481
20257
|
cfg = { ...DEFAULT_MOTION_CONFIG, ...patch, idleLoop: { ...DEFAULT_MOTION_CONFIG.idleLoop, ...patch.idleLoop } };
|
|
20482
20258
|
} catch {
|
|
20483
20259
|
log.error(`Couldn't read the --config JSON at ${opts.configPath}.`);
|
|
@@ -20495,16 +20271,16 @@ async function motionCompile(opts, log) {
|
|
|
20495
20271
|
}
|
|
20496
20272
|
const inputs = [];
|
|
20497
20273
|
for (const file of files) {
|
|
20498
|
-
const stem =
|
|
20274
|
+
const stem = path20.basename(file).replace(/\.npz$/, "");
|
|
20499
20275
|
try {
|
|
20500
|
-
inputs.push({ stem, take: loadTake(await
|
|
20276
|
+
inputs.push({ stem, take: loadTake(await fs21.readFile(file)) });
|
|
20501
20277
|
} catch (err) {
|
|
20502
20278
|
log.error(`${stem}: ${err instanceof Error ? err.message : String(err)}`);
|
|
20503
20279
|
process.exitCode = 1;
|
|
20504
20280
|
return;
|
|
20505
20281
|
}
|
|
20506
20282
|
}
|
|
20507
|
-
const setName = opts.set ??
|
|
20283
|
+
const setName = opts.set ?? path20.basename(opts.out).replace(/\.json$/, "");
|
|
20508
20284
|
let result;
|
|
20509
20285
|
try {
|
|
20510
20286
|
result = compileSet(inputs, setName, cfg);
|
|
@@ -20519,9 +20295,9 @@ async function motionCompile(opts, log) {
|
|
|
20519
20295
|
process.exitCode = 1;
|
|
20520
20296
|
return;
|
|
20521
20297
|
}
|
|
20522
|
-
await
|
|
20298
|
+
await fs21.mkdir(path20.dirname(path20.resolve(opts.out)), { recursive: true });
|
|
20523
20299
|
const json = JSON.stringify(result.data);
|
|
20524
|
-
await
|
|
20300
|
+
await fs21.writeFile(opts.out, json);
|
|
20525
20301
|
if (opts.json) {
|
|
20526
20302
|
writeJson({ out: opts.out, set: setName, tracks: Object.keys(result.data.gaits), bytes: json.length });
|
|
20527
20303
|
return;
|
|
@@ -20539,9 +20315,9 @@ var MOTION_RUNTIME_FILES = [
|
|
|
20539
20315
|
var MOTION_PRESETS = {
|
|
20540
20316
|
rifle: ["sets/rifle.json", "sets/jumps.json"]
|
|
20541
20317
|
};
|
|
20542
|
-
var MOTION_DEST =
|
|
20318
|
+
var MOTION_DEST = path20.join("src", "motion");
|
|
20543
20319
|
async function motionInstall(opts, log) {
|
|
20544
|
-
const srcDir =
|
|
20320
|
+
const srcDir = path20.join(getTemplatesDir(), "motion");
|
|
20545
20321
|
const root = opts.cwd ?? process.cwd();
|
|
20546
20322
|
const preset = opts.set;
|
|
20547
20323
|
if (preset !== void 0 && !MOTION_PRESETS[preset]) {
|
|
@@ -20552,21 +20328,21 @@ async function motionInstall(opts, log) {
|
|
|
20552
20328
|
const files = [...MOTION_RUNTIME_FILES, ...preset ? MOTION_PRESETS[preset] : []];
|
|
20553
20329
|
log.plain(c.bold(`genex motion install${preset ? ` --set ${preset}` : ""}`));
|
|
20554
20330
|
log.plain("");
|
|
20555
|
-
log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST +
|
|
20331
|
+
log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST + path20.sep)}`);
|
|
20556
20332
|
let copied = 0, skipped = 0;
|
|
20557
20333
|
try {
|
|
20558
20334
|
for (const rel of files) {
|
|
20559
|
-
const dest =
|
|
20560
|
-
const exists5 = await
|
|
20335
|
+
const dest = path20.join(root, MOTION_DEST, rel);
|
|
20336
|
+
const exists5 = await fs21.access(dest).then(() => true, () => false);
|
|
20561
20337
|
if (!opts.force && exists5) {
|
|
20562
20338
|
skipped++;
|
|
20563
|
-
log.dim(` skipped ${
|
|
20339
|
+
log.dim(` skipped ${path20.join(MOTION_DEST, rel)} (exists \u2014 use --force to overwrite)`);
|
|
20564
20340
|
continue;
|
|
20565
20341
|
}
|
|
20566
|
-
await
|
|
20567
|
-
await
|
|
20342
|
+
await fs21.mkdir(path20.dirname(dest), { recursive: true });
|
|
20343
|
+
await fs21.copyFile(path20.join(srcDir, rel), dest);
|
|
20568
20344
|
copied++;
|
|
20569
|
-
log.dim(` ${
|
|
20345
|
+
log.dim(` ${path20.join(MOTION_DEST, rel)}`);
|
|
20570
20346
|
}
|
|
20571
20347
|
} catch (err) {
|
|
20572
20348
|
log.error(`Copy failed: ${String(err)}`);
|
|
@@ -20607,7 +20383,7 @@ async function motionConstraints(opts, log) {
|
|
|
20607
20383
|
}
|
|
20608
20384
|
const doc = directionConstraint(dir, speed, duration);
|
|
20609
20385
|
const out = opts.out ?? "constraints.json";
|
|
20610
|
-
await
|
|
20386
|
+
await fs21.writeFile(out, JSON.stringify(doc));
|
|
20611
20387
|
if (opts.json) {
|
|
20612
20388
|
writeJson({ out, dir: dirName, speed, duration, waypoints: doc[0].frame_indices.length });
|
|
20613
20389
|
return;
|
|
@@ -20644,14 +20420,14 @@ async function runMotion(opts) {
|
|
|
20644
20420
|
}
|
|
20645
20421
|
|
|
20646
20422
|
// src/commands/blender.ts
|
|
20647
|
-
import
|
|
20648
|
-
import
|
|
20423
|
+
import fs22 from "fs/promises";
|
|
20424
|
+
import path21 from "path";
|
|
20649
20425
|
var SUBS2 = ["demo", "exec", "snap", "scene", "import", "export", "reset", "mcp", "serve"];
|
|
20650
20426
|
var DEFAULT_OUT_DIR = "assets/blender";
|
|
20651
20427
|
async function writeB64(dir, name, b64) {
|
|
20652
|
-
await
|
|
20653
|
-
const p =
|
|
20654
|
-
await
|
|
20428
|
+
await fs22.mkdir(dir, { recursive: true });
|
|
20429
|
+
const p = path21.join(dir, name);
|
|
20430
|
+
await fs22.writeFile(p, Buffer.from(b64, "base64"));
|
|
20655
20431
|
return p;
|
|
20656
20432
|
}
|
|
20657
20433
|
function reportScene(log, s) {
|
|
@@ -20677,7 +20453,7 @@ async function runBlender(opts) {
|
|
|
20677
20453
|
return serveLocalBlender({ port, log });
|
|
20678
20454
|
}
|
|
20679
20455
|
if (sub === "mcp") {
|
|
20680
|
-
const { runBlenderMcp } = await import("./blender-mcp-
|
|
20456
|
+
const { runBlenderMcp } = await import("./blender-mcp-SBNHGDIX.js");
|
|
20681
20457
|
return runBlenderMcp();
|
|
20682
20458
|
}
|
|
20683
20459
|
let base = blenderEndpoint();
|
|
@@ -20697,7 +20473,7 @@ async function runBlender(opts) {
|
|
|
20697
20473
|
log.plain(rest.join("\n"));
|
|
20698
20474
|
return 1;
|
|
20699
20475
|
}
|
|
20700
|
-
const outDir =
|
|
20476
|
+
const outDir = path21.resolve(opts.outDir ?? DEFAULT_OUT_DIR);
|
|
20701
20477
|
const mode = opts.mode;
|
|
20702
20478
|
if (mode !== void 0 && !isRenderMode(mode)) {
|
|
20703
20479
|
log.error(`Unknown --mode ${mode}. Use one of: ${RENDER_MODES.join(", ")}.`);
|
|
@@ -20737,14 +20513,14 @@ async function runBlender(opts) {
|
|
|
20737
20513
|
return 0;
|
|
20738
20514
|
}
|
|
20739
20515
|
case "export": {
|
|
20740
|
-
const target = opts.out ??
|
|
20516
|
+
const target = opts.out ?? path21.join(outDir, "scene.glb");
|
|
20741
20517
|
const r = await blenderCall(base, "/export", { path: "/tmp/genex-export.glb" });
|
|
20742
20518
|
if (!r.glbBase64) {
|
|
20743
20519
|
log.error(`/export answered with no GLB bytes${r.uploaded ? " (it was uploaded, not inlined)" : ""}`);
|
|
20744
20520
|
return 1;
|
|
20745
20521
|
}
|
|
20746
|
-
await
|
|
20747
|
-
await
|
|
20522
|
+
await fs22.mkdir(path21.dirname(target), { recursive: true });
|
|
20523
|
+
await fs22.writeFile(target, Buffer.from(r.glbBase64, "base64"));
|
|
20748
20524
|
log.success(`Exported ${r.bytes ?? 0} bytes`);
|
|
20749
20525
|
log.plain(` ${c.cyan(target)}`);
|
|
20750
20526
|
return 0;
|
|
@@ -20777,12 +20553,12 @@ async function runBlender(opts) {
|
|
|
20777
20553
|
return 1;
|
|
20778
20554
|
}
|
|
20779
20555
|
try {
|
|
20780
|
-
script = await
|
|
20556
|
+
script = await fs22.readFile(opts.input, "utf8");
|
|
20781
20557
|
} catch {
|
|
20782
20558
|
log.error(`Can't read ${opts.input}`);
|
|
20783
20559
|
return 1;
|
|
20784
20560
|
}
|
|
20785
|
-
label =
|
|
20561
|
+
label = path21.basename(opts.input);
|
|
20786
20562
|
}
|
|
20787
20563
|
const r = await blenderCall(base, "/exec", { script, ...mode ? { mode } : {}, sheet: { formats: SHEET_FORMATS } });
|
|
20788
20564
|
if (r.stdout?.trim()) log.plain(r.stdout.trimEnd());
|
|
@@ -20901,9 +20677,9 @@ print(f"castle: {n} objects")
|
|
|
20901
20677
|
`;
|
|
20902
20678
|
|
|
20903
20679
|
// src/commands/asset-new.ts
|
|
20904
|
-
import
|
|
20680
|
+
import fs23 from "fs";
|
|
20905
20681
|
import fsp from "fs/promises";
|
|
20906
|
-
import
|
|
20682
|
+
import path22 from "path";
|
|
20907
20683
|
import { pathToFileURL } from "url";
|
|
20908
20684
|
var EXTRA_FILES = [
|
|
20909
20685
|
"genex-asset.example.json",
|
|
@@ -20997,7 +20773,7 @@ Every milestone ends with \`npm run verify\`, then \`npx genex preview --no-buil
|
|
|
20997
20773
|
}
|
|
20998
20774
|
async function runAssetNew(options) {
|
|
20999
20775
|
const log = createLogger();
|
|
21000
|
-
const cwd = options.dir ?
|
|
20776
|
+
const cwd = options.dir ? path22.resolve(options.dir) : process.cwd();
|
|
21001
20777
|
const slug = options.assetSlug;
|
|
21002
20778
|
if (!slug) {
|
|
21003
20779
|
log.error('Usage: genex asset new <slug> [--title "<Name>"] [--dims WxDxH] [--tri-band LOW-HIGH] [--holder "<your name>"] [--out <dir>]');
|
|
@@ -21007,14 +20783,14 @@ async function runAssetNew(options) {
|
|
|
21007
20783
|
log.error(`The slug must be kebab-case (letters, digits, dashes), got: ${slug}`);
|
|
21008
20784
|
return 1;
|
|
21009
20785
|
}
|
|
21010
|
-
const templateDir =
|
|
21011
|
-
if (!
|
|
20786
|
+
const templateDir = path22.join(getTemplatesDir(), "asset-viewer");
|
|
20787
|
+
if (!fs23.existsSync(templateDir)) {
|
|
21012
20788
|
log.error(`Vendored asset-viewer template not found at ${templateDir} \u2014 reinstall the CLI.`);
|
|
21013
20789
|
return 1;
|
|
21014
20790
|
}
|
|
21015
|
-
const manifestTools = await import(pathToFileURL(
|
|
20791
|
+
const manifestTools = await import(pathToFileURL(path22.join(templateDir, "tools", "emit-manifest.mjs")).href);
|
|
21016
20792
|
const { SHARED_FILES, PARITY_FILENAME, hashSharedFiles, pascalCase } = manifestTools;
|
|
21017
|
-
const lockPath =
|
|
20793
|
+
const lockPath = path22.join(templateDir, "shared-files.sha256.json");
|
|
21018
20794
|
const lock = JSON.parse(await fsp.readFile(lockPath, "utf8"));
|
|
21019
20795
|
const actual = hashSharedFiles(templateDir);
|
|
21020
20796
|
const drifted = SHARED_FILES.filter((rel) => lock.files[rel] !== actual[rel]);
|
|
@@ -21027,8 +20803,8 @@ async function runAssetNew(options) {
|
|
|
21027
20803
|
const triBand = parseBand(options.triBand ?? "500-8000");
|
|
21028
20804
|
const holder = options.holder?.trim() || HOLDER_PLACEHOLDER;
|
|
21029
20805
|
const year = (/* @__PURE__ */ new Date()).getUTCFullYear();
|
|
21030
|
-
const outDir =
|
|
21031
|
-
if (
|
|
20806
|
+
const outDir = path22.resolve(cwd, options.out ?? slug);
|
|
20807
|
+
if (fs23.existsSync(outDir) && fs23.readdirSync(outDir).length > 0 && !options.force) {
|
|
21032
20808
|
log.error(`${outDir} already holds files. Nothing was touched \u2014 pass --force to write into it anyway.`);
|
|
21033
20809
|
return 1;
|
|
21034
20810
|
}
|
|
@@ -21056,24 +20832,24 @@ async function runAssetNew(options) {
|
|
|
21056
20832
|
};
|
|
21057
20833
|
await fsp.mkdir(outDir, { recursive: true });
|
|
21058
20834
|
for (const rel of [...SHARED_FILES, ...EXTRA_FILES]) {
|
|
21059
|
-
const to =
|
|
21060
|
-
await fsp.mkdir(
|
|
21061
|
-
await fsp.copyFile(
|
|
20835
|
+
const to = path22.join(outDir, rel);
|
|
20836
|
+
await fsp.mkdir(path22.dirname(to), { recursive: true });
|
|
20837
|
+
await fsp.copyFile(path22.join(templateDir, rel), to);
|
|
21062
20838
|
}
|
|
21063
|
-
const pkg = fillTemplate(await fsp.readFile(
|
|
20839
|
+
const pkg = fillTemplate(await fsp.readFile(path22.join(templateDir, "package.json"), "utf8"), {
|
|
21064
20840
|
slug,
|
|
21065
20841
|
name,
|
|
21066
20842
|
version
|
|
21067
20843
|
});
|
|
21068
|
-
await fsp.writeFile(
|
|
21069
|
-
await fsp.writeFile(
|
|
21070
|
-
await fsp.writeFile(
|
|
20844
|
+
await fsp.writeFile(path22.join(outDir, "package.json"), pkg, "utf8");
|
|
20845
|
+
await fsp.writeFile(path22.join(outDir, "asset.config.json"), JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
20846
|
+
await fsp.writeFile(path22.join(outDir, ".gitignore"), GITIGNORE, "utf8");
|
|
21071
20847
|
await fsp.writeFile(
|
|
21072
|
-
|
|
20848
|
+
path22.join(outDir, "DESIGN.md"),
|
|
21073
20849
|
designDoc({ name, slug, sizeMeters, triBand, holder }),
|
|
21074
20850
|
"utf8"
|
|
21075
20851
|
);
|
|
21076
|
-
const placeholder = await fsp.readFile(
|
|
20852
|
+
const placeholder = await fsp.readFile(path22.join(templateDir, "src", "asset", "PLACEHOLDER.ts"), "utf8");
|
|
21077
20853
|
const seeded = seedAssetSource(placeholder, {
|
|
21078
20854
|
slug,
|
|
21079
20855
|
name,
|
|
@@ -21084,8 +20860,8 @@ async function runAssetNew(options) {
|
|
|
21084
20860
|
pascalCase
|
|
21085
20861
|
});
|
|
21086
20862
|
const assetFilename = `${slug.replace(/-([a-z0-9])/g, (_, ch) => ch.toUpperCase())}.ts`;
|
|
21087
|
-
await fsp.mkdir(
|
|
21088
|
-
await fsp.writeFile(
|
|
20863
|
+
await fsp.mkdir(path22.join(outDir, "src", "asset"), { recursive: true });
|
|
20864
|
+
await fsp.writeFile(path22.join(outDir, "src", "asset", assetFilename), seeded, "utf8");
|
|
21089
20865
|
const copied = hashSharedFiles(outDir);
|
|
21090
20866
|
const mismatched = SHARED_FILES.filter((rel) => copied[rel] !== lock.files[rel]);
|
|
21091
20867
|
if (mismatched.length) {
|
|
@@ -21093,7 +20869,7 @@ async function runAssetNew(options) {
|
|
|
21093
20869
|
return 1;
|
|
21094
20870
|
}
|
|
21095
20871
|
await fsp.writeFile(
|
|
21096
|
-
|
|
20872
|
+
path22.join(outDir, PARITY_FILENAME),
|
|
21097
20873
|
JSON.stringify(
|
|
21098
20874
|
{
|
|
21099
20875
|
note: "Recorded by `genex asset new`. Gate G13 recomputes these; do not hand-edit the viewer.",
|
|
@@ -21137,12 +20913,12 @@ async function runAssetNew(options) {
|
|
|
21137
20913
|
}
|
|
21138
20914
|
|
|
21139
20915
|
// src/commands/tools.ts
|
|
21140
|
-
import
|
|
20916
|
+
import path25 from "path";
|
|
21141
20917
|
|
|
21142
20918
|
// src/lib/local-install.ts
|
|
21143
|
-
import
|
|
21144
|
-
import
|
|
21145
|
-
import { spawn as
|
|
20919
|
+
import fs24 from "fs/promises";
|
|
20920
|
+
import path23 from "path";
|
|
20921
|
+
import { spawn as spawn4 } from "child_process";
|
|
21146
20922
|
var CLI_PACKAGE = "@genex-ai/cli-demo";
|
|
21147
20923
|
var FULL_NAME_FALLBACK = `npx ${CLI_PACKAGE}@${CLI_CHANNEL}`;
|
|
21148
20924
|
var LOCKFILES = [
|
|
@@ -21154,17 +20930,17 @@ var LOCKFILES = [
|
|
|
21154
20930
|
];
|
|
21155
20931
|
async function exists4(p) {
|
|
21156
20932
|
try {
|
|
21157
|
-
await
|
|
20933
|
+
await fs24.access(p);
|
|
21158
20934
|
return true;
|
|
21159
20935
|
} catch {
|
|
21160
20936
|
return false;
|
|
21161
20937
|
}
|
|
21162
20938
|
}
|
|
21163
20939
|
async function detectPackageManager(cwd) {
|
|
21164
|
-
let dir =
|
|
20940
|
+
let dir = path23.resolve(cwd);
|
|
21165
20941
|
for (; ; ) {
|
|
21166
20942
|
try {
|
|
21167
|
-
const raw = await
|
|
20943
|
+
const raw = await fs24.readFile(path23.join(dir, "package.json"), "utf8");
|
|
21168
20944
|
const pm = JSON.parse(raw).packageManager;
|
|
21169
20945
|
if (typeof pm === "string") {
|
|
21170
20946
|
const name = pm.split("@")[0];
|
|
@@ -21173,18 +20949,18 @@ async function detectPackageManager(cwd) {
|
|
|
21173
20949
|
} catch {
|
|
21174
20950
|
}
|
|
21175
20951
|
for (const [file, pm] of LOCKFILES) {
|
|
21176
|
-
if (await exists4(
|
|
20952
|
+
if (await exists4(path23.join(dir, file))) return pm;
|
|
21177
20953
|
}
|
|
21178
|
-
const parent =
|
|
20954
|
+
const parent = path23.dirname(dir);
|
|
21179
20955
|
if (parent === dir) return "npm";
|
|
21180
20956
|
dir = parent;
|
|
21181
20957
|
}
|
|
21182
20958
|
}
|
|
21183
20959
|
async function findLocalCli(cwd) {
|
|
21184
|
-
let dir =
|
|
20960
|
+
let dir = path23.resolve(cwd);
|
|
21185
20961
|
for (; ; ) {
|
|
21186
|
-
if (await exists4(
|
|
21187
|
-
const parent =
|
|
20962
|
+
if (await exists4(path23.join(dir, "node_modules", CLI_PACKAGE, "package.json"))) return dir;
|
|
20963
|
+
const parent = path23.dirname(dir);
|
|
21188
20964
|
if (parent === dir) return null;
|
|
21189
20965
|
dir = parent;
|
|
21190
20966
|
}
|
|
@@ -21202,7 +20978,7 @@ function installArgs(pm, spec) {
|
|
|
21202
20978
|
}
|
|
21203
20979
|
}
|
|
21204
20980
|
function manifestName(cwd) {
|
|
21205
|
-
const slug =
|
|
20981
|
+
const slug = path23.basename(path23.resolve(cwd)).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 100);
|
|
21206
20982
|
return slug || "genex-tools-workspace";
|
|
21207
20983
|
}
|
|
21208
20984
|
function isSourceRun(moduleUrl = import.meta.url) {
|
|
@@ -21219,7 +20995,7 @@ async function spawnInstall(command, args, cwd) {
|
|
|
21219
20995
|
resolve({ ok, detail });
|
|
21220
20996
|
};
|
|
21221
20997
|
try {
|
|
21222
|
-
const child =
|
|
20998
|
+
const child = spawn4(command, args, {
|
|
21223
20999
|
cwd,
|
|
21224
21000
|
// npm/pnpm/yarn are .cmd shims on Windows; the args carry no user input.
|
|
21225
21001
|
shell: process.platform === "win32",
|
|
@@ -21260,10 +21036,10 @@ async function ensureLocalCli(log, cwd = process.cwd(), opts = {}) {
|
|
|
21260
21036
|
return;
|
|
21261
21037
|
}
|
|
21262
21038
|
const pm = await detectPackageManager(cwd);
|
|
21263
|
-
const hadManifest = await exists4(
|
|
21039
|
+
const hadManifest = await exists4(path23.join(cwd, "package.json"));
|
|
21264
21040
|
if (!hadManifest) {
|
|
21265
|
-
await
|
|
21266
|
-
|
|
21041
|
+
await fs24.writeFile(
|
|
21042
|
+
path23.join(cwd, "package.json"),
|
|
21267
21043
|
JSON.stringify({ name: manifestName(cwd), private: true }, null, 2) + "\n"
|
|
21268
21044
|
);
|
|
21269
21045
|
await ensureIgnored(cwd, "node_modules/");
|
|
@@ -21283,20 +21059,20 @@ async function ensureLocalCli(log, cwd = process.cwd(), opts = {}) {
|
|
|
21283
21059
|
}
|
|
21284
21060
|
}
|
|
21285
21061
|
async function ensureIgnored(dir, entry) {
|
|
21286
|
-
const file =
|
|
21062
|
+
const file = path23.join(dir, ".gitignore");
|
|
21287
21063
|
let content = "";
|
|
21288
21064
|
try {
|
|
21289
|
-
content = await
|
|
21065
|
+
content = await fs24.readFile(file, "utf8");
|
|
21290
21066
|
} catch {
|
|
21291
21067
|
}
|
|
21292
21068
|
if (content.split("\n").some((l) => l.trim() === entry)) return;
|
|
21293
21069
|
let next = content;
|
|
21294
21070
|
if (next.length > 0 && !next.endsWith("\n")) next += "\n";
|
|
21295
|
-
await
|
|
21071
|
+
await fs24.writeFile(file, next + entry + "\n");
|
|
21296
21072
|
}
|
|
21297
21073
|
|
|
21298
21074
|
// src/commands/doctor.ts
|
|
21299
|
-
import
|
|
21075
|
+
import path24 from "path";
|
|
21300
21076
|
var LANE_ORDER = [
|
|
21301
21077
|
"model",
|
|
21302
21078
|
"image",
|
|
@@ -21358,9 +21134,15 @@ async function runDoctor(opts = {}) {
|
|
|
21358
21134
|
rows.push(authRow(authState, email, opts.envPath));
|
|
21359
21135
|
let credits = null;
|
|
21360
21136
|
let lanes = null;
|
|
21137
|
+
let legal = null;
|
|
21361
21138
|
if (authState === "ok" && token) {
|
|
21362
|
-
[credits, lanes] = await Promise.all([
|
|
21139
|
+
[credits, lanes, legal] = await Promise.all([
|
|
21140
|
+
fetchCredits(apiUrl, token),
|
|
21141
|
+
fetchLanes(apiUrl, token),
|
|
21142
|
+
fetchLegalStatus(apiUrl, token)
|
|
21143
|
+
]);
|
|
21363
21144
|
}
|
|
21145
|
+
rows.push(termsRow(authState, legal));
|
|
21364
21146
|
rows.push(creditsRow(authState, credits));
|
|
21365
21147
|
if (opts.json) {
|
|
21366
21148
|
process.stdout.write(
|
|
@@ -21370,6 +21152,7 @@ async function runDoctor(opts = {}) {
|
|
|
21370
21152
|
node: process.versions.node,
|
|
21371
21153
|
cli: { installed, latest },
|
|
21372
21154
|
auth: { state: authState, email, apiUrl },
|
|
21155
|
+
terms: legal,
|
|
21373
21156
|
credits,
|
|
21374
21157
|
lanes: lanes ? { paused: lanes.paused, lanes: lanes.lanes } : null,
|
|
21375
21158
|
ok: !rows.some((r) => r.bad)
|
|
@@ -21471,6 +21254,17 @@ function authRow(state, email, envPath) {
|
|
|
21471
21254
|
};
|
|
21472
21255
|
}
|
|
21473
21256
|
}
|
|
21257
|
+
function termsRow(state, info) {
|
|
21258
|
+
if (state !== "ok") return { label: "Terms", value: "\u2014 (sign in first)" };
|
|
21259
|
+
if (!info) return { label: "Terms", value: "couldn't be read from this server" };
|
|
21260
|
+
if (info.accepted) return { label: "Terms", value: `accepted (${info.acceptedVersion ?? info.required})` };
|
|
21261
|
+
return {
|
|
21262
|
+
label: "Terms",
|
|
21263
|
+
value: "changed since you last agreed \u2014 every generate and publish is refused",
|
|
21264
|
+
bad: true,
|
|
21265
|
+
fix: `Open ${acceptUrl(info.acceptUrl)} and agree \u2014 one click; any refused command then continues on its own.`
|
|
21266
|
+
};
|
|
21267
|
+
}
|
|
21474
21268
|
function creditsRow(state, info) {
|
|
21475
21269
|
if (state !== "ok") return { label: "Credits", value: "\u2014 (sign in first)" };
|
|
21476
21270
|
if (!info) return { label: "Credits", value: "couldn't be read from this server" };
|
|
@@ -21522,9 +21316,21 @@ async function fetchCredits(apiUrl, token) {
|
|
|
21522
21316
|
return null;
|
|
21523
21317
|
}
|
|
21524
21318
|
}
|
|
21319
|
+
async function fetchLegalStatus(apiUrl, token) {
|
|
21320
|
+
try {
|
|
21321
|
+
const res = await apiFetch(`${apiUrl}/api/legal/status`, {
|
|
21322
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
21323
|
+
signal: AbortSignal.timeout(6e3)
|
|
21324
|
+
});
|
|
21325
|
+
if (!res.ok) return null;
|
|
21326
|
+
return await res.json();
|
|
21327
|
+
} catch {
|
|
21328
|
+
return null;
|
|
21329
|
+
}
|
|
21330
|
+
}
|
|
21525
21331
|
async function firstSkillsMarker() {
|
|
21526
21332
|
for (const target of resolveAgentTargets()) {
|
|
21527
|
-
const marker = await readSkillsMarker(
|
|
21333
|
+
const marker = await readSkillsMarker(path24.join(target.baseDir, "skills"));
|
|
21528
21334
|
if (marker) return marker;
|
|
21529
21335
|
}
|
|
21530
21336
|
return null;
|
|
@@ -21563,8 +21369,8 @@ async function runTools(opts) {
|
|
|
21563
21369
|
let totalNew = 0;
|
|
21564
21370
|
let totalUpdated = 0;
|
|
21565
21371
|
for (const t of targets) {
|
|
21566
|
-
const dest =
|
|
21567
|
-
const { copied, updated } = await copyTemplates(
|
|
21372
|
+
const dest = path25.join(t.baseDir, "skills");
|
|
21373
|
+
const { copied, updated } = await copyTemplates(path25.join(templatesDir, "skills"), dest, {
|
|
21568
21374
|
filter: (rel) => skillFamilyFilter("tools")(`skills/${rel}`)
|
|
21569
21375
|
});
|
|
21570
21376
|
await pruneRemovedSkills(dest, log);
|
|
@@ -21619,7 +21425,6 @@ async function runTools(opts) {
|
|
|
21619
21425
|
await runDoctor({ apiUrl: opts.apiUrl, envPath: opts.envPath, quiet: opts.quiet });
|
|
21620
21426
|
log.plain("");
|
|
21621
21427
|
log.success("Genex Tools is ready. \u{1F9F0}");
|
|
21622
|
-
log.plain(` Try it: ${c.cyan('npx genex image "weathered treasure map, hand-inked" --transparent')}`);
|
|
21623
21428
|
log.dim(" Assets land in ./assets/ as files you own \u2014 wire the local path into your game.");
|
|
21624
21429
|
}
|
|
21625
21430
|
|