lumina-code-agent 1.6.3 → 1.6.5
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 +130 -143
- 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,11 @@ async function onboarding() {
|
|
|
35
34
|
saveConfig({ openrouterKey: apiKey.trim(), userName: name.trim() || 'User' });
|
|
36
35
|
console.log(' ✓ Ready!\n');
|
|
37
36
|
}
|
|
38
|
-
// ──
|
|
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 ───────────────────────────────────────────────────────
|
|
37
|
+
// ── Streaming chat with real-time file creation ─────────────────────
|
|
69
38
|
async function chat(config) {
|
|
70
39
|
console.log(' Type what you want to build. I\'ll handle the rest.');
|
|
71
|
-
console.log(' Commands: /help /clear /files /
|
|
40
|
+
console.log(' Commands: /help /clear /files /exit\n');
|
|
72
41
|
const createdFiles = new Set();
|
|
73
|
-
let conversationHistory = [];
|
|
74
42
|
while (true) {
|
|
75
43
|
const input = await ask(' > ');
|
|
76
44
|
const trimmed = input.trim();
|
|
@@ -79,16 +47,15 @@ async function chat(config) {
|
|
|
79
47
|
if (trimmed === '/exit' || trimmed === '/quit')
|
|
80
48
|
break;
|
|
81
49
|
if (trimmed === '/clear') {
|
|
82
|
-
conversationHistory = [];
|
|
83
50
|
console.log(' ✓ Cleared.\n');
|
|
84
51
|
continue;
|
|
85
52
|
}
|
|
86
53
|
if (trimmed === '/files') {
|
|
87
54
|
if (createdFiles.size === 0) {
|
|
88
|
-
console.log(' No files
|
|
55
|
+
console.log(' No files yet.\n');
|
|
89
56
|
}
|
|
90
57
|
else {
|
|
91
|
-
console.log('
|
|
58
|
+
console.log(' Files:');
|
|
92
59
|
for (const f of createdFiles)
|
|
93
60
|
console.log(` ${f}`);
|
|
94
61
|
console.log('');
|
|
@@ -96,72 +63,32 @@ async function chat(config) {
|
|
|
96
63
|
continue;
|
|
97
64
|
}
|
|
98
65
|
if (trimmed === '/help') {
|
|
99
|
-
console.log('
|
|
66
|
+
console.log(' /help /clear /files /exit\n');
|
|
100
67
|
continue;
|
|
101
68
|
}
|
|
102
|
-
|
|
103
|
-
const cmd = trimmed.slice(5);
|
|
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
|
|
69
|
+
const systemPrompt = `You are LUMINA CODE. Create COMPLETE production-grade websites.
|
|
123
70
|
|
|
124
|
-
|
|
125
|
-
COMMAND: npm install something
|
|
126
|
-
COMMAND: npm run build
|
|
71
|
+
OUTPUT FORMAT — Use this exact format for every file:
|
|
127
72
|
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
133
|
-
|
|
134
|
-
Working directory: ${process.cwd()}
|
|
135
|
-
|
|
136
|
-
Example output:
|
|
137
|
-
FILENAME: index.html
|
|
138
|
-
<!DOCTYPE html>
|
|
139
|
-
<html>
|
|
140
|
-
...
|
|
141
|
-
</html>
|
|
73
|
+
FILENAME: path/to/file.ext
|
|
74
|
+
[COMPLETE file content — every line, no truncation]
|
|
142
75
|
END FILE
|
|
143
76
|
|
|
144
|
-
|
|
145
|
-
:
|
|
146
|
-
...
|
|
147
|
-
END FILE
|
|
77
|
+
For commands:
|
|
78
|
+
COMMAND: npm install something
|
|
148
79
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
80
|
+
RULES:
|
|
81
|
+
- Output COMPLETE files — every single line
|
|
82
|
+
- Use Three.js for 3D, CSS animations for motion
|
|
83
|
+
- No placeholders, no TODOs, no lorem ipsum
|
|
84
|
+
- Beautiful, cinematic, production-quality
|
|
85
|
+
- Create ALL files needed for a working project
|
|
153
86
|
|
|
154
|
-
|
|
155
|
-
COMMAND: npm run build`;
|
|
156
|
-
const messages = [
|
|
157
|
-
{ role: 'system', content: systemPrompt },
|
|
158
|
-
...conversationHistory,
|
|
159
|
-
{ role: 'user', content: trimmed },
|
|
160
|
-
];
|
|
87
|
+
Working directory: ${process.cwd()}`;
|
|
161
88
|
console.log(' ⏳ Generating...\n');
|
|
162
89
|
try {
|
|
163
90
|
const controller = new AbortController();
|
|
164
|
-
const timeout = setTimeout(() => controller.abort(),
|
|
91
|
+
const timeout = setTimeout(() => controller.abort(), 300000);
|
|
165
92
|
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
166
93
|
method: 'POST',
|
|
167
94
|
headers: {
|
|
@@ -172,8 +99,11 @@ COMMAND: npm run build`;
|
|
|
172
99
|
},
|
|
173
100
|
body: JSON.stringify({
|
|
174
101
|
model: 'openrouter/owl-alpha',
|
|
175
|
-
messages
|
|
176
|
-
|
|
102
|
+
messages: [
|
|
103
|
+
{ role: 'system', content: systemPrompt },
|
|
104
|
+
{ role: 'user', content: trimmed },
|
|
105
|
+
],
|
|
106
|
+
stream: true,
|
|
177
107
|
max_tokens: 16000,
|
|
178
108
|
temperature: 0.2,
|
|
179
109
|
}),
|
|
@@ -184,62 +114,119 @@ COMMAND: npm run build`;
|
|
|
184
114
|
const err = await res.text().catch(() => '');
|
|
185
115
|
throw new Error(`API error ${res.status}: ${err.slice(0, 200)}`);
|
|
186
116
|
}
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
catch (e) {
|
|
209
|
-
console.log(` ✗ ${file.path}: ${e.message}`);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
// Execute commands
|
|
214
|
-
if (commands.length > 0) {
|
|
215
|
-
console.log(`\n ⚙ Running ${commands.length} command(s)...\n`);
|
|
216
|
-
for (const cmd of commands) {
|
|
117
|
+
// Stream the response and create files in real-time
|
|
118
|
+
const reader = res.body.getReader();
|
|
119
|
+
const decoder = new TextDecoder();
|
|
120
|
+
let buffer = '';
|
|
121
|
+
let currentFile = null;
|
|
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;
|
|
217
138
|
try {
|
|
218
|
-
|
|
219
|
-
const
|
|
220
|
-
|
|
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
|
+
}
|
|
221
222
|
}
|
|
222
223
|
catch (e) {
|
|
223
|
-
|
|
224
|
+
// Skip malformed JSON chunks
|
|
224
225
|
}
|
|
225
226
|
}
|
|
226
227
|
}
|
|
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}`);
|
|
228
|
+
console.log(`\n 📊 Created ${fileCount} file(s) total: ${createdFiles.size}`);
|
|
236
229
|
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
230
|
}
|
|
244
231
|
catch (e) {
|
|
245
232
|
console.log(` ⚠ Error: ${e.message}\n`);
|