scad-gltf 0.1.3 → 0.2.1

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 CHANGED
@@ -22,7 +22,8 @@ The C++ source code for this custom OpenSCAD version is included directly in thi
22
22
  - **CLI Converter:** Bundled `scad-convert` CLI utility for single file and batch compiling `.scad` files with smart dependency hashing.
23
23
  - **MCP Server for AI Agents:** Bundled `scad-mcp` server enables MCP clients to iteratively design, compile, and **visually inspect** 3D models via multi-angle headless rendering and animation frame evaluation.
24
24
  - **AI Studio Extension:** Chrome extension to natively preview, prompt, take chat snapshots, open in Scadify, and locally save AI-generated 3D models directly inside Google AI Studio.
25
- - **Godot 4 Integration & Web Demos:** Native Godot 4 importer addon for procedural `.scad` assets and prebuilt AI-generated [web game examples](https://iliagrigorevdev.github.io/scad-godot/).
25
+ - **Godot 4, Rust Bevy & Web Integration:** Native Godot 4 importer addon, Rust Bevy compile-time `build.rs` integration, and prebuilt AI-generated game examples for Godot and Bevy.
26
+ - **Automated AI Generation:** Pass an OpenAI-compatible base URL (and optional API key) to the `scad-godot`, `scad-bevy`, or `scad-web` CLI to automatically spawn a local MCP server, query the LLM via standard endpoints, allow it to visually iterate, and output a ready-to-run project generator script.
26
27
 
27
28
  ---
28
29
 
@@ -219,6 +220,7 @@ When asking an LLM (like Gemini) to generate OpenSCAD code, the extension automa
219
220
  - **Instant 3D Preview:** Injects a **"Preview 3D"** button on any OpenSCAD code block to compile and render the model in an embedded 3D viewer with grid, wireframe, and full-screen support.
220
221
  - **Visual Chat Feedback (📷):** Click the snapshot button inside the 3D preview window to capture a PNG snapshot of the model and **automatically paste it into the AI Studio chat input**, allowing Gemini to visually evaluate and fix geometry.
221
222
  - **Smart Prompt Injection:** Click the floating **"✨ SCAD"** button to open the configuration modal. Select your desired engine feature set (PBR, auto-smooth, animations, baking) to automatically generate and inject the prompt rules into your chat input.
223
+ - **Local Model Refinement:** Connect to your local `scad-serve` workspace directly from the prompt modal. Select an existing `.scad` file to automatically append its source code to your prompt as a reference, enabling seamless AI iteration and refinement of your existing local designs.
222
224
  - **Open in Scadify:** Click **"Edit"** in the preview window to immediately transfer the current script into the full standalone Scadify editor via compressed URL hash.
223
225
  - **Local Workspace Saving:** Directly save and overwrite models to your local directory when running `scad-serve`.
224
226
 
@@ -259,6 +261,7 @@ Instead of generating code blindly, the AI can compile its script, render the 3D
259
261
 
260
262
  - `get_scad_prompt`: Injects the custom OpenSCAD syntax rules (PBR, animations, baking) into the AI's context.
261
263
  - `render_scad_model`: Compiles the generated `.scad` code to GLB and returns base64 images from requested camera angles (front, back, left, right, top, bottom, isometric) and specific animation keyframes.
264
+ - `compile_bevy_project` / `test_godot_project`: Tests the generated game projects for compilation and runtime errors.
262
265
 
263
266
  ### Setup
264
267
 
@@ -294,6 +297,53 @@ Try prebuilt AI-generated Godot games directly in your browser: [https://iliagri
294
297
 
295
298
  ---
296
299
 
300
+ ### Automated AI Generation (OpenAI-Compatible API)
301
+
302
+ By default, `scad-godot`, `scad-bevy`, and `scad-web` copy a heavily engineered system prompt to your clipboard to paste into an LLM. However, if you provide a **Base URL** (and optional **API Key**), the CLI will fully automate this process. It will automatically spin up the `scad-mcp` server in the background, connect it to your LLM, and allow the AI to _visually evaluate and fix_ its 3D models in real-time before saving the final `generate_project.js` script to your disk.
303
+
304
+ **Option 1: Using OpenAI (GPT-4o)**
305
+
306
+ ```bash
307
+ export OPENAI_BASE_URL="https://api.openai.com/v1"
308
+ export OPENAI_API_KEY="sk-..."
309
+ export OPENAI_MODEL="gpt-4o"
310
+ scad-bevy "A 3D spaceship shooter game"
311
+ ```
312
+
313
+ **Option 2: Using Google Gemini (via OpenAI compatibility)**
314
+ Because Gemini provides an official OpenAI-compatible endpoint, you can configure it using standard OpenAI variables:
315
+
316
+ ```bash
317
+ export OPENAI_API_KEY="AIzaSy..."
318
+ export OPENAI_BASE_URL="https://generativelanguage.googleapis.com/v1beta/openai/"
319
+ export OPENAI_MODEL="gemini-3.8-flash"
320
+ scad-godot "A fast-paced 3D hovercraft racing game"
321
+ ```
322
+
323
+ **Option 3: Using Local Server**
324
+ You can use completely local, uncensored, or fine-tuned vision models by overriding the OpenAI Base URL to point to a local inference server (like `llama.cpp`'s API server).
325
+
326
+ ```bash
327
+ export OPENAI_BASE_URL="http://127.0.0.1:8080/v1"
328
+ export OPENAI_MODEL="llama-3" # Or whatever model name you've loaded
329
+ scad-web "A 3D configurator app"
330
+ ```
331
+
332
+ **Passing Parameters via JSON**
333
+ You can also pass credentials inline as a JSON string argument instead of modifying environment variables:
334
+
335
+ ```bash
336
+ scad-godot "A futuristic tank game" '{"openaiBaseUrl": "http://127.0.0.1:8080/v1", "openaiModel": "llama-3"}'
337
+ ```
338
+
339
+ _Once the automated process completes, simply run the generated Node.js script to assemble your complete Godot, Bevy, or Vite project structure with all assets and code!_
340
+
341
+ ```bash
342
+ node generate_godot_project.js
343
+ ```
344
+
345
+ ---
346
+
297
347
  ## Extended OpenSCAD Syntax
298
348
 
299
349
  This custom fork introduces new syntax not found in standard OpenSCAD.
@@ -420,8 +470,6 @@ const promptContext = generatePrompt(description, {
420
470
  console.log(promptContext);
421
471
  ```
422
472
 
423
- ---
424
-
425
473
  ### Workflow
426
474
 
427
475
  Once connected, an AI assistant can use the server to execute the following loop:
@@ -490,7 +538,3 @@ This repository includes a custom fork of OpenSCAD in the `openscad/` subfolder
490
538
  - **Path Tracing:** The web editor utilizes [three-gpu-pathtracer](https://github.com/gkjohnson/three-gpu-pathtracer) for high-quality rendering.
491
539
  - **Environment Map (HDR)**: [Aristea Wreck Puresky](https://polyhaven.com/a/aristea_wreck_puresky) by **Jarod Guest** via [Poly Haven](https://polyhaven.com/). Licensed under [CC0](https://polyhaven.com/license).
492
540
  - **License:** See the `LICENSE` file (GPL-2.0 or later, inheriting from standard OpenSCAD).
493
-
494
- ```
495
-
496
- ```
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runCliApp } from "../src/cli-utils.js";
4
+
5
+ async function main() {
6
+ await runCliApp({
7
+ projectType: "bevy",
8
+ buildSystemPrompt: (
9
+ promptRules,
10
+ ) => `You are an expert Rust Bevy engine game developer and procedural 3D technical artist.
11
+
12
+ NAMING CONVENTION REQUIREMENT:
13
+ - All generated files, directories, models, scripts, and root folders MUST strictly use snake_case (lowercase with underscores, e.g. \`player_character.rs\`, \`enemy_walker.scad\`).
14
+ - NEVER use hyphens/minus signs (\`-\`) or spaces in any file or folder names.
15
+
16
+ What to generate:
17
+ 1. 3D Game Assets (.scad):
18
+ - Generate procedural 3D models for the game using OpenSCAD.
19
+ - CRITICAL: The SCAD to glTF converter automatically converts OpenSCAD's Z-up coordinate system to the standard glTF Y-up coordinate system. Design your models naturally in OpenSCAD.
20
+ - CRITICAL: You must use the custom OpenSCAD glTF extensions for PBR materials (e.g., \`roughness\`, \`metalness\`, \`emissive\`) and Hierarchical Node Animations (\`armature()\`, \`bone()\`). The rules and syntax for these features are provided below:
21
+
22
+ === OPENSCAD SYNTAX RULES ===
23
+ ${promptRules}
24
+ =============================
25
+
26
+ 2. Rust Bevy Project Files:
27
+ - Create the necessary files for a modern Rust Bevy engine application (e.g., \`Cargo.toml\`, \`build.rs\`, \`src/main.rs\`).
28
+ - In \`Cargo.toml\`, include \`bevy\` as a dependency.
29
+ - You MUST include this EXACT \`build.rs\` script at the root of the project to automatically compile the \`.scad\` files into \`.glb\` format inside the \`assets/models\` folder before running the game via Cargo:
30
+ \`\`\`rust
31
+ use std::process::Command;
32
+
33
+ fn main() {
34
+ // Tell Cargo to re-run this script only if the 'scad' directory changes
35
+ println!("cargo::rerun-if-changed=scad");
36
+
37
+ // Ensure cross-platform compatibility for npm global binaries
38
+ let cmd = if cfg!(target_os = "windows") {
39
+ "scad-convert.cmd"
40
+ } else {
41
+ "scad-convert"
42
+ };
43
+
44
+ let status = Command::new(cmd)
45
+ .args(["./scad", "./assets/models", "--cache"])
46
+ .status()
47
+ .expect("Failed to execute scad-convert. Is scad-gltf installed globally?");
48
+
49
+ if !status.success() {
50
+ panic!("scad-convert failed with status: {}", status);
51
+ }
52
+ }
53
+ \`\`\`
54
+ - Write the core application logic in \`src/main.rs\` to load and display the converted \`.glb\` files interactively. Provide standard Bevy game systems (camera, lights, movement, etc.).
55
+
56
+ 3. Delivery Format (Single Node.js Script):
57
+ - Output exactly ONE self-contained Node.js script. Do not output manual setup instructions.
58
+ - CRITICAL: The generated Node.js script MUST first create a root project folder (named using a slugified version of the project name) and output all files and folders inside this newly created project folder.
59
+ - When executed, this script must programmatically create the entire project directory structure and write all the files to disk using the \`fs\` module.
60
+ - The script must embed and write:
61
+ - Your generated \`.scad\` 3D assets.
62
+ - Your generated Rust Bevy project files (\`Cargo.toml\`, \`build.rs\`, \`src/main.rs\`).
63
+ - Ensure all string file contents inside the Node.js script are properly escaped.`,
64
+ buildInputRequest: (task) =>
65
+ `Design and implement a Rust Bevy engine game for the following concept: "${task}"`,
66
+ allowedTools: ["render_scad_model", "compile_bevy_project"],
67
+ });
68
+ }
69
+
70
+ // Execute and handle unhandled runtime errors
71
+ main().catch((err) => {
72
+ console.error("An unexpected error occurred:", err);
73
+ process.exit(1);
74
+ });
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runCliApp } from "../src/cli-utils.js";
4
+
5
+ async function main() {
6
+ await runCliApp({
7
+ projectType: "gen",
8
+ buildSystemPrompt: () =>
9
+ `You are an expert procedural 3D technical artist and OpenSCAD developer.
10
+ Your goal is to generate a single OpenSCAD (.scad) file based on the user's request.
11
+
12
+ CRITICAL WORKFLOW:
13
+ 1. Write the OpenSCAD code using the custom syntax rules provided in the request.
14
+ 2. Call the \`render_scad_model\` tool with your code to visually verify your design.
15
+ 3. If the model looks incorrect, adjust your code and re-render. Iterate until perfect.
16
+ 4. Provide your final OpenSCAD code in a standard markdown block (\`\`\`openscad).`,
17
+ allowedTools: ["render_scad_model"],
18
+ });
19
+ }
20
+
21
+ // Execute and handle unhandled runtime errors
22
+ main().catch((err) => {
23
+ console.error("An unexpected error occurred:", err);
24
+ process.exit(1);
25
+ });
package/bin/scad-godot.js CHANGED
@@ -3,143 +3,20 @@
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import url from "node:url";
6
- import readline from "node:readline";
6
+ import { runCliApp } from "../src/cli-utils.js";
7
7
 
8
- // Safely resolve symlinks to find the actual package directory
9
- const __filename = fs.realpathSync(url.fileURLToPath(import.meta.url));
8
+ const __filename = url.fileURLToPath(import.meta.url);
10
9
  const __dirname = path.dirname(__filename);
11
10
  const DIR = path.resolve(__dirname, "..");
12
11
 
13
- // Safely detect if actual data is being piped into the script via STDIN
14
- function hasStdinData() {
15
- try {
16
- const stat = fs.fstatSync(0); // 0 is the file descriptor for STDIN
17
- // isFIFO means piped (echo "foo" | script)
18
- // isFile means redirected (script < foo.txt)
19
- return stat.isFIFO() || stat.isFile();
20
- } catch (e) {
21
- return false;
22
- }
23
- }
24
-
25
- // Writes text to system clipboard
26
- async function writeToClipboard(text) {
27
- const clipboardy = (await import("clipboardy")).default;
28
- await clipboardy.write(text);
29
- }
30
-
31
- function waitForEnter(message) {
32
- return new Promise((resolve) => {
33
- // If standard input was piped/redirected, we need to bypass it and read from the actual terminal
34
- if (!process.stdin.isTTY) {
35
- try {
36
- const tty = process.platform === "win32" ? "CONIN$" : "/dev/tty";
37
- const fd = fs.openSync(tty, "rs");
38
- process.stdout.write(message);
39
- const buf = Buffer.alloc(1);
40
- fs.readSync(fd, buf, 0, 1, null);
41
- fs.closeSync(fd);
42
- console.log();
43
- resolve();
44
- return;
45
- } catch (e) {
46
- console.log(
47
- message +
48
- " (Auto-continuing due to non-interactive terminal environment)",
49
- );
50
- resolve();
51
- return;
52
- }
53
- }
54
-
55
- // For standard TTY terminals
56
- const rl = readline.createInterface({
57
- input: process.stdin,
58
- output: process.stdout,
59
- });
60
- rl.question(message, () => {
61
- rl.close();
62
- resolve();
63
- });
64
- });
65
- }
66
-
67
12
  async function main() {
68
- let task = "";
69
- let optionsStr = "{}";
70
-
71
- // 1. Read TASK and OPTIONS
72
- if (hasStdinData()) {
73
- try {
74
- task = fs.readFileSync(0, "utf-8").trim();
75
- } catch (e) {
76
- console.error("Error reading from STDIN:", e);
77
- }
78
- if (process.argv[2]) optionsStr = process.argv[2];
79
- } else {
80
- if (process.argv[2]) task = process.argv[2];
81
- if (process.argv[3]) optionsStr = process.argv[3];
82
- }
83
-
84
- if (!task) {
85
- console.error("Error: Task parameter is required.");
86
- console.error(
87
- 'Usage: scad-godot "<description of the game to generate>" [options_json]',
88
- );
89
- console.error(' or: echo "<description>" | scad-godot [options_json]');
90
- console.error("");
91
- console.error("Example with JSON options:");
92
- console.error(
93
- ' scad-godot "Game description" \'{"animation": false, "bakeColors": true}\'',
94
- );
95
- process.exit(1);
96
- }
97
-
98
- // 2. Parse Options JSON
99
- // Disable heavy PBR features by default
100
- let options = {
101
- transmission: false,
102
- clearcoat: false,
103
- sheen: false,
104
- iridescence: false,
105
- };
106
-
107
- if (optionsStr) {
108
- try {
109
- const parsed = JSON.parse(optionsStr);
110
- options = { ...options, ...parsed }; // User provided options override defaults
111
- } catch (e) {
112
- console.error(`Invalid JSON options: ${optionsStr}`);
113
- process.exit(1);
114
- }
115
- }
116
-
117
- // Disable the modelName instructions specifically for the Godot wrapper context
118
- options.modelName = false;
13
+ await runCliApp({
14
+ projectType: "godot",
15
+ buildSystemPrompt: (promptRules, options) => {
16
+ const hasUserScadFiles =
17
+ Array.isArray(options.scadFiles) && options.scadFiles.length > 0;
119
18
 
120
- // 3. Dynamically import and generate prompt rules from src/prompt.js
121
- let generatePrompt;
122
- try {
123
- const promptJsPath = path.join(DIR, "src", "prompt.js");
124
- const promptModuleUrl = url.pathToFileURL(promptJsPath).href;
125
- const m = await import(promptModuleUrl);
126
- generatePrompt = m.generatePrompt;
127
- } catch (e) {
128
- console.error(`Error loading ${path.join("src", "prompt.js")}:`, e);
129
- process.exit(1);
130
- }
131
-
132
- let promptRules = "";
133
- try {
134
- promptRules = generatePrompt("the 3D assets for the game", options);
135
- } catch (e) {
136
- console.error("Error generating prompt rules from prompt.js:");
137
- console.error(e);
138
- process.exit(1);
139
- }
140
-
141
- // 4. Construct the Main Godot Prompt System Text (System Instructions)
142
- const systemPrompt = `You are an expert Godot 4 game developer and procedural 3D technical artist.
19
+ const systemPrompt = `You are an expert Godot 4 game developer and procedural 3D technical artist.
143
20
 
144
21
  NAMING CONVENTION REQUIREMENT:
145
22
  - All generated files, directories, models, scripts, scenes, and root folders MUST strictly use snake_case (lowercase with underscores, e.g. \`player_character.gd\`, \`main_scene.tscn\`, \`enemy_walker.scad\`).
@@ -185,83 +62,75 @@ ${promptRules}
185
62
  - The script must embed and write:
186
63
  - Your generated \`.scad\` game assets.
187
64
  - Your generated Godot project files.
188
- - The exact source code of the provided \`addons/scad_importer/*\` files, placed in their correct respective paths.
189
- - Ensure all string file contents inside the Node.js script are properly escaped.`;
190
-
191
- // 5. Gather Addon Files content
192
- const addonDir = path.join(DIR, "godot", "addons", "scad_importer");
193
- let addonFiles = [];
194
- try {
195
- if (fs.existsSync(addonDir)) {
196
- const files = fs.readdirSync(addonDir);
197
- for (const file of files) {
198
- const fullPath = path.join(addonDir, file);
199
- // Ensure we only read text files to prevent binary/hidden files
200
- // from introducing control characters that crash browser UIs.
201
- if (
202
- fs.statSync(fullPath).isFile() &&
203
- (file.endsWith(".gd") || file.endsWith(".cfg"))
204
- ) {
205
- addonFiles.push(fullPath);
65
+ - The exact source code of the provided \`addons/scad_importer/*\` files, placed in their correct respective paths.${
66
+ hasUserScadFiles
67
+ ? "\n - The exact source code of the provided user `.scad` files, placed in the appropriate project folders."
68
+ : ""
69
+ }
70
+ - Ensure all string file contents inside the Node.js script are properly escaped.
71
+ - TESTING CAPABILITY: You have access to the \`test_godot_project\` tool. If you want to verify your code before outputting your final response, you can pass your complete generated Node.js script as the \`nodejs_script\` parameter. The server will execute it in a temporary folder and run the Godot tests automatically.`;
72
+
73
+ // Gather Addon Files content
74
+ const addonDir = path.join(DIR, "godot", "addons", "scad_importer");
75
+ let addonFiles = [];
76
+ try {
77
+ if (fs.existsSync(addonDir)) {
78
+ const files = fs.readdirSync(addonDir);
79
+ for (const file of files) {
80
+ const fullPath = path.join(addonDir, file);
81
+ if (
82
+ fs.statSync(fullPath).isFile() &&
83
+ (file.endsWith(".gd") || file.endsWith(".cfg"))
84
+ ) {
85
+ addonFiles.push(fullPath);
86
+ }
87
+ }
206
88
  }
89
+ } catch (e) {
90
+ console.error("Warning: Could not read addon directory.", e);
207
91
  }
208
- }
209
- } catch (e) {
210
- console.error("Warning: Could not read addon directory.", e);
211
- }
212
-
213
- // 6. Format the unified system instructions clipboard output
214
- let systemClipboardOutput = `${systemPrompt}\n\n`;
215
-
216
- for (const file of addonFiles) {
217
- try {
218
- // Normalize line endings to avoid mixed line-ending layout loops in web editors
219
- const content = fs.readFileSync(file, "utf-8").replace(/\r\n/g, "\n");
220
- // Format to use relative paths and force forward slashes for LLM clarity
221
- const relativePath = path.relative(DIR, file).replace(/\\/g, "/");
222
-
223
- // Add explicit language tags to prevent catastrophic regex backtracking
224
- // during the Markdown parser's language auto-detection step.
225
- const lang = file.endsWith(".gd") ? "gdscript" : "text";
226
- systemClipboardOutput += `### ${relativePath}\n---\n\`\`\`${lang}\n${content}\n\`\`\`\n\n`;
227
- } catch (e) {
228
- console.error(`Warning: Skipping '${file}'. It is not a readable file.`);
229
- }
230
- }
231
92
 
232
- systemClipboardOutput = systemClipboardOutput.trimEnd() + "\n";
233
-
234
- // 7. Format the input request output
235
- const inputRequestOutput = `Design and implement a Godot 4 project for the following game concept: "${task}"`;
236
-
237
- // 8. Write to System Clipboard (Part 1: System Instructions)
238
- try {
239
- await writeToClipboard(systemClipboardOutput);
240
- console.log("✔️ System instructions have been copied to the clipboard.");
241
- } catch (err) {
242
- console.error(
243
- "Error: Failed to copy system instructions to the clipboard.",
244
- );
245
- console.error(err.message);
246
- process.exit(1);
247
- }
93
+ let systemClipboardOutput = `${systemPrompt}\n\n`;
94
+
95
+ for (const file of addonFiles) {
96
+ try {
97
+ const content = fs.readFileSync(file, "utf-8").replace(/\r\n/g, "\n");
98
+ const relativePath = path.relative(DIR, file).replace(/\\/g, "/");
99
+ const lang = file.endsWith(".gd") ? "gdscript" : "text";
100
+ systemClipboardOutput += `### ${relativePath}\n---\n\`\`\`${lang}\n${content}\n\`\`\`\n\n`;
101
+ } catch (e) {
102
+ console.error(
103
+ `Warning: Skipping '${file}'. It is not a readable file.`,
104
+ );
105
+ }
106
+ }
248
107
 
249
- // 9. Await user confirmation
250
- await waitForEnter(
251
- "Please paste the system instructions into your LLM, then press ENTER to copy your input request...",
252
- );
108
+ if (hasUserScadFiles) {
109
+ systemClipboardOutput += `=== USER PROVIDED OPENSCAD FILES ===\n`;
110
+ systemClipboardOutput += `The following .scad files are provided as reference or base assets. You MUST embed and write them into the generated project, modifying them if necessary to fit the game logic.\n\n`;
111
+ for (const file of options.scadFiles) {
112
+ try {
113
+ const content = fs
114
+ .readFileSync(file, "utf-8")
115
+ .replace(/\r\n/g, "\n");
116
+ const relativePath = path.isAbsolute(file)
117
+ ? path.relative(process.cwd(), file).replace(/\\/g, "/")
118
+ : file.replace(/\\/g, "/");
119
+ systemClipboardOutput += `### ${relativePath}\n---\n\`\`\`openscad\n${content}\n\`\`\`\n\n`;
120
+ } catch (e) {
121
+ console.error(
122
+ `Warning: Skipping user SCAD file '${file}'. It is not a readable file.`,
123
+ );
124
+ }
125
+ }
126
+ }
253
127
 
254
- // 10. Write to System Clipboard (Part 2: Input Request)
255
- try {
256
- await writeToClipboard(inputRequestOutput);
257
- console.log(
258
- "✔️ Input request has been copied to the clipboard. You can now paste it into your LLM.",
259
- );
260
- } catch (err) {
261
- console.error("Error: Failed to copy input request to the clipboard.");
262
- console.error(err.message);
263
- process.exit(1);
264
- }
128
+ return systemClipboardOutput.trimEnd() + "\n";
129
+ },
130
+ buildInputRequest: (task) =>
131
+ `Design and implement a Godot 4 project for the following game concept: "${task}"`,
132
+ allowedTools: ["render_scad_model", "test_godot_project"],
133
+ });
265
134
  }
266
135
 
267
136
  // Execute and handle unhandled runtime errors