lumina-code-agent 1.5.1 → 1.6.0
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 +84 -152
- package/package.json +7 -21
package/dist/index.js
CHANGED
|
@@ -1,20 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @ts-nocheck
|
|
3
|
-
const hasTTY = process.stdin.isTTY && process.stdout.isTTY;
|
|
4
|
-
if (!hasTTY) {
|
|
5
|
-
console.log('');
|
|
6
|
-
console.log(' ⚡ LUMINA CODE — AI Coding Agent');
|
|
7
|
-
console.log('');
|
|
8
|
-
console.log(' Lumina Code requires a proper terminal (TTY) to run.');
|
|
9
|
-
console.log(' Please open Windows Terminal, PowerShell, or Command Prompt.');
|
|
10
|
-
console.log('');
|
|
11
|
-
process.exit(0);
|
|
12
|
-
}
|
|
13
|
-
import React, { useState, useCallback } from 'react';
|
|
14
|
-
import { Box, Text, useInput, useApp, Spacer } from 'ink';
|
|
15
3
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs';
|
|
16
4
|
import { homedir } from 'os';
|
|
17
5
|
import { join, dirname, resolve, relative } from 'path';
|
|
6
|
+
import { createInterface } from 'readline';
|
|
7
|
+
import { execSync } from 'child_process';
|
|
18
8
|
const CONFIG_DIR = join(homedir(), '.lumina');
|
|
19
9
|
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
20
10
|
function loadConfig() {
|
|
@@ -32,73 +22,52 @@ function saveConfig(config) {
|
|
|
32
22
|
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
33
23
|
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
34
24
|
}
|
|
35
|
-
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
25
|
+
const rl = createInterface({ input: process.stdin, output: stdout });
|
|
26
|
+
function ask(question) {
|
|
27
|
+
return new Promise(resolve => rl.question(question, resolve));
|
|
28
|
+
}
|
|
29
|
+
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('');
|
|
36
|
+
const apiKey = await ask(' Enter your OpenRouter API key: ');
|
|
37
|
+
if (!apiKey.trim() || apiKey.trim().length < 10) {
|
|
38
|
+
console.log(' ❌ Invalid API key. Please try again.');
|
|
39
|
+
process.exit(1);
|
|
46
40
|
}
|
|
47
|
-
|
|
41
|
+
const name = await ask(' What should I call you? (optional): ');
|
|
42
|
+
saveConfig({ openrouterKey: apiKey.trim(), userName: name.trim() || 'User' });
|
|
43
|
+
console.log('');
|
|
44
|
+
console.log(' ✓ Setup complete! Let\'s build something amazing.');
|
|
45
|
+
console.log('');
|
|
48
46
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
else if (key.backspace || key.delete) {
|
|
68
|
-
if (step === 1)
|
|
69
|
-
setApiKey(v => v.slice(0, -1));
|
|
70
|
-
if (step === 2)
|
|
71
|
-
setName(v => v.slice(0, -1));
|
|
47
|
+
async function chat(config) {
|
|
48
|
+
console.log(' Type what you want to build. I\'ll handle the rest.');
|
|
49
|
+
console.log(' Commands: /help /clear /exit');
|
|
50
|
+
console.log('');
|
|
51
|
+
let messages = [];
|
|
52
|
+
while (true) {
|
|
53
|
+
const input = await ask(' > ');
|
|
54
|
+
const trimmed = input.trim();
|
|
55
|
+
if (!trimmed)
|
|
56
|
+
continue;
|
|
57
|
+
if (trimmed === '/exit' || trimmed === '/quit')
|
|
58
|
+
break;
|
|
59
|
+
if (trimmed === '/clear') {
|
|
60
|
+
messages = [];
|
|
61
|
+
console.log(' ✓ Cleared.');
|
|
62
|
+
continue;
|
|
72
63
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (step === 2)
|
|
77
|
-
setName(v => v + input);
|
|
64
|
+
if (trimmed === '/help') {
|
|
65
|
+
console.log(' Commands: /help /clear /exit');
|
|
66
|
+
continue;
|
|
78
67
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
// ── Chat Screen ─────────────────────────────────────────────────────
|
|
83
|
-
function ChatScreen({ config }) {
|
|
84
|
-
const { exit } = useApp();
|
|
85
|
-
const [messages, setMessages] = useState([]);
|
|
86
|
-
const [input, setInput] = useState('');
|
|
87
|
-
const [status, setStatus] = useState('Ready');
|
|
88
|
-
const [thinking, setThinking] = useState(false);
|
|
89
|
-
const visibleMessages = messages.slice(-50);
|
|
90
|
-
const handleSubmit = useCallback(async (text) => {
|
|
91
|
-
const trimmed = text.trim();
|
|
92
|
-
if (!trimmed || thinking)
|
|
93
|
-
return;
|
|
94
|
-
setInput('');
|
|
95
|
-
setThinking(true);
|
|
96
|
-
setStatus('Thinking...');
|
|
97
|
-
const userMsg = { role: 'user', content: trimmed };
|
|
98
|
-
const newMessages = [...messages, userMsg];
|
|
99
|
-
setMessages(newMessages);
|
|
68
|
+
messages.push({ role: 'user', content: trimmed });
|
|
69
|
+
console.log('');
|
|
100
70
|
try {
|
|
101
|
-
const OPENROUTER_URL = 'https://openrouter.ai/api/v1/chat/completions';
|
|
102
71
|
const tools = [
|
|
103
72
|
{ type: 'function', function: { name: 'run_command', description: 'Run any shell command', parameters: { type: 'object', properties: { command: { type: 'string' }, cwd: { type: 'string' }, timeout: { type: 'number' } }, required: ['command'] } } },
|
|
104
73
|
{ type: 'function', function: { name: 'read_file', description: 'Read file contents', parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } } },
|
|
@@ -113,19 +82,17 @@ function ChatScreen({ config }) {
|
|
|
113
82
|
];
|
|
114
83
|
let iterations = 0;
|
|
115
84
|
const maxIterations = 30;
|
|
116
|
-
let currentMessages = [...
|
|
85
|
+
let currentMessages = [...messages];
|
|
117
86
|
while (iterations < maxIterations) {
|
|
118
87
|
iterations++;
|
|
119
|
-
|
|
120
|
-
const res = await fetch(
|
|
88
|
+
process.stdout.write(` ⏳ Working (step ${iterations})...\r`);
|
|
89
|
+
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
121
90
|
method: 'POST',
|
|
122
91
|
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${config.openrouterKey}`, 'HTTP-Referer': 'https://luminaai.co.in', 'X-Title': 'Lumina Code' },
|
|
123
92
|
body: JSON.stringify({
|
|
124
93
|
model: 'openrouter/owl-alpha',
|
|
125
94
|
messages: [{ role: 'system', content: `You are LUMINA CODE — an elite AI coding agent.
|
|
126
95
|
|
|
127
|
-
MODEL: openrouter/owl-alpha (1M+ context, best reasoning)
|
|
128
|
-
|
|
129
96
|
WORKFLOW:
|
|
130
97
|
1. PLAN: Analyze the task. Create a brief plan.
|
|
131
98
|
2. ACT: Execute tools step by step. Read before writing.
|
|
@@ -133,23 +100,10 @@ WORKFLOW:
|
|
|
133
100
|
4. FIX: If something fails, debug and fix immediately.
|
|
134
101
|
5. DEPLOY: If requested, deploy automatically.
|
|
135
102
|
|
|
136
|
-
QUALITY
|
|
137
|
-
|
|
138
|
-
- TypeScript with proper types (never use 'any')
|
|
139
|
-
- Error handling everywhere
|
|
140
|
-
- Responsive design (320px to 2560px)
|
|
141
|
-
- Accessible (semantic HTML, ARIA, keyboard navigation)
|
|
142
|
-
- Clean architecture, modern patterns
|
|
143
|
-
- Beautiful UI (consistent spacing, typography, color)
|
|
103
|
+
QUALITY: Production-grade code. No placeholders. No TODOs. No emoji in code.
|
|
104
|
+
Handle errors. TypeScript. Responsive. Accessible. Clean architecture.
|
|
144
105
|
|
|
145
|
-
FORBIDDEN:
|
|
146
|
-
- Lorem ipsum or placeholder content
|
|
147
|
-
- TODO/FIXME comments in production code
|
|
148
|
-
- Emoji in code or UI
|
|
149
|
-
- var keyword (always let/const)
|
|
150
|
-
- any type in TypeScript
|
|
151
|
-
- Skipping error handling
|
|
152
|
-
- Hardcoded secrets
|
|
106
|
+
FORBIDDEN: lorem ipsum, TODO comments, emoji in code, var keyword, any type, skipping error handling, hardcoded secrets.
|
|
153
107
|
|
|
154
108
|
When using a tool, output ONLY:
|
|
155
109
|
TOOL: <name>
|
|
@@ -173,28 +127,29 @@ Working directory: ${process.cwd()}` }, ...currentMessages],
|
|
|
173
127
|
const content = choice.message?.content || '';
|
|
174
128
|
const toolCalls = choice.message?.tool_calls || [];
|
|
175
129
|
currentMessages.push({ role: 'assistant', content });
|
|
176
|
-
setMessages([...currentMessages]);
|
|
177
130
|
if (toolCalls.length === 0) {
|
|
178
|
-
|
|
131
|
+
messages = currentMessages;
|
|
132
|
+
console.log(' ✓ ' + content.slice(0, 500));
|
|
133
|
+
if (content.length > 500)
|
|
134
|
+
console.log(' ...');
|
|
135
|
+
console.log('');
|
|
179
136
|
break;
|
|
180
137
|
}
|
|
181
138
|
for (const tc of toolCalls) {
|
|
182
139
|
const args = JSON.parse(tc.function.arguments || '{}');
|
|
183
140
|
const toolName = tc.function.name;
|
|
184
|
-
|
|
141
|
+
console.log(` ⚙ ${toolName}(${JSON.stringify(args).slice(0, 80)})`);
|
|
185
142
|
let output = '';
|
|
186
143
|
try {
|
|
187
|
-
const { execSync } = await import('child_process');
|
|
188
144
|
switch (toolName) {
|
|
189
145
|
case 'run_command': {
|
|
190
146
|
const cwd = args.cwd || process.cwd();
|
|
191
147
|
output = execSync(args.command, { cwd, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: args.timeout || 120000, shell: true }) || '(no output)';
|
|
192
148
|
break;
|
|
193
149
|
}
|
|
194
|
-
case 'read_file':
|
|
150
|
+
case 'read_file':
|
|
195
151
|
output = readFileSync(args.path, 'utf-8');
|
|
196
152
|
break;
|
|
197
|
-
}
|
|
198
153
|
case 'write_file': {
|
|
199
154
|
const dir = dirname(resolve(args.path));
|
|
200
155
|
if (!existsSync(dir))
|
|
@@ -204,19 +159,17 @@ Working directory: ${process.cwd()}` }, ...currentMessages],
|
|
|
204
159
|
break;
|
|
205
160
|
}
|
|
206
161
|
case 'edit_file': {
|
|
207
|
-
let
|
|
208
|
-
if (!
|
|
209
|
-
throw new Error(`
|
|
210
|
-
|
|
211
|
-
writeFileSync(args.path,
|
|
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');
|
|
212
167
|
output = `Edited ${args.path}`;
|
|
213
168
|
break;
|
|
214
169
|
}
|
|
215
|
-
case 'list_dir':
|
|
216
|
-
|
|
217
|
-
output = entries.map(e => `${e.isDirectory() ? '📁' : '📄'} ${e.name}`).join('\n');
|
|
170
|
+
case 'list_dir':
|
|
171
|
+
output = readdirSync(args.path, { withFileTypes: true }).map(e => `${e.isDirectory() ? '📁' : '📄'} ${e.name}`).join('\n');
|
|
218
172
|
break;
|
|
219
|
-
}
|
|
220
173
|
case 'search_files': {
|
|
221
174
|
const results = [];
|
|
222
175
|
const search = (dir, depth) => {
|
|
@@ -224,7 +177,7 @@ Working directory: ${process.cwd()}` }, ...currentMessages],
|
|
|
224
177
|
return;
|
|
225
178
|
try {
|
|
226
179
|
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
227
|
-
if (e.name.startsWith('.') || e.name === 'node_modules'
|
|
180
|
+
if (e.name.startsWith('.') || e.name === 'node_modules')
|
|
228
181
|
continue;
|
|
229
182
|
const fp = join(dir, e.name);
|
|
230
183
|
if (e.isDirectory())
|
|
@@ -240,68 +193,47 @@ Working directory: ${process.cwd()}` }, ...currentMessages],
|
|
|
240
193
|
break;
|
|
241
194
|
}
|
|
242
195
|
case 'grep': {
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
const regex = new RegExp(args.pattern, 'gi');
|
|
246
|
-
output = lines.map((l, i) => regex.test(l) ? `${i + 1}: ${l}` : null).filter(Boolean).join('\n') || 'No matches';
|
|
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';
|
|
247
198
|
break;
|
|
248
199
|
}
|
|
249
|
-
case 'git':
|
|
200
|
+
case 'git':
|
|
250
201
|
output = execSync(`git ${args.args}`, { cwd: args.cwd || process.cwd(), encoding: 'utf-8', maxBuffer: 1024 * 1024 }) || '(ok)';
|
|
251
202
|
break;
|
|
252
|
-
}
|
|
253
203
|
case 'npm': {
|
|
254
204
|
const pm = existsSync(join(args.cwd || process.cwd(), 'bun.lockb')) ? 'bun' : existsSync(join(args.cwd || process.cwd(), 'yarn.lock')) ? 'yarn' : 'npm';
|
|
255
205
|
output = execSync(`${pm} ${args.args}`, { cwd: args.cwd || process.cwd(), encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 120000 }) || '(ok)';
|
|
256
206
|
break;
|
|
257
207
|
}
|
|
258
|
-
case 'deploy':
|
|
208
|
+
case 'deploy':
|
|
259
209
|
output = execSync('npx vercel deploy --prod --yes', { cwd: args.cwd || process.cwd(), encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 300000 }) || '(deployed)';
|
|
260
210
|
break;
|
|
261
|
-
}
|
|
262
|
-
default:
|
|
263
|
-
output = `Unknown tool: ${toolName}`;
|
|
211
|
+
default: output = `Unknown tool: ${toolName}`;
|
|
264
212
|
}
|
|
265
213
|
}
|
|
266
214
|
catch (e) {
|
|
267
215
|
output = `Error: ${e.message}`;
|
|
268
216
|
}
|
|
269
217
|
currentMessages.push({ role: 'tool', content: output.slice(0, 2000), tool_call_id: tc.id });
|
|
270
|
-
|
|
218
|
+
console.log(` ✓ ${output.slice(0, 100)}`);
|
|
271
219
|
}
|
|
272
220
|
}
|
|
273
|
-
if (iterations >= maxIterations)
|
|
274
|
-
setStatus('Reached max iterations');
|
|
275
221
|
}
|
|
276
222
|
catch (e) {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
setThinking(false);
|
|
281
|
-
}, [input, thinking, messages, config]);
|
|
282
|
-
useInput((input, key) => {
|
|
283
|
-
if (key.ctrl && input === 'c')
|
|
284
|
-
exit();
|
|
285
|
-
if (key.ctrl && input === 'd')
|
|
286
|
-
exit();
|
|
287
|
-
if (key.return) {
|
|
288
|
-
handleSubmit(input);
|
|
289
|
-
}
|
|
290
|
-
else if (key.backspace || key.delete) {
|
|
291
|
-
setInput(v => v.slice(0, -1));
|
|
292
|
-
}
|
|
293
|
-
else if (!key.ctrl && !key.meta) {
|
|
294
|
-
setInput(v => v + input);
|
|
223
|
+
console.log(` ⚠ Error: ${e.message}`);
|
|
224
|
+
console.log('');
|
|
295
225
|
}
|
|
226
|
+
}
|
|
227
|
+
rl.close();
|
|
228
|
+
}
|
|
229
|
+
// ── Main ────────────────────────────────────────────────────────────
|
|
230
|
+
const config = loadConfig();
|
|
231
|
+
if (!config?.openrouterKey) {
|
|
232
|
+
onboarding().then(() => {
|
|
233
|
+
const newConfig = loadConfig();
|
|
234
|
+
chat(newConfig).then(() => process.exit(0));
|
|
296
235
|
});
|
|
297
|
-
return React.createElement(Box, { flexDirection: 'column', height: '100%' }, React.createElement(Box, { borderStyle: 'round', borderColor: '#7C5CFC', paddingX: 2, paddingY: 1 }, React.createElement(Text, { bold: true, color: '#7C5CFC' }, ' ⚡ LUMINA CODE'), React.createElement(Spacer, null), React.createElement(Text, { color: '#52525B' }, thinking ? ' ⏳ ' + status : ' ● ' + status)), React.createElement(Box, { flexDirection: 'column', flexGrow: 1, paddingX: 2, paddingY: 1, overflowY: 'hidden' }, visibleMessages.map((m, i) => {
|
|
298
|
-
if (m.role === 'user') {
|
|
299
|
-
return React.createElement(Box, { key: i, marginTop: 1 }, React.createElement(Text, { color: '#7C5CFC', bold: true }, '> '), React.createElement(Text, { color: '#FAFAFA' }, m.content));
|
|
300
|
-
}
|
|
301
|
-
if (m.role === 'tool') {
|
|
302
|
-
return React.createElement(Box, { key: i, marginTop: 0, paddingLeft: 2 }, React.createElement(Text, { color: '#52525B' }, ' ' + m.content.slice(0, 200)));
|
|
303
|
-
}
|
|
304
|
-
return React.createElement(Box, { key: i, marginTop: 1, paddingLeft: 2 }, React.createElement(Text, { color: '#A1A1AA' }, m.content.slice(0, 500)));
|
|
305
|
-
}), thinking && React.createElement(Text, { color: '#F59E0B' }, ' ⏳ thinking...')), React.createElement(Box, { borderStyle: 'single', borderColor: '#3F3F46', paddingX: 1 }, React.createElement(Text, { color: '#7C5CFC' }, ' > '), React.createElement(Text, { color: '#FAFAFA' }, input || 'What do you want to build?'), React.createElement(Text, { color: '#52525B' }, '▌')), React.createElement(Box, { paddingX: 2 }, React.createElement(Text, { color: '#3F3F46' }, ' Ctrl+C to exit | OWL-Alpha ')));
|
|
306
236
|
}
|
|
307
|
-
|
|
237
|
+
else {
|
|
238
|
+
chat(config).then(() => process.exit(0));
|
|
239
|
+
}
|
package/package.json
CHANGED
|
@@ -1,31 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lumina-code-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Lumina Code - AI coding agent",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"bin": {
|
|
8
|
-
"lumina": "dist/index.js"
|
|
9
|
-
},
|
|
7
|
+
"bin": { "lumina": "dist/index.js" },
|
|
10
8
|
"main": "dist/index.js",
|
|
11
|
-
"files": [
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"engines": {
|
|
16
|
-
"node": ">=18.0.0"
|
|
17
|
-
},
|
|
18
|
-
"scripts": {
|
|
19
|
-
"build": "tsc --noEmit false",
|
|
20
|
-
"prepublishOnly": "npm run build"
|
|
21
|
-
},
|
|
22
|
-
"dependencies": {
|
|
23
|
-
"ink": "^5.1.0",
|
|
24
|
-
"react": "^18.3.1",
|
|
25
|
-
"chalk": "^5.3.0"
|
|
26
|
-
},
|
|
9
|
+
"files": ["dist/", "README.md"],
|
|
10
|
+
"engines": { "node": ">=18.0.0" },
|
|
11
|
+
"scripts": { "build": "tsc --noEmit false", "prepublishOnly": "npm run build" },
|
|
12
|
+
"dependencies": {},
|
|
27
13
|
"devDependencies": {
|
|
28
|
-
"@types/
|
|
14
|
+
"@types/node": "^22.10.0",
|
|
29
15
|
"typescript": "^5.7.2"
|
|
30
16
|
}
|
|
31
17
|
}
|