lumina-code-agent 1.6.8 → 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 +135 -113
- 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(), 300000);
|
|
128
83
|
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
129
84
|
method: 'POST',
|
|
130
85
|
headers: {
|
|
@@ -136,63 +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
|
|
104
|
+
throw new Error(`API ${res.status}: ${err.slice(0, 200)}`);
|
|
152
105
|
}
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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();
|
|
167
123
|
for (const line of lines) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
124
|
+
if (!line.startsWith('data: '))
|
|
125
|
+
continue;
|
|
126
|
+
const d = line.slice(6).trim();
|
|
127
|
+
if (d === '[DONE]')
|
|
128
|
+
continue;
|
|
129
|
+
let delta = '';
|
|
130
|
+
try {
|
|
131
|
+
const p = JSON.parse(d);
|
|
132
|
+
delta = p.choices?.[0]?.delta?.content || '';
|
|
133
|
+
}
|
|
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
|
+
}
|
|
192
|
+
}
|
|
172
193
|
}
|
|
173
|
-
console.log(' ──────────────────────────────────────\n');
|
|
174
|
-
continue;
|
|
175
194
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
+
}
|
|
189
212
|
}
|
|
190
213
|
}
|
|
191
|
-
console.log(`\n 📊
|
|
192
|
-
console.log('');
|
|
214
|
+
console.log(`\n 📊 Created ${filesCreated} file(s) | Total: ${createdFiles.size}\n`);
|
|
193
215
|
}
|
|
194
216
|
catch (e) {
|
|
195
|
-
console.log(`
|
|
217
|
+
console.log(` ❌ ${e.message}\n`);
|
|
196
218
|
}
|
|
197
219
|
}
|
|
198
220
|
rl.close();
|