lumina-code-agent 1.6.5 → 1.6.7
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/index.js +101 -128
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// @ts-nocheck
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
4
4
|
import { homedir } from 'os';
|
|
5
|
-
import { join, dirname, resolve } from 'path';
|
|
5
|
+
import { join, dirname, resolve, extname } from 'path';
|
|
6
6
|
import { createInterface } from 'readline';
|
|
7
7
|
const CONFIG_DIR = join(homedir(), '.lumina');
|
|
8
8
|
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
@@ -34,7 +34,38 @@ async function onboarding() {
|
|
|
34
34
|
saveConfig({ openrouterKey: apiKey.trim(), userName: name.trim() || 'User' });
|
|
35
35
|
console.log(' ✓ Ready!\n');
|
|
36
36
|
}
|
|
37
|
-
// ──
|
|
37
|
+
// ── Extract files from complete model output ────────────────────────
|
|
38
|
+
function extractFiles(text) {
|
|
39
|
+
const files = [];
|
|
40
|
+
// Match FILENAME: path\n...content...END FILE
|
|
41
|
+
const fileRegex = /FILENAME:\s*([^\n]+)\n([\s\S]*?)END FILE/g;
|
|
42
|
+
let match;
|
|
43
|
+
while ((match = fileRegex.exec(text)) !== null) {
|
|
44
|
+
const rawPath = match[1].trim();
|
|
45
|
+
const content = match[2].trim();
|
|
46
|
+
// Skip if it's a directory path (no extension or ends with /)
|
|
47
|
+
if (!rawPath || rawPath.endsWith('/') || !extname(rawPath))
|
|
48
|
+
continue;
|
|
49
|
+
// Skip if path contains invalid chars
|
|
50
|
+
if (rawPath.includes('<') || rawPath.includes('>') || rawPath.includes('"'))
|
|
51
|
+
continue;
|
|
52
|
+
files.push({ path: rawPath, content });
|
|
53
|
+
}
|
|
54
|
+
// Also match ```filename ... ``` code blocks
|
|
55
|
+
const blockRegex = /```([\w./\-_]+)\n([\s\S]*?)```/g;
|
|
56
|
+
while ((match = blockRegex.exec(text)) !== null) {
|
|
57
|
+
const rawPath = match[1].trim();
|
|
58
|
+
const content = match[2].trim();
|
|
59
|
+
if (!rawPath || rawPath.endsWith('/') || !extname(rawPath))
|
|
60
|
+
continue;
|
|
61
|
+
// Skip common non-file patterns
|
|
62
|
+
if (rawPath.startsWith('http') || rawPath.includes('node_modules'))
|
|
63
|
+
continue;
|
|
64
|
+
files.push({ path: rawPath, content });
|
|
65
|
+
}
|
|
66
|
+
return files;
|
|
67
|
+
}
|
|
68
|
+
// ── Chat Loop ───────────────────────────────────────────────────────
|
|
38
69
|
async function chat(config) {
|
|
39
70
|
console.log(' Type what you want to build. I\'ll handle the rest.');
|
|
40
71
|
console.log(' Commands: /help /clear /files /exit\n');
|
|
@@ -66,23 +97,44 @@ async function chat(config) {
|
|
|
66
97
|
console.log(' /help /clear /files /exit\n');
|
|
67
98
|
continue;
|
|
68
99
|
}
|
|
69
|
-
const systemPrompt = `You are LUMINA CODE.
|
|
100
|
+
const systemPrompt = `You are LUMINA CODE. You create COMPLETE production-grade websites.
|
|
101
|
+
|
|
102
|
+
MANDATORY OUTPUT FORMAT — You MUST use this for EVERY file:
|
|
70
103
|
|
|
71
|
-
|
|
104
|
+
FILENAME: index.html
|
|
105
|
+
<!DOCTYPE html>
|
|
106
|
+
<html lang="en">
|
|
107
|
+
<head>
|
|
108
|
+
<meta charset="UTF-8">
|
|
109
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
110
|
+
<title>Title</title>
|
|
111
|
+
<link rel="stylesheet" href="style.css">
|
|
112
|
+
</head>
|
|
113
|
+
<body>
|
|
114
|
+
<!-- Complete HTML -->
|
|
115
|
+
<script src="script.js"></script>
|
|
116
|
+
</body>
|
|
117
|
+
</html>
|
|
118
|
+
END FILE
|
|
72
119
|
|
|
73
|
-
FILENAME:
|
|
74
|
-
|
|
120
|
+
FILENAME: style.css
|
|
121
|
+
/* Complete CSS */
|
|
75
122
|
END FILE
|
|
76
123
|
|
|
77
|
-
|
|
78
|
-
|
|
124
|
+
FILENAME: script.js
|
|
125
|
+
// Complete JavaScript
|
|
126
|
+
END FILE
|
|
79
127
|
|
|
80
|
-
RULES:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
128
|
+
CRITICAL RULES:
|
|
129
|
+
1. ALWAYS start with FILENAME: index.html
|
|
130
|
+
2. Output COMPLETE files — every line, no truncation
|
|
131
|
+
3. Do NOT describe what you will do — JUST DO IT
|
|
132
|
+
4. Do NOT output any text before FILENAME:
|
|
133
|
+
5. Do NOT use markdown code blocks — ONLY FILENAME: ... END FILE
|
|
134
|
+
6. Create ALL files needed for a complete working project
|
|
135
|
+
7. Use Three.js from CDN: https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js
|
|
136
|
+
8. Use modern CSS (grid, flexbox, custom properties)
|
|
137
|
+
9. No placeholders, no TODOs, no lorem ipsum
|
|
86
138
|
|
|
87
139
|
Working directory: ${process.cwd()}`;
|
|
88
140
|
console.log(' ⏳ Generating...\n');
|
|
@@ -103,9 +155,9 @@ Working directory: ${process.cwd()}`;
|
|
|
103
155
|
{ role: 'system', content: systemPrompt },
|
|
104
156
|
{ role: 'user', content: trimmed },
|
|
105
157
|
],
|
|
106
|
-
stream:
|
|
158
|
+
stream: false,
|
|
107
159
|
max_tokens: 16000,
|
|
108
|
-
temperature: 0.
|
|
160
|
+
temperature: 0.1,
|
|
109
161
|
}),
|
|
110
162
|
signal: controller.signal,
|
|
111
163
|
});
|
|
@@ -114,119 +166,41 @@ Working directory: ${process.cwd()}`;
|
|
|
114
166
|
const err = await res.text().catch(() => '');
|
|
115
167
|
throw new Error(`API error ${res.status}: ${err.slice(0, 200)}`);
|
|
116
168
|
}
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
let currentContent = '';
|
|
123
|
-
let inFile = false;
|
|
124
|
-
let fileCount = 0;
|
|
125
|
-
while (true) {
|
|
126
|
-
const { done, value } = await reader.read();
|
|
127
|
-
if (done)
|
|
128
|
-
break;
|
|
129
|
-
buffer += decoder.decode(value, { stream: true });
|
|
130
|
-
const lines = buffer.split('\n');
|
|
131
|
-
buffer = lines.pop(); // Keep incomplete line
|
|
132
|
-
for (const line of lines) {
|
|
133
|
-
if (!line.startsWith('data: '))
|
|
134
|
-
continue;
|
|
135
|
-
const data = line.slice(6).trim();
|
|
136
|
-
if (data === '[DONE]')
|
|
137
|
-
continue;
|
|
138
|
-
try {
|
|
139
|
-
const parsed = JSON.parse(data);
|
|
140
|
-
const delta = parsed.choices?.[0]?.delta?.content;
|
|
141
|
-
if (!delta)
|
|
142
|
-
continue;
|
|
143
|
-
// Process the delta character by character for file detection
|
|
144
|
-
for (const char of delta) {
|
|
145
|
-
if (!inFile) {
|
|
146
|
-
// Check for FILENAME: pattern
|
|
147
|
-
if (currentFile === null) {
|
|
148
|
-
// Accumulate to check for FILENAME:
|
|
149
|
-
if (!currentContent) {
|
|
150
|
-
if (char === 'F')
|
|
151
|
-
currentContent = 'F';
|
|
152
|
-
else if (currentContent === 'F' && char === 'I')
|
|
153
|
-
currentContent = 'FI';
|
|
154
|
-
else if (currentContent === 'FI' && char === 'L')
|
|
155
|
-
currentContent = 'FIL';
|
|
156
|
-
else if (currentContent === 'FIL' && char === 'E')
|
|
157
|
-
currentContent = 'FILE';
|
|
158
|
-
else if (currentContent === 'FILE' && char === 'N')
|
|
159
|
-
currentContent = 'FILEN';
|
|
160
|
-
else if (currentContent === 'FILEN' && char === 'A')
|
|
161
|
-
currentContent = 'FILENA';
|
|
162
|
-
else if (currentContent === 'FILENA' && char === 'M')
|
|
163
|
-
currentContent = 'FILENAM';
|
|
164
|
-
else if (currentContent === 'FILENAM' && char === 'E')
|
|
165
|
-
currentContent = 'FILENAME';
|
|
166
|
-
else if (currentContent === 'FILENAME' && char === ':') {
|
|
167
|
-
currentFile = '';
|
|
168
|
-
currentContent = '';
|
|
169
|
-
process.stdout.write(' 📄 Creating: ');
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
// Not a FILENAME: pattern, just output
|
|
173
|
-
if (currentContent) {
|
|
174
|
-
process.stdout.write(currentContent + char);
|
|
175
|
-
currentContent = '';
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
process.stdout.write(char);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
// We're inside a filename or file content
|
|
185
|
-
if (char === '\n' && !inFile) {
|
|
186
|
-
// End of filename line
|
|
187
|
-
const filename = currentFile.trim();
|
|
188
|
-
currentFile = filename;
|
|
189
|
-
inFile = true;
|
|
190
|
-
currentContent = '';
|
|
191
|
-
console.log(` ✓ ${filename}`);
|
|
192
|
-
fileCount++;
|
|
193
|
-
}
|
|
194
|
-
else if (inFile) {
|
|
195
|
-
currentContent += char;
|
|
196
|
-
// Check for END FILE
|
|
197
|
-
if (currentContent.endsWith('END FILE')) {
|
|
198
|
-
const fileContent = currentContent.slice(0, -8).trim();
|
|
199
|
-
try {
|
|
200
|
-
const filePath = join(process.cwd(), currentFile);
|
|
201
|
-
const dir = dirname(resolve(filePath));
|
|
202
|
-
if (!existsSync(dir))
|
|
203
|
-
mkdirSync(dir, { recursive: true });
|
|
204
|
-
writeFileSync(filePath, fileContent, 'utf-8');
|
|
205
|
-
createdFiles.add(currentFile);
|
|
206
|
-
console.log(` ✓ Saved: ${currentFile} (${fileContent.length} chars)`);
|
|
207
|
-
}
|
|
208
|
-
catch (e) {
|
|
209
|
-
console.log(` ✗ Error saving ${currentFile}: ${e.message}`);
|
|
210
|
-
}
|
|
211
|
-
currentFile = null;
|
|
212
|
-
currentContent = '';
|
|
213
|
-
inFile = false;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
currentFile += char;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
catch (e) {
|
|
224
|
-
// Skip malformed JSON chunks
|
|
225
|
-
}
|
|
226
|
-
}
|
|
169
|
+
const data = await res.json();
|
|
170
|
+
const content = data.choices?.[0]?.message?.content || '';
|
|
171
|
+
if (!content) {
|
|
172
|
+
console.log(' ⚠ No response from model.\n');
|
|
173
|
+
continue;
|
|
227
174
|
}
|
|
228
|
-
|
|
175
|
+
// Show the raw output for debugging
|
|
176
|
+
console.log(' 📝 Model output preview:');
|
|
177
|
+
console.log(' ' + content.slice(0, 200).replace(/\n/g, '\n '));
|
|
229
178
|
console.log('');
|
|
179
|
+
// Extract and create files
|
|
180
|
+
const files = extractFiles(content);
|
|
181
|
+
if (files.length === 0) {
|
|
182
|
+
console.log(' ⚠ No files detected in output. The model may not have used FILENAME: format.');
|
|
183
|
+
console.log(' Here is the full output:\n');
|
|
184
|
+
console.log(content);
|
|
185
|
+
console.log('');
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
console.log(` 📁 Creating ${files.length} file(s)...\n`);
|
|
189
|
+
for (const file of files) {
|
|
190
|
+
try {
|
|
191
|
+
const filePath = join(process.cwd(), file.path);
|
|
192
|
+
const dir = dirname(resolve(filePath));
|
|
193
|
+
if (!existsSync(dir))
|
|
194
|
+
mkdirSync(dir, { recursive: true });
|
|
195
|
+
writeFileSync(filePath, file.content, 'utf-8');
|
|
196
|
+
createdFiles.add(file.path);
|
|
197
|
+
console.log(` ✓ ${file.path} (${file.content.length} chars)`);
|
|
198
|
+
}
|
|
199
|
+
catch (e) {
|
|
200
|
+
console.log(` ✗ ${file.path}: ${e.message}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
console.log(`\n 📊 Total files: ${createdFiles.size}\n`);
|
|
230
204
|
}
|
|
231
205
|
catch (e) {
|
|
232
206
|
console.log(` ⚠ Error: ${e.message}\n`);
|
|
@@ -234,7 +208,6 @@ Working directory: ${process.cwd()}`;
|
|
|
234
208
|
}
|
|
235
209
|
rl.close();
|
|
236
210
|
}
|
|
237
|
-
// ── Main ────────────────────────────────────────────────────────────
|
|
238
211
|
const config = loadConfig();
|
|
239
212
|
if (!config?.openrouterKey) {
|
|
240
213
|
onboarding().then(() => chat(loadConfig()).then(() => process.exit(0)));
|