lumina-code-agent 1.6.1 → 1.6.3
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 +187 -169
- package/package.json +15 -5
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,32 +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(' Better than Claude Code. Better than Codex.');
|
|
33
|
-
console.log('');
|
|
34
|
-
console.log(' Welcome! Let\'s get you set up.');
|
|
35
|
-
console.log('');
|
|
28
|
+
console.log('\n ⚡ LUMINA CODE — AI Coding Agent\n');
|
|
36
29
|
const apiKey = await ask(' Enter your OpenRouter API key: ');
|
|
37
30
|
if (!apiKey.trim() || apiKey.trim().length < 10) {
|
|
38
|
-
console.log(' ❌ Invalid
|
|
31
|
+
console.log(' ❌ Invalid key.');
|
|
39
32
|
process.exit(1);
|
|
40
33
|
}
|
|
41
|
-
const name = await ask('
|
|
34
|
+
const name = await ask(' Your name (optional): ');
|
|
42
35
|
saveConfig({ openrouterKey: apiKey.trim(), userName: name.trim() || 'User' });
|
|
43
|
-
console.log('');
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
console.log(' ✓ Ready!\n');
|
|
37
|
+
}
|
|
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;
|
|
46
67
|
}
|
|
68
|
+
// ── Chat Loop ───────────────────────────────────────────────────────
|
|
47
69
|
async function chat(config) {
|
|
48
70
|
console.log(' Type what you want to build. I\'ll handle the rest.');
|
|
49
|
-
console.log(' Commands: /help /clear /exit');
|
|
50
|
-
|
|
51
|
-
let
|
|
71
|
+
console.log(' Commands: /help /clear /files /run /exit\n');
|
|
72
|
+
const createdFiles = new Set();
|
|
73
|
+
let conversationHistory = [];
|
|
52
74
|
while (true) {
|
|
53
75
|
const input = await ask(' > ');
|
|
54
76
|
const trimmed = input.trim();
|
|
@@ -57,171 +79,170 @@ async function chat(config) {
|
|
|
57
79
|
if (trimmed === '/exit' || trimmed === '/quit')
|
|
58
80
|
break;
|
|
59
81
|
if (trimmed === '/clear') {
|
|
60
|
-
|
|
61
|
-
console.log(' ✓ Cleared
|
|
82
|
+
conversationHistory = [];
|
|
83
|
+
console.log(' ✓ Cleared.\n');
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (trimmed === '/files') {
|
|
87
|
+
if (createdFiles.size === 0) {
|
|
88
|
+
console.log(' No files created yet.\n');
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
console.log(' Created files:');
|
|
92
|
+
for (const f of createdFiles)
|
|
93
|
+
console.log(` ${f}`);
|
|
94
|
+
console.log('');
|
|
95
|
+
}
|
|
62
96
|
continue;
|
|
63
97
|
}
|
|
64
98
|
if (trimmed === '/help') {
|
|
65
|
-
console.log(' Commands: /help /clear /exit');
|
|
99
|
+
console.log(' Commands: /help /clear /files /run <cmd> /exit\n');
|
|
66
100
|
continue;
|
|
67
101
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
{
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
let iterations = 0;
|
|
84
|
-
const maxIterations = 30;
|
|
85
|
-
let currentMessages = [...messages];
|
|
86
|
-
while (iterations < maxIterations) {
|
|
87
|
-
iterations++;
|
|
88
|
-
process.stdout.write(` ⏳ Working (step ${iterations})...\r`);
|
|
89
|
-
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
90
|
-
method: 'POST',
|
|
91
|
-
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${config.openrouterKey}`, 'HTTP-Referer': 'https://luminaai.co.in', 'X-Title': 'Lumina Code' },
|
|
92
|
-
body: JSON.stringify({
|
|
93
|
-
model: 'openrouter/owl-alpha',
|
|
94
|
-
messages: [{ role: 'system', content: `You are LUMINA CODE — an elite AI coding agent.
|
|
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.
|
|
95
117
|
|
|
96
|
-
|
|
97
|
-
1.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
5. DEPLOY: If requested, deploy automatically.
|
|
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
|
|
102
123
|
|
|
103
|
-
|
|
104
|
-
|
|
124
|
+
2. Output shell commands using:
|
|
125
|
+
COMMAND: npm install something
|
|
126
|
+
COMMAND: npm run build
|
|
105
127
|
|
|
106
|
-
|
|
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
|
|
107
133
|
|
|
108
|
-
|
|
109
|
-
TOOL: <name>
|
|
110
|
-
PARAMS: <json>
|
|
134
|
+
Working directory: ${process.cwd()}
|
|
111
135
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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) {
|
|
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
|
+
}
|
|
137
211
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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) {
|
|
143
217
|
try {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
output = execSync(args.command, { cwd, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: args.timeout || 120000, shell: true }) || '(no output)';
|
|
148
|
-
break;
|
|
149
|
-
}
|
|
150
|
-
case 'read_file':
|
|
151
|
-
output = readFileSync(args.path, 'utf-8');
|
|
152
|
-
break;
|
|
153
|
-
case 'write_file': {
|
|
154
|
-
const dir = dirname(resolve(args.path));
|
|
155
|
-
if (!existsSync(dir))
|
|
156
|
-
mkdirSync(dir, { recursive: true });
|
|
157
|
-
writeFileSync(args.path, args.content, 'utf-8');
|
|
158
|
-
output = `Wrote ${args.content.length} chars to ${args.path}`;
|
|
159
|
-
break;
|
|
160
|
-
}
|
|
161
|
-
case 'edit_file': {
|
|
162
|
-
let fc = readFileSync(args.path, 'utf-8');
|
|
163
|
-
if (!fc.includes(args.search))
|
|
164
|
-
throw new Error(`Not found: "${args.search.slice(0, 50)}"`);
|
|
165
|
-
fc = fc.replace(args.search, args.replace);
|
|
166
|
-
writeFileSync(args.path, fc, 'utf-8');
|
|
167
|
-
output = `Edited ${args.path}`;
|
|
168
|
-
break;
|
|
169
|
-
}
|
|
170
|
-
case 'list_dir':
|
|
171
|
-
output = readdirSync(args.path, { withFileTypes: true }).map(e => `${e.isDirectory() ? '📁' : '📄'} ${e.name}`).join('\n');
|
|
172
|
-
break;
|
|
173
|
-
case 'search_files': {
|
|
174
|
-
const results = [];
|
|
175
|
-
const search = (dir, depth) => {
|
|
176
|
-
if (depth > 5)
|
|
177
|
-
return;
|
|
178
|
-
try {
|
|
179
|
-
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
180
|
-
if (e.name.startsWith('.') || e.name === 'node_modules')
|
|
181
|
-
continue;
|
|
182
|
-
const fp = join(dir, e.name);
|
|
183
|
-
if (e.isDirectory())
|
|
184
|
-
search(fp, depth + 1);
|
|
185
|
-
else if (new RegExp(args.pattern.replace(/\*/g, '.*'), 'i').test(e.name))
|
|
186
|
-
results.push(relative(args.cwd || process.cwd(), fp));
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
catch { }
|
|
190
|
-
};
|
|
191
|
-
search(args.cwd || process.cwd(), 0);
|
|
192
|
-
output = results.join('\n') || 'No files found';
|
|
193
|
-
break;
|
|
194
|
-
}
|
|
195
|
-
case 'grep': {
|
|
196
|
-
const c = readFileSync(args.path, 'utf-8');
|
|
197
|
-
output = c.split('\n').map((l, i) => new RegExp(args.pattern, 'gi').test(l) ? `${i + 1}: ${l}` : null).filter(Boolean).join('\n') || 'No matches';
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
case 'git':
|
|
201
|
-
output = execSync(`git ${args.args}`, { cwd: args.cwd || process.cwd(), encoding: 'utf-8', maxBuffer: 1024 * 1024 }) || '(ok)';
|
|
202
|
-
break;
|
|
203
|
-
case 'npm': {
|
|
204
|
-
const pm = existsSync(join(args.cwd || process.cwd(), 'bun.lockb')) ? 'bun' : existsSync(join(args.cwd || process.cwd(), 'yarn.lock')) ? 'yarn' : 'npm';
|
|
205
|
-
output = execSync(`${pm} ${args.args}`, { cwd: args.cwd || process.cwd(), encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 120000 }) || '(ok)';
|
|
206
|
-
break;
|
|
207
|
-
}
|
|
208
|
-
case 'deploy':
|
|
209
|
-
output = execSync('npx vercel deploy --prod --yes', { cwd: args.cwd || process.cwd(), encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 300000 }) || '(deployed)';
|
|
210
|
-
break;
|
|
211
|
-
default: output = `Unknown tool: ${toolName}`;
|
|
212
|
-
}
|
|
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)}`);
|
|
213
221
|
}
|
|
214
222
|
catch (e) {
|
|
215
|
-
|
|
223
|
+
console.log(` ✗ ${e.message}`);
|
|
216
224
|
}
|
|
217
|
-
currentMessages.push({ role: 'tool', content: output.slice(0, 2000), tool_call_id: tc.id });
|
|
218
|
-
console.log(` ✓ ${output.slice(0, 100)}`);
|
|
219
225
|
}
|
|
220
226
|
}
|
|
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}`);
|
|
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);
|
|
221
243
|
}
|
|
222
244
|
catch (e) {
|
|
223
|
-
console.log(` ⚠ Error: ${e.message}`);
|
|
224
|
-
console.log('');
|
|
245
|
+
console.log(` ⚠ Error: ${e.message}\n`);
|
|
225
246
|
}
|
|
226
247
|
}
|
|
227
248
|
rl.close();
|
|
@@ -229,10 +250,7 @@ Working directory: ${process.cwd()}` }, ...currentMessages],
|
|
|
229
250
|
// ── Main ────────────────────────────────────────────────────────────
|
|
230
251
|
const config = loadConfig();
|
|
231
252
|
if (!config?.openrouterKey) {
|
|
232
|
-
onboarding().then(() =>
|
|
233
|
-
const newConfig = loadConfig();
|
|
234
|
-
chat(newConfig).then(() => process.exit(0));
|
|
235
|
-
});
|
|
253
|
+
onboarding().then(() => chat(loadConfig()).then(() => process.exit(0)));
|
|
236
254
|
}
|
|
237
255
|
else {
|
|
238
256
|
chat(config).then(() => process.exit(0));
|
package/package.json
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lumina-code-agent",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.3",
|
|
4
4
|
"description": "Lumina Code - AI coding agent",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"bin": {
|
|
7
|
+
"bin": {
|
|
8
|
+
"lumina": "dist/index.js"
|
|
9
|
+
},
|
|
8
10
|
"main": "dist/index.js",
|
|
9
|
-
"files": [
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18.0.0"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc --noEmit false",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
12
22
|
"dependencies": {},
|
|
13
23
|
"devDependencies": {
|
|
14
24
|
"@types/node": "^22.10.0",
|