lumina-code-agent 1.6.4 → 1.6.6
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 +149 -133
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,7 +4,6 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
|
4
4
|
import { homedir } from 'os';
|
|
5
5
|
import { join, dirname, resolve } from 'path';
|
|
6
6
|
import { createInterface } from 'readline';
|
|
7
|
-
import { execSync } from 'child_process';
|
|
8
7
|
const CONFIG_DIR = join(homedir(), '.lumina');
|
|
9
8
|
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
10
9
|
function loadConfig() {
|
|
@@ -35,42 +34,10 @@ async function onboarding() {
|
|
|
35
34
|
saveConfig({ openrouterKey: apiKey.trim(), userName: name.trim() || 'User' });
|
|
36
35
|
console.log(' ✓ Ready!\n');
|
|
37
36
|
}
|
|
38
|
-
// ── Parse code blocks from model output ─────────────────────────────
|
|
39
|
-
function parseCodeBlocks(text) {
|
|
40
|
-
const files = [];
|
|
41
|
-
// Match ```filename ... ``` blocks
|
|
42
|
-
const blockRegex = /```([\w./\-_]+)?\s*\n([\s\S]*?)```/g;
|
|
43
|
-
let match;
|
|
44
|
-
while ((match = blockRegex.exec(text)) !== null) {
|
|
45
|
-
const filename = match[1]?.trim();
|
|
46
|
-
const content = match[2].trim();
|
|
47
|
-
if (filename && content) {
|
|
48
|
-
files.push({ path: filename, content });
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
// Also match FILE: path ... END FILE blocks
|
|
52
|
-
const fileRegex = /FILE:\s*([\w./\-_]+)\n([\s\S]*?)END FILE/g;
|
|
53
|
-
while ((match = fileRegex.exec(text)) !== null) {
|
|
54
|
-
files.push({ path: match[1].trim(), content: match[2].trim() });
|
|
55
|
-
}
|
|
56
|
-
return files;
|
|
57
|
-
}
|
|
58
|
-
// ── Execute shell commands from model output ────────────────────────
|
|
59
|
-
function extractCommands(text) {
|
|
60
|
-
const commands = [];
|
|
61
|
-
const cmdRegex = /COMMAND:\s*(.+)/g;
|
|
62
|
-
let match;
|
|
63
|
-
while ((match = cmdRegex.exec(text)) !== null) {
|
|
64
|
-
commands.push(match[1].trim());
|
|
65
|
-
}
|
|
66
|
-
return commands;
|
|
67
|
-
}
|
|
68
|
-
// ── Chat Loop ───────────────────────────────────────────────────────
|
|
69
37
|
async function chat(config) {
|
|
70
38
|
console.log(' Type what you want to build. I\'ll handle the rest.');
|
|
71
|
-
console.log(' Commands: /help /clear /files /
|
|
39
|
+
console.log(' Commands: /help /clear /files /exit\n');
|
|
72
40
|
const createdFiles = new Set();
|
|
73
|
-
let conversationHistory = [];
|
|
74
41
|
while (true) {
|
|
75
42
|
const input = await ask(' > ');
|
|
76
43
|
const trimmed = input.trim();
|
|
@@ -79,16 +46,15 @@ async function chat(config) {
|
|
|
79
46
|
if (trimmed === '/exit' || trimmed === '/quit')
|
|
80
47
|
break;
|
|
81
48
|
if (trimmed === '/clear') {
|
|
82
|
-
conversationHistory = [];
|
|
83
49
|
console.log(' ✓ Cleared.\n');
|
|
84
50
|
continue;
|
|
85
51
|
}
|
|
86
52
|
if (trimmed === '/files') {
|
|
87
53
|
if (createdFiles.size === 0) {
|
|
88
|
-
console.log(' No files
|
|
54
|
+
console.log(' No files yet.\n');
|
|
89
55
|
}
|
|
90
56
|
else {
|
|
91
|
-
console.log('
|
|
57
|
+
console.log(' Files:');
|
|
92
58
|
for (const f of createdFiles)
|
|
93
59
|
console.log(` ${f}`);
|
|
94
60
|
console.log('');
|
|
@@ -96,72 +62,56 @@ async function chat(config) {
|
|
|
96
62
|
continue;
|
|
97
63
|
}
|
|
98
64
|
if (trimmed === '/help') {
|
|
99
|
-
console.log('
|
|
65
|
+
console.log(' /help /clear /files /exit\n');
|
|
100
66
|
continue;
|
|
101
67
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
console.log(` Running: ${cmd}`);
|
|
106
|
-
const out = execSync(cmd, { cwd: process.cwd(), encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 120000 });
|
|
107
|
-
console.log(` ${out.slice(0, 500)}`);
|
|
108
|
-
}
|
|
109
|
-
catch (e) {
|
|
110
|
-
console.log(` Error: ${e.message}`);
|
|
111
|
-
}
|
|
112
|
-
console.log('');
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
// Build the prompt
|
|
116
|
-
const systemPrompt = `You are LUMINA CODE — an elite AI coding agent. You create COMPLETE, production-grade websites and apps.
|
|
117
|
-
|
|
118
|
-
CRITICAL RULES:
|
|
119
|
-
1. Output COMPLETE files using this exact format:
|
|
120
|
-
FILENAME: path/to/file.ext
|
|
121
|
-
[complete file content]
|
|
122
|
-
END FILE
|
|
123
|
-
|
|
124
|
-
2. Output shell commands using:
|
|
125
|
-
COMMAND: npm install something
|
|
126
|
-
COMMAND: npm run build
|
|
68
|
+
// Ultra-explicit system prompt
|
|
69
|
+
const systemPrompt = `You are LUMINA CODE. You MUST output files using this EXACT format. Do NOT describe what you will do. DO IT.
|
|
127
70
|
|
|
128
|
-
|
|
129
|
-
4. Use modern HTML/CSS/JS, Three.js for 3D, CSS animations
|
|
130
|
-
5. No placeholders, no TODOs, no lorem ipsum
|
|
131
|
-
6. Beautiful, cinematic, production-quality code
|
|
132
|
-
7. Each file should be COMPLETE — not snippets
|
|
71
|
+
OUTPUT THIS EXACT FORMAT — NO EXCEPTIONS:
|
|
133
72
|
|
|
134
|
-
Working directory: ${process.cwd()}
|
|
135
|
-
|
|
136
|
-
Example output:
|
|
137
73
|
FILENAME: index.html
|
|
138
74
|
<!DOCTYPE html>
|
|
139
|
-
<html>
|
|
140
|
-
|
|
75
|
+
<html lang="en">
|
|
76
|
+
<head>
|
|
77
|
+
<meta charset="UTF-8">
|
|
78
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
79
|
+
<title>Page</title>
|
|
80
|
+
<style>
|
|
81
|
+
/* ALL CSS here */
|
|
82
|
+
</style>
|
|
83
|
+
</head>
|
|
84
|
+
<body>
|
|
85
|
+
<!-- ALL HTML here -->
|
|
86
|
+
<script>
|
|
87
|
+
// ALL JavaScript here
|
|
88
|
+
</script>
|
|
89
|
+
</body>
|
|
141
90
|
</html>
|
|
142
91
|
END FILE
|
|
143
92
|
|
|
144
93
|
FILENAME: style.css
|
|
145
|
-
|
|
146
|
-
...
|
|
94
|
+
/* ALL CSS */
|
|
147
95
|
END FILE
|
|
148
96
|
|
|
149
97
|
FILENAME: script.js
|
|
150
|
-
|
|
151
|
-
...
|
|
98
|
+
// ALL JavaScript
|
|
152
99
|
END FILE
|
|
153
100
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
101
|
+
RULES:
|
|
102
|
+
- Start IMMEDIATELY with FILENAME: index.html
|
|
103
|
+
- Output COMPLETE file contents — every single line
|
|
104
|
+
- Do NOT output any text before FILENAME:
|
|
105
|
+
- Do NOT describe what you will do
|
|
106
|
+
- Do NOT output markdown code blocks
|
|
107
|
+
- ONLY use FILENAME: ... END FILE format
|
|
108
|
+
- Create ALL files needed for a complete working project
|
|
109
|
+
|
|
110
|
+
Working directory: ${process.cwd()}`;
|
|
161
111
|
console.log(' ⏳ Generating...\n');
|
|
162
112
|
try {
|
|
163
113
|
const controller = new AbortController();
|
|
164
|
-
const timeout = setTimeout(() => controller.abort(),
|
|
114
|
+
const timeout = setTimeout(() => controller.abort(), 300000);
|
|
165
115
|
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
166
116
|
method: 'POST',
|
|
167
117
|
headers: {
|
|
@@ -172,10 +122,13 @@ COMMAND: npm run build`;
|
|
|
172
122
|
},
|
|
173
123
|
body: JSON.stringify({
|
|
174
124
|
model: 'openrouter/owl-alpha',
|
|
175
|
-
messages
|
|
176
|
-
|
|
125
|
+
messages: [
|
|
126
|
+
{ role: 'system', content: systemPrompt },
|
|
127
|
+
{ role: 'user', content: trimmed },
|
|
128
|
+
],
|
|
129
|
+
stream: true,
|
|
177
130
|
max_tokens: 16000,
|
|
178
|
-
temperature: 0.
|
|
131
|
+
temperature: 0.1,
|
|
179
132
|
}),
|
|
180
133
|
signal: controller.signal,
|
|
181
134
|
});
|
|
@@ -184,62 +137,126 @@ COMMAND: npm run build`;
|
|
|
184
137
|
const err = await res.text().catch(() => '');
|
|
185
138
|
throw new Error(`API error ${res.status}: ${err.slice(0, 200)}`);
|
|
186
139
|
}
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
140
|
+
// Stream and parse files in real-time
|
|
141
|
+
const reader = res.body.getReader();
|
|
142
|
+
const decoder = new TextDecoder();
|
|
143
|
+
let buffer = '';
|
|
144
|
+
let currentFile = null;
|
|
145
|
+
let currentContent = '';
|
|
146
|
+
let inFile = false;
|
|
147
|
+
let fileCount = 0;
|
|
148
|
+
let outputBuffer = '';
|
|
149
|
+
while (true) {
|
|
150
|
+
const { done, value } = await reader.read();
|
|
151
|
+
if (done)
|
|
152
|
+
break;
|
|
153
|
+
buffer += decoder.decode(value, { stream: true });
|
|
154
|
+
const lines = buffer.split('\n');
|
|
155
|
+
buffer = lines.pop();
|
|
156
|
+
for (const line of lines) {
|
|
157
|
+
if (!line.startsWith('data: '))
|
|
158
|
+
continue;
|
|
159
|
+
const data = line.slice(6).trim();
|
|
160
|
+
if (data === '[DONE]')
|
|
161
|
+
continue;
|
|
162
|
+
let delta = '';
|
|
199
163
|
try {
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
if (!existsSync(dir))
|
|
203
|
-
mkdirSync(dir, { recursive: true });
|
|
204
|
-
writeFileSync(filePath, file.content, 'utf-8');
|
|
205
|
-
createdFiles.add(file.path);
|
|
206
|
-
console.log(` ✓ ${file.path} (${file.content.length} chars)`);
|
|
164
|
+
const parsed = JSON.parse(data);
|
|
165
|
+
delta = parsed.choices?.[0]?.delta?.content || '';
|
|
207
166
|
}
|
|
208
|
-
catch
|
|
209
|
-
|
|
167
|
+
catch {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (!delta)
|
|
171
|
+
continue;
|
|
172
|
+
// Process delta for file detection
|
|
173
|
+
for (let i = 0; i < delta.length; i++) {
|
|
174
|
+
const char = delta[i];
|
|
175
|
+
if (!inFile) {
|
|
176
|
+
// Look for FILENAME:
|
|
177
|
+
outputBuffer += char;
|
|
178
|
+
if (outputBuffer.endsWith('FILENAME:')) {
|
|
179
|
+
// Found it! Extract filename on next line
|
|
180
|
+
currentFile = '';
|
|
181
|
+
inFile = true;
|
|
182
|
+
currentContent = '';
|
|
183
|
+
outputBuffer = '';
|
|
184
|
+
process.stdout.write(' 📄 ');
|
|
185
|
+
}
|
|
186
|
+
else if (outputBuffer.length > 20) {
|
|
187
|
+
// Not matching, flush
|
|
188
|
+
process.stdout.write(outputBuffer);
|
|
189
|
+
outputBuffer = '';
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
// In filename or content
|
|
194
|
+
if (char === '\n' && !currentFile.includes('/')) {
|
|
195
|
+
// Still in filename line (no path separator yet)
|
|
196
|
+
if (currentFile.length > 0 && !currentFile.includes('.')) {
|
|
197
|
+
// This is a path separator line, keep accumulating
|
|
198
|
+
currentFile += char;
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
// End of filename
|
|
202
|
+
currentFile = currentFile.trim();
|
|
203
|
+
console.log(currentFile);
|
|
204
|
+
fileCount++;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else if (currentFile && !currentContent && char === '\n' && currentFile.includes('.')) {
|
|
208
|
+
// First newline after filename, start content
|
|
209
|
+
currentContent = '';
|
|
210
|
+
}
|
|
211
|
+
else if (currentContent !== null) {
|
|
212
|
+
currentContent += char;
|
|
213
|
+
// Check for END FILE
|
|
214
|
+
if (currentContent.endsWith('END FILE')) {
|
|
215
|
+
const fileContent = currentContent.slice(0, -8).trim();
|
|
216
|
+
try {
|
|
217
|
+
const filePath = join(process.cwd(), currentFile);
|
|
218
|
+
const dir = dirname(resolve(filePath));
|
|
219
|
+
if (!existsSync(dir))
|
|
220
|
+
mkdirSync(dir, { recursive: true });
|
|
221
|
+
writeFileSync(filePath, fileContent, 'utf-8');
|
|
222
|
+
createdFiles.add(currentFile);
|
|
223
|
+
console.log(` ✓ Saved: ${currentFile} (${fileContent.length} chars)`);
|
|
224
|
+
}
|
|
225
|
+
catch (e) {
|
|
226
|
+
console.log(` ✗ Error: ${currentFile}: ${e.message}`);
|
|
227
|
+
}
|
|
228
|
+
currentFile = null;
|
|
229
|
+
currentContent = null;
|
|
230
|
+
inFile = false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
currentFile += char;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
210
237
|
}
|
|
211
238
|
}
|
|
212
239
|
}
|
|
213
|
-
//
|
|
214
|
-
if (
|
|
215
|
-
|
|
216
|
-
|
|
240
|
+
// Handle any remaining content
|
|
241
|
+
if (inFile && currentFile && currentContent) {
|
|
242
|
+
const fileContent = currentContent.replace(/END FILE$/, '').trim();
|
|
243
|
+
if (fileContent) {
|
|
217
244
|
try {
|
|
218
|
-
|
|
219
|
-
const
|
|
220
|
-
|
|
245
|
+
const filePath = join(process.cwd(), currentFile);
|
|
246
|
+
const dir = dirname(resolve(filePath));
|
|
247
|
+
if (!existsSync(dir))
|
|
248
|
+
mkdirSync(dir, { recursive: true });
|
|
249
|
+
writeFileSync(filePath, fileContent, 'utf-8');
|
|
250
|
+
createdFiles.add(currentFile);
|
|
251
|
+
console.log(` ✓ Saved: ${currentFile} (${fileContent.length} chars)`);
|
|
221
252
|
}
|
|
222
253
|
catch (e) {
|
|
223
|
-
console.log(` ✗ ${e.message}`);
|
|
254
|
+
console.log(` ✗ Error: ${currentFile}: ${e.message}`);
|
|
224
255
|
}
|
|
225
256
|
}
|
|
226
257
|
}
|
|
227
|
-
|
|
228
|
-
const textOnly = content
|
|
229
|
-
.replace(/FILENAME:[\s\S]*?END FILE/g, '')
|
|
230
|
-
.replace(/COMMAND:.+/g, '')
|
|
231
|
-
.trim();
|
|
232
|
-
if (textOnly) {
|
|
233
|
-
console.log(`\n 💬 ${textOnly.slice(0, 500)}`);
|
|
234
|
-
}
|
|
235
|
-
console.log(`\n 📊 Total files created: ${createdFiles.size}`);
|
|
258
|
+
console.log(`\n 📊 Created ${fileCount} file(s) | Total: ${createdFiles.size}`);
|
|
236
259
|
console.log('');
|
|
237
|
-
// Save conversation
|
|
238
|
-
conversationHistory.push({ role: 'user', content: trimmed });
|
|
239
|
-
conversationHistory.push({ role: 'assistant', content });
|
|
240
|
-
// Keep last 10 messages
|
|
241
|
-
if (conversationHistory.length > 20)
|
|
242
|
-
conversationHistory = conversationHistory.slice(-20);
|
|
243
260
|
}
|
|
244
261
|
catch (e) {
|
|
245
262
|
console.log(` ⚠ Error: ${e.message}\n`);
|
|
@@ -247,7 +264,6 @@ COMMAND: npm run build`;
|
|
|
247
264
|
}
|
|
248
265
|
rl.close();
|
|
249
266
|
}
|
|
250
|
-
// ── Main ────────────────────────────────────────────────────────────
|
|
251
267
|
const config = loadConfig();
|
|
252
268
|
if (!config?.openrouterKey) {
|
|
253
269
|
onboarding().then(() => chat(loadConfig()).then(() => process.exit(0)));
|