scad-gltf 0.1.0
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 +489 -0
- package/bin/scad-convert.js +221 -0
- package/bin/scad-godot.js +247 -0
- package/bin/scad-mcp.js +508 -0
- package/bin/scad-serve.js +225 -0
- package/bin/scad-web.js +206 -0
- package/editor/dist/aristea_wreck_puresky_2k.hdr +0 -0
- package/editor/dist/assets/OutputPass-Bvl6NigM.js +4317 -0
- package/editor/dist/assets/index-V9cEH4KX.js +4318 -0
- package/editor/dist/assets/index-t9MYrExo.css +1 -0
- package/editor/dist/assets/openscad-CdBCY4mx.wasm +0 -0
- package/editor/dist/assets/preview-C1zc24MJ.js +1 -0
- package/editor/dist/assets/preview-fQfL_FJ-.css +1 -0
- package/editor/dist/assets/prompt-ui-8EFRz0ju.js +126 -0
- package/editor/dist/content-loader.js +4 -0
- package/editor/dist/content.css +255 -0
- package/editor/dist/content.js +24 -0
- package/editor/dist/icon.png +0 -0
- package/editor/dist/index.html +187 -0
- package/editor/dist/manifest.json +23 -0
- package/editor/dist/manifest.webmanifest +1 -0
- package/editor/dist/preview.html +27 -0
- package/editor/dist/registerSW.js +1 -0
- package/editor/dist/sw.js +1 -0
- package/editor/dist/workbox-9c191d2f.js +1 -0
- package/godot/README.md +64 -0
- package/godot/addons/scad_importer/plugin.cfg +7 -0
- package/godot/addons/scad_importer/scad_importer.gd +197 -0
- package/godot/addons/scad_importer/scad_plugin.gd +12 -0
- package/godot/examples/README.md +82 -0
- package/godot/examples/fruit_fusion_3d.js +1574 -0
- package/godot/examples/package.json +5 -0
- package/package.json +63 -0
- package/src/convert.js +94 -0
- package/src/ext/openscad.js +14 -0
- package/src/ext/openscad.wasm +0 -0
- package/src/prompt.js +229 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
5
|
+
import crypto from "crypto";
|
|
6
|
+
import { convertScadToGltf } from "../src/convert.js";
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
const wasmPath = path.resolve(__dirname, "../src/ext/openscad.wasm");
|
|
11
|
+
|
|
12
|
+
global.fetch = async (url) => {
|
|
13
|
+
const normalizedPath = url.toString().startsWith("file://")
|
|
14
|
+
? fileURLToPath(url.toString())
|
|
15
|
+
: url.toString();
|
|
16
|
+
|
|
17
|
+
const buffer = fs.readFileSync(normalizedPath);
|
|
18
|
+
return new Response(buffer, {
|
|
19
|
+
status: 200,
|
|
20
|
+
headers: { "Content-Type": "application/wasm" },
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Recursively resolves local dependencies (include / use) to extract their contents.
|
|
26
|
+
* Returns a map of absolute file paths to their string contents.
|
|
27
|
+
*/
|
|
28
|
+
function getDependencies(filePath, visited = new Map()) {
|
|
29
|
+
if (visited.has(filePath)) return visited;
|
|
30
|
+
visited.set(filePath, ""); // Prevent infinite recursion cycles
|
|
31
|
+
|
|
32
|
+
if (!fs.existsSync(filePath)) {
|
|
33
|
+
return visited;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
37
|
+
visited.set(filePath, content);
|
|
38
|
+
|
|
39
|
+
// Match include <...> or "..." and use <...> or "..."
|
|
40
|
+
const includeRegex = /(?:include|use)\s*([<"])([^>"]+)([>"])/g;
|
|
41
|
+
let match;
|
|
42
|
+
while ((match = includeRegex.exec(content)) !== null) {
|
|
43
|
+
const depRelativePath = match[2];
|
|
44
|
+
const depAbsolutePath = path.resolve(
|
|
45
|
+
path.dirname(filePath),
|
|
46
|
+
depRelativePath,
|
|
47
|
+
);
|
|
48
|
+
getDependencies(depAbsolutePath, visited);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return visited;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function run() {
|
|
55
|
+
const allArgs = process.argv.slice(2);
|
|
56
|
+
const useCache = allArgs.includes("--cache");
|
|
57
|
+
const args = allArgs.filter((arg) => arg !== "--cache");
|
|
58
|
+
|
|
59
|
+
const inputPath = args[0];
|
|
60
|
+
const outputPath = args[1];
|
|
61
|
+
const optionsJson = args[2];
|
|
62
|
+
|
|
63
|
+
if (!inputPath || !outputPath) {
|
|
64
|
+
console.error(
|
|
65
|
+
"Usage: scad-convert <input.scad | input_dir> <output.glb | output_dir> [options_json] [--cache]",
|
|
66
|
+
);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!fs.existsSync(inputPath)) {
|
|
71
|
+
console.error(`Input file or directory not found: ${inputPath}`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const isInputDirectory = fs.statSync(inputPath).isDirectory();
|
|
76
|
+
let inputFiles = [];
|
|
77
|
+
|
|
78
|
+
if (isInputDirectory) {
|
|
79
|
+
const files = fs.readdirSync(inputPath);
|
|
80
|
+
for (const file of files) {
|
|
81
|
+
if (file.toLowerCase().endsWith(".scad")) {
|
|
82
|
+
inputFiles.push(path.join(inputPath, file));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (inputFiles.length === 0) {
|
|
87
|
+
console.log(`No .scad files found in directory: ${inputPath}`);
|
|
88
|
+
process.exit(0);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!fs.existsSync(outputPath)) {
|
|
92
|
+
fs.mkdirSync(outputPath, { recursive: true });
|
|
93
|
+
} else if (!fs.statSync(outputPath).isDirectory()) {
|
|
94
|
+
console.error("Output must be a directory when input is a directory.");
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
inputFiles.push(inputPath);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let options = {};
|
|
102
|
+
if (optionsJson) {
|
|
103
|
+
if (optionsJson.startsWith("{")) {
|
|
104
|
+
options = JSON.parse(optionsJson);
|
|
105
|
+
} else {
|
|
106
|
+
const decoded = Buffer.from(optionsJson, "base64").toString("utf8");
|
|
107
|
+
options = JSON.parse(decoded);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let hasErrors = false;
|
|
112
|
+
|
|
113
|
+
for (const file of inputFiles) {
|
|
114
|
+
let finalOutputPath = outputPath;
|
|
115
|
+
|
|
116
|
+
if (
|
|
117
|
+
isInputDirectory ||
|
|
118
|
+
(fs.existsSync(outputPath) && fs.statSync(outputPath).isDirectory())
|
|
119
|
+
) {
|
|
120
|
+
const baseName = path.basename(file, path.extname(file));
|
|
121
|
+
finalOutputPath = path.join(outputPath, `${baseName}.glb`);
|
|
122
|
+
} else {
|
|
123
|
+
const ext = path.extname(outputPath).toLowerCase();
|
|
124
|
+
if (ext !== ".glb" && ext !== ".gltf") {
|
|
125
|
+
fs.mkdirSync(outputPath, { recursive: true });
|
|
126
|
+
const baseName = path.basename(file, path.extname(file));
|
|
127
|
+
finalOutputPath = path.join(outputPath, `${baseName}.glb`);
|
|
128
|
+
} else {
|
|
129
|
+
const parentDir = path.dirname(outputPath);
|
|
130
|
+
if (!fs.existsSync(parentDir))
|
|
131
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const importFilePath = `${finalOutputPath}.import`;
|
|
136
|
+
|
|
137
|
+
// 1. Gather all dependencies automatically
|
|
138
|
+
const depsMap = getDependencies(file);
|
|
139
|
+
const scadCode = depsMap.get(file);
|
|
140
|
+
depsMap.delete(file);
|
|
141
|
+
|
|
142
|
+
let totalContentForHash = scadCode;
|
|
143
|
+
const additionalFiles = {};
|
|
144
|
+
const baseDir = path.dirname(file);
|
|
145
|
+
|
|
146
|
+
// Sort paths to ensure consistent deterministic hashing
|
|
147
|
+
const sortedDepPaths = Array.from(depsMap.keys()).sort();
|
|
148
|
+
for (const depPath of sortedDepPaths) {
|
|
149
|
+
const content = depsMap.get(depPath);
|
|
150
|
+
totalContentForHash += content;
|
|
151
|
+
// Calculate path relative to the main file, normalize for VFS
|
|
152
|
+
let relPath = path.relative(baseDir, depPath).replace(/\\/g, "/");
|
|
153
|
+
additionalFiles[relPath] = content;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let needsConversion = true;
|
|
157
|
+
let currentHash = null;
|
|
158
|
+
|
|
159
|
+
if (useCache) {
|
|
160
|
+
const hashData = totalContentForHash + JSON.stringify(options);
|
|
161
|
+
currentHash = crypto.createHash("sha256").update(hashData).digest("hex");
|
|
162
|
+
|
|
163
|
+
if (fs.existsSync(finalOutputPath) && fs.existsSync(importFilePath)) {
|
|
164
|
+
try {
|
|
165
|
+
const importData = JSON.parse(
|
|
166
|
+
fs.readFileSync(importFilePath, "utf8"),
|
|
167
|
+
);
|
|
168
|
+
if (importData.hash === currentHash) {
|
|
169
|
+
needsConversion = false;
|
|
170
|
+
}
|
|
171
|
+
} catch (e) {}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!needsConversion) {
|
|
176
|
+
console.log(`Skipped ${path.basename(file)} (no changes detected)`);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (useCache || isInputDirectory) {
|
|
181
|
+
console.log(`Converting ${path.basename(file)} -> ${finalOutputPath}...`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const glbData = await convertScadToGltf(scadCode, {
|
|
186
|
+
wasmUrl: pathToFileURL(wasmPath).href,
|
|
187
|
+
additionalFiles,
|
|
188
|
+
...options,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
fs.writeFileSync(finalOutputPath, glbData);
|
|
192
|
+
|
|
193
|
+
if (useCache) {
|
|
194
|
+
fs.writeFileSync(
|
|
195
|
+
importFilePath,
|
|
196
|
+
JSON.stringify(
|
|
197
|
+
{
|
|
198
|
+
hash: currentHash,
|
|
199
|
+
source: path.basename(file),
|
|
200
|
+
timestamp: new Date().toISOString(),
|
|
201
|
+
},
|
|
202
|
+
null,
|
|
203
|
+
2,
|
|
204
|
+
),
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error(`SCAD Conversion Error for ${file}:`, error);
|
|
209
|
+
hasErrors = true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (hasErrors) {
|
|
214
|
+
console.error("Batch completed with errors.");
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
process.exit(0);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
run();
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import url from "node:url";
|
|
6
|
+
import readline from "node:readline";
|
|
7
|
+
|
|
8
|
+
// Safely resolve symlinks to find the actual package directory
|
|
9
|
+
const __filename = fs.realpathSync(url.fileURLToPath(import.meta.url));
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
const DIR = path.resolve(__dirname, "..");
|
|
12
|
+
|
|
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
|
+
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;
|
|
119
|
+
|
|
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.
|
|
143
|
+
|
|
144
|
+
What to generate:
|
|
145
|
+
1. 3D Game Assets (.scad):
|
|
146
|
+
- Generate procedural 3D models for the game using OpenSCAD.
|
|
147
|
+
- CRITICAL: The SCAD to glTF converter used by the Godot importer automatically converts OpenSCAD's Z-up coordinate system to Godot's Y-up coordinate system. Design your models naturally in OpenSCAD using this exact mapping:
|
|
148
|
+
* OpenSCAD +X (Right) -> Godot +X (Right)
|
|
149
|
+
* OpenSCAD +Y (Forward) -> Godot -Z (Forward)
|
|
150
|
+
* OpenSCAD +Z (Up) -> Godot +Y (Up)
|
|
151
|
+
DO NOT manually apply root rotations (e.g., \`rotate([90, 0, 0])\`) to compensate for Godot.
|
|
152
|
+
- CRITICAL: You must use the custom OpenSCAD glTF extensions for PBR materials (e.g., \`roughness\`, \`metalness\`, \`emissive\`) and Skeletal Animations (\`armature()\`, \`bone()\`). The rules and syntax for these features are provided below:
|
|
153
|
+
|
|
154
|
+
=== OPENSCAD SYNTAX RULES ===
|
|
155
|
+
${promptRules}
|
|
156
|
+
=============================
|
|
157
|
+
|
|
158
|
+
2. Godot 4 Project Files:
|
|
159
|
+
- Create the necessary GDScript (\`.gd\`) and scene (\`.tscn\`) files to implement the game logic, responsive player input controls, and a core gameplay loop.
|
|
160
|
+
- The scenes should directly instance the generated \`.scad\` files (the provided addon will handle importing them as 3D scenes).
|
|
161
|
+
- Generate a \`project.godot\` file. It must configure the project and automatically enable the \`scad_importer\` plugin.
|
|
162
|
+
- Generate a \`.gitignore\` file that ignores the \`.godot/\` folder.
|
|
163
|
+
- Generate a \`README.md\` file that documents the project, gameplay mechanics, and controls.
|
|
164
|
+
|
|
165
|
+
3. Delivery Format (Single Node.js Script):
|
|
166
|
+
- Output exactly ONE self-contained Node.js script. Do not output manual setup instructions.
|
|
167
|
+
- 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.
|
|
168
|
+
- When executed, this script must programmatically create the entire project directory structure and write all the files to disk using the \`fs\` module.
|
|
169
|
+
- The script must embed and write:
|
|
170
|
+
- Your generated \`.scad\` game assets.
|
|
171
|
+
- Your generated Godot project files.
|
|
172
|
+
- The exact source code of the provided \`addons/scad_importer/*\` files, placed in their correct respective paths.
|
|
173
|
+
- Ensure all string file contents inside the Node.js script are properly escaped.`;
|
|
174
|
+
|
|
175
|
+
// 5. Gather Addon Files content
|
|
176
|
+
const addonDir = path.join(DIR, "godot", "addons", "scad_importer");
|
|
177
|
+
let addonFiles = [];
|
|
178
|
+
try {
|
|
179
|
+
if (fs.existsSync(addonDir)) {
|
|
180
|
+
const files = fs.readdirSync(addonDir);
|
|
181
|
+
for (const file of files) {
|
|
182
|
+
const fullPath = path.join(addonDir, file);
|
|
183
|
+
if (fs.statSync(fullPath).isFile()) {
|
|
184
|
+
addonFiles.push(fullPath);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
} catch (e) {
|
|
189
|
+
console.error("Warning: Could not read addon directory.", e);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 6. Format the unified system instructions clipboard output
|
|
193
|
+
let systemClipboardOutput = "";
|
|
194
|
+
|
|
195
|
+
systemClipboardOutput += `### SYSTEM_PROMPT\n---\n\`\`\`\n${systemPrompt}\n\`\`\`\n\n`;
|
|
196
|
+
|
|
197
|
+
for (const file of addonFiles) {
|
|
198
|
+
try {
|
|
199
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
200
|
+
// Format to use relative paths and force forward slashes for LLM clarity
|
|
201
|
+
const relativePath = path.relative(DIR, file).replace(/\\/g, "/");
|
|
202
|
+
systemClipboardOutput += `### ${relativePath}\n---\n\`\`\`\n${content}\n\`\`\`\n\n`;
|
|
203
|
+
} catch (e) {
|
|
204
|
+
console.error(`Warning: Skipping '${file}'. It is not a readable file.`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
systemClipboardOutput = systemClipboardOutput.trimEnd() + "\n";
|
|
209
|
+
|
|
210
|
+
// 7. Format the input request output
|
|
211
|
+
const inputRequestOutput = `Input Task:\nDesign and implement a Godot 4 project for the following game concept: "${task}"`;
|
|
212
|
+
|
|
213
|
+
// 8. Write to System Clipboard (Part 1: System Instructions)
|
|
214
|
+
try {
|
|
215
|
+
await writeToClipboard(systemClipboardOutput);
|
|
216
|
+
console.log("✔️ System instructions have been copied to the clipboard.");
|
|
217
|
+
} catch (err) {
|
|
218
|
+
console.error(
|
|
219
|
+
"Error: Failed to copy system instructions to the clipboard.",
|
|
220
|
+
);
|
|
221
|
+
console.error(err.message);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 9. Await user confirmation
|
|
226
|
+
await waitForEnter(
|
|
227
|
+
"Please paste the system instructions into your LLM, then press ENTER to copy your input request...",
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
// 10. Write to System Clipboard (Part 2: Input Request)
|
|
231
|
+
try {
|
|
232
|
+
await writeToClipboard(inputRequestOutput);
|
|
233
|
+
console.log(
|
|
234
|
+
"✔️ Input request has been copied to the clipboard. You can now paste it into your LLM.",
|
|
235
|
+
);
|
|
236
|
+
} catch (err) {
|
|
237
|
+
console.error("Error: Failed to copy input request to the clipboard.");
|
|
238
|
+
console.error(err.message);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Execute and handle unhandled runtime errors
|
|
244
|
+
main().catch((err) => {
|
|
245
|
+
console.error("An unexpected error occurred:", err);
|
|
246
|
+
process.exit(1);
|
|
247
|
+
});
|