lumina-code-agent 1.6.4 → 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.
Files changed (2) hide show
  1. package/dist/index.js +130 -143
  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,11 @@ 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 ───────────────────────────────────────────────────────
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 /run /exit\n');
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 created yet.\n');
55
+ console.log(' No files yet.\n');
89
56
  }
90
57
  else {
91
- console.log(' Created files:');
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(' Commands: /help /clear /files /run <cmd> /exit\n');
66
+ console.log(' /help /clear /files /exit\n');
100
67
  continue;
101
68
  }
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
69
+ const systemPrompt = `You are LUMINA CODE. Create COMPLETE production-grade websites.
123
70
 
124
- 2. Output shell commands using:
125
- COMMAND: npm install something
126
- COMMAND: npm run build
71
+ OUTPUT FORMAT Use this exact format for every file:
127
72
 
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
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
- FILENAME: style.css
145
- :root { --bg: #0a0a0f; }
146
- ...
147
- END FILE
77
+ For commands:
78
+ COMMAND: npm install something
148
79
 
149
- FILENAME: script.js
150
- import * as THREE from 'three';
151
- ...
152
- END FILE
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
- 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
- ];
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(), 180000);
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
- stream: false,
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
- 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) {
199
- 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)`);
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
- 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)}`);
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
- console.log(` ✗ ${e.message}`);
224
+ // Skip malformed JSON chunks
224
225
  }
225
226
  }
226
227
  }
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}`);
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`);
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.5",
4
4
  "description": "Lumina Code - AI coding agent",
5
5
  "license": "MIT",
6
6
  "type": "module",