@genex-ai/cli-demo 1.11.3-dev.547 → 1.13.0-dev.550
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{blender-mcp-OXFN3RVI.js → blender-mcp-LLN4FPA4.js} +34 -6
- package/dist/blender-serve-BF4FZ55Z.js +244 -0
- package/dist/{chunk-ZHIP7LCA.js → chunk-HYCSNWYX.js} +15 -67
- package/dist/chunk-WXHTNSFB.js +441 -0
- package/dist/index.js +460 -679
- package/package.json +1 -1
- package/templates/blender-service/demo/castle.py +117 -0
- package/templates/blender-service/gpu_witness.py +245 -0
- package/templates/blender-service/ops.py +225 -0
- package/templates/blender-service/pool.py +910 -0
- package/templates/blender-service/server.py +603 -0
- package/templates/blender-service/supervisor.py +221 -0
- package/templates/blender-service/views.py +281 -0
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BLENDER_SETUP_HINT,
|
|
3
|
+
SHEET_FORMATS,
|
|
4
|
+
acquireSeat,
|
|
3
5
|
blenderCall,
|
|
4
6
|
blenderEndpoint,
|
|
5
|
-
getCliVersion,
|
|
6
7
|
isRenderMode,
|
|
7
|
-
sceneSummary
|
|
8
|
-
|
|
8
|
+
sceneSummary,
|
|
9
|
+
sheetOf
|
|
10
|
+
} from "./chunk-WXHTNSFB.js";
|
|
11
|
+
import {
|
|
12
|
+
getCliVersion
|
|
13
|
+
} from "./chunk-HYCSNWYX.js";
|
|
9
14
|
|
|
10
15
|
// src/commands/blender-mcp.ts
|
|
11
16
|
import fs from "fs/promises";
|
|
@@ -17,7 +22,8 @@ var MODE_SCHEMA = {
|
|
|
17
22
|
description: "solid = material colours; wireframe = topology; normals = matcap that exposes inverted faces; lit = EEVEE, real lighting and shadows \u2014 GPU tier only, and it errors on the CPU tier rather than quietly falling back."
|
|
18
23
|
};
|
|
19
24
|
function sheet(reply2) {
|
|
20
|
-
|
|
25
|
+
const s = sheetOf(reply2);
|
|
26
|
+
return s ? [{ type: "image", data: s.b64, mimeType: s.mime }] : [];
|
|
21
27
|
}
|
|
22
28
|
var TOOLS = [
|
|
23
29
|
{
|
|
@@ -41,6 +47,7 @@ var TOOLS = [
|
|
|
41
47
|
// Omitted rather than defaulted: the server picks per tier (`lit` on
|
|
42
48
|
// GPU, `solid` on CPU) and echoes what it used.
|
|
43
49
|
...isRenderMode(a.mode) ? { mode: a.mode } : {},
|
|
50
|
+
sheet: { formats: SHEET_FORMATS },
|
|
44
51
|
verify: a.verify !== false
|
|
45
52
|
});
|
|
46
53
|
const lines = [];
|
|
@@ -55,6 +62,9 @@ ${r.error.trimEnd()}`);
|
|
|
55
62
|
`NOTE: the service restarted (${r.sceneRestored.reason ?? "unknown"}) and the scene was restored from a checkpoint. Your most recent step may be missing \u2014 check the scene before continuing.`
|
|
56
63
|
);
|
|
57
64
|
}
|
|
65
|
+
if (r.contactSheetUnchanged) {
|
|
66
|
+
lines.push("The scene is unchanged since the last render, so no new image was produced.");
|
|
67
|
+
}
|
|
58
68
|
lines.push(sceneSummary(r.scene));
|
|
59
69
|
return {
|
|
60
70
|
content: [{ type: "text", text: lines.filter(Boolean).join("\n") }, ...sheet(r)],
|
|
@@ -69,7 +79,10 @@ ${r.error.trimEnd()}`);
|
|
|
69
79
|
description: "Re-render the current scene without changing it. Use `normals` to check for inverted faces and `wireframe` to check topology.",
|
|
70
80
|
inputSchema: { type: "object", properties: { mode: MODE_SCHEMA } },
|
|
71
81
|
run: async (a, base) => {
|
|
72
|
-
const r = await blenderCall(base, "/render",
|
|
82
|
+
const r = await blenderCall(base, "/render", {
|
|
83
|
+
...isRenderMode(a.mode) ? { mode: a.mode } : {},
|
|
84
|
+
sheet: { formats: SHEET_FORMATS }
|
|
85
|
+
});
|
|
73
86
|
return {
|
|
74
87
|
content: [
|
|
75
88
|
{ type: "text", text: `rendered ${String(r.mode ?? "")} in ${r.ms ?? "?"}ms`.replace(" ", " ") },
|
|
@@ -163,7 +176,7 @@ async function handle(msg) {
|
|
|
163
176
|
const args = msg.params?.arguments ?? {};
|
|
164
177
|
const tool = TOOLS.find((t) => t.name === name);
|
|
165
178
|
if (!tool) return fail(id, -32602, `Unknown tool: ${name}`);
|
|
166
|
-
const base =
|
|
179
|
+
const base = await mcpEndpoint();
|
|
167
180
|
if (!base) {
|
|
168
181
|
return reply(id, { content: [{ type: "text", text: BLENDER_SETUP_HINT }], isError: true });
|
|
169
182
|
}
|
|
@@ -178,6 +191,21 @@ async function handle(msg) {
|
|
|
178
191
|
}
|
|
179
192
|
fail(id, -32601, `Method not found: ${method}`);
|
|
180
193
|
}
|
|
194
|
+
var stderrLog = {
|
|
195
|
+
step: (m) => process.stderr.write(`[genex-blender-mcp] ${m}
|
|
196
|
+
`),
|
|
197
|
+
warn: (m) => process.stderr.write(`[genex-blender-mcp] ${m}
|
|
198
|
+
`),
|
|
199
|
+
error: (m) => process.stderr.write(`[genex-blender-mcp] ${m}
|
|
200
|
+
`)
|
|
201
|
+
};
|
|
202
|
+
async function mcpEndpoint() {
|
|
203
|
+
const seat = await acquireSeat({ log: stderrLog });
|
|
204
|
+
if (seat.kind === "granted") return seat.grant.url;
|
|
205
|
+
if (seat.kind === "refused") stderrLog.error(`no Blender seat: ${seat.reason}`);
|
|
206
|
+
if (seat.kind === "warming") stderrLog.warn("the Blender seat is still warming \u2014 the next call will try again");
|
|
207
|
+
return blenderEndpoint();
|
|
208
|
+
}
|
|
181
209
|
async function runBlenderMcp() {
|
|
182
210
|
process.stderr.write(`[genex-blender-mcp] ${TOOLS.length} tools, cli ${getCliVersion()}
|
|
183
211
|
`);
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import {
|
|
2
|
+
c,
|
|
3
|
+
getTemplatesDir
|
|
4
|
+
} from "./chunk-HYCSNWYX.js";
|
|
5
|
+
|
|
6
|
+
// src/lib/blender-serve.ts
|
|
7
|
+
import { spawn } from "child_process";
|
|
8
|
+
import fs2 from "fs";
|
|
9
|
+
import os2 from "os";
|
|
10
|
+
import path2 from "path";
|
|
11
|
+
import "url";
|
|
12
|
+
|
|
13
|
+
// src/lib/blender-local.ts
|
|
14
|
+
import { spawnSync } from "child_process";
|
|
15
|
+
import fs from "fs";
|
|
16
|
+
import os from "os";
|
|
17
|
+
import path from "path";
|
|
18
|
+
var CANDIDATES = {
|
|
19
|
+
darwin: [
|
|
20
|
+
"/Applications/Blender.app/Contents/MacOS/Blender",
|
|
21
|
+
path.join(os.homedir(), "Applications/Blender.app/Contents/MacOS/Blender")
|
|
22
|
+
],
|
|
23
|
+
win32: [
|
|
24
|
+
"C:\\Program Files\\Blender Foundation\\Blender 5.0\\blender.exe",
|
|
25
|
+
"C:\\Program Files\\Blender Foundation\\Blender 4.5\\blender.exe",
|
|
26
|
+
"C:\\Program Files\\Blender Foundation\\Blender 4.2\\blender.exe"
|
|
27
|
+
],
|
|
28
|
+
linux: ["/usr/bin/blender", "/usr/local/bin/blender", "/snap/bin/blender"]
|
|
29
|
+
};
|
|
30
|
+
function probe(bin) {
|
|
31
|
+
if (!fs.existsSync(bin)) return null;
|
|
32
|
+
const r = spawnSync(bin, ["--version"], { encoding: "utf8", timeout: 2e4 });
|
|
33
|
+
const m = /Blender\s+(\d+)\.(\d+)(?:\.(\d+))?/.exec(r.stdout ?? "");
|
|
34
|
+
if (!m) return null;
|
|
35
|
+
const major = Number(m[1]);
|
|
36
|
+
const minor = Number(m[2]);
|
|
37
|
+
return { version: `${major}.${minor}${m[3] ? `.${m[3]}` : ""}`, major, minor };
|
|
38
|
+
}
|
|
39
|
+
function findBlender() {
|
|
40
|
+
const pinned = process.env.GENEX_BLENDER_BIN?.trim();
|
|
41
|
+
if (pinned) {
|
|
42
|
+
const v = probe(pinned);
|
|
43
|
+
return v ? { bin: pinned, ...v, source: "env" } : null;
|
|
44
|
+
}
|
|
45
|
+
const onPath = spawnSync(process.platform === "win32" ? "where" : "which", ["blender"], {
|
|
46
|
+
encoding: "utf8",
|
|
47
|
+
timeout: 1e4
|
|
48
|
+
});
|
|
49
|
+
const first = (onPath.stdout ?? "").split(/\r?\n/).map((s) => s.trim()).find(Boolean);
|
|
50
|
+
if (first) {
|
|
51
|
+
const v = probe(first);
|
|
52
|
+
if (v) return { bin: first, ...v, source: "path" };
|
|
53
|
+
}
|
|
54
|
+
for (const bin of CANDIDATES[process.platform] ?? []) {
|
|
55
|
+
const v = probe(bin);
|
|
56
|
+
if (v) return { bin, ...v, source: "install" };
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
function vetBlender(info) {
|
|
61
|
+
const warnings = [];
|
|
62
|
+
if (info.major < 4 || info.major === 4 && info.minor < 2) {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
warnings,
|
|
66
|
+
reason: `Blender ${info.version} is too old \u2014 this needs 4.2 or newer (EEVEE Next). Install a current Blender, or point GENEX_BLENDER_BIN at one.`
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (info.major > 5) {
|
|
70
|
+
warnings.push(
|
|
71
|
+
`Blender ${info.version} is newer than anything this has been tested against (4.2\u20135.x). If renders look wrong, that is the first thing to suspect.`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
return { ok: true, warnings };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/lib/blender-serve.ts
|
|
78
|
+
function serviceDir() {
|
|
79
|
+
return path2.join(getTemplatesDir(), "blender-service");
|
|
80
|
+
}
|
|
81
|
+
var POLL_MS = 1e3;
|
|
82
|
+
var TERM_GRACE_MS = 5e3;
|
|
83
|
+
async function serveLocalBlender(opts) {
|
|
84
|
+
const { log } = opts;
|
|
85
|
+
const info = findBlender();
|
|
86
|
+
if (!info) {
|
|
87
|
+
log.error("No Blender found on this machine.");
|
|
88
|
+
log.plain(
|
|
89
|
+
[
|
|
90
|
+
" Install Blender 4.2 or newer, then re-run. Or point at one you already have:",
|
|
91
|
+
` ${c.cyan("export GENEX_BLENDER_BIN=/path/to/blender")}`,
|
|
92
|
+
"",
|
|
93
|
+
` ${c.dim("macOS:")} https://www.blender.org/download/ (or: brew install --cask blender)`,
|
|
94
|
+
` ${c.dim("Linux:")} your distro's blender package, or the tarball from blender.org`,
|
|
95
|
+
` ${c.dim("Windows:")} https://www.blender.org/download/`
|
|
96
|
+
].join("\n")
|
|
97
|
+
);
|
|
98
|
+
return 1;
|
|
99
|
+
}
|
|
100
|
+
const verdict = vetBlender(info);
|
|
101
|
+
if (!verdict.ok) {
|
|
102
|
+
log.error(verdict.reason ?? "This Blender cannot be used.");
|
|
103
|
+
return 1;
|
|
104
|
+
}
|
|
105
|
+
for (const w of verdict.warnings) log.warn(w);
|
|
106
|
+
const server = path2.join(serviceDir(), "server.py");
|
|
107
|
+
if (!fs2.existsSync(server)) {
|
|
108
|
+
log.error(`Service files are missing: ${server}`);
|
|
109
|
+
log.plain(` ${c.dim("From a source checkout, run:")} node apps/cli/scripts/sync-blender-service.mjs`);
|
|
110
|
+
return 1;
|
|
111
|
+
}
|
|
112
|
+
const stateDir = opts.stateDir ?? path2.join(os2.homedir(), ".genex", "blender");
|
|
113
|
+
fs2.mkdirSync(stateDir, { recursive: true });
|
|
114
|
+
const deadlineFile = path2.join(stateDir, "deadline");
|
|
115
|
+
try {
|
|
116
|
+
fs2.unlinkSync(deadlineFile);
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
log.step(`Blender ${info.version} ${c.dim(`(${info.source}: ${info.bin})`)}`);
|
|
120
|
+
log.plain(
|
|
121
|
+
` ${c.dim("This runs on YOUR machine and YOUR GPU. Nothing is uploaded, nothing is rented.")}`
|
|
122
|
+
);
|
|
123
|
+
let stopping = false;
|
|
124
|
+
let child = null;
|
|
125
|
+
const stop = (signal) => {
|
|
126
|
+
stopping = true;
|
|
127
|
+
if (child) child.kill(signal);
|
|
128
|
+
};
|
|
129
|
+
process.on("SIGINT", () => stop("SIGINT"));
|
|
130
|
+
process.on("SIGTERM", () => stop("SIGTERM"));
|
|
131
|
+
while (!stopping) {
|
|
132
|
+
const started = Date.now();
|
|
133
|
+
child = spawnBlender(info, server, stateDir, opts.port);
|
|
134
|
+
const exit = await superviseUntilExit(child, deadlineFile, log);
|
|
135
|
+
child = null;
|
|
136
|
+
if (stopping) return 0;
|
|
137
|
+
if (exit.code === 0 && !exit.killed) {
|
|
138
|
+
if (fs2.existsSync(path2.join(stateDir, "destroy-requested"))) {
|
|
139
|
+
log.info("Blender exited cleanly.");
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
log.warn("Blender exited 0 without being asked to (a script called sys.exit()?). Restarting; the scene is restored from its checkpoint.");
|
|
143
|
+
}
|
|
144
|
+
if (!exit.killed && Date.now() - started < 15e3) {
|
|
145
|
+
log.error(`Blender exited ${exit.code} almost immediately \u2014 not restarting.`);
|
|
146
|
+
log.plain(` ${c.dim("Run with GENEX_BLENDER_DEBUG=1 to see its output.")}`);
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
149
|
+
log.warn(
|
|
150
|
+
exit.killed ? `Killed ${exit.route ?? "a request"} \u2014 it exceeded its ${exit.budget ?? "?"}s budget. Restarting; the scene is restored from its checkpoint.` : `Blender exited ${exit.code}. Restarting.`
|
|
151
|
+
);
|
|
152
|
+
try {
|
|
153
|
+
fs2.writeFileSync(
|
|
154
|
+
path2.join(stateDir, "restart-reason"),
|
|
155
|
+
JSON.stringify({ at: Date.now() / 1e3, reason: exit.killed ? "deadline_exceeded" : "crashed", route: exit.route })
|
|
156
|
+
);
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return 0;
|
|
161
|
+
}
|
|
162
|
+
function spawnBlender(info, server, stateDir, port) {
|
|
163
|
+
return spawn(
|
|
164
|
+
info.bin,
|
|
165
|
+
[
|
|
166
|
+
"--background",
|
|
167
|
+
"--factory-startup",
|
|
168
|
+
"-noaudio",
|
|
169
|
+
// Without this a script that raises exits 0 and the failure looks healthy.
|
|
170
|
+
"--python-exit-code",
|
|
171
|
+
"1",
|
|
172
|
+
"--python",
|
|
173
|
+
server,
|
|
174
|
+
"--",
|
|
175
|
+
"--port",
|
|
176
|
+
String(port)
|
|
177
|
+
],
|
|
178
|
+
{
|
|
179
|
+
env: {
|
|
180
|
+
...process.env,
|
|
181
|
+
// The local lane is a GPU lane when the machine has one, but it must not
|
|
182
|
+
// REFUSE when it does not — unlike the pod, where software rendering
|
|
183
|
+
// means paying GPU rates for CPU work.
|
|
184
|
+
// Blender writes __pycache__ beside whatever it imports, and the
|
|
185
|
+
// service lives inside the installed package — so without this a user's
|
|
186
|
+
// node_modules accumulates .pyc files, and in a source checkout they
|
|
187
|
+
// land in templates/ and get vendored into the next publish. Caught by
|
|
188
|
+
// blender-parity.test.ts after exactly that happened.
|
|
189
|
+
PYTHONDONTWRITEBYTECODE: "1",
|
|
190
|
+
GENEX_BLENDER_REQUIRE_GPU: "detect",
|
|
191
|
+
GENEX_BLENDER_RUN_DIR: stateDir,
|
|
192
|
+
GENEX_BLENDER_CHECKPOINT: path2.join(stateDir, "checkpoint.blend")
|
|
193
|
+
},
|
|
194
|
+
stdio: process.env.GENEX_BLENDER_DEBUG === "1" ? "inherit" : ["ignore", "ignore", "pipe"]
|
|
195
|
+
}
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
function superviseUntilExit(child, deadlineFile, log) {
|
|
199
|
+
return new Promise((resolve) => {
|
|
200
|
+
let done = false;
|
|
201
|
+
let tail = "";
|
|
202
|
+
child.stderr?.on("data", (d) => {
|
|
203
|
+
tail = (tail + String(d)).slice(-2e3);
|
|
204
|
+
});
|
|
205
|
+
const timer = setInterval(() => {
|
|
206
|
+
let d = null;
|
|
207
|
+
try {
|
|
208
|
+
d = JSON.parse(fs2.readFileSync(deadlineFile, "utf8"));
|
|
209
|
+
} catch {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (typeof d.deadline === "number" && Date.now() / 1e3 > d.deadline) {
|
|
213
|
+
clearInterval(timer);
|
|
214
|
+
if (done) return;
|
|
215
|
+
done = true;
|
|
216
|
+
const route = d.route;
|
|
217
|
+
const budget = d.budget;
|
|
218
|
+
child.kill("SIGTERM");
|
|
219
|
+
const hard = setTimeout(() => child.kill("SIGKILL"), TERM_GRACE_MS);
|
|
220
|
+
child.once("exit", () => {
|
|
221
|
+
clearTimeout(hard);
|
|
222
|
+
try {
|
|
223
|
+
fs2.unlinkSync(deadlineFile);
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
resolve({ code: null, killed: true, route, budget });
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}, POLL_MS);
|
|
230
|
+
child.once("exit", (code) => {
|
|
231
|
+
clearInterval(timer);
|
|
232
|
+
if (done) return;
|
|
233
|
+
done = true;
|
|
234
|
+
if (code !== 0 && tail.trim() && process.env.GENEX_BLENDER_DEBUG !== "1") {
|
|
235
|
+
log.plain(c.dim(tail.trim().split("\n").slice(-6).join("\n")));
|
|
236
|
+
}
|
|
237
|
+
resolve({ code, killed: false });
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
export {
|
|
242
|
+
serveLocalBlender,
|
|
243
|
+
serviceDir
|
|
244
|
+
};
|
|
@@ -91,67 +91,20 @@ function isDir(p) {
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
// src/
|
|
95
|
-
var
|
|
96
|
-
var
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
" docker run -d --platform=linux/amd64 -p 8099:8080 genex-blender:m0",
|
|
109
|
-
" export GENEX_BLENDER_URL=http://localhost:8099"
|
|
110
|
-
].join("\n");
|
|
111
|
-
async function blenderCall(base, route, body) {
|
|
112
|
-
const ctl = new AbortController();
|
|
113
|
-
const timer = setTimeout(() => ctl.abort(), BLENDER_TIMEOUT_MS);
|
|
114
|
-
try {
|
|
115
|
-
const res = await fetch(`${base}${route}`, {
|
|
116
|
-
method: body === void 0 ? "GET" : "POST",
|
|
117
|
-
headers: {
|
|
118
|
-
...body === void 0 ? {} : { "Content-Type": "application/json" },
|
|
119
|
-
// Sent only when set. The service treats an unset secret as open, which
|
|
120
|
-
// is right on localhost; the deployed compose makes it mandatory.
|
|
121
|
-
...process.env.GENEX_BLENDER_SECRET ? { "x-genex-internal": process.env.GENEX_BLENDER_SECRET } : {}
|
|
122
|
-
},
|
|
123
|
-
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
124
|
-
signal: ctl.signal
|
|
125
|
-
});
|
|
126
|
-
const text = await res.text();
|
|
127
|
-
let json;
|
|
128
|
-
try {
|
|
129
|
-
json = JSON.parse(text);
|
|
130
|
-
} catch {
|
|
131
|
-
throw new Error(`${route} answered ${res.status} with non-JSON: ${text.slice(0, 200)}`);
|
|
132
|
-
}
|
|
133
|
-
if (!res.ok) {
|
|
134
|
-
if (res.status === 401) {
|
|
135
|
-
throw new Error(
|
|
136
|
-
`${route} refused the request (401) \u2014 set GENEX_BLENDER_SECRET to the service's secret`
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
throw new Error(`${route} failed (${res.status}): ${json.error ?? text.slice(0, 200)}`);
|
|
140
|
-
}
|
|
141
|
-
return json;
|
|
142
|
-
} catch (err) {
|
|
143
|
-
if (err instanceof Error && err.name === "AbortError") {
|
|
144
|
-
throw new Error(`${route} timed out after ${BLENDER_TIMEOUT_MS / 1e3}s`);
|
|
145
|
-
}
|
|
146
|
-
throw err;
|
|
147
|
-
} finally {
|
|
148
|
-
clearTimeout(timer);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
function sceneSummary(s) {
|
|
152
|
-
if (!s) return "";
|
|
153
|
-
return `objects ${s.objectCount} meshes ${s.meshCount} tris ${s.totalTris} materials ${s.materialCount} radius ${s.bounds.radius}`;
|
|
154
|
-
}
|
|
94
|
+
// src/utils/colors.ts
|
|
95
|
+
var useColor = Boolean(process.stdout.isTTY) && process.env.NO_COLOR === void 0 && process.env.TERM !== "dumb";
|
|
96
|
+
var ESC = String.fromCharCode(27);
|
|
97
|
+
var code = (open, close) => (s) => useColor ? `${ESC}[${open}m${s}${ESC}[${close}m` : s;
|
|
98
|
+
var c = {
|
|
99
|
+
bold: code(1, 22),
|
|
100
|
+
dim: code(2, 22),
|
|
101
|
+
red: code(31, 39),
|
|
102
|
+
green: code(32, 39),
|
|
103
|
+
yellow: code(33, 39),
|
|
104
|
+
blue: code(34, 39),
|
|
105
|
+
cyan: code(36, 39),
|
|
106
|
+
gray: code(90, 39)
|
|
107
|
+
};
|
|
155
108
|
|
|
156
109
|
export {
|
|
157
110
|
CLI_CHANNEL,
|
|
@@ -169,10 +122,5 @@ export {
|
|
|
169
122
|
getTemplatesDir,
|
|
170
123
|
getCliVersion,
|
|
171
124
|
resolveAgentTargets,
|
|
172
|
-
|
|
173
|
-
isRenderMode,
|
|
174
|
-
blenderEndpoint,
|
|
175
|
-
BLENDER_SETUP_HINT,
|
|
176
|
-
blenderCall,
|
|
177
|
-
sceneSummary
|
|
125
|
+
c
|
|
178
126
|
};
|