lumina-code-agent 1.6.2 → 1.6.4
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 +168 -225
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @ts-nocheck
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
4
4
|
import { homedir } from 'os';
|
|
5
|
-
import { join, dirname, resolve
|
|
5
|
+
import { join, dirname, resolve } from 'path';
|
|
6
6
|
import { createInterface } from 'readline';
|
|
7
7
|
import { execSync } from 'child_process';
|
|
8
8
|
const CONFIG_DIR = join(homedir(), '.lumina');
|
|
@@ -23,125 +23,54 @@ function saveConfig(config) {
|
|
|
23
23
|
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
24
24
|
}
|
|
25
25
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
26
|
-
function ask(question)
|
|
27
|
-
return new Promise(resolve => rl.question(question, resolve));
|
|
28
|
-
}
|
|
26
|
+
function ask(q) { return new Promise(r => rl.question(q, r)); }
|
|
29
27
|
async function onboarding() {
|
|
30
|
-
console.log('');
|
|
31
|
-
console.log(' ⚡ LUMINA CODE — AI Coding Agent');
|
|
32
|
-
console.log('');
|
|
28
|
+
console.log('\n ⚡ LUMINA CODE — AI Coding Agent\n');
|
|
33
29
|
const apiKey = await ask(' Enter your OpenRouter API key: ');
|
|
34
30
|
if (!apiKey.trim() || apiKey.trim().length < 10) {
|
|
35
|
-
console.log(' ❌ Invalid
|
|
31
|
+
console.log(' ❌ Invalid key.');
|
|
36
32
|
process.exit(1);
|
|
37
33
|
}
|
|
38
34
|
const name = await ask(' Your name (optional): ');
|
|
39
35
|
saveConfig({ openrouterKey: apiKey.trim(), userName: name.trim() || 'User' });
|
|
40
|
-
console.log(' ✓ Ready
|
|
41
|
-
console.log('');
|
|
36
|
+
console.log(' ✓ Ready!\n');
|
|
42
37
|
}
|
|
43
|
-
// ──
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
case 'read_file': {
|
|
55
|
-
const p = args.path.startsWith('/') ? args.path : join(workDir, args.path);
|
|
56
|
-
if (!existsSync(p)) {
|
|
57
|
-
output = `File not found: ${args.path}`;
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
output = readFileSync(p, 'utf-8');
|
|
61
|
-
break;
|
|
62
|
-
}
|
|
63
|
-
case 'write_file': {
|
|
64
|
-
const p = args.path.startsWith('/') ? args.path : join(workDir, args.path);
|
|
65
|
-
const dir = dirname(resolve(p));
|
|
66
|
-
if (!existsSync(dir))
|
|
67
|
-
mkdirSync(dir, { recursive: true });
|
|
68
|
-
writeFileSync(p, args.content, 'utf-8');
|
|
69
|
-
output = `✓ Created ${args.path} (${args.content.length} chars)`;
|
|
70
|
-
break;
|
|
71
|
-
}
|
|
72
|
-
case 'edit_file': {
|
|
73
|
-
const p = args.path.startsWith('/') ? args.path : join(workDir, args.path);
|
|
74
|
-
let fc = readFileSync(p, 'utf-8');
|
|
75
|
-
if (!fc.includes(args.search))
|
|
76
|
-
throw new Error(`Not found: "${args.search.slice(0, 50)}"`);
|
|
77
|
-
fc = fc.replace(args.search, args.replace);
|
|
78
|
-
writeFileSync(p, fc, 'utf-8');
|
|
79
|
-
output = `✓ Edited ${args.path}`;
|
|
80
|
-
break;
|
|
81
|
-
}
|
|
82
|
-
case 'list_dir': {
|
|
83
|
-
const p = args.path || workDir;
|
|
84
|
-
const entries = readdirSync(p, { withFileTypes: true });
|
|
85
|
-
output = entries.slice(0, 50).map(e => `${e.isDirectory() ? '📁' : '📄'} ${e.name}`).join('\n');
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
case 'search_files': {
|
|
89
|
-
const results = [];
|
|
90
|
-
const search = (dir, depth) => {
|
|
91
|
-
if (depth > 5)
|
|
92
|
-
return;
|
|
93
|
-
try {
|
|
94
|
-
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
95
|
-
if (e.name.startsWith('.') || e.name === 'node_modules' || e.name === 'dist' || e.name === '.git')
|
|
96
|
-
continue;
|
|
97
|
-
const fp = join(dir, e.name);
|
|
98
|
-
if (e.isDirectory())
|
|
99
|
-
search(fp, depth + 1);
|
|
100
|
-
else if (new RegExp(args.pattern.replace(/\*/g, '.*').replace(/\?/g, '.'), 'i').test(e.name))
|
|
101
|
-
results.push(relative(workDir, fp));
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
catch { }
|
|
105
|
-
};
|
|
106
|
-
search(args.cwd || workDir, 0);
|
|
107
|
-
output = results.join('\n') || 'No files found';
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
case 'grep': {
|
|
111
|
-
const p = args.path.startsWith('/') ? args.path : join(workDir, args.path);
|
|
112
|
-
const c = readFileSync(p, 'utf-8');
|
|
113
|
-
output = c.split('\n').map((l, i) => new RegExp(args.pattern, 'gi').test(l) ? `${i + 1}: ${l}` : null).filter(Boolean).join('\n') || 'No matches';
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
116
|
-
case 'git': {
|
|
117
|
-
output = execSync(`git ${args.args}`, { cwd: args.cwd || workDir, encoding: 'utf-8', maxBuffer: 1024 * 1024 }) || '(ok)';
|
|
118
|
-
break;
|
|
119
|
-
}
|
|
120
|
-
case 'npm': {
|
|
121
|
-
const pm = existsSync(join(args.cwd || workDir, 'bun.lockb')) ? 'bun' : existsSync(join(args.cwd || workDir, 'yarn.lock')) ? 'yarn' : 'npm';
|
|
122
|
-
output = execSync(`${pm} ${args.args}`, { cwd: args.cwd || workDir, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 120000 }) || '(ok)';
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
case 'deploy': {
|
|
126
|
-
output = execSync('npx vercel deploy --prod --yes', { cwd: args.cwd || workDir, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 300000 }) || '(deployed)';
|
|
127
|
-
break;
|
|
128
|
-
}
|
|
129
|
-
default:
|
|
130
|
-
output = `Unknown tool: ${name}`;
|
|
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 });
|
|
131
49
|
}
|
|
132
50
|
}
|
|
133
|
-
|
|
134
|
-
|
|
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() });
|
|
135
55
|
}
|
|
136
|
-
return
|
|
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;
|
|
137
67
|
}
|
|
138
68
|
// ── Chat Loop ───────────────────────────────────────────────────────
|
|
139
69
|
async function chat(config) {
|
|
140
70
|
console.log(' Type what you want to build. I\'ll handle the rest.');
|
|
141
|
-
console.log(' Commands: /help /clear /files /exit');
|
|
142
|
-
console.log('');
|
|
143
|
-
let messages = [];
|
|
71
|
+
console.log(' Commands: /help /clear /files /run /exit\n');
|
|
144
72
|
const createdFiles = new Set();
|
|
73
|
+
let conversationHistory = [];
|
|
145
74
|
while (true) {
|
|
146
75
|
const input = await ask(' > ');
|
|
147
76
|
const trimmed = input.trim();
|
|
@@ -150,153 +79,170 @@ async function chat(config) {
|
|
|
150
79
|
if (trimmed === '/exit' || trimmed === '/quit')
|
|
151
80
|
break;
|
|
152
81
|
if (trimmed === '/clear') {
|
|
153
|
-
|
|
154
|
-
console.log(' ✓ Cleared
|
|
82
|
+
conversationHistory = [];
|
|
83
|
+
console.log(' ✓ Cleared.\n');
|
|
155
84
|
continue;
|
|
156
85
|
}
|
|
157
86
|
if (trimmed === '/files') {
|
|
158
87
|
if (createdFiles.size === 0) {
|
|
159
|
-
console.log(' No files created yet
|
|
88
|
+
console.log(' No files created yet.\n');
|
|
160
89
|
}
|
|
161
90
|
else {
|
|
162
91
|
console.log(' Created files:');
|
|
163
92
|
for (const f of createdFiles)
|
|
164
93
|
console.log(` ${f}`);
|
|
94
|
+
console.log('');
|
|
165
95
|
}
|
|
166
96
|
continue;
|
|
167
97
|
}
|
|
168
98
|
if (trimmed === '/help') {
|
|
169
|
-
console.log(' Commands: /help /clear /files /exit');
|
|
99
|
+
console.log(' Commands: /help /clear /files /run <cmd> /exit\n');
|
|
170
100
|
continue;
|
|
171
101
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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.
|
|
182
117
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
- Accessible (semantic HTML, ARIA, keyboard navigation)
|
|
189
|
-
- Clean architecture, modern patterns
|
|
190
|
-
- Beautiful UI (consistent spacing, typography, color)
|
|
191
|
-
- Use modern CSS (grid, flexbox, custom properties)
|
|
192
|
-
- Use Three.js or CSS 3D for 3D effects
|
|
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
|
|
193
123
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
- Emoji in code or UI
|
|
198
|
-
- var keyword (always let/const)
|
|
199
|
-
- any type in TypeScript
|
|
200
|
-
- Skipping error handling
|
|
201
|
-
- Hardcoded secrets
|
|
124
|
+
2. Output shell commands using:
|
|
125
|
+
COMMAND: npm install something
|
|
126
|
+
COMMAND: npm run build
|
|
202
127
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
|
206
133
|
|
|
207
134
|
Working directory: ${process.cwd()}
|
|
208
135
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
136
|
+
Example output:
|
|
137
|
+
FILENAME: index.html
|
|
138
|
+
<!DOCTYPE html>
|
|
139
|
+
<html>
|
|
140
|
+
...
|
|
141
|
+
</html>
|
|
142
|
+
END FILE
|
|
143
|
+
|
|
144
|
+
FILENAME: style.css
|
|
145
|
+
:root { --bg: #0a0a0f; }
|
|
146
|
+
...
|
|
147
|
+
END FILE
|
|
148
|
+
|
|
149
|
+
FILENAME: script.js
|
|
150
|
+
import * as THREE from 'three';
|
|
151
|
+
...
|
|
152
|
+
END FILE
|
|
153
|
+
|
|
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
|
+
];
|
|
161
|
+
console.log(' ⏳ Generating...\n');
|
|
162
|
+
try {
|
|
163
|
+
const controller = new AbortController();
|
|
164
|
+
const timeout = setTimeout(() => controller.abort(), 180000);
|
|
165
|
+
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
166
|
+
method: 'POST',
|
|
167
|
+
headers: {
|
|
168
|
+
'Content-Type': 'application/json',
|
|
169
|
+
Authorization: `Bearer ${config.openrouterKey}`,
|
|
170
|
+
'HTTP-Referer': 'https://luminaai.co.in',
|
|
171
|
+
'X-Title': 'Lumina Code',
|
|
172
|
+
},
|
|
173
|
+
body: JSON.stringify({
|
|
174
|
+
model: 'openrouter/owl-alpha',
|
|
175
|
+
messages,
|
|
176
|
+
stream: false,
|
|
177
|
+
max_tokens: 16000,
|
|
178
|
+
temperature: 0.2,
|
|
179
|
+
}),
|
|
180
|
+
signal: controller.signal,
|
|
181
|
+
});
|
|
182
|
+
clearTimeout(timeout);
|
|
183
|
+
if (!res.ok) {
|
|
184
|
+
const err = await res.text().catch(() => '');
|
|
185
|
+
throw new Error(`API error ${res.status}: ${err.slice(0, 200)}`);
|
|
186
|
+
}
|
|
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) {
|
|
269
199
|
try {
|
|
270
|
-
|
|
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)`);
|
|
271
207
|
}
|
|
272
|
-
catch {
|
|
273
|
-
|
|
274
|
-
console.log(` ⚙ ${toolName}(${JSON.stringify(args).slice(0, 100)})`);
|
|
275
|
-
const output = await executeTool(toolName, args, process.cwd());
|
|
276
|
-
// Track created files
|
|
277
|
-
if (toolName === 'write_file' && args.path) {
|
|
278
|
-
createdFiles.add(args.path);
|
|
279
|
-
console.log(` ✓ Created: ${args.path}`);
|
|
208
|
+
catch (e) {
|
|
209
|
+
console.log(` ✗ ${file.path}: ${e.message}`);
|
|
280
210
|
}
|
|
281
|
-
else {
|
|
282
|
-
const shortOut = output.slice(0, 150).replace(/\n/g, ' ');
|
|
283
|
-
console.log(` ✓ ${shortOut}`);
|
|
284
|
-
}
|
|
285
|
-
currentMessages.push({ role: 'tool', content: output.slice(0, 3000), tool_call_id: tc.id });
|
|
286
211
|
}
|
|
287
212
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
console.log(
|
|
213
|
+
// Execute commands
|
|
214
|
+
if (commands.length > 0) {
|
|
215
|
+
console.log(`\n ⚙ Running ${commands.length} command(s)...\n`);
|
|
216
|
+
for (const cmd of commands) {
|
|
217
|
+
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)}`);
|
|
221
|
+
}
|
|
222
|
+
catch (e) {
|
|
223
|
+
console.log(` ✗ ${e.message}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
291
226
|
}
|
|
292
|
-
|
|
293
|
-
|
|
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)}`);
|
|
294
234
|
}
|
|
235
|
+
console.log(`\n 📊 Total files created: ${createdFiles.size}`);
|
|
295
236
|
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);
|
|
296
243
|
}
|
|
297
244
|
catch (e) {
|
|
298
|
-
console.log(` ⚠ Error: ${e.message}`);
|
|
299
|
-
console.log('');
|
|
245
|
+
console.log(` ⚠ Error: ${e.message}\n`);
|
|
300
246
|
}
|
|
301
247
|
}
|
|
302
248
|
rl.close();
|
|
@@ -304,10 +250,7 @@ PARAMS: {"key": "value"}`;
|
|
|
304
250
|
// ── Main ────────────────────────────────────────────────────────────
|
|
305
251
|
const config = loadConfig();
|
|
306
252
|
if (!config?.openrouterKey) {
|
|
307
|
-
onboarding().then(() =>
|
|
308
|
-
const newConfig = loadConfig();
|
|
309
|
-
chat(newConfig).then(() => process.exit(0));
|
|
310
|
-
});
|
|
253
|
+
onboarding().then(() => chat(loadConfig()).then(() => process.exit(0)));
|
|
311
254
|
}
|
|
312
255
|
else {
|
|
313
256
|
chat(config).then(() => process.exit(0));
|