@genex-ai/cli-demo 1.34.2 → 1.34.3-dev.704
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/README.md +21 -3
- package/dist/{blender-mcp-X66ZZ4TN.js → blender-mcp-XLEK6SLM.js} +2 -2
- package/dist/{blender-serve-PVVCH3S6.js → blender-serve-GW3LTFOX.js} +1 -1
- package/dist/{chunk-5FA2WLM7.js → chunk-AQW7HUPV.js} +42 -4
- package/dist/{chunk-OJAXYQ5L.js → chunk-MRV3Q3WU.js} +100 -81
- package/dist/index.js +438 -395
- package/package.json +1 -1
- package/templates/skills/genex-getting-started/SKILL.md +2 -2
- package/templates/skills/genex-updates/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@ as the latest available snapshot.
|
|
|
67
67
|
claude,codex,cursor`, or a single custom dir with `--dir`.
|
|
68
68
|
2. **Authorizes you** — opens the Genex auth site (web) in your browser. If the
|
|
69
69
|
browser can't open, it prints the URL to open manually.
|
|
70
|
-
3. **Saves your token** —
|
|
70
|
+
3. **Saves your token** — saves an origin-bound credential under `~/.genex/env.origins/` (per-user;
|
|
71
71
|
reused across projects).
|
|
72
72
|
4. **Creates the draft project** — `POST /api/projects` provisions a managed
|
|
73
73
|
Forgejo repo (one per user; no SSH key) and stores the project metadata (id,
|
|
@@ -285,7 +285,25 @@ GENEX_AUTH_URL=https://staging.genex.dev genex init
|
|
|
285
285
|
The token env file path is configurable the same way: `--env <path>` per
|
|
286
286
|
command, or `GENEX_ENV_FILE=<path>` for a whole session (an explicit `--env`
|
|
287
287
|
still wins). Useful for running as a second account — a curator or a test
|
|
288
|
-
runner — without touching the default
|
|
288
|
+
runner — without touching the default profile. Each profile stores credentials
|
|
289
|
+
under `<env-path>.origins/<origin-hash>.json`, and pending sign-ins under
|
|
290
|
+
`<env-path>.auth/<origin-hash>.json`. Dev, production and custom servers coexist.
|
|
291
|
+
|
|
292
|
+
API destinations must be HTTPS origins without paths, queries, fragments or
|
|
293
|
+
embedded credentials. HTTP is supported for exact loopback hosts (`localhost`,
|
|
294
|
+
`127.0.0.1`, `[::1]`). Select custom servers explicitly with `--api-url` (and
|
|
295
|
+
`--auth-url` for their dashboard). Project metadata can select a saved credential
|
|
296
|
+
for its server, but cannot redirect a credential or initiate a new custom login.
|
|
297
|
+
API redirects are refused, including device-code polls.
|
|
298
|
+
|
|
299
|
+
Legacy `GENEX_TOKEN` files remain readable only at the configured CLI origin
|
|
300
|
+
(the installed channel's default or `GENEX_API_URL`), never a metadata-selected
|
|
301
|
+
origin. The first read records that binding permanently; switching channels
|
|
302
|
+
cannot move it. The legacy file remains intact, including hosted-session token
|
|
303
|
+
rotation. For an old custom-server file, set `GENEX_API_URL` to its known issuer
|
|
304
|
+
before the first read, or run `genex auth --api-url <origin>` to sign in again.
|
|
305
|
+
Older CLI versions do not read the new profiles; use the updated CLI for both
|
|
306
|
+
stands.
|
|
289
307
|
|
|
290
308
|
To control which browser is launched, set `GENEX_BROWSER` (or the conventional
|
|
291
309
|
`BROWSER`) to an opener command. It's parsed like a shell command, so arguments
|
|
@@ -346,7 +364,7 @@ The CLI uses a loopback-redirect flow (the same pattern as `gh auth login` and
|
|
|
346
364
|
> the exact `state` it received. A missing or mismatched `state` is rejected
|
|
347
365
|
> (CSRF protection).
|
|
348
366
|
|
|
349
|
-
3. The CLI verifies `state`,
|
|
367
|
+
3. The CLI verifies `state`, saves the credential in the selected origin/profile, and
|
|
350
368
|
shows a success page in the browser.
|
|
351
369
|
|
|
352
370
|
If the browser can't be opened, the URL is printed and — when run in an
|
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
isRenderMode,
|
|
8
8
|
sceneSummary,
|
|
9
9
|
sheetOf
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-MRV3Q3WU.js";
|
|
11
11
|
import {
|
|
12
12
|
getCliVersion
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-AQW7HUPV.js";
|
|
14
14
|
|
|
15
15
|
// src/commands/blender-mcp.ts
|
|
16
16
|
import fs from "fs/promises";
|
|
@@ -1,9 +1,44 @@
|
|
|
1
|
+
// src/lib/credential-origin.ts
|
|
2
|
+
import { createHash } from "crypto";
|
|
3
|
+
function normalizeApiOrigin(raw) {
|
|
4
|
+
const url = new URL(raw);
|
|
5
|
+
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
6
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback) || url.username || url.password || url.search || url.hash || url.pathname.replace(/\/+$/, "") !== "") {
|
|
7
|
+
throw new Error("Genex API URL must be an HTTPS origin (HTTP is allowed on loopback only), without credentials, a path, query or fragment.");
|
|
8
|
+
}
|
|
9
|
+
return url.origin;
|
|
10
|
+
}
|
|
11
|
+
function originKey(apiUrl) {
|
|
12
|
+
return createHash("sha256").update(normalizeApiOrigin(apiUrl)).digest("hex");
|
|
13
|
+
}
|
|
14
|
+
var destinations = /* @__PURE__ */ new Map();
|
|
15
|
+
function bindToken(token, apiUrl) {
|
|
16
|
+
const key = createHash("sha256").update(token).digest("hex");
|
|
17
|
+
const origins = destinations.get(key) ?? /* @__PURE__ */ new Set();
|
|
18
|
+
origins.add(normalizeApiOrigin(apiUrl));
|
|
19
|
+
destinations.set(key, origins);
|
|
20
|
+
return token;
|
|
21
|
+
}
|
|
22
|
+
function assertTokenDestination(token, requestUrl) {
|
|
23
|
+
const key = createHash("sha256").update(token).digest("hex");
|
|
24
|
+
const origins = destinations.get(key);
|
|
25
|
+
const url = new URL(requestUrl);
|
|
26
|
+
if (url.username || url.password || origins && !origins.has(url.origin)) {
|
|
27
|
+
throw new Error("Refusing to send a Genex credential to a different API origin. Run genex auth for the intended server.");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function assertAuthorizationOrigin(apiUrl, selectedApiUrl) {
|
|
31
|
+
if (normalizeApiOrigin(apiUrl) !== normalizeApiOrigin(selectedApiUrl)) {
|
|
32
|
+
throw new Error(`No saved sign-in for ${normalizeApiOrigin(apiUrl)}. Select that server explicitly with genex auth --api-url before retrying.`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
1
36
|
// src/config.ts
|
|
2
37
|
import fs from "fs";
|
|
3
38
|
import os from "os";
|
|
4
39
|
import path from "path";
|
|
5
40
|
import { fileURLToPath } from "url";
|
|
6
|
-
var RAW_CHANNEL = "
|
|
41
|
+
var RAW_CHANNEL = "dev";
|
|
7
42
|
var CLI_CHANNEL = RAW_CHANNEL === "dev" ? "dev" : "latest";
|
|
8
43
|
var STANDS = {
|
|
9
44
|
prod: { api: "https://api.genex.games", dashboard: "https://genex.games" },
|
|
@@ -20,7 +55,6 @@ function getAnimsBase(override) {
|
|
|
20
55
|
function getAnimsCacheDir() {
|
|
21
56
|
return path.join(getGenexDir(), "cache", "anims");
|
|
22
57
|
}
|
|
23
|
-
var ENV_TOKEN_KEY = "GENEX_TOKEN";
|
|
24
58
|
var AUTH_URL_ENV = "GENEX_AUTH_URL";
|
|
25
59
|
var API_URL_ENV = "GENEX_API_URL";
|
|
26
60
|
var ENV_FILE_ENV = "GENEX_ENV_FILE";
|
|
@@ -30,7 +64,7 @@ function getAuthUrl(override) {
|
|
|
30
64
|
}
|
|
31
65
|
function getApiUrl(override) {
|
|
32
66
|
const raw = override || process.env[API_URL_ENV] || DEFAULT_API_URL;
|
|
33
|
-
return raw
|
|
67
|
+
return normalizeApiOrigin(raw);
|
|
34
68
|
}
|
|
35
69
|
function getGenexDir() {
|
|
36
70
|
return path.join(os.homedir(), ".genex");
|
|
@@ -107,13 +141,17 @@ var c = {
|
|
|
107
141
|
};
|
|
108
142
|
|
|
109
143
|
export {
|
|
144
|
+
normalizeApiOrigin,
|
|
145
|
+
originKey,
|
|
146
|
+
bindToken,
|
|
147
|
+
assertTokenDestination,
|
|
148
|
+
assertAuthorizationOrigin,
|
|
110
149
|
CLI_CHANNEL,
|
|
111
150
|
STANDS,
|
|
112
151
|
DEFAULT_AUTH_URL,
|
|
113
152
|
DEFAULT_API_URL,
|
|
114
153
|
getAnimsBase,
|
|
115
154
|
getAnimsCacheDir,
|
|
116
|
-
ENV_TOKEN_KEY,
|
|
117
155
|
ENV_FILE_ENV,
|
|
118
156
|
getAuthUrl,
|
|
119
157
|
getApiUrl,
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CLI_CHANNEL,
|
|
3
3
|
ENV_FILE_ENV,
|
|
4
|
-
|
|
4
|
+
assertTokenDestination,
|
|
5
|
+
bindToken,
|
|
5
6
|
c,
|
|
6
7
|
getApiUrl,
|
|
7
8
|
getAuthUrl,
|
|
8
9
|
getCliVersion,
|
|
9
|
-
getGenexEnvPath
|
|
10
|
-
|
|
10
|
+
getGenexEnvPath,
|
|
11
|
+
normalizeApiOrigin,
|
|
12
|
+
originKey
|
|
13
|
+
} from "./chunk-AQW7HUPV.js";
|
|
11
14
|
|
|
12
15
|
// src/lib/terms.ts
|
|
13
16
|
import readline from "readline";
|
|
@@ -396,7 +399,9 @@ async function apiFetch(url, init = {}, opts = {}) {
|
|
|
396
399
|
if (assetsMode && !headers.has(ASSETS_MODE_HEADER)) {
|
|
397
400
|
headers.set(ASSETS_MODE_HEADER, assetsMode);
|
|
398
401
|
}
|
|
399
|
-
const
|
|
402
|
+
const bearer = headers.get("authorization")?.replace(/^Bearer\s+/i, "");
|
|
403
|
+
if (bearer) assertTokenDestination(bearer, url);
|
|
404
|
+
const res = await fetch(url, { ...init, headers, redirect: "error" });
|
|
400
405
|
if (res.status === 403 && !opts.noTermsWait && await isTermsRefusal(res)) {
|
|
401
406
|
const log = stderrLogger;
|
|
402
407
|
reportTermsRefusal(log, await termsRefusalUrl(res));
|
|
@@ -490,46 +495,22 @@ async function fetchSignedInEmail(apiUrl, token) {
|
|
|
490
495
|
}
|
|
491
496
|
|
|
492
497
|
// src/lib/blender-client.ts
|
|
493
|
-
import
|
|
494
|
-
import
|
|
498
|
+
import fs5 from "fs";
|
|
499
|
+
import path5 from "path";
|
|
495
500
|
|
|
496
501
|
// src/lib/store.ts
|
|
502
|
+
import fs4 from "fs/promises";
|
|
503
|
+
import path4 from "path";
|
|
504
|
+
|
|
505
|
+
// src/lib/secret-file.ts
|
|
497
506
|
import fs3 from "fs/promises";
|
|
498
507
|
import path3 from "path";
|
|
508
|
+
import { randomUUID } from "crypto";
|
|
499
509
|
|
|
500
510
|
// src/lib/env.ts
|
|
501
511
|
import fs2 from "fs/promises";
|
|
502
512
|
import path2 from "path";
|
|
503
513
|
import { spawn as spawn2 } from "child_process";
|
|
504
|
-
async function writeEnvVar(envPath, key, value) {
|
|
505
|
-
let content = "";
|
|
506
|
-
let existed = false;
|
|
507
|
-
try {
|
|
508
|
-
content = await fs2.readFile(envPath, "utf8");
|
|
509
|
-
existed = true;
|
|
510
|
-
} catch {
|
|
511
|
-
}
|
|
512
|
-
const assignment = `${key}=${formatValue(value)}`;
|
|
513
|
-
const keyPattern = new RegExp(
|
|
514
|
-
`^(\\s*export\\s+)?${escapeRegExp(key)}=.*$`,
|
|
515
|
-
"gm"
|
|
516
|
-
);
|
|
517
|
-
let next;
|
|
518
|
-
let mode;
|
|
519
|
-
if (keyPattern.test(content)) {
|
|
520
|
-
next = content.replace(keyPattern, assignment);
|
|
521
|
-
mode = "updated";
|
|
522
|
-
} else {
|
|
523
|
-
let prefix = content;
|
|
524
|
-
if (prefix.length > 0 && !prefix.endsWith("\n")) prefix += "\n";
|
|
525
|
-
next = prefix + assignment + "\n";
|
|
526
|
-
mode = existed ? "appended" : "created";
|
|
527
|
-
}
|
|
528
|
-
await fs2.mkdir(path2.dirname(envPath), { recursive: true });
|
|
529
|
-
await fs2.writeFile(envPath, next, { mode: 384 });
|
|
530
|
-
await restrictFilePermissions(envPath);
|
|
531
|
-
return { mode, path: envPath };
|
|
532
|
-
}
|
|
533
514
|
async function restrictFilePermissions(filePath) {
|
|
534
515
|
if (process.platform !== "win32") {
|
|
535
516
|
await fs2.chmod(filePath, 384).catch(() => {
|
|
@@ -552,26 +533,31 @@ async function restrictFilePermissions(filePath) {
|
|
|
552
533
|
}
|
|
553
534
|
});
|
|
554
535
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
536
|
+
|
|
537
|
+
// src/lib/secret-file.ts
|
|
538
|
+
async function writeSecretFile(file, content, exclusive = false) {
|
|
539
|
+
await fs3.mkdir(path3.dirname(file), { recursive: true, mode: 448 });
|
|
540
|
+
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
541
|
+
try {
|
|
542
|
+
await fs3.writeFile(temporary, content, { mode: 384, flag: "wx" });
|
|
543
|
+
await restrictFilePermissions(temporary);
|
|
544
|
+
if (exclusive) await fs3.link(temporary, file);
|
|
545
|
+
else await fs3.rename(temporary, file);
|
|
546
|
+
} finally {
|
|
547
|
+
await fs3.rm(temporary, { force: true });
|
|
558
548
|
}
|
|
559
|
-
return value;
|
|
560
|
-
}
|
|
561
|
-
function escapeRegExp(s) {
|
|
562
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
563
549
|
}
|
|
564
550
|
|
|
565
551
|
// src/lib/store.ts
|
|
566
552
|
function getProjectMetadataPath(cwd = process.cwd()) {
|
|
567
|
-
return
|
|
553
|
+
return path4.join(cwd, ".genex", "project.json");
|
|
568
554
|
}
|
|
569
555
|
function getWorkspacePath(cwd = process.cwd()) {
|
|
570
|
-
return
|
|
556
|
+
return path4.join(cwd, ".genex", "workspace.json");
|
|
571
557
|
}
|
|
572
558
|
async function readWorkspace(cwd = process.cwd()) {
|
|
573
559
|
try {
|
|
574
|
-
const raw = await
|
|
560
|
+
const raw = await fs4.readFile(getWorkspacePath(cwd), "utf8");
|
|
575
561
|
return JSON.parse(raw);
|
|
576
562
|
} catch {
|
|
577
563
|
return null;
|
|
@@ -579,39 +565,72 @@ async function readWorkspace(cwd = process.cwd()) {
|
|
|
579
565
|
}
|
|
580
566
|
async function writeWorkspace(meta, cwd = process.cwd()) {
|
|
581
567
|
const file = getWorkspacePath(cwd);
|
|
582
|
-
await
|
|
583
|
-
await
|
|
584
|
-
await
|
|
568
|
+
await fs4.mkdir(path4.dirname(file), { recursive: true });
|
|
569
|
+
await fs4.writeFile(file, JSON.stringify(meta, null, 2) + "\n", { mode: 384 });
|
|
570
|
+
await fs4.chmod(file, 384).catch(() => {
|
|
585
571
|
});
|
|
586
572
|
return { path: file };
|
|
587
573
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
574
|
+
function credentialPath(envPath, apiUrl = getApiUrl()) {
|
|
575
|
+
return path4.join(`${getGenexEnvPath(envPath)}.origins`, `${originKey(apiUrl)}.json`);
|
|
576
|
+
}
|
|
577
|
+
async function writeUserToken(token, envPath, apiUrl = getApiUrl()) {
|
|
578
|
+
const origin = normalizeApiOrigin(apiUrl);
|
|
579
|
+
const file = credentialPath(envPath, origin);
|
|
580
|
+
await writeSecretFile(file, JSON.stringify({ apiUrl: origin, token }) + "\n");
|
|
581
|
+
bindToken(token, origin);
|
|
582
|
+
return { path: file };
|
|
591
583
|
}
|
|
592
|
-
async function rotateRejectedEnv(envPath) {
|
|
593
|
-
const file =
|
|
594
|
-
const
|
|
595
|
-
const aside = `${file}.rejected-${stamp}`;
|
|
584
|
+
async function rotateRejectedEnv(envPath, apiUrl = getApiUrl()) {
|
|
585
|
+
const file = credentialPath(envPath, apiUrl);
|
|
586
|
+
const aside = `${file}.rejected-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
596
587
|
try {
|
|
597
|
-
await
|
|
588
|
+
await fs4.rename(file, aside);
|
|
589
|
+
await writeSecretFile(file, JSON.stringify({ apiUrl: normalizeApiOrigin(apiUrl), token: null }));
|
|
598
590
|
return aside;
|
|
599
|
-
} catch {
|
|
591
|
+
} catch (error) {
|
|
592
|
+
if (error.code !== "ENOENT") throw error;
|
|
593
|
+
await writeSecretFile(file, JSON.stringify({ apiUrl: normalizeApiOrigin(apiUrl), token: null }));
|
|
600
594
|
return null;
|
|
601
595
|
}
|
|
602
596
|
}
|
|
603
|
-
async function readUserToken(envPath) {
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
597
|
+
async function readUserToken(envPath, apiUrl = getApiUrl()) {
|
|
598
|
+
const origin = normalizeApiOrigin(apiUrl);
|
|
599
|
+
const file = credentialPath(envPath, origin);
|
|
600
|
+
try {
|
|
601
|
+
const record = JSON.parse(await fs4.readFile(file, "utf8"));
|
|
602
|
+
return record.apiUrl === origin && typeof record.token === "string" && record.token ? bindToken(record.token, origin) : null;
|
|
603
|
+
} catch (error) {
|
|
604
|
+
if (error.code !== "ENOENT") return null;
|
|
605
|
+
}
|
|
606
|
+
let legacyFile = getGenexEnvPath(envPath);
|
|
607
|
+
let token = await readTokenFromFile(legacyFile);
|
|
608
|
+
if (!token && !envPath && !process.env[ENV_FILE_ENV]) {
|
|
609
|
+
legacyFile = path4.join(process.cwd(), ".env");
|
|
610
|
+
token = await readTokenFromFile(legacyFile);
|
|
611
|
+
}
|
|
612
|
+
if (!token) return null;
|
|
613
|
+
const claim = `${legacyFile}.origins/legacy-origin.json`;
|
|
614
|
+
let claimed;
|
|
615
|
+
try {
|
|
616
|
+
claimed = JSON.parse(await fs4.readFile(claim, "utf8"));
|
|
617
|
+
} catch (error) {
|
|
618
|
+
if (error.code !== "ENOENT") return null;
|
|
619
|
+
if (origin !== getApiUrl()) return null;
|
|
620
|
+
try {
|
|
621
|
+
await writeSecretFile(claim, JSON.stringify({ apiUrl: origin }), true);
|
|
622
|
+
} catch (error2) {
|
|
623
|
+
if (error2.code !== "EEXIST") throw error2;
|
|
624
|
+
}
|
|
625
|
+
claimed = JSON.parse(await fs4.readFile(claim, "utf8"));
|
|
608
626
|
}
|
|
609
|
-
return null;
|
|
627
|
+
if (claimed.apiUrl !== origin) return null;
|
|
628
|
+
return bindToken(token, origin);
|
|
610
629
|
}
|
|
611
630
|
async function readTokenFromFile(file) {
|
|
612
631
|
let content;
|
|
613
632
|
try {
|
|
614
|
-
content = await
|
|
633
|
+
content = await fs4.readFile(file, "utf8");
|
|
615
634
|
} catch {
|
|
616
635
|
return null;
|
|
617
636
|
}
|
|
@@ -627,7 +646,7 @@ function stripQuotes(v) {
|
|
|
627
646
|
}
|
|
628
647
|
async function readProject(cwd = process.cwd()) {
|
|
629
648
|
try {
|
|
630
|
-
const raw = await
|
|
649
|
+
const raw = await fs4.readFile(getProjectMetadataPath(cwd), "utf8");
|
|
631
650
|
return JSON.parse(raw);
|
|
632
651
|
} catch {
|
|
633
652
|
return null;
|
|
@@ -636,11 +655,11 @@ async function readProject(cwd = process.cwd()) {
|
|
|
636
655
|
var SCRATCH_DIR = ".genex/scratch";
|
|
637
656
|
async function writeProject(meta, cwd = process.cwd()) {
|
|
638
657
|
const file = getProjectMetadataPath(cwd);
|
|
639
|
-
await
|
|
640
|
-
await
|
|
658
|
+
await fs4.mkdir(path4.dirname(file), { recursive: true });
|
|
659
|
+
await fs4.mkdir(path4.join(cwd, SCRATCH_DIR), { recursive: true }).catch(() => {
|
|
641
660
|
});
|
|
642
|
-
await
|
|
643
|
-
await
|
|
661
|
+
await fs4.writeFile(file, JSON.stringify(meta, null, 2) + "\n", { mode: 384 });
|
|
662
|
+
await fs4.chmod(file, 384).catch(() => {
|
|
644
663
|
});
|
|
645
664
|
return { path: file };
|
|
646
665
|
}
|
|
@@ -678,10 +697,10 @@ function sheetOf(r) {
|
|
|
678
697
|
function sheetExt(mime) {
|
|
679
698
|
return mime === "image/webp" ? "webp" : "png";
|
|
680
699
|
}
|
|
681
|
-
var SEAT_FILE =
|
|
700
|
+
var SEAT_FILE = path5.join(".genex", "blender-seat.json");
|
|
682
701
|
function readSeatGrant(cwd = process.cwd()) {
|
|
683
702
|
try {
|
|
684
|
-
const raw = JSON.parse(
|
|
703
|
+
const raw = JSON.parse(fs5.readFileSync(path5.join(cwd, SEAT_FILE), "utf8"));
|
|
685
704
|
if (typeof raw.url !== "string" || typeof raw.token !== "string") return null;
|
|
686
705
|
return {
|
|
687
706
|
url: raw.url,
|
|
@@ -694,13 +713,13 @@ function readSeatGrant(cwd = process.cwd()) {
|
|
|
694
713
|
}
|
|
695
714
|
}
|
|
696
715
|
function writeSeatGrant(grant, cwd = process.cwd()) {
|
|
697
|
-
const file =
|
|
698
|
-
|
|
699
|
-
|
|
716
|
+
const file = path5.join(cwd, SEAT_FILE);
|
|
717
|
+
fs5.mkdirSync(path5.dirname(file), { recursive: true });
|
|
718
|
+
fs5.writeFileSync(file, JSON.stringify(grant, null, 2) + "\n", { mode: 384 });
|
|
700
719
|
}
|
|
701
720
|
function deleteSeatGrant(cwd = process.cwd()) {
|
|
702
721
|
try {
|
|
703
|
-
|
|
722
|
+
fs5.unlinkSync(path5.join(cwd, SEAT_FILE));
|
|
704
723
|
} catch {
|
|
705
724
|
}
|
|
706
725
|
}
|
|
@@ -710,7 +729,7 @@ async function touchCliSeat() {
|
|
|
710
729
|
if (Date.now() - lastTouchAt < TOUCH_THROTTLE_MS) return;
|
|
711
730
|
lastTouchAt = Date.now();
|
|
712
731
|
try {
|
|
713
|
-
const token = await readUserToken();
|
|
732
|
+
const token = await readUserToken(void 0, getApiUrl());
|
|
714
733
|
if (!token) return;
|
|
715
734
|
await apiFetch(`${getApiUrl()}/api/blender/seat/touch`, {
|
|
716
735
|
method: "POST",
|
|
@@ -782,7 +801,7 @@ ${json.trace.slice(-4e3)}` : "";
|
|
|
782
801
|
}
|
|
783
802
|
if (seat?.kind === "cli") void touchCliSeat();
|
|
784
803
|
if (seat?.kind === "hosted" && seat.seatId && route === "/health") {
|
|
785
|
-
const token = await readUserToken();
|
|
804
|
+
const token = await readUserToken(void 0, getApiUrl());
|
|
786
805
|
if (!token) throw new Error("Cannot acknowledge Blender readiness: not signed in");
|
|
787
806
|
const claim = await apiFetch(`${getApiUrl()}/api/blender/seat/touch`, {
|
|
788
807
|
method: "POST",
|
|
@@ -816,7 +835,7 @@ async function acquireSeat(opts) {
|
|
|
816
835
|
const held = readSeatGrant(opts.cwd);
|
|
817
836
|
if (held) return { kind: "granted", grant: held };
|
|
818
837
|
if (!hostedBlenderLane()) return { kind: "off" };
|
|
819
|
-
const token = opts.token !== void 0 ? opts.token : await readUserToken();
|
|
838
|
+
const token = opts.token !== void 0 ? opts.token : await readUserToken(void 0, getApiUrl());
|
|
820
839
|
if (!token) return { kind: "refused", reason: "not signed in" };
|
|
821
840
|
const doFetch = opts.fetchImpl ?? apiFetch;
|
|
822
841
|
const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
@@ -857,7 +876,7 @@ async function acquireSeat(opts) {
|
|
|
857
876
|
}
|
|
858
877
|
}
|
|
859
878
|
async function acquireCliSeat(opts) {
|
|
860
|
-
const token = opts.token !== void 0 ? opts.token : await readUserToken();
|
|
879
|
+
const token = opts.token !== void 0 ? opts.token : await readUserToken(void 0, getApiUrl());
|
|
861
880
|
if (!token) return { kind: "refused", reason: "not signed in" };
|
|
862
881
|
const doFetch = opts.fetchImpl ?? apiFetch;
|
|
863
882
|
const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
@@ -909,7 +928,7 @@ async function acquireCliSeat(opts) {
|
|
|
909
928
|
}
|
|
910
929
|
}
|
|
911
930
|
async function releaseCliSeat(opts) {
|
|
912
|
-
const token = opts.token !== void 0 ? opts.token : await readUserToken();
|
|
931
|
+
const token = opts.token !== void 0 ? opts.token : await readUserToken(void 0, getApiUrl());
|
|
913
932
|
if (!token) return { ok: false, reason: "not signed in" };
|
|
914
933
|
const doFetch = opts.fetchImpl ?? apiFetch;
|
|
915
934
|
const res = await doFetch(`${getApiUrl()}/api/blender/seat`, {
|
|
@@ -944,7 +963,7 @@ export {
|
|
|
944
963
|
printedStructuredError,
|
|
945
964
|
apiFetch,
|
|
946
965
|
fetchSignedInEmail,
|
|
947
|
-
|
|
966
|
+
writeSecretFile,
|
|
948
967
|
readWorkspace,
|
|
949
968
|
writeWorkspace,
|
|
950
969
|
writeUserToken,
|