lumina-code-agent 1.4.0 → 1.5.1
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 +34 -47
- package/package.json +15 -5
package/dist/index.js
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @ts-nocheck
|
|
3
|
-
|
|
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';
|
|
4
14
|
import { Box, Text, useInput, useApp, Spacer } from 'ink';
|
|
5
15
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs';
|
|
6
16
|
import { homedir } from 'os';
|
|
@@ -22,27 +32,26 @@ function saveConfig(config) {
|
|
|
22
32
|
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
23
33
|
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
24
34
|
}
|
|
25
|
-
// ──
|
|
26
|
-
function
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
});
|
|
38
|
-
return React.createElement(Box, null, React.createElement(Text, { color: '#7C5CFC' }, ' > '), React.createElement(Text, { color: '#FAFAFA' }, value || placeholder || 'Type something...'), React.createElement(Text, { color: '#52525B' }, '▌'));
|
|
35
|
+
// ── Main App (handles onboarding vs chat) ───────────────────────────
|
|
36
|
+
function App() {
|
|
37
|
+
const [config, setConfig] = useState(loadConfig());
|
|
38
|
+
const [screen, setScreen] = useState(config?.openrouterKey ? 'chat' : 'onboarding');
|
|
39
|
+
const handleOnboardingComplete = useCallback(() => {
|
|
40
|
+
const newConfig = loadConfig();
|
|
41
|
+
setConfig(newConfig);
|
|
42
|
+
setScreen('chat');
|
|
43
|
+
}, []);
|
|
44
|
+
if (screen === 'onboarding') {
|
|
45
|
+
return React.createElement(OnboardingScreen, { onComplete: handleOnboardingComplete });
|
|
46
|
+
}
|
|
47
|
+
return React.createElement(ChatScreen, { config });
|
|
39
48
|
}
|
|
40
49
|
// ── Onboarding Screen ───────────────────────────────────────────────
|
|
41
|
-
function
|
|
50
|
+
function OnboardingScreen({ onComplete }) {
|
|
42
51
|
const [step, setStep] = useState(0);
|
|
43
52
|
const [apiKey, setApiKey] = useState('');
|
|
44
53
|
const [name, setName] = useState('');
|
|
45
|
-
|
|
54
|
+
useInput((input, key) => {
|
|
46
55
|
if (key.return) {
|
|
47
56
|
if (step === 0) {
|
|
48
57
|
setStep(1);
|
|
@@ -67,8 +76,7 @@ function Onboarding({ onComplete }) {
|
|
|
67
76
|
if (step === 2)
|
|
68
77
|
setName(v => v + input);
|
|
69
78
|
}
|
|
70
|
-
}
|
|
71
|
-
useInput(handleKey);
|
|
79
|
+
});
|
|
72
80
|
return React.createElement(Box, { flexDirection: 'column', padding: 2 }, React.createElement(Box, { flexDirection: 'column', marginBottom: 2 }, React.createElement(Text, { bold: true, color: '#7C5CFC' }, ' ⚡ LUMINA CODE'), React.createElement(Text, { color: '#52525B' }, ' AI Coding Agent')), step === 0 && React.createElement(Box, { flexDirection: 'column' }, React.createElement(Text, { color: '#A1A1AA' }, ' Welcome! Let\'s get you set up.'), React.createElement(Text, { color: '#52525B' }, ' Press Enter to continue...')), step === 1 && React.createElement(Box, { flexDirection: 'column' }, React.createElement(Text, { color: '#A1A1AA' }, ' Enter your OpenRouter API key:'), React.createElement(Text, { color: '#52525B' }, ' (Get one at https://openrouter.ai/keys)'), React.createElement(Box, { marginTop: 1 }, React.createElement(Text, { color: '#7C5CFC' }, ' > '), React.createElement(Text, { color: '#FAFAFA' }, apiKey || 'sk-or-...'), React.createElement(Text, { color: '#52525B' }, '▌')), React.createElement(Text, { color: '#3F3F46', marginTop: 1 }, ' Press Enter to continue')), step === 2 && React.createElement(Box, { flexDirection: 'column' }, React.createElement(Text, { color: '#A1A1AA' }, ' What should I call you? (optional)'), React.createElement(Box, { marginTop: 1 }, React.createElement(Text, { color: '#7C5CFC' }, ' > '), React.createElement(Text, { color: '#FAFAFA' }, name || 'Your name'), React.createElement(Text, { color: '#52525B' }, '▌')), React.createElement(Text, { color: '#3F3F46', marginTop: 1 }, ' Press Enter to start coding!')));
|
|
73
81
|
}
|
|
74
82
|
// ── Chat Screen ─────────────────────────────────────────────────────
|
|
@@ -78,7 +86,6 @@ function ChatScreen({ config }) {
|
|
|
78
86
|
const [input, setInput] = useState('');
|
|
79
87
|
const [status, setStatus] = useState('Ready');
|
|
80
88
|
const [thinking, setThinking] = useState(false);
|
|
81
|
-
const scrollRef = useRef(0);
|
|
82
89
|
const visibleMessages = messages.slice(-50);
|
|
83
90
|
const handleSubmit = useCallback(async (text) => {
|
|
84
91
|
const trimmed = text.trim();
|
|
@@ -87,7 +94,6 @@ function ChatScreen({ config }) {
|
|
|
87
94
|
setInput('');
|
|
88
95
|
setThinking(true);
|
|
89
96
|
setStatus('Thinking...');
|
|
90
|
-
scrollRef.current++;
|
|
91
97
|
const userMsg = { role: 'user', content: trimmed };
|
|
92
98
|
const newMessages = [...messages, userMsg];
|
|
93
99
|
setMessages(newMessages);
|
|
@@ -95,11 +101,11 @@ function ChatScreen({ config }) {
|
|
|
95
101
|
const OPENROUTER_URL = 'https://openrouter.ai/api/v1/chat/completions';
|
|
96
102
|
const tools = [
|
|
97
103
|
{ 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'] } } },
|
|
98
|
-
{ type: 'function', function: { name: 'read_file', description: 'Read file contents
|
|
99
|
-
{ type: 'function', function: { name: 'write_file', description: 'Create or overwrite a file
|
|
100
|
-
{ type: 'function', function: { name: 'edit_file', description: 'Make precise edits to a file
|
|
104
|
+
{ type: 'function', function: { name: 'read_file', description: 'Read file contents', parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } } },
|
|
105
|
+
{ type: 'function', function: { name: 'write_file', description: 'Create or overwrite a file', parameters: { type: 'object', properties: { path: { type: 'string' }, content: { type: 'string' } }, required: ['path', 'content'] } } },
|
|
106
|
+
{ type: 'function', function: { name: 'edit_file', description: 'Make precise edits to a file', parameters: { type: 'object', properties: { path: { type: 'string' }, search: { type: 'string' }, replace: { type: 'string' } }, required: ['path', 'search', 'replace'] } } },
|
|
101
107
|
{ type: 'function', function: { name: 'list_dir', description: 'List directory contents', parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } } },
|
|
102
|
-
{ type: 'function', function: { name: 'search_files', description: 'Find files by
|
|
108
|
+
{ type: 'function', function: { name: 'search_files', description: 'Find files by pattern', parameters: { type: 'object', properties: { pattern: { type: 'string' }, cwd: { type: 'string' } }, required: ['pattern'] } } },
|
|
103
109
|
{ type: 'function', function: { name: 'grep', description: 'Search file contents', parameters: { type: 'object', properties: { pattern: { type: 'string' }, path: { type: 'string' } }, required: ['pattern', 'path'] } } },
|
|
104
110
|
{ type: 'function', function: { name: 'git', description: 'Run git commands', parameters: { type: 'object', properties: { args: { type: 'string' }, cwd: { type: 'string' } }, required: ['args'] } } },
|
|
105
111
|
{ type: 'function', function: { name: 'npm', description: 'Run npm/yarn/pnpm/bun commands', parameters: { type: 'object', properties: { args: { type: 'string' }, cwd: { type: 'string' } }, required: ['args'] } } },
|
|
@@ -168,17 +174,14 @@ Working directory: ${process.cwd()}` }, ...currentMessages],
|
|
|
168
174
|
const toolCalls = choice.message?.tool_calls || [];
|
|
169
175
|
currentMessages.push({ role: 'assistant', content });
|
|
170
176
|
setMessages([...currentMessages]);
|
|
171
|
-
scrollRef.current++;
|
|
172
177
|
if (toolCalls.length === 0) {
|
|
173
178
|
setStatus('Done');
|
|
174
179
|
break;
|
|
175
180
|
}
|
|
176
|
-
// Execute tools
|
|
177
181
|
for (const tc of toolCalls) {
|
|
178
182
|
const args = JSON.parse(tc.function.arguments || '{}');
|
|
179
183
|
const toolName = tc.function.name;
|
|
180
184
|
setStatus(`Running: ${toolName}...`);
|
|
181
|
-
scrollRef.current++;
|
|
182
185
|
let output = '';
|
|
183
186
|
try {
|
|
184
187
|
const { execSync } = await import('child_process');
|
|
@@ -265,7 +268,6 @@ Working directory: ${process.cwd()}` }, ...currentMessages],
|
|
|
265
268
|
}
|
|
266
269
|
currentMessages.push({ role: 'tool', content: output.slice(0, 2000), tool_call_id: tc.id });
|
|
267
270
|
setMessages([...currentMessages]);
|
|
268
|
-
scrollRef.current++;
|
|
269
271
|
}
|
|
270
272
|
}
|
|
271
273
|
if (iterations >= maxIterations)
|
|
@@ -292,11 +294,7 @@ Working directory: ${process.cwd()}` }, ...currentMessages],
|
|
|
292
294
|
setInput(v => v + input);
|
|
293
295
|
}
|
|
294
296
|
});
|
|
295
|
-
return React.createElement(Box, { flexDirection: 'column', height: '100%' },
|
|
296
|
-
// Header
|
|
297
|
-
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)),
|
|
298
|
-
// Messages
|
|
299
|
-
React.createElement(Box, { flexDirection: 'column', flexGrow: 1, paddingX: 2, paddingY: 1, overflowY: 'hidden' }, visibleMessages.map((m, i) => {
|
|
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) => {
|
|
300
298
|
if (m.role === 'user') {
|
|
301
299
|
return React.createElement(Box, { key: i, marginTop: 1 }, React.createElement(Text, { color: '#7C5CFC', bold: true }, '> '), React.createElement(Text, { color: '#FAFAFA' }, m.content));
|
|
302
300
|
}
|
|
@@ -304,17 +302,6 @@ Working directory: ${process.cwd()}` }, ...currentMessages],
|
|
|
304
302
|
return React.createElement(Box, { key: i, marginTop: 0, paddingLeft: 2 }, React.createElement(Text, { color: '#52525B' }, ' ' + m.content.slice(0, 200)));
|
|
305
303
|
}
|
|
306
304
|
return React.createElement(Box, { key: i, marginTop: 1, paddingLeft: 2 }, React.createElement(Text, { color: '#A1A1AA' }, m.content.slice(0, 500)));
|
|
307
|
-
}), thinking && React.createElement(Text, { color: '#F59E0B' }, ' ⏳ thinking...')),
|
|
308
|
-
// Input
|
|
309
|
-
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' }, '▌')),
|
|
310
|
-
// Footer
|
|
311
|
-
React.createElement(Box, { paddingX: 2 }, React.createElement(Text, { color: '#3F3F46' }, ' Ctrl+C to exit | OWL-Alpha ')));
|
|
312
|
-
}
|
|
313
|
-
// ── Main App ────────────────────────────────────────────────────────
|
|
314
|
-
export default function App() {
|
|
315
|
-
const config = loadConfig();
|
|
316
|
-
if (!config?.openrouterKey) {
|
|
317
|
-
return React.createElement(Onboarding, { onComplete: () => { } });
|
|
318
|
-
}
|
|
319
|
-
return React.createElement(ChatScreen, { config });
|
|
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 ')));
|
|
320
306
|
}
|
|
307
|
+
export default App;
|
package/package.json
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lumina-code-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
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
|
"ink": "^5.1.0",
|
|
14
24
|
"react": "^18.3.1",
|