lumina-code-agent 1.6.9 → 1.6.10
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 +133 -127
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// @ts-nocheck
|
|
3
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
|
const CONFIG_DIR = join(homedir(), '.lumina');
|
|
8
8
|
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
@@ -24,107 +24,62 @@ function saveConfig(config) {
|
|
|
24
24
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
25
25
|
function ask(q) { return new Promise(r => rl.question(q, r)); }
|
|
26
26
|
async function onboarding() {
|
|
27
|
-
console.log('\n
|
|
28
|
-
|
|
27
|
+
console.log('\n╔══════════════════════════════════════╗');
|
|
28
|
+
console.log('║ ⚡ LUMINA CODE — AI Coding Agent ║');
|
|
29
|
+
console.log('╚══════════════════════════════════════╝\n');
|
|
30
|
+
const apiKey = await ask(' 🔑 OpenRouter API key: ');
|
|
29
31
|
if (!apiKey.trim() || apiKey.trim().length < 10) {
|
|
30
|
-
console.log(' ❌ Invalid
|
|
32
|
+
console.log(' ❌ Invalid.');
|
|
31
33
|
process.exit(1);
|
|
32
34
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
console.log(' ✓ Ready!\n');
|
|
36
|
-
}
|
|
37
|
-
function extractFiles(text) {
|
|
38
|
-
const files = [];
|
|
39
|
-
const fileRegex = /FILENAME:\s*([^\n]+)\n([\s\S]*?)END FILE/g;
|
|
40
|
-
let match;
|
|
41
|
-
while ((match = fileRegex.exec(text)) !== null) {
|
|
42
|
-
const rawPath = match[1].trim();
|
|
43
|
-
const content = match[2].trim();
|
|
44
|
-
if (!rawPath || rawPath.endsWith('/') || !extname(rawPath))
|
|
45
|
-
continue;
|
|
46
|
-
if (rawPath.includes('<') || rawPath.includes('>') || rawPath.includes('"'))
|
|
47
|
-
continue;
|
|
48
|
-
files.push({ path: rawPath, content });
|
|
49
|
-
}
|
|
50
|
-
return files;
|
|
35
|
+
saveConfig({ openrouterKey: apiKey.trim(), userName: 'User' });
|
|
36
|
+
console.log(' ✅ Ready!\n');
|
|
51
37
|
}
|
|
52
38
|
async function chat(config) {
|
|
53
|
-
console.log(' Type what you want to build.
|
|
54
|
-
console.log(' Commands: /help /clear /files /exit\n');
|
|
39
|
+
console.log(' 💬 Type what you want to build. /exit to quit.\n');
|
|
55
40
|
const createdFiles = new Set();
|
|
56
|
-
|
|
57
|
-
const input = await ask(' > ');
|
|
58
|
-
const trimmed = input.trim();
|
|
59
|
-
if (!trimmed)
|
|
60
|
-
continue;
|
|
61
|
-
if (trimmed === '/exit' || trimmed === '/quit')
|
|
62
|
-
break;
|
|
63
|
-
if (trimmed === '/clear') {
|
|
64
|
-
console.log(' ✓ Cleared.\n');
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
if (trimmed === '/files') {
|
|
68
|
-
if (createdFiles.size === 0) {
|
|
69
|
-
console.log(' No files yet.\n');
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
console.log(' Files:');
|
|
73
|
-
for (const f of createdFiles)
|
|
74
|
-
console.log(` ${f}`);
|
|
75
|
-
console.log('');
|
|
76
|
-
}
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
if (trimmed === '/help') {
|
|
80
|
-
console.log(' /help /clear /files /exit\n');
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
const systemPrompt = `You are LUMINA CODE. Create COMPLETE production-grade websites.
|
|
84
|
-
|
|
85
|
-
OUTPUT FORMAT — Use this EXACT format for EVERY file:
|
|
41
|
+
const SYSTEM = `You are LUMINA CODE — the world's best AI coding agent.
|
|
86
42
|
|
|
43
|
+
OUTPUT FORMAT — MANDATORY:
|
|
87
44
|
FILENAME: index.html
|
|
88
45
|
<!DOCTYPE html>
|
|
89
46
|
<html lang="en">
|
|
90
47
|
<head>
|
|
91
48
|
<meta charset="UTF-8">
|
|
92
|
-
<meta name="viewport" content="width=device-width,
|
|
49
|
+
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
93
50
|
<title>Title</title>
|
|
94
51
|
<link rel="stylesheet" href="style.css">
|
|
95
52
|
</head>
|
|
96
53
|
<body>
|
|
97
|
-
<!--
|
|
54
|
+
<!-- COMPLETE HTML -->
|
|
98
55
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
|
56
|
+
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
|
|
57
|
+
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollTrigger.min.js"></script>
|
|
99
58
|
<script src="script.js"></script>
|
|
100
59
|
</body>
|
|
101
60
|
</html>
|
|
102
61
|
END FILE
|
|
103
62
|
|
|
104
63
|
FILENAME: style.css
|
|
105
|
-
/*
|
|
64
|
+
/* COMPLETE CSS — no truncation */
|
|
106
65
|
END FILE
|
|
107
66
|
|
|
108
67
|
FILENAME: script.js
|
|
109
|
-
//
|
|
68
|
+
// COMPLETE JS — Three.js robot, animations, scroll triggers
|
|
110
69
|
END FILE
|
|
111
70
|
|
|
112
|
-
RULES:
|
|
113
|
-
- Start IMMEDIATELY with FILENAME: index.html
|
|
114
|
-
- Output COMPLETE files — every line, no truncation
|
|
115
|
-
- Do NOT describe what you will do — JUST DO IT
|
|
116
|
-
- Do NOT output any text before FILENAME:
|
|
117
|
-
- Do NOT use markdown code blocks
|
|
118
|
-
- Create ALL files needed for a complete working project
|
|
119
|
-
- Use Three.js from CDN for 3D
|
|
120
|
-
- No placeholders, no TODOs, no lorem ipsum
|
|
71
|
+
RULES: Start IMMEDIATELY with FILENAME: index.html. Do NOT describe. JUST OUTPUT COMPLETE FILES. No placeholders. No TODOs. Robot: Three.js primitives, PBR materials, wave/blink animations, scroll-driven sections (Hero=wave, Features=point, About=think, Contact=wave goodbye), particles, dark gradient bg.
|
|
121
72
|
|
|
122
73
|
Working directory: ${process.cwd()}`;
|
|
123
|
-
|
|
124
|
-
|
|
74
|
+
while (true) {
|
|
75
|
+
const input = await ask(' > ');
|
|
76
|
+
const trimmed = input.trim();
|
|
77
|
+
if (!trimmed)
|
|
78
|
+
continue;
|
|
79
|
+
if (trimmed === '/exit')
|
|
80
|
+
break;
|
|
81
|
+
console.log('\n ⏳ Generating...\n');
|
|
125
82
|
try {
|
|
126
|
-
const controller = new AbortController();
|
|
127
|
-
const timeout = setTimeout(() => controller.abort(), 600000); // 10 min timeout
|
|
128
83
|
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
129
84
|
method: 'POST',
|
|
130
85
|
headers: {
|
|
@@ -136,79 +91,130 @@ Working directory: ${process.cwd()}`;
|
|
|
136
91
|
body: JSON.stringify({
|
|
137
92
|
model: 'openrouter/owl-alpha',
|
|
138
93
|
messages: [
|
|
139
|
-
{ role: 'system', content:
|
|
94
|
+
{ role: 'system', content: SYSTEM },
|
|
140
95
|
{ role: 'user', content: trimmed },
|
|
141
96
|
],
|
|
142
|
-
stream:
|
|
143
|
-
max_tokens:
|
|
97
|
+
stream: true,
|
|
98
|
+
max_tokens: 56000,
|
|
144
99
|
temperature: 0.1,
|
|
145
100
|
}),
|
|
146
|
-
signal: controller.signal,
|
|
147
101
|
});
|
|
148
|
-
clearTimeout(timeout);
|
|
149
102
|
if (!res.ok) {
|
|
150
103
|
const err = await res.text().catch(() => '');
|
|
151
|
-
throw new Error(`API
|
|
152
|
-
}
|
|
153
|
-
// Read response as text first, then parse
|
|
154
|
-
const rawText = await res.text();
|
|
155
|
-
let data;
|
|
156
|
-
try {
|
|
157
|
-
data = JSON.parse(rawText);
|
|
104
|
+
throw new Error(`API ${res.status}: ${err.slice(0, 200)}`);
|
|
158
105
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
106
|
+
// Stream and parse files in real-time
|
|
107
|
+
const reader = res.body.getReader();
|
|
108
|
+
const decoder = new TextDecoder();
|
|
109
|
+
let buf = '';
|
|
110
|
+
let fullText = '';
|
|
111
|
+
let fileBuf = '';
|
|
112
|
+
let inFile = false;
|
|
113
|
+
let fileName = '';
|
|
114
|
+
let fileContent = '';
|
|
115
|
+
let filesCreated = 0;
|
|
116
|
+
while (true) {
|
|
117
|
+
const { done, value } = await reader.read();
|
|
118
|
+
if (done)
|
|
119
|
+
break;
|
|
120
|
+
buf += decoder.decode(value, { stream: true });
|
|
121
|
+
const lines = buf.split('\n');
|
|
122
|
+
buf = lines.pop();
|
|
123
|
+
for (const line of lines) {
|
|
124
|
+
if (!line.startsWith('data: '))
|
|
125
|
+
continue;
|
|
126
|
+
const d = line.slice(6).trim();
|
|
127
|
+
if (d === '[DONE]')
|
|
128
|
+
continue;
|
|
129
|
+
let delta = '';
|
|
165
130
|
try {
|
|
166
|
-
|
|
131
|
+
const p = JSON.parse(d);
|
|
132
|
+
delta = p.choices?.[0]?.delta?.content || '';
|
|
167
133
|
}
|
|
168
|
-
catch
|
|
169
|
-
|
|
134
|
+
catch {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (!delta)
|
|
138
|
+
continue;
|
|
139
|
+
fullText += delta;
|
|
140
|
+
// Real-time file detection
|
|
141
|
+
for (const ch of delta) {
|
|
142
|
+
if (!inFile) {
|
|
143
|
+
fileBuf += ch;
|
|
144
|
+
if (fileBuf.endsWith('FILENAME:')) {
|
|
145
|
+
inFile = true;
|
|
146
|
+
fileName = '';
|
|
147
|
+
fileContent = '';
|
|
148
|
+
fileBuf = '';
|
|
149
|
+
process.stdout.write(' 📄 ');
|
|
150
|
+
}
|
|
151
|
+
else if (fileBuf.length > 50) {
|
|
152
|
+
// Not matching, flush to output
|
|
153
|
+
process.stdout.write(fileBuf);
|
|
154
|
+
fileBuf = '';
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
if (ch === '\n' && !fileName.includes('.')) {
|
|
159
|
+
fileName = fileName.trim();
|
|
160
|
+
process.stdout.write(fileName + '\n');
|
|
161
|
+
}
|
|
162
|
+
else if (ch === '\n' && fileName && !fileContent) {
|
|
163
|
+
// First newline after filename, start content
|
|
164
|
+
fileContent = '';
|
|
165
|
+
}
|
|
166
|
+
else if (fileContent !== null) {
|
|
167
|
+
fileContent += ch;
|
|
168
|
+
if (fileContent.endsWith('END FILE')) {
|
|
169
|
+
const content = fileContent.slice(0, -8).trim();
|
|
170
|
+
try {
|
|
171
|
+
const fp = join(process.cwd(), fileName);
|
|
172
|
+
const dir = dirname(resolve(fp));
|
|
173
|
+
if (!existsSync(dir))
|
|
174
|
+
mkdirSync(dir, { recursive: true });
|
|
175
|
+
writeFileSync(fp, content, 'utf-8');
|
|
176
|
+
createdFiles.add(fileName);
|
|
177
|
+
filesCreated++;
|
|
178
|
+
console.log(` ✅ Saved: ${fileName} (${content.length} chars)`);
|
|
179
|
+
}
|
|
180
|
+
catch (e) {
|
|
181
|
+
console.log(` ❌ ${fileName}: ${e.message}`);
|
|
182
|
+
}
|
|
183
|
+
inFile = false;
|
|
184
|
+
fileName = '';
|
|
185
|
+
fileContent = null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
fileName += ch;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
170
192
|
}
|
|
171
193
|
}
|
|
172
|
-
else {
|
|
173
|
-
throw new Error('Response was truncated. Try a shorter prompt.');
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
const content = data.choices?.[0]?.message?.content || '';
|
|
177
|
-
if (!content) {
|
|
178
|
-
console.log(' ⚠ No response from model.\n');
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
console.log(` ✓ AI responded (${content.length} chars)`);
|
|
182
|
-
const files = extractFiles(content);
|
|
183
|
-
if (files.length === 0) {
|
|
184
|
-
console.log(' ⚠ No files detected in output.\n');
|
|
185
|
-
// Show first 50 lines of output
|
|
186
|
-
const lines = content.split('\n').slice(0, 50);
|
|
187
|
-
console.log(' ── Output preview ──');
|
|
188
|
-
for (const line of lines)
|
|
189
|
-
console.log(' ' + line);
|
|
190
|
-
console.log(' ── End ──\n');
|
|
191
|
-
continue;
|
|
192
194
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
195
|
+
// Handle any remaining file
|
|
196
|
+
if (inFile && fileName && fileContent) {
|
|
197
|
+
const content = fileContent.replace(/END FILE$/, '').trim();
|
|
198
|
+
if (content) {
|
|
199
|
+
try {
|
|
200
|
+
const fp = join(process.cwd(), fileName);
|
|
201
|
+
const dir = dirname(resolve(fp));
|
|
202
|
+
if (!existsSync(dir))
|
|
203
|
+
mkdirSync(dir, { recursive: true });
|
|
204
|
+
writeFileSync(fp, content, 'utf-8');
|
|
205
|
+
createdFiles.add(fileName);
|
|
206
|
+
filesCreated++;
|
|
207
|
+
console.log(` ✅ Saved: ${fileName} (${content.length} chars)`);
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
console.log(` ❌ ${fileName}: ${e.message}`);
|
|
211
|
+
}
|
|
206
212
|
}
|
|
207
213
|
}
|
|
208
|
-
console.log(`\n 📊 Total
|
|
214
|
+
console.log(`\n 📊 Created ${filesCreated} file(s) | Total: ${createdFiles.size}\n`);
|
|
209
215
|
}
|
|
210
216
|
catch (e) {
|
|
211
|
-
console.log(`
|
|
217
|
+
console.log(` ❌ ${e.message}\n`);
|
|
212
218
|
}
|
|
213
219
|
}
|
|
214
220
|
rl.close();
|