@genex-ai/cli-demo 1.14.3-dev.576 → 1.15.1-dev.578
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{blender-mcp-6GQRUQWF.js → blender-mcp-SBNHGDIX.js} +1 -1
- package/dist/chunk-JGX6YRC7.js +791 -0
- package/dist/index.js +349 -531
- package/package.json +1 -1
- package/dist/chunk-TFRTQ37H.js +0 -449
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,
|
|
@@ -873,10 +888,22 @@ import os4 from "os";
|
|
|
873
888
|
import path4 from "path";
|
|
874
889
|
|
|
875
890
|
// src/lib/workspace.ts
|
|
876
|
-
async function
|
|
877
|
-
|
|
891
|
+
async function resolveWorkspace(cwd = process.cwd()) {
|
|
892
|
+
const project = await readProject(cwd);
|
|
893
|
+
if (project) {
|
|
894
|
+
const slug = typeof project.slug === "string" && project.slug.trim() ? project.slug.trim() : null;
|
|
895
|
+
return { mode: "game", slug };
|
|
896
|
+
}
|
|
878
897
|
const ws = await readWorkspace(cwd);
|
|
879
|
-
return ws?.mode === "tools" ? "tools" : null;
|
|
898
|
+
return { mode: ws?.mode === "tools" ? "tools" : null, slug: null };
|
|
899
|
+
}
|
|
900
|
+
async function workspaceMode(cwd = process.cwd()) {
|
|
901
|
+
return (await resolveWorkspace(cwd)).mode;
|
|
902
|
+
}
|
|
903
|
+
function workspaceHeaderLabel(ws) {
|
|
904
|
+
if (ws.mode === "tools") return "tools";
|
|
905
|
+
if (ws.mode === "game" && ws.slug) return { game: ws.slug };
|
|
906
|
+
return null;
|
|
880
907
|
}
|
|
881
908
|
var REFUSED_IN_TOOLS = /* @__PURE__ */ new Set([
|
|
882
909
|
"link",
|
|
@@ -1748,177 +1775,9 @@ async function runInit(opts) {
|
|
|
1748
1775
|
}
|
|
1749
1776
|
|
|
1750
1777
|
// src/commands/link.ts
|
|
1751
|
-
import fs9 from "fs/promises";
|
|
1752
|
-
import os6 from "os";
|
|
1753
|
-
import path9 from "path";
|
|
1754
|
-
|
|
1755
|
-
// src/lib/source-sync.ts
|
|
1756
1778
|
import fs8 from "fs/promises";
|
|
1757
|
-
import path8 from "path";
|
|
1758
1779
|
import os5 from "os";
|
|
1759
|
-
|
|
1760
|
-
// src/utils/run.ts
|
|
1761
|
-
import { spawn as spawn2 } from "child_process";
|
|
1762
|
-
var WIN_SHELL_COMMANDS = /* @__PURE__ */ new Set(["npm", "npx"]);
|
|
1763
|
-
function run(cmd, args, env) {
|
|
1764
|
-
const shell = process.platform === "win32" && WIN_SHELL_COMMANDS.has(cmd);
|
|
1765
|
-
return new Promise((resolve) => {
|
|
1766
|
-
let child;
|
|
1767
|
-
try {
|
|
1768
|
-
child = spawn2(cmd, args, { env: env ? { ...process.env, ...env } : process.env, shell });
|
|
1769
|
-
} catch {
|
|
1770
|
-
resolve({ code: -1, out: "", err: `${cmd} not found` });
|
|
1771
|
-
return;
|
|
1772
|
-
}
|
|
1773
|
-
let out = "";
|
|
1774
|
-
let err = "";
|
|
1775
|
-
child.stdout?.on("data", (d) => out += String(d));
|
|
1776
|
-
child.stderr?.on("data", (d) => err += String(d));
|
|
1777
|
-
child.on("error", () => resolve({ code: -1, out, err: `${cmd} not found` }));
|
|
1778
|
-
child.on("close", (code) => resolve({ code: code ?? -1, out, err }));
|
|
1779
|
-
});
|
|
1780
|
-
}
|
|
1781
|
-
|
|
1782
|
-
// src/lib/source-sync.ts
|
|
1783
|
-
async function readRemoteSource(apiUrl, token, projectId) {
|
|
1784
|
-
try {
|
|
1785
|
-
const res = await apiFetch(`${apiUrl}/api/projects/${projectId}`, {
|
|
1786
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
1787
|
-
});
|
|
1788
|
-
if (!res.ok) return null;
|
|
1789
|
-
const data = await res.json().catch(() => null);
|
|
1790
|
-
if (!data?.project) return null;
|
|
1791
|
-
return {
|
|
1792
|
-
stagingCommit: data.project.stagingCommitSha ?? null,
|
|
1793
|
-
commit: data.project.commitSha ?? null
|
|
1794
|
-
};
|
|
1795
|
-
} catch {
|
|
1796
|
-
return null;
|
|
1797
|
-
}
|
|
1798
|
-
}
|
|
1799
|
-
function isStale(local, remote) {
|
|
1800
|
-
if (!local || !remote) return false;
|
|
1801
|
-
return local !== remote;
|
|
1802
|
-
}
|
|
1803
|
-
function inHostedSession() {
|
|
1804
|
-
return process.env.GENEX_HOSTED_SESSION === "1";
|
|
1805
|
-
}
|
|
1806
|
-
function mayForceOverAnotherDevice() {
|
|
1807
|
-
return !inHostedSession();
|
|
1808
|
-
}
|
|
1809
|
-
function reportForceRefused(log) {
|
|
1810
|
-
log.error("`--force` is not available inside a Genex chat session.");
|
|
1811
|
-
log.dim(" It would replace work shipped from another machine, and that is not yours to discard here.");
|
|
1812
|
-
log.dim(` Do this instead \u2014 it keeps both sides:`);
|
|
1813
|
-
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-*)`);
|
|
1814
|
-
log.dim(` 2. redo your change on top of it \u2014 you know what you just changed`);
|
|
1815
|
-
log.dim(` 3. ${c.cyan("npx genex preview")}`);
|
|
1816
|
-
log.dim(" If the user explicitly wants their chat version to win, ask them to run `npx genex preview --force` themselves.");
|
|
1817
|
-
}
|
|
1818
|
-
function reportStale(log, slug, local, remote) {
|
|
1819
|
-
log.error(`${c.cyan(slug)} was updated from another device \u2014 nothing was deployed.`);
|
|
1820
|
-
if (remote && local) {
|
|
1821
|
-
const short = remote.slice(0, 7) !== local.slice(0, 7);
|
|
1822
|
-
const r = short ? remote.slice(0, 7) : remote;
|
|
1823
|
-
const l = short ? local.slice(0, 7) : local;
|
|
1824
|
-
log.dim(` the draft is on ${r}, this folder last shipped ${l}`);
|
|
1825
|
-
}
|
|
1826
|
-
log.dim(` ${c.cyan("npx genex pull")} \u2014 take the other device's work (refuses if you have unshipped changes)`);
|
|
1827
|
-
log.dim(` ${c.cyan("npx genex preview --force")} \u2014 keep yours and replace theirs`);
|
|
1828
|
-
}
|
|
1829
|
-
async function sourceTreeHash(cwd) {
|
|
1830
|
-
const gitDir = await fs8.mkdtemp(path8.join(os5.tmpdir(), "genex-tree-"));
|
|
1831
|
-
const base = { GIT_DIR: gitDir };
|
|
1832
|
-
try {
|
|
1833
|
-
if ((await run("git", ["init", "-q"], base)).code !== 0) return null;
|
|
1834
|
-
await fs8.writeFile(
|
|
1835
|
-
path8.join(gitDir, "info", "exclude"),
|
|
1836
|
-
["node_modules/", "dist/", ".git/", ".genex/", ".env", ".env.*", "!.env.example", ""].join("\n")
|
|
1837
|
-
);
|
|
1838
|
-
if ((await run("git", ["lfs", "version"], base)).code === 0) {
|
|
1839
|
-
const filters = [
|
|
1840
|
-
["filter.lfs.clean", "git-lfs clean -- %f"],
|
|
1841
|
-
["filter.lfs.smudge", "git-lfs smudge -- %f"],
|
|
1842
|
-
["filter.lfs.process", "git-lfs filter-process"],
|
|
1843
|
-
["filter.lfs.required", "true"]
|
|
1844
|
-
];
|
|
1845
|
-
for (const [key, value] of filters) await run("git", ["config", key, value], base);
|
|
1846
|
-
}
|
|
1847
|
-
const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path8.join(gitDir, "index-tree") };
|
|
1848
|
-
if ((await run("git", ["add", "-A"], env)).code !== 0) return null;
|
|
1849
|
-
const tree = (await run("git", ["write-tree"], env)).out.trim();
|
|
1850
|
-
return /^[0-9a-f]{40}$/.test(tree) ? tree : null;
|
|
1851
|
-
} catch {
|
|
1852
|
-
return null;
|
|
1853
|
-
} finally {
|
|
1854
|
-
await fs8.rm(gitDir, { recursive: true, force: true }).catch(() => {
|
|
1855
|
-
});
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
|
-
function urlHasEmbeddedCredentials(url) {
|
|
1859
|
-
return /^[a-z][a-z0-9+.-]*:\/\/[^/@]+@/i.test(url);
|
|
1860
|
-
}
|
|
1861
|
-
function credentialHelperOff(url) {
|
|
1862
|
-
if (!urlHasEmbeddedCredentials(url)) return {};
|
|
1863
|
-
return { GIT_CONFIG_COUNT: "1", GIT_CONFIG_KEY_0: "credential.helper", GIT_CONFIG_VALUE_0: "" };
|
|
1864
|
-
}
|
|
1865
|
-
async function fetchCloneGrant(apiUrl, token, projectId, log) {
|
|
1866
|
-
let res;
|
|
1867
|
-
try {
|
|
1868
|
-
res = await apiFetch(`${apiUrl}/api/projects/${projectId}/push-token`, {
|
|
1869
|
-
method: "POST",
|
|
1870
|
-
headers: { Authorization: `Bearer ${token}`, "X-Genex-Source-Intent": "read" }
|
|
1871
|
-
});
|
|
1872
|
-
} catch (err) {
|
|
1873
|
-
log.error(`Couldn't reach the API to authorize the source read: ${String(err)}`);
|
|
1874
|
-
return null;
|
|
1875
|
-
}
|
|
1876
|
-
if (res.status === 401) {
|
|
1877
|
-
log.error("Not authorized \u2014 your token may have expired. Re-run `genex auth`.");
|
|
1878
|
-
return null;
|
|
1879
|
-
}
|
|
1880
|
-
if (!res.ok) {
|
|
1881
|
-
log.error(`Couldn't authorize the source read (HTTP ${res.status}).`);
|
|
1882
|
-
return null;
|
|
1883
|
-
}
|
|
1884
|
-
const data = await res.json().catch(() => null);
|
|
1885
|
-
const url = data?.pushUrl ?? data?.cloneUrl;
|
|
1886
|
-
if (!url) {
|
|
1887
|
-
log.error("The API didn't return a source URL.");
|
|
1888
|
-
return null;
|
|
1889
|
-
}
|
|
1890
|
-
return { cloneUrl: url, sourceRef: data?.sourceRef ?? null };
|
|
1891
|
-
}
|
|
1892
|
-
async function cloneSource(grant, dest, log) {
|
|
1893
|
-
const args = grant.sourceRef ? ["clone", "--branch", grant.sourceRef, grant.cloneUrl, dest] : ["clone", grant.cloneUrl, dest];
|
|
1894
|
-
const env = {
|
|
1895
|
-
GIT_LFS_SKIP_SMUDGE: "1",
|
|
1896
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
1897
|
-
...credentialHelperOff(grant.cloneUrl)
|
|
1898
|
-
};
|
|
1899
|
-
const cloned = await run("git", args, env);
|
|
1900
|
-
if (cloned.code !== 0) {
|
|
1901
|
-
log.error("Couldn't download the game's source.");
|
|
1902
|
-
log.dim(` git clone exited ${cloned.code}`);
|
|
1903
|
-
return false;
|
|
1904
|
-
}
|
|
1905
|
-
const repo = { ...env, GIT_DIR: path8.join(dest, ".git"), GIT_WORK_TREE: dest };
|
|
1906
|
-
const filters = [
|
|
1907
|
-
["filter.lfs.clean", "git-lfs clean -- %f"],
|
|
1908
|
-
["filter.lfs.smudge", "git-lfs smudge -- %f"],
|
|
1909
|
-
["filter.lfs.process", "git-lfs filter-process"],
|
|
1910
|
-
["filter.lfs.required", "true"]
|
|
1911
|
-
];
|
|
1912
|
-
for (const [key, value] of filters) await run("git", ["config", key, value], repo);
|
|
1913
|
-
const pulled = await run("git", ["lfs", "pull"], repo);
|
|
1914
|
-
if (pulled.code !== 0) {
|
|
1915
|
-
log.warn("Binary assets (models, textures, audio) are still pointer files \u2014 `git lfs pull` failed.");
|
|
1916
|
-
log.dim(" The source is there; run `git lfs pull` in this folder once git-lfs works.");
|
|
1917
|
-
}
|
|
1918
|
-
return true;
|
|
1919
|
-
}
|
|
1920
|
-
|
|
1921
|
-
// src/commands/link.ts
|
|
1780
|
+
import path8 from "path";
|
|
1922
1781
|
async function runLink(opts) {
|
|
1923
1782
|
const log = createLogger({ quiet: opts.quiet });
|
|
1924
1783
|
log.plain(c.bold("genex link"));
|
|
@@ -1998,7 +1857,7 @@ async function runLink(opts) {
|
|
|
1998
1857
|
}
|
|
1999
1858
|
async function isEmptyDir(cwd) {
|
|
2000
1859
|
try {
|
|
2001
|
-
const entries = await
|
|
1860
|
+
const entries = await fs8.readdir(cwd);
|
|
2002
1861
|
return entries.every((e) => e === ".git" || e === ".genex" || e === ".DS_Store");
|
|
2003
1862
|
} catch {
|
|
2004
1863
|
return false;
|
|
@@ -2012,13 +1871,13 @@ async function downloadSource(apiUrl, token, project, log) {
|
|
|
2012
1871
|
return false;
|
|
2013
1872
|
}
|
|
2014
1873
|
log.step(`Downloading the ${grant.sourceRef === "preview" ? "draft" : "published"} source\u2026`);
|
|
2015
|
-
const staging = await
|
|
2016
|
-
const fresh =
|
|
1874
|
+
const staging = await fs8.mkdtemp(path8.join(os5.tmpdir(), "genex-link-"));
|
|
1875
|
+
const fresh = path8.join(staging, "source");
|
|
2017
1876
|
try {
|
|
2018
1877
|
if (!await cloneSource(grant, fresh, log)) return false;
|
|
2019
|
-
await
|
|
2020
|
-
for (const entry of await
|
|
2021
|
-
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), {
|
|
2022
1881
|
recursive: true,
|
|
2023
1882
|
force: true
|
|
2024
1883
|
});
|
|
@@ -2026,28 +1885,28 @@ async function downloadSource(apiUrl, token, project, log) {
|
|
|
2026
1885
|
log.success("Downloaded.");
|
|
2027
1886
|
return true;
|
|
2028
1887
|
} finally {
|
|
2029
|
-
await
|
|
1888
|
+
await fs8.rm(staging, { recursive: true, force: true }).catch(() => {
|
|
2030
1889
|
});
|
|
2031
1890
|
}
|
|
2032
1891
|
}
|
|
2033
1892
|
async function ensureSlugEnv(slug, log, cwd = process.cwd()) {
|
|
2034
|
-
const file =
|
|
1893
|
+
const file = path8.join(cwd, ".env");
|
|
2035
1894
|
let content;
|
|
2036
1895
|
try {
|
|
2037
|
-
content = await
|
|
1896
|
+
content = await fs8.readFile(file, "utf8");
|
|
2038
1897
|
} catch {
|
|
2039
1898
|
return;
|
|
2040
1899
|
}
|
|
2041
1900
|
const re = /^(\s*VITE_GENEX_SLUG=)(.*)$/m;
|
|
2042
1901
|
const m = content.match(re);
|
|
2043
1902
|
if (!m) {
|
|
2044
|
-
await
|
|
1903
|
+
await fs8.appendFile(file, `${content.endsWith("\n") ? "" : "\n"}VITE_GENEX_SLUG=${slug}
|
|
2045
1904
|
`);
|
|
2046
1905
|
log.dim(` added VITE_GENEX_SLUG=${slug} to .env`);
|
|
2047
1906
|
return;
|
|
2048
1907
|
}
|
|
2049
1908
|
if (m[2].trim() === slug) return;
|
|
2050
|
-
await
|
|
1909
|
+
await fs8.writeFile(file, content.replace(re, `$1${slug}`));
|
|
2051
1910
|
log.dim(` updated .env: VITE_GENEX_SLUG=${slug} (was ${m[2].trim()})`);
|
|
2052
1911
|
}
|
|
2053
1912
|
async function fetchOwnProject(apiUrl, token, slug, log) {
|
|
@@ -2101,9 +1960,9 @@ async function listOwnSlugs(apiUrl, token, log) {
|
|
|
2101
1960
|
}
|
|
2102
1961
|
|
|
2103
1962
|
// src/commands/pull.ts
|
|
2104
|
-
import
|
|
2105
|
-
import
|
|
2106
|
-
import
|
|
1963
|
+
import fs9 from "fs/promises";
|
|
1964
|
+
import path9 from "path";
|
|
1965
|
+
import os6 from "os";
|
|
2107
1966
|
function isMachineLocal(entry) {
|
|
2108
1967
|
if (entry === ".genex" || entry === "node_modules" || entry === ".git") return true;
|
|
2109
1968
|
if (entry === ".env.example") return false;
|
|
@@ -2162,23 +2021,23 @@ async function runPull(opts) {
|
|
|
2162
2021
|
process.exitCode = 1;
|
|
2163
2022
|
return;
|
|
2164
2023
|
}
|
|
2165
|
-
const staging = await
|
|
2166
|
-
const fresh =
|
|
2024
|
+
const staging = await fs9.mkdtemp(path9.join(os6.tmpdir(), "genex-pull-"));
|
|
2025
|
+
const fresh = path9.join(staging, "source");
|
|
2167
2026
|
try {
|
|
2168
2027
|
if (!await cloneSource(grant, fresh, log)) {
|
|
2169
2028
|
process.exitCode = 1;
|
|
2170
2029
|
return;
|
|
2171
2030
|
}
|
|
2172
|
-
await
|
|
2031
|
+
await fs9.rm(path9.join(fresh, ".git"), { recursive: true, force: true });
|
|
2173
2032
|
const kept = await keepReplaced(cwd, log);
|
|
2174
2033
|
await replaceTree(cwd, fresh);
|
|
2175
2034
|
if (kept) {
|
|
2176
2035
|
log.plain("");
|
|
2177
|
-
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)}`);
|
|
2178
2037
|
log.dim(" Nothing was thrown away \u2014 re-apply from there, or delete it when you are done.");
|
|
2179
2038
|
}
|
|
2180
2039
|
} finally {
|
|
2181
|
-
await
|
|
2040
|
+
await fs9.rm(staging, { recursive: true, force: true }).catch(() => {
|
|
2182
2041
|
});
|
|
2183
2042
|
}
|
|
2184
2043
|
const remote = await readRemoteSource(apiUrl, token, meta.id);
|
|
@@ -2197,13 +2056,13 @@ async function runPull(opts) {
|
|
|
2197
2056
|
}
|
|
2198
2057
|
async function keepReplaced(cwd, log) {
|
|
2199
2058
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2200
|
-
const dest =
|
|
2059
|
+
const dest = path9.join(cwd, ".genex", `replaced-${stamp}`);
|
|
2201
2060
|
try {
|
|
2202
|
-
const entries = (await
|
|
2061
|
+
const entries = (await fs9.readdir(cwd)).filter((e) => !isMachineLocal(e));
|
|
2203
2062
|
if (entries.length === 0) return null;
|
|
2204
|
-
await
|
|
2063
|
+
await fs9.mkdir(dest, { recursive: true });
|
|
2205
2064
|
for (const entry of entries) {
|
|
2206
|
-
await
|
|
2065
|
+
await fs9.cp(path9.join(cwd, entry), path9.join(dest, entry), { recursive: true });
|
|
2207
2066
|
}
|
|
2208
2067
|
return dest;
|
|
2209
2068
|
} catch (err) {
|
|
@@ -2213,19 +2072,19 @@ async function keepReplaced(cwd, log) {
|
|
|
2213
2072
|
}
|
|
2214
2073
|
}
|
|
2215
2074
|
async function replaceTree(dest, src) {
|
|
2216
|
-
for (const entry of await
|
|
2075
|
+
for (const entry of await fs9.readdir(dest)) {
|
|
2217
2076
|
if (isMachineLocal(entry)) continue;
|
|
2218
|
-
await
|
|
2077
|
+
await fs9.rm(path9.join(dest, entry), { recursive: true, force: true });
|
|
2219
2078
|
}
|
|
2220
|
-
for (const entry of await
|
|
2079
|
+
for (const entry of await fs9.readdir(src)) {
|
|
2221
2080
|
if (isMachineLocal(entry)) continue;
|
|
2222
|
-
await
|
|
2081
|
+
await fs9.cp(path9.join(src, entry), path9.join(dest, entry), { recursive: true });
|
|
2223
2082
|
}
|
|
2224
2083
|
}
|
|
2225
2084
|
|
|
2226
2085
|
// src/commands/rename.ts
|
|
2227
|
-
import
|
|
2228
|
-
import
|
|
2086
|
+
import fs10 from "fs/promises";
|
|
2087
|
+
import path10 from "path";
|
|
2229
2088
|
async function runRename(opts) {
|
|
2230
2089
|
const log = createLogger({ quiet: opts.quiet });
|
|
2231
2090
|
log.plain(c.bold("genex rename"));
|
|
@@ -2313,23 +2172,23 @@ async function runRename(opts) {
|
|
|
2313
2172
|
log.info("Run `genex preview` (or `publish`) to rebuild \u2014 the new slug is baked into the bundle.");
|
|
2314
2173
|
}
|
|
2315
2174
|
async function rewriteSlugEnv(from, to, log, cwd = process.cwd()) {
|
|
2316
|
-
const file =
|
|
2175
|
+
const file = path10.join(cwd, ".env");
|
|
2317
2176
|
let content;
|
|
2318
2177
|
try {
|
|
2319
|
-
content = await
|
|
2178
|
+
content = await fs10.readFile(file, "utf8");
|
|
2320
2179
|
} catch {
|
|
2321
2180
|
return;
|
|
2322
2181
|
}
|
|
2323
2182
|
const re = /^(\s*VITE_GENEX_SLUG=)(.*)$/m;
|
|
2324
2183
|
if (!re.test(content)) return;
|
|
2325
|
-
await
|
|
2184
|
+
await fs10.writeFile(file, content.replace(re, `$1${to}`));
|
|
2326
2185
|
log.dim(` .env: VITE_GENEX_SLUG=${to} (was ${from})`);
|
|
2327
2186
|
}
|
|
2328
2187
|
async function rewriteBakedSlug(from, to, log, cwd = process.cwd()) {
|
|
2329
|
-
const file =
|
|
2188
|
+
const file = path10.join(cwd, "src", "genex.config.ts");
|
|
2330
2189
|
let content;
|
|
2331
2190
|
try {
|
|
2332
|
-
content = await
|
|
2191
|
+
content = await fs10.readFile(file, "utf8");
|
|
2333
2192
|
} catch {
|
|
2334
2193
|
return;
|
|
2335
2194
|
}
|
|
@@ -2339,7 +2198,7 @@ async function rewriteBakedSlug(from, to, log, cwd = process.cwd()) {
|
|
|
2339
2198
|
log.warn(` src/genex.config.ts has no "${from}" literal \u2014 check its slug by hand.`);
|
|
2340
2199
|
return;
|
|
2341
2200
|
}
|
|
2342
|
-
await
|
|
2201
|
+
await fs10.writeFile(file, content.replace(quoted, `"${to}"`));
|
|
2343
2202
|
log.dim(` src/genex.config.ts: baked slug -> ${to}`);
|
|
2344
2203
|
}
|
|
2345
2204
|
|
|
@@ -2470,13 +2329,13 @@ function relTime(iso) {
|
|
|
2470
2329
|
import "child_process";
|
|
2471
2330
|
|
|
2472
2331
|
// src/lib/generation-ledger.ts
|
|
2473
|
-
import
|
|
2474
|
-
import
|
|
2475
|
-
var ledgerPath = (cwd) =>
|
|
2332
|
+
import fs11 from "fs/promises";
|
|
2333
|
+
import path11 from "path";
|
|
2334
|
+
var ledgerPath = (cwd) => path11.join(cwd, ".genex", "generations.ndjson");
|
|
2476
2335
|
async function append(cwd, event) {
|
|
2477
2336
|
try {
|
|
2478
|
-
await
|
|
2479
|
-
await
|
|
2337
|
+
await fs11.access(path11.join(cwd, ".genex"));
|
|
2338
|
+
await fs11.appendFile(ledgerPath(cwd), `${JSON.stringify(event)}
|
|
2480
2339
|
`, "utf8");
|
|
2481
2340
|
} catch {
|
|
2482
2341
|
}
|
|
@@ -2484,7 +2343,7 @@ async function append(cwd, event) {
|
|
|
2484
2343
|
async function readLedger(cwd = process.cwd()) {
|
|
2485
2344
|
let raw;
|
|
2486
2345
|
try {
|
|
2487
|
-
raw = await
|
|
2346
|
+
raw = await fs11.readFile(ledgerPath(cwd), "utf8");
|
|
2488
2347
|
} catch {
|
|
2489
2348
|
return [];
|
|
2490
2349
|
}
|
|
@@ -2689,9 +2548,9 @@ function auditGenerationPlan(input) {
|
|
|
2689
2548
|
|
|
2690
2549
|
// src/lib/deploy.ts
|
|
2691
2550
|
import crypto3 from "crypto";
|
|
2692
|
-
import
|
|
2693
|
-
import
|
|
2694
|
-
import
|
|
2551
|
+
import fs14 from "fs/promises";
|
|
2552
|
+
import os7 from "os";
|
|
2553
|
+
import path13 from "path";
|
|
2695
2554
|
|
|
2696
2555
|
// ../../packages/mobile-scan/src/image-dims.ts
|
|
2697
2556
|
function u32be(b, o) {
|
|
@@ -2955,12 +2814,12 @@ function tierFor(estVramMb) {
|
|
|
2955
2814
|
}
|
|
2956
2815
|
|
|
2957
2816
|
// src/commands/ui.ts
|
|
2958
|
-
import
|
|
2959
|
-
import
|
|
2817
|
+
import fs13 from "fs/promises";
|
|
2818
|
+
import path12 from "path";
|
|
2960
2819
|
import { PNG as PNG2 } from "pngjs";
|
|
2961
2820
|
|
|
2962
2821
|
// src/lib/png-tools.ts
|
|
2963
|
-
import
|
|
2822
|
+
import fs12 from "fs/promises";
|
|
2964
2823
|
import { PNG } from "pngjs";
|
|
2965
2824
|
var ALPHA_TRANSPARENT_MAX = 16;
|
|
2966
2825
|
var isHttpUrl = (s) => /^https?:\/\//i.test(s);
|
|
@@ -2971,12 +2830,12 @@ async function loadPng(input) {
|
|
|
2971
2830
|
if (!res.ok) throw new Error(`Couldn't fetch ${input} (HTTP ${res.status}).`);
|
|
2972
2831
|
buf = Buffer.from(await res.arrayBuffer());
|
|
2973
2832
|
} else {
|
|
2974
|
-
buf = await
|
|
2833
|
+
buf = await fs12.readFile(input);
|
|
2975
2834
|
}
|
|
2976
2835
|
return PNG.sync.read(buf);
|
|
2977
2836
|
}
|
|
2978
2837
|
async function writePng(file, png) {
|
|
2979
|
-
await
|
|
2838
|
+
await fs12.writeFile(file, PNG.sync.write(png));
|
|
2980
2839
|
}
|
|
2981
2840
|
function cropPng(image, box) {
|
|
2982
2841
|
const out = new PNG({ width: box.w, height: box.h });
|
|
@@ -3276,7 +3135,7 @@ async function uiExtract(opts, log) {
|
|
|
3276
3135
|
const dilatePx = opts.dilate ?? 0;
|
|
3277
3136
|
const sheet = await loadPng(input);
|
|
3278
3137
|
const { width: W, height: H, data } = sheet;
|
|
3279
|
-
await
|
|
3138
|
+
await fs13.mkdir(outDir, { recursive: true });
|
|
3280
3139
|
log.plain(c.bold("genex ui extract"));
|
|
3281
3140
|
log.dim(` ${input} (${W}x${H}) \u2192 ${outDir}, ${names.length} names`);
|
|
3282
3141
|
let hasTransparency = false;
|
|
@@ -3505,7 +3364,7 @@ async function uiExtract(opts, log) {
|
|
|
3505
3364
|
rimPixels: speckle.sampled
|
|
3506
3365
|
});
|
|
3507
3366
|
}
|
|
3508
|
-
const outPath =
|
|
3367
|
+
const outPath = path12.join(outDir, `${name}.png`);
|
|
3509
3368
|
await writePng(outPath, out);
|
|
3510
3369
|
const sidecar = {
|
|
3511
3370
|
name,
|
|
@@ -3524,7 +3383,7 @@ async function uiExtract(opts, log) {
|
|
|
3524
3383
|
defringed
|
|
3525
3384
|
};
|
|
3526
3385
|
const { name: _n, out: _o, ...sidecarBody } = sidecar;
|
|
3527
|
-
await
|
|
3386
|
+
await fs13.writeFile(
|
|
3528
3387
|
outPath.replace(/\.png$/i, "") + ".bbox.json",
|
|
3529
3388
|
JSON.stringify(sidecarBody, null, 2)
|
|
3530
3389
|
);
|
|
@@ -3533,8 +3392,8 @@ async function uiExtract(opts, log) {
|
|
|
3533
3392
|
`${name}.png ${cropW}x${cropH} (ar ${sidecar.aspectRatio}) at sheet ${padX0},${padY0}`
|
|
3534
3393
|
);
|
|
3535
3394
|
}
|
|
3536
|
-
const debugPath =
|
|
3537
|
-
await
|
|
3395
|
+
const debugPath = path12.join(outDir, "extract-debug.json");
|
|
3396
|
+
await fs13.writeFile(
|
|
3538
3397
|
debugPath,
|
|
3539
3398
|
JSON.stringify(
|
|
3540
3399
|
{
|
|
@@ -3996,7 +3855,7 @@ async function uiMasks(opts, log) {
|
|
|
3996
3855
|
const registrationTolerance = opts.registrationTolerance ?? 0.04;
|
|
3997
3856
|
const edgeFlushMax = opts.edgeFlushMax ?? 0.04;
|
|
3998
3857
|
const loosened = registrationTolerance > 0.04 || edgeFlushMax > 0.04 || minCoverage < 0.01 || maxCoverage > 0.85;
|
|
3999
|
-
await
|
|
3858
|
+
await fs13.mkdir(outDir, { recursive: true });
|
|
4000
3859
|
log.plain(c.bold("genex ui masks"));
|
|
4001
3860
|
log.dim(` ${input} (${image.width}x${image.height}), ${pairs.length} pair(s) \u2192 ${outDir}`);
|
|
4002
3861
|
const sheetComponents = detectSheetComponents(image, opts.minPixels ?? 2e3);
|
|
@@ -4049,11 +3908,11 @@ async function uiMasks(opts, log) {
|
|
|
4049
3908
|
});
|
|
4050
3909
|
}
|
|
4051
3910
|
const overlay = makeOverlay(clean2, converted.png);
|
|
4052
|
-
const framePath =
|
|
4053
|
-
const maskPath =
|
|
4054
|
-
const annotatedPath =
|
|
4055
|
-
const overlayPath =
|
|
4056
|
-
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`);
|
|
4057
3916
|
await writePng(framePath, clean2);
|
|
4058
3917
|
await writePng(maskPath, converted.png);
|
|
4059
3918
|
await writePng(annotatedPath, annotated);
|
|
@@ -4100,7 +3959,7 @@ async function uiMasks(opts, log) {
|
|
|
4100
3959
|
},
|
|
4101
3960
|
overlay: overlayPath
|
|
4102
3961
|
};
|
|
4103
|
-
await
|
|
3962
|
+
await fs13.writeFile(metaPath, `${JSON.stringify(meta, null, 2)}
|
|
4104
3963
|
`);
|
|
4105
3964
|
results.push(meta);
|
|
4106
3965
|
const fb = converted.bbox;
|
|
@@ -4116,8 +3975,8 @@ async function uiMasks(opts, log) {
|
|
|
4116
3975
|
);
|
|
4117
3976
|
}
|
|
4118
3977
|
}
|
|
4119
|
-
const indexPath =
|
|
4120
|
-
await
|
|
3978
|
+
const indexPath = path12.join(outDir, "annotated-progress.json");
|
|
3979
|
+
await fs13.writeFile(indexPath, `${JSON.stringify({ input, loosened, pairs: results }, null, 2)}
|
|
4121
3980
|
`);
|
|
4122
3981
|
log.plain("");
|
|
4123
3982
|
log.success(`Wrote ${pairs.length} frame+mask pair(s) \u2192 ${outDir} (index: ${indexPath}).`);
|
|
@@ -4243,7 +4102,7 @@ async function uiTextColor(opts, log) {
|
|
|
4243
4102
|
};
|
|
4244
4103
|
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
4245
4104
|
`);
|
|
4246
|
-
if (opts.out) await
|
|
4105
|
+
if (opts.out) await fs13.writeFile(opts.out, `${JSON.stringify(result, null, 2)}
|
|
4247
4106
|
`);
|
|
4248
4107
|
if (opts.cropPath) log.dim(` crop \u2192 ${opts.cropPath}`);
|
|
4249
4108
|
}
|
|
@@ -4275,7 +4134,7 @@ async function uiTrim(opts, log) {
|
|
|
4275
4134
|
const sidecar = computeBBoxes(trimmed);
|
|
4276
4135
|
const speckleAllowed = !!(speckle && speckle.ratio > SPECKLE_MAX_RATIO);
|
|
4277
4136
|
const sidecarPath = outPath.replace(/\.png$/i, "") + ".bbox.json";
|
|
4278
|
-
await
|
|
4137
|
+
await fs13.writeFile(
|
|
4279
4138
|
sidecarPath,
|
|
4280
4139
|
JSON.stringify(speckleAllowed ? { ...sidecar, speckleAllowed: true } : sidecar, null, 2)
|
|
4281
4140
|
);
|
|
@@ -4382,7 +4241,7 @@ async function uiPlate(opts, log) {
|
|
|
4382
4241
|
fail("No interior found \u2014 the image is fully transparent (or erode ate everything). Check --in / lower --erode.");
|
|
4383
4242
|
}
|
|
4384
4243
|
await writePng(outPath, out);
|
|
4385
|
-
const name =
|
|
4244
|
+
const name = path12.basename(outPath);
|
|
4386
4245
|
log.plain(c.bold("genex ui plate"));
|
|
4387
4246
|
log.success(`${outPath} ${W}x${H}, interior ${(count / (W * H) * 100).toFixed(1)}% (erode ${erode}px)`);
|
|
4388
4247
|
log.dim(" Wire it as the plate's silhouette (same box as the frame <img>, plate UNDER the art):");
|
|
@@ -4547,13 +4406,13 @@ async function walkFiles(dir) {
|
|
|
4547
4406
|
const out = [];
|
|
4548
4407
|
let entries;
|
|
4549
4408
|
try {
|
|
4550
|
-
entries = await
|
|
4409
|
+
entries = await fs13.readdir(dir, { withFileTypes: true });
|
|
4551
4410
|
} catch {
|
|
4552
4411
|
return out;
|
|
4553
4412
|
}
|
|
4554
4413
|
for (const entry of entries) {
|
|
4555
4414
|
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
4556
|
-
const p =
|
|
4415
|
+
const p = path12.join(dir, entry.name);
|
|
4557
4416
|
if (entry.isDirectory()) out.push(...await walkFiles(p));
|
|
4558
4417
|
else out.push(p);
|
|
4559
4418
|
}
|
|
@@ -4562,7 +4421,7 @@ async function walkFiles(dir) {
|
|
|
4562
4421
|
async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
4563
4422
|
const viewportFindings = [];
|
|
4564
4423
|
try {
|
|
4565
|
-
const indexHtml = await
|
|
4424
|
+
const indexHtml = await fs13.readFile(path12.join(cwd, "index.html"), "utf8");
|
|
4566
4425
|
if (!/<meta[^>]+name=["']viewport["']/i.test(indexHtml)) {
|
|
4567
4426
|
viewportFindings.push({
|
|
4568
4427
|
kind: "viewport-meta",
|
|
@@ -4576,28 +4435,28 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4576
4435
|
}
|
|
4577
4436
|
} catch {
|
|
4578
4437
|
}
|
|
4579
|
-
const absAssets =
|
|
4438
|
+
const absAssets = path12.resolve(cwd, assetDir);
|
|
4580
4439
|
try {
|
|
4581
|
-
if (!(await
|
|
4440
|
+
if (!(await fs13.stat(absAssets)).isDirectory()) {
|
|
4582
4441
|
return viewportFindings.length > 0 ? viewportFindings : null;
|
|
4583
4442
|
}
|
|
4584
4443
|
} catch {
|
|
4585
4444
|
return viewportFindings.length > 0 ? viewportFindings : null;
|
|
4586
4445
|
}
|
|
4587
|
-
const srcFiles = (await walkFiles(
|
|
4588
|
-
(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())
|
|
4589
4448
|
);
|
|
4590
4449
|
try {
|
|
4591
|
-
for (const name of await
|
|
4592
|
-
const ext =
|
|
4593
|
-
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));
|
|
4594
4453
|
}
|
|
4595
4454
|
} catch {
|
|
4596
4455
|
}
|
|
4597
4456
|
const sources = [];
|
|
4598
4457
|
for (const p of srcFiles) {
|
|
4599
4458
|
try {
|
|
4600
|
-
sources.push({ rel:
|
|
4459
|
+
sources.push({ rel: path12.relative(cwd, p), text: await fs13.readFile(p, "utf8") });
|
|
4601
4460
|
} catch {
|
|
4602
4461
|
}
|
|
4603
4462
|
}
|
|
@@ -4607,11 +4466,11 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4607
4466
|
const metaByName = /* @__PURE__ */ new Map();
|
|
4608
4467
|
const bboxByPng = /* @__PURE__ */ new Map();
|
|
4609
4468
|
for (const p of assetFiles) {
|
|
4610
|
-
const base =
|
|
4469
|
+
const base = path12.basename(p);
|
|
4611
4470
|
const metaMatch = /^(.+)\.annotated-progress\.json$/.exec(base);
|
|
4612
4471
|
if (metaMatch) {
|
|
4613
4472
|
try {
|
|
4614
|
-
const meta = JSON.parse(await
|
|
4473
|
+
const meta = JSON.parse(await fs13.readFile(p, "utf8"));
|
|
4615
4474
|
metaByName.set(metaMatch[1], {
|
|
4616
4475
|
cleanCrop: meta.clean?.crop ?? null,
|
|
4617
4476
|
loosened: meta.loosened === true
|
|
@@ -4622,7 +4481,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4622
4481
|
}
|
|
4623
4482
|
if (base.endsWith(".bbox.json")) {
|
|
4624
4483
|
try {
|
|
4625
|
-
const sidecar = JSON.parse(await
|
|
4484
|
+
const sidecar = JSON.parse(await fs13.readFile(p, "utf8"));
|
|
4626
4485
|
if (sidecar.sheetBBox) bboxByPng.set(base.replace(/\.bbox\.json$/, ".png"), sidecar.sheetBBox);
|
|
4627
4486
|
} catch {
|
|
4628
4487
|
}
|
|
@@ -4631,7 +4490,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4631
4490
|
for (const [name, meta] of metaByName) {
|
|
4632
4491
|
if (!meta.cleanCrop || !referenced(`${name}-mask.png`)) continue;
|
|
4633
4492
|
for (const p of assetFiles) {
|
|
4634
|
-
const base =
|
|
4493
|
+
const base = path12.basename(p);
|
|
4635
4494
|
if (!base.toLowerCase().endsWith(".png")) continue;
|
|
4636
4495
|
if (base !== `${name}.png` && !base.startsWith(`${name}-`)) continue;
|
|
4637
4496
|
if (/-mask\.png$|-frame\.png$|-overlay\.png$|-annotated-source\.png$/.test(base)) continue;
|
|
@@ -4655,7 +4514,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4655
4514
|
}
|
|
4656
4515
|
const pngByBase = /* @__PURE__ */ new Map();
|
|
4657
4516
|
for (const p of assetFiles) {
|
|
4658
|
-
const base =
|
|
4517
|
+
const base = path12.basename(p);
|
|
4659
4518
|
if (base.toLowerCase().endsWith(".png")) pngByBase.set(base, p);
|
|
4660
4519
|
}
|
|
4661
4520
|
for (const [maskBase, maskPath] of pngByBase) {
|
|
@@ -4668,8 +4527,8 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4668
4527
|
let frame;
|
|
4669
4528
|
let mask;
|
|
4670
4529
|
try {
|
|
4671
|
-
frame = PNG2.sync.read(await
|
|
4672
|
-
mask = PNG2.sync.read(await
|
|
4530
|
+
frame = PNG2.sync.read(await fs13.readFile(framePath));
|
|
4531
|
+
mask = PNG2.sync.read(await fs13.readFile(maskPath));
|
|
4673
4532
|
} catch {
|
|
4674
4533
|
continue;
|
|
4675
4534
|
}
|
|
@@ -4688,7 +4547,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4688
4547
|
if (!referenced(base)) continue;
|
|
4689
4548
|
let png;
|
|
4690
4549
|
try {
|
|
4691
|
-
png = PNG2.sync.read(await
|
|
4550
|
+
png = PNG2.sync.read(await fs13.readFile(p));
|
|
4692
4551
|
} catch {
|
|
4693
4552
|
continue;
|
|
4694
4553
|
}
|
|
@@ -4708,7 +4567,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4708
4567
|
}
|
|
4709
4568
|
const maskReported = /* @__PURE__ */ new Set();
|
|
4710
4569
|
for (const p of assetFiles) {
|
|
4711
|
-
const m = /^(.+)\.annotated-progress\.json$/.exec(
|
|
4570
|
+
const m = /^(.+)\.annotated-progress\.json$/.exec(path12.basename(p));
|
|
4712
4571
|
if (!m) continue;
|
|
4713
4572
|
const maskBase = `${m[1]}-mask.png`;
|
|
4714
4573
|
if (!referenced(maskBase)) {
|
|
@@ -4720,14 +4579,14 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4720
4579
|
}
|
|
4721
4580
|
}
|
|
4722
4581
|
for (const p of assetFiles) {
|
|
4723
|
-
const base =
|
|
4582
|
+
const base = path12.basename(p);
|
|
4724
4583
|
if (!base.toLowerCase().endsWith(".png")) continue;
|
|
4725
4584
|
if (/-annotated-source\.png$|-overlay\.png$/.test(base)) continue;
|
|
4726
4585
|
if (maskReported.has(base)) continue;
|
|
4727
4586
|
if (!referenced(base)) {
|
|
4728
4587
|
findings.push({
|
|
4729
4588
|
kind: "unwired-sprite",
|
|
4730
|
-
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.)`
|
|
4731
4590
|
});
|
|
4732
4591
|
}
|
|
4733
4592
|
}
|
|
@@ -4779,78 +4638,6 @@ async function uiAudit(opts, log) {
|
|
|
4779
4638
|
if (opts.strict || hard.length > 0) process.exitCode = 1;
|
|
4780
4639
|
}
|
|
4781
4640
|
|
|
4782
|
-
// src/lib/terms.ts
|
|
4783
|
-
import readline2 from "readline";
|
|
4784
|
-
var TERMS_ERROR_CODE = "terms_acceptance_required";
|
|
4785
|
-
async function isTermsRefusal(res) {
|
|
4786
|
-
if (res.status !== 403) return false;
|
|
4787
|
-
try {
|
|
4788
|
-
const body = await res.clone().json();
|
|
4789
|
-
return body?.error === TERMS_ERROR_CODE;
|
|
4790
|
-
} catch {
|
|
4791
|
-
return false;
|
|
4792
|
-
}
|
|
4793
|
-
}
|
|
4794
|
-
function reportTermsRefusal(log, authUrl) {
|
|
4795
|
-
log.error("Your account needs to accept the current Terms before publishing.");
|
|
4796
|
-
log.dim(" Nothing was deployed, and nothing on your game changed.");
|
|
4797
|
-
if (!inHostedSession()) {
|
|
4798
|
-
log.dim(` Run ${c.cyan("npx genex accept")} to read them and agree here.`);
|
|
4799
|
-
}
|
|
4800
|
-
log.dim(` Open ${c.cyan(`${getAuthUrl(authUrl)}/terms`)} and agree, then try again.`);
|
|
4801
|
-
}
|
|
4802
|
-
function askOnStdin(question) {
|
|
4803
|
-
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
4804
|
-
return new Promise((resolve) => {
|
|
4805
|
-
rl.question(question, (answer) => {
|
|
4806
|
-
rl.close();
|
|
4807
|
-
resolve(answer);
|
|
4808
|
-
});
|
|
4809
|
-
});
|
|
4810
|
-
}
|
|
4811
|
-
async function runAccept(opts) {
|
|
4812
|
-
const { token, log } = opts;
|
|
4813
|
-
const interactive = opts.interactive ?? Boolean(process.stdin.isTTY);
|
|
4814
|
-
const apiUrl = getApiUrl(opts.apiUrl);
|
|
4815
|
-
const web = getAuthUrl();
|
|
4816
|
-
const status = await apiFetch(`${apiUrl}/api/legal/status`, {
|
|
4817
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
4818
|
-
}).catch(() => null);
|
|
4819
|
-
if (status?.ok) {
|
|
4820
|
-
const body = await status.json().catch(() => null);
|
|
4821
|
-
if (body?.accepted) {
|
|
4822
|
-
log.success("Already accepted \u2014 nothing to do.");
|
|
4823
|
-
return true;
|
|
4824
|
-
}
|
|
4825
|
-
log.plain("Before publishing, please read and agree to:");
|
|
4826
|
-
for (const doc of body?.documents ?? []) log.plain(` ${doc.title} ${c.cyan(doc.url)}`);
|
|
4827
|
-
log.plain("");
|
|
4828
|
-
}
|
|
4829
|
-
if (!interactive) {
|
|
4830
|
-
log.error("Accepting the Terms needs a person, so this cannot run unattended.");
|
|
4831
|
-
log.dim(" Agreeing is a contract, and an agent cannot do it on your behalf.");
|
|
4832
|
-
log.dim(` Open ${c.cyan(`${web}/terms`)} and agree there, then re-run your command.`);
|
|
4833
|
-
return false;
|
|
4834
|
-
}
|
|
4835
|
-
const ask = opts.ask ?? askOnStdin;
|
|
4836
|
-
const answer = (await ask("Type 'agree' to accept: ")).trim().toLowerCase();
|
|
4837
|
-
if (answer !== "agree") {
|
|
4838
|
-
log.error("Not accepted \u2014 nothing was recorded.");
|
|
4839
|
-
return false;
|
|
4840
|
-
}
|
|
4841
|
-
const res = await apiFetch(`${apiUrl}/api/legal/accept`, {
|
|
4842
|
-
method: "POST",
|
|
4843
|
-
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
4844
|
-
body: JSON.stringify({ surface: "cli" })
|
|
4845
|
-
}).catch(() => null);
|
|
4846
|
-
if (!res?.ok) {
|
|
4847
|
-
log.error(`Couldn't record your acceptance (HTTP ${res?.status ?? "no response"}).`);
|
|
4848
|
-
return false;
|
|
4849
|
-
}
|
|
4850
|
-
log.success("Accepted. Re-run your command.");
|
|
4851
|
-
return true;
|
|
4852
|
-
}
|
|
4853
|
-
|
|
4854
4641
|
// src/lib/deploy.ts
|
|
4855
4642
|
function printMobilePreflight(files, log) {
|
|
4856
4643
|
try {
|
|
@@ -4914,7 +4701,7 @@ async function printUiAuditPreflight(log) {
|
|
|
4914
4701
|
}
|
|
4915
4702
|
async function printPipelineStatePreflight(log, cwd = process.cwd()) {
|
|
4916
4703
|
try {
|
|
4917
|
-
const design = await
|
|
4704
|
+
const design = await fs14.readFile(path13.join(cwd, "DESIGN.md"), "utf8").catch(() => "");
|
|
4918
4705
|
const warnings = [];
|
|
4919
4706
|
if (design.length > 0 && !/##\s*build plan & status/i.test(design)) {
|
|
4920
4707
|
warnings.push(
|
|
@@ -4922,7 +4709,7 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
|
|
|
4922
4709
|
);
|
|
4923
4710
|
}
|
|
4924
4711
|
if (!/player character:/i.test(design)) {
|
|
4925
|
-
const hasCharacter = await
|
|
4712
|
+
const hasCharacter = await fs14.access(path13.join(cwd, "public", "assets", "meshy-character.json")).then(() => true, () => false);
|
|
4926
4713
|
if (!hasCharacter && await loadsPlayerBody(cwd)) {
|
|
4927
4714
|
warnings.push(
|
|
4928
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).`
|
|
@@ -4937,12 +4724,12 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
|
|
|
4937
4724
|
async function loadsPlayerBody(cwd) {
|
|
4938
4725
|
const BODY_LOADERS = /\bloadPlayerCharacter\s*\(|\bloadVrm(?:Clone)?\s*\(/;
|
|
4939
4726
|
try {
|
|
4940
|
-
const entries = await
|
|
4727
|
+
const entries = await fs14.readdir(path13.join(cwd, "src"), { recursive: true });
|
|
4941
4728
|
for (const rel of entries) {
|
|
4942
4729
|
if (rel.includes("node_modules")) continue;
|
|
4943
|
-
if (rel.split(
|
|
4730
|
+
if (rel.split(path13.sep)[0] === "controllers" || rel.startsWith("controllers/")) continue;
|
|
4944
4731
|
if (!/\.(ts|tsx|js|jsx)$/.test(rel)) continue;
|
|
4945
|
-
const text = await
|
|
4732
|
+
const text = await fs14.readFile(path13.join(cwd, "src", rel), "utf8").catch(() => "");
|
|
4946
4733
|
if (BODY_LOADERS.test(text)) return true;
|
|
4947
4734
|
}
|
|
4948
4735
|
} catch {
|
|
@@ -4979,9 +4766,9 @@ async function deployGame(ctx, opts, log) {
|
|
|
4979
4766
|
}
|
|
4980
4767
|
log.success("Built.");
|
|
4981
4768
|
}
|
|
4982
|
-
const distDir =
|
|
4769
|
+
const distDir = path13.join(cwd, "dist");
|
|
4983
4770
|
const siteDir = await isDir(distDir) ? distDir : cwd;
|
|
4984
|
-
const rel =
|
|
4771
|
+
const rel = path13.relative(cwd, siteDir) || ".";
|
|
4985
4772
|
if (siteDir === cwd) await writeGitignore(cwd, log);
|
|
4986
4773
|
const files = await collectFiles(siteDir);
|
|
4987
4774
|
if (files.length === 0) {
|
|
@@ -5070,7 +4857,7 @@ async function deployGame(ctx, opts, log) {
|
|
|
5070
4857
|
}
|
|
5071
4858
|
async function hasBuildScript(cwd) {
|
|
5072
4859
|
try {
|
|
5073
|
-
const pkg = JSON.parse(await
|
|
4860
|
+
const pkg = JSON.parse(await fs14.readFile(path13.join(cwd, "package.json"), "utf8"));
|
|
5074
4861
|
return Boolean(pkg.scripts?.build);
|
|
5075
4862
|
} catch {
|
|
5076
4863
|
return false;
|
|
@@ -5079,12 +4866,12 @@ async function hasBuildScript(cwd) {
|
|
|
5079
4866
|
async function collectFiles(root) {
|
|
5080
4867
|
const out = [];
|
|
5081
4868
|
const walk2 = async (dir, prefix) => {
|
|
5082
|
-
for (const e of await
|
|
4869
|
+
for (const e of await fs14.readdir(dir, { withFileTypes: true })) {
|
|
5083
4870
|
const relPath = prefix ? `${prefix}/${e.name}` : e.name;
|
|
5084
4871
|
if (e.isDirectory()) {
|
|
5085
|
-
if (!EXCLUDE_DIRS.has(e.name)) await walk2(
|
|
4872
|
+
if (!EXCLUDE_DIRS.has(e.name)) await walk2(path13.join(dir, e.name), relPath);
|
|
5086
4873
|
} else if (e.isFile() && !isSecretEnvFile(e.name)) {
|
|
5087
|
-
out.push({ relPath, bytes: await
|
|
4874
|
+
out.push({ relPath, bytes: await fs14.readFile(path13.join(dir, e.name)) });
|
|
5088
4875
|
}
|
|
5089
4876
|
}
|
|
5090
4877
|
};
|
|
@@ -5337,7 +5124,7 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
|
|
|
5337
5124
|
log.error("Couldn't save your game's source \u2014 please try again.");
|
|
5338
5125
|
return false;
|
|
5339
5126
|
};
|
|
5340
|
-
const gitDir = await
|
|
5127
|
+
const gitDir = await fs14.mkdtemp(path13.join(os7.tmpdir(), "genex-source-"));
|
|
5341
5128
|
const base = { GIT_DIR: gitDir };
|
|
5342
5129
|
if (urlHasEmbeddedCredentials(pushUrl)) {
|
|
5343
5130
|
base.GIT_CONFIG_COUNT = "1";
|
|
@@ -5352,12 +5139,12 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
|
|
|
5352
5139
|
};
|
|
5353
5140
|
try {
|
|
5354
5141
|
if ((await run("git", ["init", "-q"], base)).code !== 0) return failed();
|
|
5355
|
-
await
|
|
5356
|
-
|
|
5142
|
+
await fs14.writeFile(
|
|
5143
|
+
path13.join(gitDir, "info", "exclude"),
|
|
5357
5144
|
// .env* are secrets — never publish them; `!` keeps the non-secret template.
|
|
5358
5145
|
["node_modules/", "dist/", ".git/", ".genex/", ".env", ".env.*", "!.env.example", ""].join("\n")
|
|
5359
5146
|
);
|
|
5360
|
-
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") };
|
|
5361
5148
|
let lfs = (await run("git", ["lfs", "version"], base)).code !== 0 ? false : true;
|
|
5362
5149
|
if (!lfs) {
|
|
5363
5150
|
log.step("Installing git-lfs (keeps large binary assets out of the source push)\u2026");
|
|
@@ -5409,7 +5196,7 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
|
|
|
5409
5196
|
} catch {
|
|
5410
5197
|
return failed();
|
|
5411
5198
|
} finally {
|
|
5412
|
-
await
|
|
5199
|
+
await fs14.rm(gitDir, { recursive: true, force: true }).catch(() => {
|
|
5413
5200
|
});
|
|
5414
5201
|
}
|
|
5415
5202
|
}
|
|
@@ -5445,7 +5232,7 @@ async function fetchPushUrl(ctx, log, expectStagingCommit) {
|
|
|
5445
5232
|
return null;
|
|
5446
5233
|
}
|
|
5447
5234
|
if (await isTermsRefusal(res)) {
|
|
5448
|
-
reportTermsRefusal(log);
|
|
5235
|
+
if (!printedStructuredError(res)) reportTermsRefusal(log);
|
|
5449
5236
|
return null;
|
|
5450
5237
|
}
|
|
5451
5238
|
if (!res.ok) {
|
|
@@ -5463,7 +5250,7 @@ async function fetchPushUrl(ctx, log, expectStagingCommit) {
|
|
|
5463
5250
|
}
|
|
5464
5251
|
async function isDir(p) {
|
|
5465
5252
|
try {
|
|
5466
|
-
return (await
|
|
5253
|
+
return (await fs14.stat(p)).isDirectory();
|
|
5467
5254
|
} catch {
|
|
5468
5255
|
return false;
|
|
5469
5256
|
}
|
|
@@ -5945,11 +5732,11 @@ async function promoteBuild(apiUrl, projectId, token, log) {
|
|
|
5945
5732
|
}
|
|
5946
5733
|
|
|
5947
5734
|
// src/lib/detect-features.ts
|
|
5948
|
-
import
|
|
5949
|
-
import
|
|
5735
|
+
import fs15 from "fs/promises";
|
|
5736
|
+
import path14 from "path";
|
|
5950
5737
|
async function detectEmbedSdkVersion(cwd = process.cwd()) {
|
|
5951
5738
|
try {
|
|
5952
|
-
const raw = await
|
|
5739
|
+
const raw = await fs15.readFile(path14.join(cwd, "package.json"), "utf8");
|
|
5953
5740
|
const pkg = JSON.parse(raw);
|
|
5954
5741
|
const version = pkg.dependencies?.["@genex-ai/embed-sdk"] ?? pkg.devDependencies?.["@genex-ai/embed-sdk"];
|
|
5955
5742
|
return typeof version === "string" && version ? version : null;
|
|
@@ -5959,7 +5746,7 @@ async function detectEmbedSdkVersion(cwd = process.cwd()) {
|
|
|
5959
5746
|
}
|
|
5960
5747
|
async function detectMultiplayer(cwd = process.cwd()) {
|
|
5961
5748
|
try {
|
|
5962
|
-
const raw = await
|
|
5749
|
+
const raw = await fs15.readFile(path14.join(cwd, "package.json"), "utf8");
|
|
5963
5750
|
const pkg = JSON.parse(raw);
|
|
5964
5751
|
return Boolean(
|
|
5965
5752
|
pkg.dependencies?.["@genex-ai/multiplayer"] ?? pkg.devDependencies?.["@genex-ai/multiplayer"]
|
|
@@ -5971,7 +5758,7 @@ async function detectMultiplayer(cwd = process.cwd()) {
|
|
|
5971
5758
|
async function detectMatchmaking(log, cwd = process.cwd()) {
|
|
5972
5759
|
let pkg;
|
|
5973
5760
|
try {
|
|
5974
|
-
pkg = JSON.parse(await
|
|
5761
|
+
pkg = JSON.parse(await fs15.readFile(path14.join(cwd, "package.json"), "utf8"));
|
|
5975
5762
|
} catch (err) {
|
|
5976
5763
|
log.dim(` (skipping matchmaking \u2014 couldn't read package.json: ${String(err)})`);
|
|
5977
5764
|
return null;
|
|
@@ -5989,15 +5776,15 @@ async function detectMatchmaking(log, cwd = process.cwd()) {
|
|
|
5989
5776
|
var TOUCH_KIT_MARKERS = /controllers\/touch\/|controllers\/character\/touch-joystick|TouchJoystick|VirtualButton|DragZone|RotateOverlay/;
|
|
5990
5777
|
async function detectMobileControls(cwd = process.cwd()) {
|
|
5991
5778
|
try {
|
|
5992
|
-
const raw = await
|
|
5779
|
+
const raw = await fs15.readFile(path14.join(cwd, "package.json"), "utf8");
|
|
5993
5780
|
const pkg = JSON.parse(raw);
|
|
5994
5781
|
if (pkg.genex?.mobileControls === true) return true;
|
|
5995
5782
|
} catch {
|
|
5996
5783
|
}
|
|
5997
|
-
const srcDir =
|
|
5784
|
+
const srcDir = path14.join(cwd, "src");
|
|
5998
5785
|
let entries;
|
|
5999
5786
|
try {
|
|
6000
|
-
entries = await
|
|
5787
|
+
entries = await fs15.readdir(srcDir, { recursive: true });
|
|
6001
5788
|
} catch {
|
|
6002
5789
|
return false;
|
|
6003
5790
|
}
|
|
@@ -6005,7 +5792,7 @@ async function detectMobileControls(cwd = process.cwd()) {
|
|
|
6005
5792
|
if (rel.includes("node_modules")) continue;
|
|
6006
5793
|
if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(rel)) continue;
|
|
6007
5794
|
try {
|
|
6008
|
-
const content = await
|
|
5795
|
+
const content = await fs15.readFile(path14.join(srcDir, rel), "utf8");
|
|
6009
5796
|
if (TOUCH_KIT_MARKERS.test(content)) return true;
|
|
6010
5797
|
} catch {
|
|
6011
5798
|
}
|
|
@@ -6014,10 +5801,10 @@ async function detectMobileControls(cwd = process.cwd()) {
|
|
|
6014
5801
|
}
|
|
6015
5802
|
var GAME_STATE_CALLS = /savePlayerState|saveWorldState|submitScore/;
|
|
6016
5803
|
async function detectGameStateUsage(cwd = process.cwd()) {
|
|
6017
|
-
const srcDir =
|
|
5804
|
+
const srcDir = path14.join(cwd, "src");
|
|
6018
5805
|
let entries;
|
|
6019
5806
|
try {
|
|
6020
|
-
entries = await
|
|
5807
|
+
entries = await fs15.readdir(srcDir, { recursive: true });
|
|
6021
5808
|
} catch {
|
|
6022
5809
|
return false;
|
|
6023
5810
|
}
|
|
@@ -6025,7 +5812,7 @@ async function detectGameStateUsage(cwd = process.cwd()) {
|
|
|
6025
5812
|
if (rel.includes("node_modules")) continue;
|
|
6026
5813
|
if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(rel)) continue;
|
|
6027
5814
|
try {
|
|
6028
|
-
const content = await
|
|
5815
|
+
const content = await fs15.readFile(path14.join(srcDir, rel), "utf8");
|
|
6029
5816
|
if (GAME_STATE_CALLS.test(content)) return true;
|
|
6030
5817
|
} catch {
|
|
6031
5818
|
}
|
|
@@ -6076,19 +5863,19 @@ async function detectSurfaceScan(cwd = process.cwd()) {
|
|
|
6076
5863
|
deferredAudioContext: [],
|
|
6077
5864
|
usesThree: false
|
|
6078
5865
|
};
|
|
6079
|
-
const srcDir =
|
|
5866
|
+
const srcDir = path14.join(cwd, "src");
|
|
6080
5867
|
let entries;
|
|
6081
5868
|
try {
|
|
6082
|
-
entries = await
|
|
5869
|
+
entries = await fs15.readdir(srcDir, { recursive: true });
|
|
6083
5870
|
} catch {
|
|
6084
5871
|
return found;
|
|
6085
5872
|
}
|
|
6086
5873
|
for (const nativeRel of entries) {
|
|
6087
5874
|
if (nativeRel.includes("node_modules")) continue;
|
|
6088
5875
|
if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(nativeRel)) continue;
|
|
6089
|
-
const raw = await
|
|
5876
|
+
const raw = await fs15.readFile(path14.join(srcDir, nativeRel), "utf8").catch(() => "");
|
|
6090
5877
|
if (!raw) continue;
|
|
6091
|
-
const rel = nativeRel.split(
|
|
5878
|
+
const rel = nativeRel.split(path14.sep).join("/");
|
|
6092
5879
|
const content = raw.replace(/\/\*[\s\S]*?\*\//g, (m2) => m2.replace(/[^\n]/g, " ")).replace(/(^|[^:])\/\/[^\n]*/gm, (m2, p1) => p1 + " ".repeat(m2.length - p1.length));
|
|
6093
5880
|
const repeatRe = /\.repeat\.set\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\)/g;
|
|
6094
5881
|
let m;
|
|
@@ -6169,25 +5956,25 @@ async function detectGenerationAudit(cwd = process.cwd()) {
|
|
|
6169
5956
|
let haystack = "";
|
|
6170
5957
|
const read = async (file) => {
|
|
6171
5958
|
try {
|
|
6172
|
-
haystack += await
|
|
5959
|
+
haystack += await fs15.readFile(file, "utf8");
|
|
6173
5960
|
} catch {
|
|
6174
5961
|
}
|
|
6175
5962
|
};
|
|
6176
5963
|
try {
|
|
6177
|
-
for (const entry of await
|
|
5964
|
+
for (const entry of await fs15.readdir(cwd, { withFileTypes: true })) {
|
|
6178
5965
|
if (entry.isFile() && /\.(ts|tsx|js|jsx|css|html|json)$/.test(entry.name)) {
|
|
6179
|
-
await read(
|
|
5966
|
+
await read(path14.join(cwd, entry.name));
|
|
6180
5967
|
}
|
|
6181
5968
|
}
|
|
6182
5969
|
} catch {
|
|
6183
5970
|
}
|
|
6184
5971
|
for (const sub of ["src", "public"]) {
|
|
6185
5972
|
try {
|
|
6186
|
-
const entries = await
|
|
5973
|
+
const entries = await fs15.readdir(path14.join(cwd, sub), { recursive: true });
|
|
6187
5974
|
for (const rel of entries) {
|
|
6188
5975
|
if (rel.includes("node_modules")) continue;
|
|
6189
5976
|
if (!/\.(ts|tsx|js|jsx|css|html|json|txt)$/.test(rel)) continue;
|
|
6190
|
-
await read(
|
|
5977
|
+
await read(path14.join(cwd, sub, rel));
|
|
6191
5978
|
}
|
|
6192
5979
|
} catch {
|
|
6193
5980
|
}
|
|
@@ -6334,7 +6121,7 @@ async function borrowEvidence(meta, cwd) {
|
|
|
6334
6121
|
} catch {
|
|
6335
6122
|
return true;
|
|
6336
6123
|
}
|
|
6337
|
-
const gitConfig = await
|
|
6124
|
+
const gitConfig = await fs15.readFile(path14.join(cwd, ".git", "config"), "utf8").catch(() => "");
|
|
6338
6125
|
for (const m of gitConfig.matchAll(/url\s*=\s*(\S+)/g)) {
|
|
6339
6126
|
try {
|
|
6340
6127
|
const u = new URL(m[1]);
|
|
@@ -6342,7 +6129,7 @@ async function borrowEvidence(meta, cwd) {
|
|
|
6342
6129
|
} catch {
|
|
6343
6130
|
}
|
|
6344
6131
|
}
|
|
6345
|
-
const readme = await
|
|
6132
|
+
const readme = await fs15.readFile(path14.join(cwd, "README.md"), "utf8").catch(() => "");
|
|
6346
6133
|
return readme.includes(host) || /\b(upstream|originally by|ported from|borrowed from)\b/i.test(readme);
|
|
6347
6134
|
}
|
|
6348
6135
|
|
|
@@ -6604,11 +6391,12 @@ async function runAcceptCommand(opts) {
|
|
|
6604
6391
|
process.exitCode = 1;
|
|
6605
6392
|
return;
|
|
6606
6393
|
}
|
|
6607
|
-
const meta = await readProject();
|
|
6394
|
+
const [meta, workspace] = await Promise.all([readProject(), readWorkspace()]);
|
|
6395
|
+
const apiUrl = meta?.apiUrl ?? workspace?.apiUrl;
|
|
6608
6396
|
const ok = await runAccept({
|
|
6609
6397
|
token,
|
|
6610
6398
|
log,
|
|
6611
|
-
...
|
|
6399
|
+
...apiUrl ? { apiUrl } : {}
|
|
6612
6400
|
});
|
|
6613
6401
|
if (!ok) process.exitCode = 1;
|
|
6614
6402
|
}
|
|
@@ -6822,8 +6610,8 @@ async function runRollback(opts) {
|
|
|
6822
6610
|
}
|
|
6823
6611
|
|
|
6824
6612
|
// src/commands/generate.ts
|
|
6825
|
-
import
|
|
6826
|
-
import
|
|
6613
|
+
import fs17 from "fs/promises";
|
|
6614
|
+
import path16 from "path";
|
|
6827
6615
|
import { PNG as PNG4 } from "pngjs";
|
|
6828
6616
|
|
|
6829
6617
|
// src/lib/glass.ts
|
|
@@ -6904,8 +6692,8 @@ function solveMagentaGlass(source) {
|
|
|
6904
6692
|
}
|
|
6905
6693
|
|
|
6906
6694
|
// src/lib/download-assets.ts
|
|
6907
|
-
import
|
|
6908
|
-
import
|
|
6695
|
+
import fs16 from "fs/promises";
|
|
6696
|
+
import path15 from "path";
|
|
6909
6697
|
var RUNG_ROLE = /@\d+$/;
|
|
6910
6698
|
function isPrimaryRole(role) {
|
|
6911
6699
|
return !RUNG_ROLE.test(role);
|
|
@@ -6925,7 +6713,7 @@ async function downloadAssets(files, opts) {
|
|
|
6925
6713
|
const result = { saved: [], failures: [] };
|
|
6926
6714
|
if (wanted.length === 0) return result;
|
|
6927
6715
|
try {
|
|
6928
|
-
await
|
|
6716
|
+
await fs16.mkdir(opts.outDir, { recursive: true });
|
|
6929
6717
|
} catch (err) {
|
|
6930
6718
|
result.failures.push(
|
|
6931
6719
|
`couldn't create ${opts.outDir} (${err instanceof Error ? err.message : String(err)})`
|
|
@@ -6934,12 +6722,12 @@ async function downloadAssets(files, opts) {
|
|
|
6934
6722
|
}
|
|
6935
6723
|
const multi = wanted.length > 1;
|
|
6936
6724
|
for (const file of wanted) {
|
|
6937
|
-
const dest =
|
|
6725
|
+
const dest = path15.join(opts.outDir, localAssetName(opts.kind, opts.prompt, opts.id, file, multi));
|
|
6938
6726
|
try {
|
|
6939
6727
|
const res = await fetch(file.url);
|
|
6940
6728
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
6941
6729
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
6942
|
-
await
|
|
6730
|
+
await fs16.writeFile(dest, buf);
|
|
6943
6731
|
result.saved.push({ role: file.role, path: dest, url: file.url, bytes: buf.byteLength });
|
|
6944
6732
|
} catch (err) {
|
|
6945
6733
|
result.failures.push(
|
|
@@ -6994,7 +6782,7 @@ function mockLaneRefusal(kind, provider) {
|
|
|
6994
6782
|
}
|
|
6995
6783
|
|
|
6996
6784
|
// src/lib/open.ts
|
|
6997
|
-
import { spawn as
|
|
6785
|
+
import { spawn as spawn3 } from "child_process";
|
|
6998
6786
|
function tokenize(cmd) {
|
|
6999
6787
|
return cmd.trim().split(/\s+/).filter(Boolean);
|
|
7000
6788
|
}
|
|
@@ -7023,7 +6811,7 @@ function openUrl(url) {
|
|
|
7023
6811
|
}
|
|
7024
6812
|
}
|
|
7025
6813
|
try {
|
|
7026
|
-
const child =
|
|
6814
|
+
const child = spawn3(command, args, { stdio: "ignore", detached: true });
|
|
7027
6815
|
child.on("error", () => {
|
|
7028
6816
|
});
|
|
7029
6817
|
child.unref();
|
|
@@ -7083,7 +6871,7 @@ var isRemoteRef = (value) => /^(https?:\/\/|data:)/i.test(value);
|
|
|
7083
6871
|
async function inlineLocalImage(filePath, flag) {
|
|
7084
6872
|
let bytes;
|
|
7085
6873
|
try {
|
|
7086
|
-
bytes = await
|
|
6874
|
+
bytes = await fs17.readFile(filePath);
|
|
7087
6875
|
} catch {
|
|
7088
6876
|
return { ok: false, error: `Couldn't read the ${flag} file at ${filePath}.` };
|
|
7089
6877
|
}
|
|
@@ -7093,7 +6881,7 @@ async function inlineLocalImage(filePath, flag) {
|
|
|
7093
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.`
|
|
7094
6882
|
};
|
|
7095
6883
|
}
|
|
7096
|
-
const mime = IMAGE_MIME_BY_EXT[
|
|
6884
|
+
const mime = IMAGE_MIME_BY_EXT[path16.extname(filePath).toLowerCase()] ?? "image/png";
|
|
7097
6885
|
return { ok: true, dataUri: `data:${mime};base64,${bytes.toString("base64")}` };
|
|
7098
6886
|
}
|
|
7099
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.";
|
|
@@ -7251,7 +7039,7 @@ async function runGenerate(kind, opts) {
|
|
|
7251
7039
|
let typedPrompt = opts.prompt?.trim();
|
|
7252
7040
|
if (!typedPrompt && kind === "model" && opts.imageUrl) {
|
|
7253
7041
|
const ref = opts.imageUrl.startsWith("data:") ? "local image" : opts.imageUrl;
|
|
7254
|
-
typedPrompt = `from image: ${
|
|
7042
|
+
typedPrompt = `from image: ${path16.basename(ref).slice(0, 120)}`;
|
|
7255
7043
|
}
|
|
7256
7044
|
if (!typedPrompt) {
|
|
7257
7045
|
log.error(`Missing prompt. Usage: ${c.cyan(`genex ${kind} "<prompt>"`)}`);
|
|
@@ -7295,7 +7083,7 @@ async function runGenerate(kind, opts) {
|
|
|
7295
7083
|
return;
|
|
7296
7084
|
}
|
|
7297
7085
|
try {
|
|
7298
|
-
const bytes = await
|
|
7086
|
+
const bytes = await fs17.readFile(opts.inpaintUrl);
|
|
7299
7087
|
opts = { ...opts, inpaintUrl: `data:image/png;base64,${bytes.toString("base64")}` };
|
|
7300
7088
|
} catch {
|
|
7301
7089
|
log.error(`Couldn't read the --inpaint mask at ${opts.inpaintUrl}.`);
|
|
@@ -7480,7 +7268,7 @@ async function reportGlassTerminal(view, outDir, log, json) {
|
|
|
7480
7268
|
return;
|
|
7481
7269
|
}
|
|
7482
7270
|
await recordTerminal(view.id, "completed", files.map((f) => f.url));
|
|
7483
|
-
await
|
|
7271
|
+
await fs17.mkdir(outDir, { recursive: true });
|
|
7484
7272
|
const solved = [];
|
|
7485
7273
|
for (let i = 0; i < files.length; i++) {
|
|
7486
7274
|
const f = files[i];
|
|
@@ -7512,8 +7300,8 @@ async function reportGlassTerminal(view, outDir, log, json) {
|
|
|
7512
7300
|
});
|
|
7513
7301
|
continue;
|
|
7514
7302
|
}
|
|
7515
|
-
const outPath =
|
|
7516
|
-
await
|
|
7303
|
+
const outPath = path16.join(outDir, `glass-${i + 1}.png`);
|
|
7304
|
+
await fs17.writeFile(outPath, PNG4.sync.write(r.png));
|
|
7517
7305
|
solved.push({
|
|
7518
7306
|
path: outPath,
|
|
7519
7307
|
url: f.url,
|
|
@@ -8279,8 +8067,8 @@ async function toRow(e, v, cwd) {
|
|
|
8279
8067
|
}
|
|
8280
8068
|
|
|
8281
8069
|
// src/commands/controller.ts
|
|
8282
|
-
import
|
|
8283
|
-
import
|
|
8070
|
+
import fs19 from "fs/promises";
|
|
8071
|
+
import path18 from "path";
|
|
8284
8072
|
|
|
8285
8073
|
// ../../packages/meshy-animation-catalog/src/index.ts
|
|
8286
8074
|
import { createHash } from "crypto";
|
|
@@ -17410,9 +17198,9 @@ function searchMeshyAnimations(query, options = {}) {
|
|
|
17410
17198
|
}
|
|
17411
17199
|
|
|
17412
17200
|
// src/lib/anims.ts
|
|
17413
|
-
import
|
|
17414
|
-
import
|
|
17415
|
-
var ANIMS_DEST =
|
|
17201
|
+
import fs18 from "fs/promises";
|
|
17202
|
+
import path17 from "path";
|
|
17203
|
+
var ANIMS_DEST = path17.join("public", "assets", "anims");
|
|
17416
17204
|
var HIDDEN_TAG = "reference";
|
|
17417
17205
|
async function runAnims(opts) {
|
|
17418
17206
|
const log = createLogger({ quiet: opts.quiet });
|
|
@@ -17428,7 +17216,7 @@ async function runAnims(opts) {
|
|
|
17428
17216
|
printCatalog(log, manifest, selectors);
|
|
17429
17217
|
return;
|
|
17430
17218
|
}
|
|
17431
|
-
const controllerMarker =
|
|
17219
|
+
const controllerMarker = path17.join(root, "src", "controllers", "character");
|
|
17432
17220
|
if (!await exists2(controllerMarker)) {
|
|
17433
17221
|
log.error(
|
|
17434
17222
|
`No character controller in this game (missing ${c.cyan("src/controllers/character/")}).`
|
|
@@ -17437,11 +17225,11 @@ async function runAnims(opts) {
|
|
|
17437
17225
|
process.exitCode = 1;
|
|
17438
17226
|
return;
|
|
17439
17227
|
}
|
|
17440
|
-
const destDir =
|
|
17441
|
-
const gameManifestPath =
|
|
17228
|
+
const destDir = path17.join(root, ANIMS_DEST);
|
|
17229
|
+
const gameManifestPath = path17.join(destDir, "manifest.json");
|
|
17442
17230
|
if (opts.reset) {
|
|
17443
|
-
await
|
|
17444
|
-
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)`);
|
|
17445
17233
|
}
|
|
17446
17234
|
if (selectors.length === 0) {
|
|
17447
17235
|
const installed = await readGameManifest(gameManifestPath);
|
|
@@ -17479,35 +17267,35 @@ async function runAnims(opts) {
|
|
|
17479
17267
|
}
|
|
17480
17268
|
}
|
|
17481
17269
|
const wanted = [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
17482
|
-
const cacheDir =
|
|
17270
|
+
const cacheDir = path17.join(
|
|
17483
17271
|
opts.cacheDir ?? getAnimsCacheDir(),
|
|
17484
17272
|
`${manifest.library}-v${manifest.version}`
|
|
17485
17273
|
);
|
|
17486
|
-
await
|
|
17487
|
-
await
|
|
17274
|
+
await fs18.mkdir(cacheDir, { recursive: true });
|
|
17275
|
+
await fs18.mkdir(destDir, { recursive: true });
|
|
17488
17276
|
const base = getAnimsBase(opts.animsBase);
|
|
17489
17277
|
let installedCount = 0;
|
|
17490
17278
|
let presentCount = 0;
|
|
17491
17279
|
let addedBytes = 0;
|
|
17492
17280
|
const failures = [];
|
|
17493
17281
|
for (const entry of wanted) {
|
|
17494
|
-
const dest =
|
|
17282
|
+
const dest = path17.join(destDir, entry.file);
|
|
17495
17283
|
if (await hasSize(dest, entry.bytes)) {
|
|
17496
17284
|
presentCount++;
|
|
17497
17285
|
continue;
|
|
17498
17286
|
}
|
|
17499
17287
|
try {
|
|
17500
|
-
const cached =
|
|
17288
|
+
const cached = path17.join(cacheDir, entry.file);
|
|
17501
17289
|
if (!await hasSize(cached, entry.bytes)) {
|
|
17502
17290
|
const res = await fetch(base + entry.file);
|
|
17503
17291
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
17504
17292
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
17505
|
-
await
|
|
17293
|
+
await fs18.writeFile(cached, buf);
|
|
17506
17294
|
}
|
|
17507
|
-
await
|
|
17295
|
+
await fs18.copyFile(cached, dest);
|
|
17508
17296
|
installedCount++;
|
|
17509
17297
|
addedBytes += entry.bytes;
|
|
17510
|
-
log.dim(` ${
|
|
17298
|
+
log.dim(` ${path17.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
|
|
17511
17299
|
} catch (err) {
|
|
17512
17300
|
failures.push(`${entry.name} (${err instanceof Error ? err.message : String(err)})`);
|
|
17513
17301
|
}
|
|
@@ -17523,13 +17311,13 @@ async function runAnims(opts) {
|
|
|
17523
17311
|
version: manifest.version,
|
|
17524
17312
|
clips: [...union].sort((a, b) => a.localeCompare(b))
|
|
17525
17313
|
};
|
|
17526
|
-
await
|
|
17314
|
+
await fs18.writeFile(gameManifestPath, JSON.stringify(gameManifest, null, 2) + "\n");
|
|
17527
17315
|
log.plain("");
|
|
17528
17316
|
const parts = [`${installedCount} clip${installedCount === 1 ? "" : "s"} installed`];
|
|
17529
17317
|
if (presentCount > 0) parts.push(`${presentCount} already present`);
|
|
17530
17318
|
if (bundledSkips > 0) parts.push(`${bundledSkips} already bundled in animation-library.glb`);
|
|
17531
17319
|
log.success(
|
|
17532
|
-
`${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST +
|
|
17320
|
+
`${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path17.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
|
|
17533
17321
|
);
|
|
17534
17322
|
for (const [selector, entries] of resolved) {
|
|
17535
17323
|
const names = entries.filter((e) => !coreNames.has(e.name)).map((e) => e.name);
|
|
@@ -17561,8 +17349,8 @@ async function loadManifest(baseOverride) {
|
|
|
17561
17349
|
}
|
|
17562
17350
|
} catch {
|
|
17563
17351
|
}
|
|
17564
|
-
const snapshotPath =
|
|
17565
|
-
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"));
|
|
17566
17354
|
return { manifest, source: "snapshot" };
|
|
17567
17355
|
}
|
|
17568
17356
|
function resolveSelectors(manifest, selectors) {
|
|
@@ -17680,21 +17468,21 @@ function printCatalog(log, manifest, selectors) {
|
|
|
17680
17468
|
}
|
|
17681
17469
|
async function readGameManifest(file) {
|
|
17682
17470
|
try {
|
|
17683
|
-
return JSON.parse(await
|
|
17471
|
+
return JSON.parse(await fs18.readFile(file, "utf8"));
|
|
17684
17472
|
} catch {
|
|
17685
17473
|
return null;
|
|
17686
17474
|
}
|
|
17687
17475
|
}
|
|
17688
17476
|
async function hasSize(file, bytes) {
|
|
17689
17477
|
try {
|
|
17690
|
-
return (await
|
|
17478
|
+
return (await fs18.stat(file)).size === bytes;
|
|
17691
17479
|
} catch {
|
|
17692
17480
|
return false;
|
|
17693
17481
|
}
|
|
17694
17482
|
}
|
|
17695
17483
|
async function exists2(p) {
|
|
17696
17484
|
try {
|
|
17697
|
-
await
|
|
17485
|
+
await fs18.access(p);
|
|
17698
17486
|
return true;
|
|
17699
17487
|
} catch {
|
|
17700
17488
|
return false;
|
|
@@ -17888,8 +17676,8 @@ var CONTROLLER_FILE_SETS = {
|
|
|
17888
17676
|
]
|
|
17889
17677
|
}
|
|
17890
17678
|
};
|
|
17891
|
-
var CODE_DEST =
|
|
17892
|
-
var ASSETS_DEST =
|
|
17679
|
+
var CODE_DEST = path18.join("src", "controllers");
|
|
17680
|
+
var ASSETS_DEST = path18.join("public", "assets");
|
|
17893
17681
|
async function runController(opts) {
|
|
17894
17682
|
const log = createLogger({ quiet: opts.quiet });
|
|
17895
17683
|
if (opts.kind?.trim() === "anims") {
|
|
@@ -17906,31 +17694,31 @@ async function runController(opts) {
|
|
|
17906
17694
|
process.exitCode = 1;
|
|
17907
17695
|
return;
|
|
17908
17696
|
}
|
|
17909
|
-
const srcDir =
|
|
17697
|
+
const srcDir = path18.join(getTemplatesDir(), "controllers");
|
|
17910
17698
|
const root = opts.cwd ?? process.cwd();
|
|
17911
17699
|
const set = CONTROLLER_FILE_SETS[kind];
|
|
17912
17700
|
log.plain(c.bold(`genex controller ${kind}`));
|
|
17913
17701
|
log.plain("");
|
|
17914
|
-
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)}`);
|
|
17915
17703
|
const plan = [
|
|
17916
|
-
...set.code.map((rel) => ({ from: rel, rel:
|
|
17704
|
+
...set.code.map((rel) => ({ from: rel, rel: path18.join(CODE_DEST, rel) })),
|
|
17917
17705
|
...set.assets.map((rel) => ({
|
|
17918
17706
|
from: rel,
|
|
17919
|
-
rel:
|
|
17707
|
+
rel: path18.join(ASSETS_DEST, path18.basename(rel))
|
|
17920
17708
|
}))
|
|
17921
17709
|
];
|
|
17922
17710
|
let copied = 0;
|
|
17923
17711
|
let skipped = 0;
|
|
17924
17712
|
try {
|
|
17925
17713
|
for (const file of plan) {
|
|
17926
|
-
const dest =
|
|
17714
|
+
const dest = path18.join(root, file.rel);
|
|
17927
17715
|
if (!opts.force && await exists3(dest)) {
|
|
17928
17716
|
skipped++;
|
|
17929
17717
|
log.dim(` skipped ${file.rel} (exists \u2014 use --force to overwrite)`);
|
|
17930
17718
|
continue;
|
|
17931
17719
|
}
|
|
17932
|
-
await
|
|
17933
|
-
await
|
|
17720
|
+
await fs19.mkdir(path18.dirname(dest), { recursive: true });
|
|
17721
|
+
await fs19.copyFile(path18.join(srcDir, file.from), dest);
|
|
17934
17722
|
copied++;
|
|
17935
17723
|
log.dim(` ${file.rel}`);
|
|
17936
17724
|
}
|
|
@@ -17983,7 +17771,7 @@ async function runController(opts) {
|
|
|
17983
17771
|
for (const line of set.sketch) {
|
|
17984
17772
|
log.dim(` ${line}`);
|
|
17985
17773
|
}
|
|
17986
|
-
if (kind === "character" && !await exists3(
|
|
17774
|
+
if (kind === "character" && !await exists3(path18.join(root, ASSETS_DEST, "meshy-character.json"))) {
|
|
17987
17775
|
log.plain("");
|
|
17988
17776
|
log.plain(
|
|
17989
17777
|
` ${stepOffset + 3}. This game has no generated character yet \u2014 ${c.cyan(
|
|
@@ -18015,9 +17803,9 @@ async function installMeshyCharacterManifest(args) {
|
|
|
18015
17803
|
throw new Error("The API returned an invalid Meshy character manifest.");
|
|
18016
17804
|
}
|
|
18017
17805
|
assertCompleteMeshyControllerPack(manifest);
|
|
18018
|
-
const destination =
|
|
18019
|
-
await
|
|
18020
|
-
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(
|
|
18021
17809
|
destination,
|
|
18022
17810
|
`${JSON.stringify(manifest, null, 2)}
|
|
18023
17811
|
`
|
|
@@ -18155,14 +17943,14 @@ function assertCompleteMeshyControllerPack(manifest) {
|
|
|
18155
17943
|
}
|
|
18156
17944
|
async function installFallbackAvatar(args) {
|
|
18157
17945
|
const { root, srcDir, log } = args;
|
|
18158
|
-
const dest =
|
|
18159
|
-
await
|
|
18160
|
-
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);
|
|
18161
17949
|
log.dim(" public/assets/avatar.vrm (fallback avatar \u2014 bundled CC0 default)");
|
|
18162
17950
|
}
|
|
18163
17951
|
async function exists3(p) {
|
|
18164
17952
|
try {
|
|
18165
|
-
await
|
|
17953
|
+
await fs19.access(p);
|
|
18166
17954
|
return true;
|
|
18167
17955
|
} catch {
|
|
18168
17956
|
return false;
|
|
@@ -18170,8 +17958,8 @@ async function exists3(p) {
|
|
|
18170
17958
|
}
|
|
18171
17959
|
|
|
18172
17960
|
// src/commands/character.ts
|
|
18173
|
-
import
|
|
18174
|
-
import
|
|
17961
|
+
import fs20 from "fs/promises";
|
|
17962
|
+
import path19 from "path";
|
|
18175
17963
|
function exactAnimation(selector) {
|
|
18176
17964
|
const trimmed = selector.trim();
|
|
18177
17965
|
if (/^-?\d+$/.test(trimmed)) return animationById(Number(trimmed));
|
|
@@ -18251,7 +18039,7 @@ function showAmbiguity(selector, candidates, log, json = false) {
|
|
|
18251
18039
|
}
|
|
18252
18040
|
process.exitCode = 1;
|
|
18253
18041
|
}
|
|
18254
|
-
var INSTALLED_MANIFEST =
|
|
18042
|
+
var INSTALLED_MANIFEST = path19.join("public", "assets", "meshy-character.json");
|
|
18255
18043
|
async function resolveAdoptTarget(selector) {
|
|
18256
18044
|
const trimmed = selector?.trim();
|
|
18257
18045
|
if (trimmed && !trimmed.endsWith(".json")) {
|
|
@@ -18260,7 +18048,7 @@ async function resolveAdoptTarget(selector) {
|
|
|
18260
18048
|
const file = trimmed ?? INSTALLED_MANIFEST;
|
|
18261
18049
|
let raw;
|
|
18262
18050
|
try {
|
|
18263
|
-
raw = await
|
|
18051
|
+
raw = await fs20.readFile(file, "utf8");
|
|
18264
18052
|
} catch {
|
|
18265
18053
|
return {
|
|
18266
18054
|
ok: false,
|
|
@@ -18743,22 +18531,22 @@ async function context2(opts) {
|
|
|
18743
18531
|
const project = await readProject();
|
|
18744
18532
|
return { token, apiUrl: getApiUrl(opts.apiUrl ?? project?.apiUrl) };
|
|
18745
18533
|
}
|
|
18746
|
-
async function readVideo(
|
|
18534
|
+
async function readVideo(path26, log) {
|
|
18747
18535
|
let bytes;
|
|
18748
18536
|
try {
|
|
18749
|
-
bytes = await readFile(
|
|
18537
|
+
bytes = await readFile(path26);
|
|
18750
18538
|
} catch {
|
|
18751
|
-
log.error(`Can't read ${
|
|
18539
|
+
log.error(`Can't read ${path26}.`);
|
|
18752
18540
|
return null;
|
|
18753
18541
|
}
|
|
18754
18542
|
if (bytes.byteLength > MAX_VIDEO_BYTES) {
|
|
18755
|
-
log.error(`${basename(
|
|
18543
|
+
log.error(`${basename(path26)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
|
|
18756
18544
|
return null;
|
|
18757
18545
|
}
|
|
18758
18546
|
return bytes;
|
|
18759
18547
|
}
|
|
18760
|
-
async function uploadVideo(apiUrl, token, characterId,
|
|
18761
|
-
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";
|
|
18762
18550
|
const minted = await apiFetch(
|
|
18763
18551
|
`${apiUrl}/api/characters/${encodeURIComponent(characterId)}/motions/video-url`,
|
|
18764
18552
|
{
|
|
@@ -18773,7 +18561,7 @@ async function uploadVideo(apiUrl, token, characterId, path27, bytes, log) {
|
|
|
18773
18561
|
return null;
|
|
18774
18562
|
}
|
|
18775
18563
|
const { uploadUrl, videoUrl } = await minted.json();
|
|
18776
|
-
log.dim(` uploading ${basename(
|
|
18564
|
+
log.dim(` uploading ${basename(path26)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
|
|
18777
18565
|
const put = await fetch(uploadUrl, {
|
|
18778
18566
|
method: "PUT",
|
|
18779
18567
|
headers: { "Content-Type": contentType, "Content-Length": String(bytes.byteLength) },
|
|
@@ -19089,8 +18877,8 @@ function rank(items, query) {
|
|
|
19089
18877
|
}
|
|
19090
18878
|
|
|
19091
18879
|
// src/commands/motion.ts
|
|
19092
|
-
import
|
|
19093
|
-
import
|
|
18880
|
+
import fs21 from "fs/promises";
|
|
18881
|
+
import path20 from "path";
|
|
19094
18882
|
|
|
19095
18883
|
// src/lib/motion/npz.ts
|
|
19096
18884
|
import zlib from "zlib";
|
|
@@ -20341,7 +20129,7 @@ async function motionGen(opts, log) {
|
|
|
20341
20129
|
}
|
|
20342
20130
|
if (opts.constraintsPath !== void 0) {
|
|
20343
20131
|
try {
|
|
20344
|
-
const raw = await
|
|
20132
|
+
const raw = await fs21.readFile(opts.constraintsPath, "utf8");
|
|
20345
20133
|
generationOptions.constraints = JSON.parse(raw);
|
|
20346
20134
|
} catch {
|
|
20347
20135
|
log.error(`Couldn't read the --constraints JSON at ${opts.constraintsPath}.`);
|
|
@@ -20365,10 +20153,10 @@ async function motionGen(opts, log) {
|
|
|
20365
20153
|
async function expandTakes(selectors) {
|
|
20366
20154
|
const out = [];
|
|
20367
20155
|
for (const sel of selectors) {
|
|
20368
|
-
const st = await
|
|
20156
|
+
const st = await fs21.stat(sel).catch(() => null);
|
|
20369
20157
|
if (st?.isDirectory()) {
|
|
20370
|
-
const names = await
|
|
20371
|
-
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));
|
|
20372
20160
|
} else if (st?.isFile()) {
|
|
20373
20161
|
out.push(sel);
|
|
20374
20162
|
} else {
|
|
@@ -20403,7 +20191,7 @@ async function motionVerify(opts, log) {
|
|
|
20403
20191
|
let gates = DEFAULT_GATES;
|
|
20404
20192
|
if (opts.gatesPath) {
|
|
20405
20193
|
try {
|
|
20406
|
-
gates = mergeGates(DEFAULT_GATES, JSON.parse(await
|
|
20194
|
+
gates = mergeGates(DEFAULT_GATES, JSON.parse(await fs21.readFile(opts.gatesPath, "utf8")));
|
|
20407
20195
|
} catch {
|
|
20408
20196
|
log.error(`Couldn't read the --gates JSON at ${opts.gatesPath}.`);
|
|
20409
20197
|
process.exitCode = 1;
|
|
@@ -20425,9 +20213,9 @@ async function motionVerify(opts, log) {
|
|
|
20425
20213
|
}
|
|
20426
20214
|
const reports = [];
|
|
20427
20215
|
for (const file of files) {
|
|
20428
|
-
const stem =
|
|
20216
|
+
const stem = path20.basename(file).replace(/\.npz$/, "");
|
|
20429
20217
|
try {
|
|
20430
|
-
reports.push(analyzeTake(stem, await
|
|
20218
|
+
reports.push(analyzeTake(stem, await fs21.readFile(file), gates));
|
|
20431
20219
|
} catch (err) {
|
|
20432
20220
|
reports.push({
|
|
20433
20221
|
take: stem,
|
|
@@ -20465,7 +20253,7 @@ async function motionCompile(opts, log) {
|
|
|
20465
20253
|
let cfg = DEFAULT_MOTION_CONFIG;
|
|
20466
20254
|
if (opts.configPath) {
|
|
20467
20255
|
try {
|
|
20468
|
-
const patch = JSON.parse(await
|
|
20256
|
+
const patch = JSON.parse(await fs21.readFile(opts.configPath, "utf8"));
|
|
20469
20257
|
cfg = { ...DEFAULT_MOTION_CONFIG, ...patch, idleLoop: { ...DEFAULT_MOTION_CONFIG.idleLoop, ...patch.idleLoop } };
|
|
20470
20258
|
} catch {
|
|
20471
20259
|
log.error(`Couldn't read the --config JSON at ${opts.configPath}.`);
|
|
@@ -20483,16 +20271,16 @@ async function motionCompile(opts, log) {
|
|
|
20483
20271
|
}
|
|
20484
20272
|
const inputs = [];
|
|
20485
20273
|
for (const file of files) {
|
|
20486
|
-
const stem =
|
|
20274
|
+
const stem = path20.basename(file).replace(/\.npz$/, "");
|
|
20487
20275
|
try {
|
|
20488
|
-
inputs.push({ stem, take: loadTake(await
|
|
20276
|
+
inputs.push({ stem, take: loadTake(await fs21.readFile(file)) });
|
|
20489
20277
|
} catch (err) {
|
|
20490
20278
|
log.error(`${stem}: ${err instanceof Error ? err.message : String(err)}`);
|
|
20491
20279
|
process.exitCode = 1;
|
|
20492
20280
|
return;
|
|
20493
20281
|
}
|
|
20494
20282
|
}
|
|
20495
|
-
const setName = opts.set ??
|
|
20283
|
+
const setName = opts.set ?? path20.basename(opts.out).replace(/\.json$/, "");
|
|
20496
20284
|
let result;
|
|
20497
20285
|
try {
|
|
20498
20286
|
result = compileSet(inputs, setName, cfg);
|
|
@@ -20507,9 +20295,9 @@ async function motionCompile(opts, log) {
|
|
|
20507
20295
|
process.exitCode = 1;
|
|
20508
20296
|
return;
|
|
20509
20297
|
}
|
|
20510
|
-
await
|
|
20298
|
+
await fs21.mkdir(path20.dirname(path20.resolve(opts.out)), { recursive: true });
|
|
20511
20299
|
const json = JSON.stringify(result.data);
|
|
20512
|
-
await
|
|
20300
|
+
await fs21.writeFile(opts.out, json);
|
|
20513
20301
|
if (opts.json) {
|
|
20514
20302
|
writeJson({ out: opts.out, set: setName, tracks: Object.keys(result.data.gaits), bytes: json.length });
|
|
20515
20303
|
return;
|
|
@@ -20527,9 +20315,9 @@ var MOTION_RUNTIME_FILES = [
|
|
|
20527
20315
|
var MOTION_PRESETS = {
|
|
20528
20316
|
rifle: ["sets/rifle.json", "sets/jumps.json"]
|
|
20529
20317
|
};
|
|
20530
|
-
var MOTION_DEST =
|
|
20318
|
+
var MOTION_DEST = path20.join("src", "motion");
|
|
20531
20319
|
async function motionInstall(opts, log) {
|
|
20532
|
-
const srcDir =
|
|
20320
|
+
const srcDir = path20.join(getTemplatesDir(), "motion");
|
|
20533
20321
|
const root = opts.cwd ?? process.cwd();
|
|
20534
20322
|
const preset = opts.set;
|
|
20535
20323
|
if (preset !== void 0 && !MOTION_PRESETS[preset]) {
|
|
@@ -20540,21 +20328,21 @@ async function motionInstall(opts, log) {
|
|
|
20540
20328
|
const files = [...MOTION_RUNTIME_FILES, ...preset ? MOTION_PRESETS[preset] : []];
|
|
20541
20329
|
log.plain(c.bold(`genex motion install${preset ? ` --set ${preset}` : ""}`));
|
|
20542
20330
|
log.plain("");
|
|
20543
|
-
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)}`);
|
|
20544
20332
|
let copied = 0, skipped = 0;
|
|
20545
20333
|
try {
|
|
20546
20334
|
for (const rel of files) {
|
|
20547
|
-
const dest =
|
|
20548
|
-
const exists5 = await
|
|
20335
|
+
const dest = path20.join(root, MOTION_DEST, rel);
|
|
20336
|
+
const exists5 = await fs21.access(dest).then(() => true, () => false);
|
|
20549
20337
|
if (!opts.force && exists5) {
|
|
20550
20338
|
skipped++;
|
|
20551
|
-
log.dim(` skipped ${
|
|
20339
|
+
log.dim(` skipped ${path20.join(MOTION_DEST, rel)} (exists \u2014 use --force to overwrite)`);
|
|
20552
20340
|
continue;
|
|
20553
20341
|
}
|
|
20554
|
-
await
|
|
20555
|
-
await
|
|
20342
|
+
await fs21.mkdir(path20.dirname(dest), { recursive: true });
|
|
20343
|
+
await fs21.copyFile(path20.join(srcDir, rel), dest);
|
|
20556
20344
|
copied++;
|
|
20557
|
-
log.dim(` ${
|
|
20345
|
+
log.dim(` ${path20.join(MOTION_DEST, rel)}`);
|
|
20558
20346
|
}
|
|
20559
20347
|
} catch (err) {
|
|
20560
20348
|
log.error(`Copy failed: ${String(err)}`);
|
|
@@ -20595,7 +20383,7 @@ async function motionConstraints(opts, log) {
|
|
|
20595
20383
|
}
|
|
20596
20384
|
const doc = directionConstraint(dir, speed, duration);
|
|
20597
20385
|
const out = opts.out ?? "constraints.json";
|
|
20598
|
-
await
|
|
20386
|
+
await fs21.writeFile(out, JSON.stringify(doc));
|
|
20599
20387
|
if (opts.json) {
|
|
20600
20388
|
writeJson({ out, dir: dirName, speed, duration, waypoints: doc[0].frame_indices.length });
|
|
20601
20389
|
return;
|
|
@@ -20632,14 +20420,14 @@ async function runMotion(opts) {
|
|
|
20632
20420
|
}
|
|
20633
20421
|
|
|
20634
20422
|
// src/commands/blender.ts
|
|
20635
|
-
import
|
|
20636
|
-
import
|
|
20423
|
+
import fs22 from "fs/promises";
|
|
20424
|
+
import path21 from "path";
|
|
20637
20425
|
var SUBS2 = ["demo", "exec", "snap", "scene", "import", "export", "reset", "mcp", "serve"];
|
|
20638
20426
|
var DEFAULT_OUT_DIR = "assets/blender";
|
|
20639
20427
|
async function writeB64(dir, name, b64) {
|
|
20640
|
-
await
|
|
20641
|
-
const p =
|
|
20642
|
-
await
|
|
20428
|
+
await fs22.mkdir(dir, { recursive: true });
|
|
20429
|
+
const p = path21.join(dir, name);
|
|
20430
|
+
await fs22.writeFile(p, Buffer.from(b64, "base64"));
|
|
20643
20431
|
return p;
|
|
20644
20432
|
}
|
|
20645
20433
|
function reportScene(log, s) {
|
|
@@ -20665,7 +20453,7 @@ async function runBlender(opts) {
|
|
|
20665
20453
|
return serveLocalBlender({ port, log });
|
|
20666
20454
|
}
|
|
20667
20455
|
if (sub === "mcp") {
|
|
20668
|
-
const { runBlenderMcp } = await import("./blender-mcp-
|
|
20456
|
+
const { runBlenderMcp } = await import("./blender-mcp-SBNHGDIX.js");
|
|
20669
20457
|
return runBlenderMcp();
|
|
20670
20458
|
}
|
|
20671
20459
|
let base = blenderEndpoint();
|
|
@@ -20685,7 +20473,7 @@ async function runBlender(opts) {
|
|
|
20685
20473
|
log.plain(rest.join("\n"));
|
|
20686
20474
|
return 1;
|
|
20687
20475
|
}
|
|
20688
|
-
const outDir =
|
|
20476
|
+
const outDir = path21.resolve(opts.outDir ?? DEFAULT_OUT_DIR);
|
|
20689
20477
|
const mode = opts.mode;
|
|
20690
20478
|
if (mode !== void 0 && !isRenderMode(mode)) {
|
|
20691
20479
|
log.error(`Unknown --mode ${mode}. Use one of: ${RENDER_MODES.join(", ")}.`);
|
|
@@ -20725,14 +20513,14 @@ async function runBlender(opts) {
|
|
|
20725
20513
|
return 0;
|
|
20726
20514
|
}
|
|
20727
20515
|
case "export": {
|
|
20728
|
-
const target = opts.out ??
|
|
20516
|
+
const target = opts.out ?? path21.join(outDir, "scene.glb");
|
|
20729
20517
|
const r = await blenderCall(base, "/export", { path: "/tmp/genex-export.glb" });
|
|
20730
20518
|
if (!r.glbBase64) {
|
|
20731
20519
|
log.error(`/export answered with no GLB bytes${r.uploaded ? " (it was uploaded, not inlined)" : ""}`);
|
|
20732
20520
|
return 1;
|
|
20733
20521
|
}
|
|
20734
|
-
await
|
|
20735
|
-
await
|
|
20522
|
+
await fs22.mkdir(path21.dirname(target), { recursive: true });
|
|
20523
|
+
await fs22.writeFile(target, Buffer.from(r.glbBase64, "base64"));
|
|
20736
20524
|
log.success(`Exported ${r.bytes ?? 0} bytes`);
|
|
20737
20525
|
log.plain(` ${c.cyan(target)}`);
|
|
20738
20526
|
return 0;
|
|
@@ -20765,12 +20553,12 @@ async function runBlender(opts) {
|
|
|
20765
20553
|
return 1;
|
|
20766
20554
|
}
|
|
20767
20555
|
try {
|
|
20768
|
-
script = await
|
|
20556
|
+
script = await fs22.readFile(opts.input, "utf8");
|
|
20769
20557
|
} catch {
|
|
20770
20558
|
log.error(`Can't read ${opts.input}`);
|
|
20771
20559
|
return 1;
|
|
20772
20560
|
}
|
|
20773
|
-
label =
|
|
20561
|
+
label = path21.basename(opts.input);
|
|
20774
20562
|
}
|
|
20775
20563
|
const r = await blenderCall(base, "/exec", { script, ...mode ? { mode } : {}, sheet: { formats: SHEET_FORMATS } });
|
|
20776
20564
|
if (r.stdout?.trim()) log.plain(r.stdout.trimEnd());
|
|
@@ -20889,9 +20677,9 @@ print(f"castle: {n} objects")
|
|
|
20889
20677
|
`;
|
|
20890
20678
|
|
|
20891
20679
|
// src/commands/asset-new.ts
|
|
20892
|
-
import
|
|
20680
|
+
import fs23 from "fs";
|
|
20893
20681
|
import fsp from "fs/promises";
|
|
20894
|
-
import
|
|
20682
|
+
import path22 from "path";
|
|
20895
20683
|
import { pathToFileURL } from "url";
|
|
20896
20684
|
var EXTRA_FILES = [
|
|
20897
20685
|
"genex-asset.example.json",
|
|
@@ -20985,7 +20773,7 @@ Every milestone ends with \`npm run verify\`, then \`npx genex preview --no-buil
|
|
|
20985
20773
|
}
|
|
20986
20774
|
async function runAssetNew(options) {
|
|
20987
20775
|
const log = createLogger();
|
|
20988
|
-
const cwd = options.dir ?
|
|
20776
|
+
const cwd = options.dir ? path22.resolve(options.dir) : process.cwd();
|
|
20989
20777
|
const slug = options.assetSlug;
|
|
20990
20778
|
if (!slug) {
|
|
20991
20779
|
log.error('Usage: genex asset new <slug> [--title "<Name>"] [--dims WxDxH] [--tri-band LOW-HIGH] [--holder "<your name>"] [--out <dir>]');
|
|
@@ -20995,14 +20783,14 @@ async function runAssetNew(options) {
|
|
|
20995
20783
|
log.error(`The slug must be kebab-case (letters, digits, dashes), got: ${slug}`);
|
|
20996
20784
|
return 1;
|
|
20997
20785
|
}
|
|
20998
|
-
const templateDir =
|
|
20999
|
-
if (!
|
|
20786
|
+
const templateDir = path22.join(getTemplatesDir(), "asset-viewer");
|
|
20787
|
+
if (!fs23.existsSync(templateDir)) {
|
|
21000
20788
|
log.error(`Vendored asset-viewer template not found at ${templateDir} \u2014 reinstall the CLI.`);
|
|
21001
20789
|
return 1;
|
|
21002
20790
|
}
|
|
21003
|
-
const manifestTools = await import(pathToFileURL(
|
|
20791
|
+
const manifestTools = await import(pathToFileURL(path22.join(templateDir, "tools", "emit-manifest.mjs")).href);
|
|
21004
20792
|
const { SHARED_FILES, PARITY_FILENAME, hashSharedFiles, pascalCase } = manifestTools;
|
|
21005
|
-
const lockPath =
|
|
20793
|
+
const lockPath = path22.join(templateDir, "shared-files.sha256.json");
|
|
21006
20794
|
const lock = JSON.parse(await fsp.readFile(lockPath, "utf8"));
|
|
21007
20795
|
const actual = hashSharedFiles(templateDir);
|
|
21008
20796
|
const drifted = SHARED_FILES.filter((rel) => lock.files[rel] !== actual[rel]);
|
|
@@ -21015,8 +20803,8 @@ async function runAssetNew(options) {
|
|
|
21015
20803
|
const triBand = parseBand(options.triBand ?? "500-8000");
|
|
21016
20804
|
const holder = options.holder?.trim() || HOLDER_PLACEHOLDER;
|
|
21017
20805
|
const year = (/* @__PURE__ */ new Date()).getUTCFullYear();
|
|
21018
|
-
const outDir =
|
|
21019
|
-
if (
|
|
20806
|
+
const outDir = path22.resolve(cwd, options.out ?? slug);
|
|
20807
|
+
if (fs23.existsSync(outDir) && fs23.readdirSync(outDir).length > 0 && !options.force) {
|
|
21020
20808
|
log.error(`${outDir} already holds files. Nothing was touched \u2014 pass --force to write into it anyway.`);
|
|
21021
20809
|
return 1;
|
|
21022
20810
|
}
|
|
@@ -21044,24 +20832,24 @@ async function runAssetNew(options) {
|
|
|
21044
20832
|
};
|
|
21045
20833
|
await fsp.mkdir(outDir, { recursive: true });
|
|
21046
20834
|
for (const rel of [...SHARED_FILES, ...EXTRA_FILES]) {
|
|
21047
|
-
const to =
|
|
21048
|
-
await fsp.mkdir(
|
|
21049
|
-
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);
|
|
21050
20838
|
}
|
|
21051
|
-
const pkg = fillTemplate(await fsp.readFile(
|
|
20839
|
+
const pkg = fillTemplate(await fsp.readFile(path22.join(templateDir, "package.json"), "utf8"), {
|
|
21052
20840
|
slug,
|
|
21053
20841
|
name,
|
|
21054
20842
|
version
|
|
21055
20843
|
});
|
|
21056
|
-
await fsp.writeFile(
|
|
21057
|
-
await fsp.writeFile(
|
|
21058
|
-
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");
|
|
21059
20847
|
await fsp.writeFile(
|
|
21060
|
-
|
|
20848
|
+
path22.join(outDir, "DESIGN.md"),
|
|
21061
20849
|
designDoc({ name, slug, sizeMeters, triBand, holder }),
|
|
21062
20850
|
"utf8"
|
|
21063
20851
|
);
|
|
21064
|
-
const placeholder = await fsp.readFile(
|
|
20852
|
+
const placeholder = await fsp.readFile(path22.join(templateDir, "src", "asset", "PLACEHOLDER.ts"), "utf8");
|
|
21065
20853
|
const seeded = seedAssetSource(placeholder, {
|
|
21066
20854
|
slug,
|
|
21067
20855
|
name,
|
|
@@ -21072,8 +20860,8 @@ async function runAssetNew(options) {
|
|
|
21072
20860
|
pascalCase
|
|
21073
20861
|
});
|
|
21074
20862
|
const assetFilename = `${slug.replace(/-([a-z0-9])/g, (_, ch) => ch.toUpperCase())}.ts`;
|
|
21075
|
-
await fsp.mkdir(
|
|
21076
|
-
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");
|
|
21077
20865
|
const copied = hashSharedFiles(outDir);
|
|
21078
20866
|
const mismatched = SHARED_FILES.filter((rel) => copied[rel] !== lock.files[rel]);
|
|
21079
20867
|
if (mismatched.length) {
|
|
@@ -21081,7 +20869,7 @@ async function runAssetNew(options) {
|
|
|
21081
20869
|
return 1;
|
|
21082
20870
|
}
|
|
21083
20871
|
await fsp.writeFile(
|
|
21084
|
-
|
|
20872
|
+
path22.join(outDir, PARITY_FILENAME),
|
|
21085
20873
|
JSON.stringify(
|
|
21086
20874
|
{
|
|
21087
20875
|
note: "Recorded by `genex asset new`. Gate G13 recomputes these; do not hand-edit the viewer.",
|
|
@@ -21125,12 +20913,12 @@ async function runAssetNew(options) {
|
|
|
21125
20913
|
}
|
|
21126
20914
|
|
|
21127
20915
|
// src/commands/tools.ts
|
|
21128
|
-
import
|
|
20916
|
+
import path25 from "path";
|
|
21129
20917
|
|
|
21130
20918
|
// src/lib/local-install.ts
|
|
21131
|
-
import
|
|
21132
|
-
import
|
|
21133
|
-
import { spawn as
|
|
20919
|
+
import fs24 from "fs/promises";
|
|
20920
|
+
import path23 from "path";
|
|
20921
|
+
import { spawn as spawn4 } from "child_process";
|
|
21134
20922
|
var CLI_PACKAGE = "@genex-ai/cli-demo";
|
|
21135
20923
|
var FULL_NAME_FALLBACK = `npx ${CLI_PACKAGE}@${CLI_CHANNEL}`;
|
|
21136
20924
|
var LOCKFILES = [
|
|
@@ -21142,17 +20930,17 @@ var LOCKFILES = [
|
|
|
21142
20930
|
];
|
|
21143
20931
|
async function exists4(p) {
|
|
21144
20932
|
try {
|
|
21145
|
-
await
|
|
20933
|
+
await fs24.access(p);
|
|
21146
20934
|
return true;
|
|
21147
20935
|
} catch {
|
|
21148
20936
|
return false;
|
|
21149
20937
|
}
|
|
21150
20938
|
}
|
|
21151
20939
|
async function detectPackageManager(cwd) {
|
|
21152
|
-
let dir =
|
|
20940
|
+
let dir = path23.resolve(cwd);
|
|
21153
20941
|
for (; ; ) {
|
|
21154
20942
|
try {
|
|
21155
|
-
const raw = await
|
|
20943
|
+
const raw = await fs24.readFile(path23.join(dir, "package.json"), "utf8");
|
|
21156
20944
|
const pm = JSON.parse(raw).packageManager;
|
|
21157
20945
|
if (typeof pm === "string") {
|
|
21158
20946
|
const name = pm.split("@")[0];
|
|
@@ -21161,18 +20949,18 @@ async function detectPackageManager(cwd) {
|
|
|
21161
20949
|
} catch {
|
|
21162
20950
|
}
|
|
21163
20951
|
for (const [file, pm] of LOCKFILES) {
|
|
21164
|
-
if (await exists4(
|
|
20952
|
+
if (await exists4(path23.join(dir, file))) return pm;
|
|
21165
20953
|
}
|
|
21166
|
-
const parent =
|
|
20954
|
+
const parent = path23.dirname(dir);
|
|
21167
20955
|
if (parent === dir) return "npm";
|
|
21168
20956
|
dir = parent;
|
|
21169
20957
|
}
|
|
21170
20958
|
}
|
|
21171
20959
|
async function findLocalCli(cwd) {
|
|
21172
|
-
let dir =
|
|
20960
|
+
let dir = path23.resolve(cwd);
|
|
21173
20961
|
for (; ; ) {
|
|
21174
|
-
if (await exists4(
|
|
21175
|
-
const parent =
|
|
20962
|
+
if (await exists4(path23.join(dir, "node_modules", CLI_PACKAGE, "package.json"))) return dir;
|
|
20963
|
+
const parent = path23.dirname(dir);
|
|
21176
20964
|
if (parent === dir) return null;
|
|
21177
20965
|
dir = parent;
|
|
21178
20966
|
}
|
|
@@ -21190,7 +20978,7 @@ function installArgs(pm, spec) {
|
|
|
21190
20978
|
}
|
|
21191
20979
|
}
|
|
21192
20980
|
function manifestName(cwd) {
|
|
21193
|
-
const slug =
|
|
20981
|
+
const slug = path23.basename(path23.resolve(cwd)).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 100);
|
|
21194
20982
|
return slug || "genex-tools-workspace";
|
|
21195
20983
|
}
|
|
21196
20984
|
function isSourceRun(moduleUrl = import.meta.url) {
|
|
@@ -21207,7 +20995,7 @@ async function spawnInstall(command, args, cwd) {
|
|
|
21207
20995
|
resolve({ ok, detail });
|
|
21208
20996
|
};
|
|
21209
20997
|
try {
|
|
21210
|
-
const child =
|
|
20998
|
+
const child = spawn4(command, args, {
|
|
21211
20999
|
cwd,
|
|
21212
21000
|
// npm/pnpm/yarn are .cmd shims on Windows; the args carry no user input.
|
|
21213
21001
|
shell: process.platform === "win32",
|
|
@@ -21248,10 +21036,10 @@ async function ensureLocalCli(log, cwd = process.cwd(), opts = {}) {
|
|
|
21248
21036
|
return;
|
|
21249
21037
|
}
|
|
21250
21038
|
const pm = await detectPackageManager(cwd);
|
|
21251
|
-
const hadManifest = await exists4(
|
|
21039
|
+
const hadManifest = await exists4(path23.join(cwd, "package.json"));
|
|
21252
21040
|
if (!hadManifest) {
|
|
21253
|
-
await
|
|
21254
|
-
|
|
21041
|
+
await fs24.writeFile(
|
|
21042
|
+
path23.join(cwd, "package.json"),
|
|
21255
21043
|
JSON.stringify({ name: manifestName(cwd), private: true }, null, 2) + "\n"
|
|
21256
21044
|
);
|
|
21257
21045
|
await ensureIgnored(cwd, "node_modules/");
|
|
@@ -21271,20 +21059,20 @@ async function ensureLocalCli(log, cwd = process.cwd(), opts = {}) {
|
|
|
21271
21059
|
}
|
|
21272
21060
|
}
|
|
21273
21061
|
async function ensureIgnored(dir, entry) {
|
|
21274
|
-
const file =
|
|
21062
|
+
const file = path23.join(dir, ".gitignore");
|
|
21275
21063
|
let content = "";
|
|
21276
21064
|
try {
|
|
21277
|
-
content = await
|
|
21065
|
+
content = await fs24.readFile(file, "utf8");
|
|
21278
21066
|
} catch {
|
|
21279
21067
|
}
|
|
21280
21068
|
if (content.split("\n").some((l) => l.trim() === entry)) return;
|
|
21281
21069
|
let next = content;
|
|
21282
21070
|
if (next.length > 0 && !next.endsWith("\n")) next += "\n";
|
|
21283
|
-
await
|
|
21071
|
+
await fs24.writeFile(file, next + entry + "\n");
|
|
21284
21072
|
}
|
|
21285
21073
|
|
|
21286
21074
|
// src/commands/doctor.ts
|
|
21287
|
-
import
|
|
21075
|
+
import path24 from "path";
|
|
21288
21076
|
var LANE_ORDER = [
|
|
21289
21077
|
"model",
|
|
21290
21078
|
"image",
|
|
@@ -21346,9 +21134,15 @@ async function runDoctor(opts = {}) {
|
|
|
21346
21134
|
rows.push(authRow(authState, email, opts.envPath));
|
|
21347
21135
|
let credits = null;
|
|
21348
21136
|
let lanes = null;
|
|
21137
|
+
let legal = null;
|
|
21349
21138
|
if (authState === "ok" && token) {
|
|
21350
|
-
[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
|
+
]);
|
|
21351
21144
|
}
|
|
21145
|
+
rows.push(termsRow(authState, legal));
|
|
21352
21146
|
rows.push(creditsRow(authState, credits));
|
|
21353
21147
|
if (opts.json) {
|
|
21354
21148
|
process.stdout.write(
|
|
@@ -21358,6 +21152,7 @@ async function runDoctor(opts = {}) {
|
|
|
21358
21152
|
node: process.versions.node,
|
|
21359
21153
|
cli: { installed, latest },
|
|
21360
21154
|
auth: { state: authState, email, apiUrl },
|
|
21155
|
+
terms: legal,
|
|
21361
21156
|
credits,
|
|
21362
21157
|
lanes: lanes ? { paused: lanes.paused, lanes: lanes.lanes } : null,
|
|
21363
21158
|
ok: !rows.some((r) => r.bad)
|
|
@@ -21459,6 +21254,17 @@ function authRow(state, email, envPath) {
|
|
|
21459
21254
|
};
|
|
21460
21255
|
}
|
|
21461
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
|
+
}
|
|
21462
21268
|
function creditsRow(state, info) {
|
|
21463
21269
|
if (state !== "ok") return { label: "Credits", value: "\u2014 (sign in first)" };
|
|
21464
21270
|
if (!info) return { label: "Credits", value: "couldn't be read from this server" };
|
|
@@ -21510,9 +21316,21 @@ async function fetchCredits(apiUrl, token) {
|
|
|
21510
21316
|
return null;
|
|
21511
21317
|
}
|
|
21512
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
|
+
}
|
|
21513
21331
|
async function firstSkillsMarker() {
|
|
21514
21332
|
for (const target of resolveAgentTargets()) {
|
|
21515
|
-
const marker = await readSkillsMarker(
|
|
21333
|
+
const marker = await readSkillsMarker(path24.join(target.baseDir, "skills"));
|
|
21516
21334
|
if (marker) return marker;
|
|
21517
21335
|
}
|
|
21518
21336
|
return null;
|
|
@@ -21551,8 +21369,8 @@ async function runTools(opts) {
|
|
|
21551
21369
|
let totalNew = 0;
|
|
21552
21370
|
let totalUpdated = 0;
|
|
21553
21371
|
for (const t of targets) {
|
|
21554
|
-
const dest =
|
|
21555
|
-
const { copied, updated } = await copyTemplates(
|
|
21372
|
+
const dest = path25.join(t.baseDir, "skills");
|
|
21373
|
+
const { copied, updated } = await copyTemplates(path25.join(templatesDir, "skills"), dest, {
|
|
21556
21374
|
filter: (rel) => skillFamilyFilter("tools")(`skills/${rel}`)
|
|
21557
21375
|
});
|
|
21558
21376
|
await pruneRemovedSkills(dest, log);
|
|
@@ -21607,7 +21425,6 @@ async function runTools(opts) {
|
|
|
21607
21425
|
await runDoctor({ apiUrl: opts.apiUrl, envPath: opts.envPath, quiet: opts.quiet });
|
|
21608
21426
|
log.plain("");
|
|
21609
21427
|
log.success("Genex Tools is ready. \u{1F9F0}");
|
|
21610
|
-
log.plain(` Try it: ${c.cyan('npx genex image "weathered treasure map, hand-inked" --transparent')}`);
|
|
21611
21428
|
log.dim(" Assets land in ./assets/ as files you own \u2014 wire the local path into your game.");
|
|
21612
21429
|
}
|
|
21613
21430
|
|
|
@@ -22825,8 +22642,9 @@ async function main() {
|
|
|
22825
22642
|
log.plain(getCliVersion());
|
|
22826
22643
|
return;
|
|
22827
22644
|
}
|
|
22828
|
-
const
|
|
22829
|
-
|
|
22645
|
+
const workspace = await resolveWorkspace();
|
|
22646
|
+
const mode = workspace.mode;
|
|
22647
|
+
setWorkspaceHeader(workspaceHeaderLabel(workspace));
|
|
22830
22648
|
if (parsed.help || parsed.command === void 0) {
|
|
22831
22649
|
const perCommand = parsed.command === void 0 ? void 0 : COMMAND_HELP[parsed.command];
|
|
22832
22650
|
log.plain(perCommand ?? (mode === "tools" ? HELP_TOOLS : HELP));
|