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.
Files changed (2) hide show
  1. package/dist/index.js +149 -133
  2. 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 /run /exit\n');
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 created yet.\n');
54
+ console.log(' No files yet.\n');
89
55
  }
90
56
  else {
91
- console.log(' Created files:');
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(' Commands: /help /clear /files /run <cmd> /exit\n');
65
+ console.log(' /help /clear /files /exit\n');
100
66
  continue;
101
67
  }
102
- if (trimmed.startsWith('/run ')) {
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
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
- 3. Create ALL necessary files for a complete, working project
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
- :root { --bg: #0a0a0f; }
146
- ...
94
+ /* ALL CSS */
147
95
  END FILE
148
96
 
149
97
  FILENAME: script.js
150
- import * as THREE from 'three';
151
- ...
98
+ // ALL JavaScript
152
99
  END FILE
153
100
 
154
- COMMAND: npm install three
155
- COMMAND: npm run build`;
156
- const messages = [
157
- { role: 'system', content: systemPrompt },
158
- ...conversationHistory,
159
- { role: 'user', content: trimmed },
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(), 180000);
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
- stream: false,
125
+ messages: [
126
+ { role: 'system', content: systemPrompt },
127
+ { role: 'user', content: trimmed },
128
+ ],
129
+ stream: true,
177
130
  max_tokens: 16000,
178
- temperature: 0.2,
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
- const data = await res.json();
188
- const content = data.choices?.[0]?.message?.content || '';
189
- if (!content) {
190
- console.log(' ⚠ No response from model. Try again.\n');
191
- continue;
192
- }
193
- // Parse and create files
194
- const files = parseCodeBlocks(content);
195
- const commands = extractCommands(content);
196
- if (files.length > 0) {
197
- console.log(` 📁 Creating ${files.length} file(s)...\n`);
198
- for (const file of files) {
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 filePath = join(process.cwd(), file.path);
201
- const dir = dirname(resolve(filePath));
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 (e) {
209
- console.log(` ✗ ${file.path}: ${e.message}`);
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
- // Execute commands
214
- if (commands.length > 0) {
215
- console.log(`\n ⚙ Running ${commands.length} command(s)...\n`);
216
- for (const cmd of commands) {
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
- console.log(` $ ${cmd}`);
219
- const out = execSync(cmd, { cwd: process.cwd(), encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 120000 });
220
- console.log(` ✓ ${out.slice(0, 200)}`);
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
- // Show any text output from the model
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)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumina-code-agent",
3
- "version": "1.6.4",
3
+ "version": "1.6.6",
4
4
  "description": "Lumina Code - AI coding agent",
5
5
  "license": "MIT",
6
6
  "type": "module",