@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/cli-demo",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.1-dev.578",
|
|
4
4
|
"description": "Set up your project's agent workspace (.claude/.codex/.cursor in the game folder), authorize, create a game project, generate AI assets, and publish (genex CLI).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/dist/chunk-TFRTQ37H.js
DELETED
|
@@ -1,449 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CLI_CHANNEL,
|
|
3
|
-
ENV_FILE_ENV,
|
|
4
|
-
ENV_TOKEN_KEY,
|
|
5
|
-
c,
|
|
6
|
-
getApiUrl,
|
|
7
|
-
getCliVersion,
|
|
8
|
-
getGenexEnvPath
|
|
9
|
-
} from "./chunk-HYCSNWYX.js";
|
|
10
|
-
|
|
11
|
-
// src/lib/api.ts
|
|
12
|
-
var CLI_VERSION_HEADER = "x-genex-cli-version";
|
|
13
|
-
var WORKSPACE_HEADER = "x-genex-workspace";
|
|
14
|
-
var workspaceLabel = null;
|
|
15
|
-
function setWorkspaceHeader(label) {
|
|
16
|
-
workspaceLabel = label;
|
|
17
|
-
}
|
|
18
|
-
function formatUpdateRequired(body) {
|
|
19
|
-
const action = body.action ?? `npm i -D @genex-ai/cli-demo@${CLI_CHANNEL}`;
|
|
20
|
-
const message = body.message ?? `Genex CLI ${body.clientVersion ?? getCliVersion()} is below the minimum supported version${body.minVersion ? ` ${body.minVersion}` : ""}.`;
|
|
21
|
-
return [`${c.red("\u2717")} ${message}`, ` Update now \u2014 run: ${action} (then re-run this command)`];
|
|
22
|
-
}
|
|
23
|
-
function shortDate(iso) {
|
|
24
|
-
const d = new Date(iso);
|
|
25
|
-
if (Number.isNaN(d.getTime())) return iso;
|
|
26
|
-
return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
27
|
-
}
|
|
28
|
-
function formatInsufficientCredits(body) {
|
|
29
|
-
const message = body.message ?? `this generation costs ${body.price ?? "?"} credits; your balance is ${body.balance ?? 0}.`;
|
|
30
|
-
const lines = [`${c.red("\u2717")} Out of credits \u2014 ${lowerFirst(message)}`];
|
|
31
|
-
if (body.refillAt && body.refillTo) {
|
|
32
|
-
lines.push(` Credits refill to ${body.refillTo} on ${shortDate(body.refillAt)}.`);
|
|
33
|
-
}
|
|
34
|
-
if (body.url) lines.push(` Get more or check your balance: ${body.url}`);
|
|
35
|
-
return lines;
|
|
36
|
-
}
|
|
37
|
-
function formatVerificationRequired(body) {
|
|
38
|
-
const lines = [
|
|
39
|
-
`${c.red("\u2717")} Email not verified \u2014 verify your email to unlock your free generation credits.`
|
|
40
|
-
];
|
|
41
|
-
if (body.url) lines.push(` Verify here: ${body.url} (then re-run this command)`);
|
|
42
|
-
return lines;
|
|
43
|
-
}
|
|
44
|
-
function lowerFirst(s) {
|
|
45
|
-
return s ? s[0].toLowerCase() + s.slice(1) : s;
|
|
46
|
-
}
|
|
47
|
-
var structuredPrinted = /* @__PURE__ */ new WeakSet();
|
|
48
|
-
function printedStructuredError(res) {
|
|
49
|
-
return structuredPrinted.has(res);
|
|
50
|
-
}
|
|
51
|
-
async function apiFetch(url, init = {}) {
|
|
52
|
-
const headers = new Headers(init.headers);
|
|
53
|
-
if (!headers.has(CLI_VERSION_HEADER)) headers.set(CLI_VERSION_HEADER, getCliVersion());
|
|
54
|
-
if (workspaceLabel && !headers.has(WORKSPACE_HEADER)) {
|
|
55
|
-
headers.set(WORKSPACE_HEADER, workspaceLabel);
|
|
56
|
-
}
|
|
57
|
-
const res = await fetch(url, { ...init, headers });
|
|
58
|
-
if (res.status === 426) {
|
|
59
|
-
try {
|
|
60
|
-
const body = await res.clone().json();
|
|
61
|
-
if (body?.error === "cli_update_required") {
|
|
62
|
-
for (const line of formatUpdateRequired(body)) process.stderr.write(line + "\n");
|
|
63
|
-
}
|
|
64
|
-
} catch {
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
if (res.status === 402) {
|
|
68
|
-
try {
|
|
69
|
-
const body = await res.clone().json();
|
|
70
|
-
if (body?.error === "insufficient_credits") {
|
|
71
|
-
for (const line of formatInsufficientCredits(body)) process.stderr.write(line + "\n");
|
|
72
|
-
structuredPrinted.add(res);
|
|
73
|
-
}
|
|
74
|
-
} catch {
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
if (res.status === 403) {
|
|
78
|
-
try {
|
|
79
|
-
const body = await res.clone().json();
|
|
80
|
-
if (body?.error === "email_verification_required") {
|
|
81
|
-
for (const line of formatVerificationRequired(body)) process.stderr.write(line + "\n");
|
|
82
|
-
structuredPrinted.add(res);
|
|
83
|
-
}
|
|
84
|
-
} catch {
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
if (res.status === 503) {
|
|
88
|
-
try {
|
|
89
|
-
const body = await res.clone().json();
|
|
90
|
-
if (body?.error === "generation_paused") {
|
|
91
|
-
process.stderr.write(
|
|
92
|
-
`${c.red("\u2717")} ${body.message ?? "Generation is temporarily paused platform-wide. Try again later."}
|
|
93
|
-
`
|
|
94
|
-
);
|
|
95
|
-
structuredPrinted.add(res);
|
|
96
|
-
}
|
|
97
|
-
} catch {
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return res;
|
|
101
|
-
}
|
|
102
|
-
async function fetchSignedInEmail(apiUrl, token) {
|
|
103
|
-
try {
|
|
104
|
-
const res = await apiFetch(`${apiUrl}/api/auth/get-session`, {
|
|
105
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
106
|
-
});
|
|
107
|
-
if (!res.ok) return null;
|
|
108
|
-
const data = await res.json().catch(() => null);
|
|
109
|
-
return data?.user?.email ?? null;
|
|
110
|
-
} catch {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// src/lib/blender-client.ts
|
|
116
|
-
import fs from "fs";
|
|
117
|
-
import path from "path";
|
|
118
|
-
var BLENDER_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
119
|
-
var RENDER_MODES = ["solid", "wireframe", "normals", "lit"];
|
|
120
|
-
function isRenderMode(v) {
|
|
121
|
-
return typeof v === "string" && RENDER_MODES.includes(v);
|
|
122
|
-
}
|
|
123
|
-
function blenderEndpoint() {
|
|
124
|
-
const raw = process.env.GENEX_BLENDER_URL?.trim();
|
|
125
|
-
return raw ? raw.replace(/\/+$/, "") : void 0;
|
|
126
|
-
}
|
|
127
|
-
function blenderSetupHint() {
|
|
128
|
-
if (process.env.GENEX_HOSTED_SESSION === "1") {
|
|
129
|
-
return [
|
|
130
|
-
"The Blender lane is off on this stand \u2014 there is no seat to acquire.",
|
|
131
|
-
" Build the space in code instead ($genex-threejs-procedural-assets); do not wait for it."
|
|
132
|
-
].join("\n");
|
|
133
|
-
}
|
|
134
|
-
return [
|
|
135
|
-
"No Blender endpoint. Run the service against your own Blender in another terminal:",
|
|
136
|
-
" npx genex blender serve",
|
|
137
|
-
" export GENEX_BLENDER_URL=http://localhost:8088",
|
|
138
|
-
" GENEX_BLENDER_URL may also point at any running genex-blender service."
|
|
139
|
-
].join("\n");
|
|
140
|
-
}
|
|
141
|
-
var SHEET_FORMATS = ["webp", "png"];
|
|
142
|
-
function sheetOf(r) {
|
|
143
|
-
if (r.contactSheet?.b64) return { b64: r.contactSheet.b64, mime: r.contactSheet.mime };
|
|
144
|
-
if (r.contactSheetPng) return { b64: r.contactSheetPng, mime: "image/png" };
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
function sheetExt(mime) {
|
|
148
|
-
return mime === "image/webp" ? "webp" : "png";
|
|
149
|
-
}
|
|
150
|
-
var SEAT_FILE = path.join(".genex", "blender-seat.json");
|
|
151
|
-
function readSeatGrant(cwd = process.cwd()) {
|
|
152
|
-
try {
|
|
153
|
-
const raw = JSON.parse(fs.readFileSync(path.join(cwd, SEAT_FILE), "utf8"));
|
|
154
|
-
return typeof raw.url === "string" && typeof raw.token === "string" ? { url: raw.url, token: raw.token } : null;
|
|
155
|
-
} catch {
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
function writeSeatGrant(grant, cwd = process.cwd()) {
|
|
160
|
-
const file = path.join(cwd, SEAT_FILE);
|
|
161
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
162
|
-
fs.writeFileSync(file, JSON.stringify(grant, null, 2) + "\n", { mode: 384 });
|
|
163
|
-
}
|
|
164
|
-
async function blenderCall(base, route, body) {
|
|
165
|
-
const seat = readSeatGrant();
|
|
166
|
-
const ctl = new AbortController();
|
|
167
|
-
const timer = setTimeout(() => ctl.abort(), BLENDER_TIMEOUT_MS);
|
|
168
|
-
try {
|
|
169
|
-
const res = await fetch(`${base}${route}`, {
|
|
170
|
-
method: body === void 0 ? "GET" : "POST",
|
|
171
|
-
headers: {
|
|
172
|
-
// A REAL User-Agent, because the vendor proxy in front of a hosted pod
|
|
173
|
-
// sits behind Cloudflare, and Cloudflare's error 1010 refuses the
|
|
174
|
-
// default Python UA outright (measured: Python-urllib/3.x -> 403; any
|
|
175
|
-
// override -> 200). Whether it also refuses undici's default is
|
|
176
|
-
// untested, and this is the client every hosted agent will reach the
|
|
177
|
-
// pod with -- so the question is closed here rather than found in a
|
|
178
|
-
// sandbox as a 403 that looks like a bad token.
|
|
179
|
-
"User-Agent": `genex-cli/${getCliVersion()}`,
|
|
180
|
-
...body === void 0 ? {} : { "Content-Type": "application/json" },
|
|
181
|
-
// TWO credentials, two headers, and the router is strict about which is
|
|
182
|
-
// which: a hosted SEAT presents its token as `x-genex-seat` on
|
|
183
|
-
// `/s/<sid>/…`, while `x-genex-internal` is the POD secret and is honoured
|
|
184
|
-
// only on `/pool/*`. The first cut sent the seat token under the pod
|
|
185
|
-
// header and every hosted call would have been a 401 whose message
|
|
186
|
-
// pointed at the wrong knob (review finding).
|
|
187
|
-
...seat?.token ? { "x-genex-seat": seat.token } : {},
|
|
188
|
-
// Sent only when set. The service treats an unset secret as open, which
|
|
189
|
-
// is right on localhost; the deployed compose makes it mandatory.
|
|
190
|
-
...!seat?.token && process.env.GENEX_BLENDER_SECRET ? { "x-genex-internal": process.env.GENEX_BLENDER_SECRET } : {}
|
|
191
|
-
},
|
|
192
|
-
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
193
|
-
signal: ctl.signal
|
|
194
|
-
});
|
|
195
|
-
const text = await res.text();
|
|
196
|
-
let json;
|
|
197
|
-
try {
|
|
198
|
-
json = JSON.parse(text);
|
|
199
|
-
} catch {
|
|
200
|
-
throw new Error(`${route} answered ${res.status} with non-JSON: ${text.slice(0, 200)}`);
|
|
201
|
-
}
|
|
202
|
-
if (!res.ok) {
|
|
203
|
-
if (res.status === 401) {
|
|
204
|
-
throw new Error(
|
|
205
|
-
seat ? `${route} refused the seat token (401) \u2014 the seat may have been closed; run the command again to acquire a new one` : `${route} refused the request (401) \u2014 set GENEX_BLENDER_SECRET to the service's secret`
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
const detail = typeof json.detail === "string" ? ` \u2014 ${json.detail}` : "";
|
|
209
|
-
throw new Error(`${route} failed (${res.status}): ${json.error ?? text.slice(0, 200)}${detail}`);
|
|
210
|
-
}
|
|
211
|
-
return json;
|
|
212
|
-
} catch (err) {
|
|
213
|
-
if (err instanceof Error && err.name === "AbortError") {
|
|
214
|
-
throw new Error(`${route} timed out after ${BLENDER_TIMEOUT_MS / 1e3}s`);
|
|
215
|
-
}
|
|
216
|
-
throw err;
|
|
217
|
-
} finally {
|
|
218
|
-
clearTimeout(timer);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
function sceneSummary(s) {
|
|
222
|
-
if (!s) return "";
|
|
223
|
-
return `objects ${s.objectCount} meshes ${s.meshCount} tris ${s.totalTris} materials ${s.materialCount} radius ${s.bounds.radius}`;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// src/lib/store.ts
|
|
227
|
-
import fs3 from "fs/promises";
|
|
228
|
-
import path3 from "path";
|
|
229
|
-
|
|
230
|
-
// src/lib/env.ts
|
|
231
|
-
import fs2 from "fs/promises";
|
|
232
|
-
import path2 from "path";
|
|
233
|
-
import { spawn } from "child_process";
|
|
234
|
-
async function writeEnvVar(envPath, key, value) {
|
|
235
|
-
let content = "";
|
|
236
|
-
let existed = false;
|
|
237
|
-
try {
|
|
238
|
-
content = await fs2.readFile(envPath, "utf8");
|
|
239
|
-
existed = true;
|
|
240
|
-
} catch {
|
|
241
|
-
}
|
|
242
|
-
const assignment = `${key}=${formatValue(value)}`;
|
|
243
|
-
const keyPattern = new RegExp(
|
|
244
|
-
`^(\\s*export\\s+)?${escapeRegExp(key)}=.*$`,
|
|
245
|
-
"gm"
|
|
246
|
-
);
|
|
247
|
-
let next;
|
|
248
|
-
let mode;
|
|
249
|
-
if (keyPattern.test(content)) {
|
|
250
|
-
next = content.replace(keyPattern, assignment);
|
|
251
|
-
mode = "updated";
|
|
252
|
-
} else {
|
|
253
|
-
let prefix = content;
|
|
254
|
-
if (prefix.length > 0 && !prefix.endsWith("\n")) prefix += "\n";
|
|
255
|
-
next = prefix + assignment + "\n";
|
|
256
|
-
mode = existed ? "appended" : "created";
|
|
257
|
-
}
|
|
258
|
-
await fs2.mkdir(path2.dirname(envPath), { recursive: true });
|
|
259
|
-
await fs2.writeFile(envPath, next, { mode: 384 });
|
|
260
|
-
await restrictFilePermissions(envPath);
|
|
261
|
-
return { mode, path: envPath };
|
|
262
|
-
}
|
|
263
|
-
async function restrictFilePermissions(filePath) {
|
|
264
|
-
if (process.platform !== "win32") {
|
|
265
|
-
await fs2.chmod(filePath, 384).catch(() => {
|
|
266
|
-
});
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
const user = process.env.USERNAME ?? process.env.USER;
|
|
270
|
-
if (!user) return;
|
|
271
|
-
await new Promise((resolve) => {
|
|
272
|
-
try {
|
|
273
|
-
const child = spawn(
|
|
274
|
-
"icacls",
|
|
275
|
-
[filePath, "/inheritance:r", "/grant:r", `${user}:F`],
|
|
276
|
-
{ stdio: "ignore" }
|
|
277
|
-
);
|
|
278
|
-
child.on("error", () => resolve());
|
|
279
|
-
child.on("close", () => resolve());
|
|
280
|
-
} catch {
|
|
281
|
-
resolve();
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
function formatValue(value) {
|
|
286
|
-
if (/[\s#"'$`\\]/.test(value)) {
|
|
287
|
-
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
288
|
-
}
|
|
289
|
-
return value;
|
|
290
|
-
}
|
|
291
|
-
function escapeRegExp(s) {
|
|
292
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// src/lib/store.ts
|
|
296
|
-
function getProjectMetadataPath(cwd = process.cwd()) {
|
|
297
|
-
return path3.join(cwd, ".genex", "project.json");
|
|
298
|
-
}
|
|
299
|
-
function getWorkspacePath(cwd = process.cwd()) {
|
|
300
|
-
return path3.join(cwd, ".genex", "workspace.json");
|
|
301
|
-
}
|
|
302
|
-
async function readWorkspace(cwd = process.cwd()) {
|
|
303
|
-
try {
|
|
304
|
-
const raw = await fs3.readFile(getWorkspacePath(cwd), "utf8");
|
|
305
|
-
return JSON.parse(raw);
|
|
306
|
-
} catch {
|
|
307
|
-
return null;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
async function writeWorkspace(meta, cwd = process.cwd()) {
|
|
311
|
-
const file = getWorkspacePath(cwd);
|
|
312
|
-
await fs3.mkdir(path3.dirname(file), { recursive: true });
|
|
313
|
-
await fs3.writeFile(file, JSON.stringify(meta, null, 2) + "\n", { mode: 384 });
|
|
314
|
-
await fs3.chmod(file, 384).catch(() => {
|
|
315
|
-
});
|
|
316
|
-
return { path: file };
|
|
317
|
-
}
|
|
318
|
-
async function writeUserToken(token, envPath) {
|
|
319
|
-
const { path: written } = await writeEnvVar(getGenexEnvPath(envPath), ENV_TOKEN_KEY, token);
|
|
320
|
-
return { path: written };
|
|
321
|
-
}
|
|
322
|
-
async function rotateRejectedEnv(envPath) {
|
|
323
|
-
const file = getGenexEnvPath(envPath);
|
|
324
|
-
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
325
|
-
const aside = `${file}.rejected-${stamp}`;
|
|
326
|
-
try {
|
|
327
|
-
await fs3.rename(file, aside);
|
|
328
|
-
return aside;
|
|
329
|
-
} catch {
|
|
330
|
-
return null;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
async function readUserToken(envPath) {
|
|
334
|
-
const fromGenex = await readTokenFromFile(getGenexEnvPath(envPath));
|
|
335
|
-
if (fromGenex) return fromGenex;
|
|
336
|
-
if (!envPath && !process.env[ENV_FILE_ENV]) {
|
|
337
|
-
return readTokenFromFile(path3.join(process.cwd(), ".env"));
|
|
338
|
-
}
|
|
339
|
-
return null;
|
|
340
|
-
}
|
|
341
|
-
async function readTokenFromFile(file) {
|
|
342
|
-
let content;
|
|
343
|
-
try {
|
|
344
|
-
content = await fs3.readFile(file, "utf8");
|
|
345
|
-
} catch {
|
|
346
|
-
return null;
|
|
347
|
-
}
|
|
348
|
-
const m = content.match(/^\s*(?:export\s+)?GENEX_TOKEN=(.*)$/m);
|
|
349
|
-
if (!m) return null;
|
|
350
|
-
return stripQuotes(m[1].trim()) || null;
|
|
351
|
-
}
|
|
352
|
-
function stripQuotes(v) {
|
|
353
|
-
if (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'")) {
|
|
354
|
-
return v.slice(1, -1);
|
|
355
|
-
}
|
|
356
|
-
return v;
|
|
357
|
-
}
|
|
358
|
-
async function readProject(cwd = process.cwd()) {
|
|
359
|
-
try {
|
|
360
|
-
const raw = await fs3.readFile(getProjectMetadataPath(cwd), "utf8");
|
|
361
|
-
return JSON.parse(raw);
|
|
362
|
-
} catch {
|
|
363
|
-
return null;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
async function writeProject(meta, cwd = process.cwd()) {
|
|
367
|
-
const file = getProjectMetadataPath(cwd);
|
|
368
|
-
await fs3.mkdir(path3.dirname(file), { recursive: true });
|
|
369
|
-
await fs3.writeFile(file, JSON.stringify(meta, null, 2) + "\n", { mode: 384 });
|
|
370
|
-
await fs3.chmod(file, 384).catch(() => {
|
|
371
|
-
});
|
|
372
|
-
return { path: file };
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// src/lib/blender-seat.ts
|
|
376
|
-
var SEAT_WAIT_BUDGET_MS = 9e4;
|
|
377
|
-
var SEAT_POLL_MS = 1e4;
|
|
378
|
-
function hostedBlenderLane() {
|
|
379
|
-
return process.env.GENEX_BLENDER_LANE === "1";
|
|
380
|
-
}
|
|
381
|
-
async function acquireSeat(opts) {
|
|
382
|
-
const held = readSeatGrant(opts.cwd);
|
|
383
|
-
if (held) return { kind: "granted", grant: held };
|
|
384
|
-
if (!hostedBlenderLane()) return { kind: "off" };
|
|
385
|
-
const token = opts.token !== void 0 ? opts.token : await readUserToken();
|
|
386
|
-
if (!token) return { kind: "refused", reason: "not signed in" };
|
|
387
|
-
const doFetch = opts.fetchImpl ?? apiFetch;
|
|
388
|
-
const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
389
|
-
const deadline = Date.now() + (opts.budgetMs ?? SEAT_WAIT_BUDGET_MS);
|
|
390
|
-
let announced = false;
|
|
391
|
-
for (; ; ) {
|
|
392
|
-
const res = await doFetch(`${getApiUrl()}/api/blender/seat`, {
|
|
393
|
-
method: "POST",
|
|
394
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
395
|
-
});
|
|
396
|
-
if (res.status === 201) {
|
|
397
|
-
const body = await res.json();
|
|
398
|
-
if (typeof body.url !== "string" || typeof body.token !== "string") {
|
|
399
|
-
return { kind: "refused", reason: "the API answered a seat without a url and token" };
|
|
400
|
-
}
|
|
401
|
-
const grant = { url: body.url, token: body.token };
|
|
402
|
-
writeSeatGrant(grant, opts.cwd);
|
|
403
|
-
return { kind: "granted", grant };
|
|
404
|
-
}
|
|
405
|
-
if (res.status === 202 || res.status === 429) {
|
|
406
|
-
if (!announced) {
|
|
407
|
-
opts.log.step("Warming up a Blender seat (about 20 s on a warm pod, ~2 min on a cold one, longer on a host pulling the image for the first time)\u2026");
|
|
408
|
-
announced = true;
|
|
409
|
-
}
|
|
410
|
-
if (Date.now() >= deadline) return { kind: "warming" };
|
|
411
|
-
await sleep(SEAT_POLL_MS);
|
|
412
|
-
continue;
|
|
413
|
-
}
|
|
414
|
-
if (res.status === 404) return { kind: "off" };
|
|
415
|
-
let reason = `HTTP ${res.status}`;
|
|
416
|
-
try {
|
|
417
|
-
const body = await res.json();
|
|
418
|
-
reason = body.reason ?? body.error ?? reason;
|
|
419
|
-
} catch {
|
|
420
|
-
}
|
|
421
|
-
return { kind: "refused", reason };
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
export {
|
|
426
|
-
setWorkspaceHeader,
|
|
427
|
-
printedStructuredError,
|
|
428
|
-
apiFetch,
|
|
429
|
-
fetchSignedInEmail,
|
|
430
|
-
restrictFilePermissions,
|
|
431
|
-
getWorkspacePath,
|
|
432
|
-
readWorkspace,
|
|
433
|
-
writeWorkspace,
|
|
434
|
-
writeUserToken,
|
|
435
|
-
rotateRejectedEnv,
|
|
436
|
-
readUserToken,
|
|
437
|
-
readProject,
|
|
438
|
-
writeProject,
|
|
439
|
-
RENDER_MODES,
|
|
440
|
-
isRenderMode,
|
|
441
|
-
blenderEndpoint,
|
|
442
|
-
blenderSetupHint,
|
|
443
|
-
SHEET_FORMATS,
|
|
444
|
-
sheetOf,
|
|
445
|
-
sheetExt,
|
|
446
|
-
blenderCall,
|
|
447
|
-
sceneSummary,
|
|
448
|
-
acquireSeat
|
|
449
|
-
};
|