lumina-code-agent 1.2.0 → 1.4.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 +302 -54
- package/package.json +3 -7
package/dist/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @ts-nocheck
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import React, { useState, useCallback, useRef } from 'react';
|
|
4
|
+
import { Box, Text, useInput, useApp, Spacer } from 'ink';
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs';
|
|
5
6
|
import { homedir } from 'os';
|
|
6
|
-
import { join } from 'path';
|
|
7
|
+
import { join, dirname, resolve, relative } from 'path';
|
|
7
8
|
const CONFIG_DIR = join(homedir(), '.lumina');
|
|
8
9
|
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
9
|
-
|
|
10
|
+
function loadConfig() {
|
|
10
11
|
try {
|
|
11
12
|
if (!existsSync(CONFIG_FILE))
|
|
12
13
|
return null;
|
|
@@ -16,57 +17,304 @@ async function loadConfig() {
|
|
|
16
17
|
return null;
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
|
-
|
|
20
|
+
function saveConfig(config) {
|
|
20
21
|
if (!existsSync(CONFIG_DIR))
|
|
21
22
|
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
24
|
+
}
|
|
25
|
+
// ── Simple Text Input Component ─────────────────────────────────────
|
|
26
|
+
function PromptInput({ value, onChange, onSubmit, placeholder }) {
|
|
27
|
+
useInput((input, key) => {
|
|
28
|
+
if (key.return && onSubmit) {
|
|
29
|
+
onSubmit(value);
|
|
30
|
+
}
|
|
31
|
+
else if (key.backspace || key.delete) {
|
|
32
|
+
onChange(value.slice(0, -1));
|
|
33
|
+
}
|
|
34
|
+
else if (!key.ctrl && !key.meta && input) {
|
|
35
|
+
onChange(value + input);
|
|
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' }, '▌'));
|
|
39
|
+
}
|
|
40
|
+
// ── Onboarding Screen ───────────────────────────────────────────────
|
|
41
|
+
function Onboarding({ onComplete }) {
|
|
42
|
+
const [step, setStep] = useState(0);
|
|
43
|
+
const [apiKey, setApiKey] = useState('');
|
|
44
|
+
const [name, setName] = useState('');
|
|
45
|
+
const handleKey = useCallback((input, key) => {
|
|
46
|
+
if (key.return) {
|
|
47
|
+
if (step === 0) {
|
|
48
|
+
setStep(1);
|
|
49
|
+
}
|
|
50
|
+
else if (step === 1 && apiKey.trim().length > 10) {
|
|
51
|
+
setStep(2);
|
|
52
|
+
}
|
|
53
|
+
else if (step === 2) {
|
|
54
|
+
saveConfig({ openrouterKey: apiKey.trim(), userName: name.trim() || 'User' });
|
|
55
|
+
onComplete();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else if (key.backspace || key.delete) {
|
|
59
|
+
if (step === 1)
|
|
60
|
+
setApiKey(v => v.slice(0, -1));
|
|
61
|
+
if (step === 2)
|
|
62
|
+
setName(v => v.slice(0, -1));
|
|
63
|
+
}
|
|
64
|
+
else if (!key.ctrl && !key.meta && input) {
|
|
65
|
+
if (step === 1)
|
|
66
|
+
setApiKey(v => v + input);
|
|
67
|
+
if (step === 2)
|
|
68
|
+
setName(v => v + input);
|
|
69
|
+
}
|
|
70
|
+
}, [step, apiKey, name, onComplete]);
|
|
71
|
+
useInput(handleKey);
|
|
72
|
+
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!')));
|
|
28
73
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
74
|
+
// ── Chat Screen ─────────────────────────────────────────────────────
|
|
75
|
+
function ChatScreen({ config }) {
|
|
76
|
+
const { exit } = useApp();
|
|
77
|
+
const [messages, setMessages] = useState([]);
|
|
78
|
+
const [input, setInput] = useState('');
|
|
79
|
+
const [status, setStatus] = useState('Ready');
|
|
80
|
+
const [thinking, setThinking] = useState(false);
|
|
81
|
+
const scrollRef = useRef(0);
|
|
82
|
+
const visibleMessages = messages.slice(-50);
|
|
83
|
+
const handleSubmit = useCallback(async (text) => {
|
|
84
|
+
const trimmed = text.trim();
|
|
85
|
+
if (!trimmed || thinking)
|
|
86
|
+
return;
|
|
87
|
+
setInput('');
|
|
88
|
+
setThinking(true);
|
|
89
|
+
setStatus('Thinking...');
|
|
90
|
+
scrollRef.current++;
|
|
91
|
+
const userMsg = { role: 'user', content: trimmed };
|
|
92
|
+
const newMessages = [...messages, userMsg];
|
|
93
|
+
setMessages(newMessages);
|
|
94
|
+
try {
|
|
95
|
+
const OPENROUTER_URL = 'https://openrouter.ai/api/v1/chat/completions';
|
|
96
|
+
const tools = [
|
|
97
|
+
{ 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. ALWAYS read before editing.', parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } } },
|
|
99
|
+
{ type: 'function', function: { name: 'write_file', description: 'Create or overwrite a file. Creates directories automatically.', parameters: { type: 'object', properties: { path: { type: 'string' }, content: { type: 'string' } }, required: ['path', 'content'] } } },
|
|
100
|
+
{ type: 'function', function: { name: 'edit_file', description: 'Make precise edits to a file. search/replace.', parameters: { type: 'object', properties: { path: { type: 'string' }, search: { type: 'string' }, replace: { type: 'string' } }, required: ['path', 'search', 'replace'] } } },
|
|
101
|
+
{ 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 glob pattern', parameters: { type: 'object', properties: { pattern: { type: 'string' }, cwd: { type: 'string' } }, required: ['pattern'] } } },
|
|
103
|
+
{ type: 'function', function: { name: 'grep', description: 'Search file contents', parameters: { type: 'object', properties: { pattern: { type: 'string' }, path: { type: 'string' } }, required: ['pattern', 'path'] } } },
|
|
104
|
+
{ type: 'function', function: { name: 'git', description: 'Run git commands', parameters: { type: 'object', properties: { args: { type: 'string' }, cwd: { type: 'string' } }, required: ['args'] } } },
|
|
105
|
+
{ type: 'function', function: { name: 'npm', description: 'Run npm/yarn/pnpm/bun commands', parameters: { type: 'object', properties: { args: { type: 'string' }, cwd: { type: 'string' } }, required: ['args'] } } },
|
|
106
|
+
{ type: 'function', function: { name: 'deploy', description: 'Deploy to Vercel', parameters: { type: 'object', properties: { target: { type: 'string' }, cwd: { type: 'string' } } } } },
|
|
107
|
+
];
|
|
108
|
+
let iterations = 0;
|
|
109
|
+
const maxIterations = 30;
|
|
110
|
+
let currentMessages = [...newMessages];
|
|
111
|
+
while (iterations < maxIterations) {
|
|
112
|
+
iterations++;
|
|
113
|
+
setStatus(`Working (step ${iterations})...`);
|
|
114
|
+
const res = await fetch(OPENROUTER_URL, {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${config.openrouterKey}`, 'HTTP-Referer': 'https://luminaai.co.in', 'X-Title': 'Lumina Code' },
|
|
117
|
+
body: JSON.stringify({
|
|
118
|
+
model: 'openrouter/owl-alpha',
|
|
119
|
+
messages: [{ role: 'system', content: `You are LUMINA CODE — an elite AI coding agent.
|
|
120
|
+
|
|
121
|
+
MODEL: openrouter/owl-alpha (1M+ context, best reasoning)
|
|
122
|
+
|
|
123
|
+
WORKFLOW:
|
|
124
|
+
1. PLAN: Analyze the task. Create a brief plan.
|
|
125
|
+
2. ACT: Execute tools step by step. Read before writing.
|
|
126
|
+
3. VERIFY: Run builds, check for errors.
|
|
127
|
+
4. FIX: If something fails, debug and fix immediately.
|
|
128
|
+
5. DEPLOY: If requested, deploy automatically.
|
|
129
|
+
|
|
130
|
+
QUALITY STANDARDS:
|
|
131
|
+
- Production-grade code, always
|
|
132
|
+
- TypeScript with proper types (never use 'any')
|
|
133
|
+
- Error handling everywhere
|
|
134
|
+
- Responsive design (320px to 2560px)
|
|
135
|
+
- Accessible (semantic HTML, ARIA, keyboard navigation)
|
|
136
|
+
- Clean architecture, modern patterns
|
|
137
|
+
- Beautiful UI (consistent spacing, typography, color)
|
|
138
|
+
|
|
139
|
+
FORBIDDEN:
|
|
140
|
+
- Lorem ipsum or placeholder content
|
|
141
|
+
- TODO/FIXME comments in production code
|
|
142
|
+
- Emoji in code or UI
|
|
143
|
+
- var keyword (always let/const)
|
|
144
|
+
- any type in TypeScript
|
|
145
|
+
- Skipping error handling
|
|
146
|
+
- Hardcoded secrets
|
|
147
|
+
|
|
148
|
+
When using a tool, output ONLY:
|
|
149
|
+
TOOL: <name>
|
|
150
|
+
PARAMS: <json>
|
|
151
|
+
|
|
152
|
+
Working directory: ${process.cwd()}` }, ...currentMessages],
|
|
153
|
+
tools,
|
|
154
|
+
stream: false,
|
|
155
|
+
max_tokens: 32000,
|
|
156
|
+
temperature: 0.1,
|
|
157
|
+
}),
|
|
158
|
+
});
|
|
159
|
+
if (!res.ok) {
|
|
160
|
+
const err = await res.text().catch(() => '');
|
|
161
|
+
throw new Error(`API error ${res.status}: ${err.slice(0, 200)}`);
|
|
162
|
+
}
|
|
163
|
+
const data = await res.json();
|
|
164
|
+
const choice = data.choices?.[0];
|
|
165
|
+
if (!choice)
|
|
166
|
+
throw new Error('No response from model');
|
|
167
|
+
const content = choice.message?.content || '';
|
|
168
|
+
const toolCalls = choice.message?.tool_calls || [];
|
|
169
|
+
currentMessages.push({ role: 'assistant', content });
|
|
170
|
+
setMessages([...currentMessages]);
|
|
171
|
+
scrollRef.current++;
|
|
172
|
+
if (toolCalls.length === 0) {
|
|
173
|
+
setStatus('Done');
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
// Execute tools
|
|
177
|
+
for (const tc of toolCalls) {
|
|
178
|
+
const args = JSON.parse(tc.function.arguments || '{}');
|
|
179
|
+
const toolName = tc.function.name;
|
|
180
|
+
setStatus(`Running: ${toolName}...`);
|
|
181
|
+
scrollRef.current++;
|
|
182
|
+
let output = '';
|
|
183
|
+
try {
|
|
184
|
+
const { execSync } = await import('child_process');
|
|
185
|
+
switch (toolName) {
|
|
186
|
+
case 'run_command': {
|
|
187
|
+
const cwd = args.cwd || process.cwd();
|
|
188
|
+
output = execSync(args.command, { cwd, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: args.timeout || 120000, shell: true }) || '(no output)';
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case 'read_file': {
|
|
192
|
+
output = readFileSync(args.path, 'utf-8');
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
case 'write_file': {
|
|
196
|
+
const dir = dirname(resolve(args.path));
|
|
197
|
+
if (!existsSync(dir))
|
|
198
|
+
mkdirSync(dir, { recursive: true });
|
|
199
|
+
writeFileSync(args.path, args.content, 'utf-8');
|
|
200
|
+
output = `Wrote ${args.content.length} chars to ${args.path}`;
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
case 'edit_file': {
|
|
204
|
+
let fileContent = readFileSync(args.path, 'utf-8');
|
|
205
|
+
if (!fileContent.includes(args.search))
|
|
206
|
+
throw new Error(`String not found: "${args.search.slice(0, 50)}"`);
|
|
207
|
+
fileContent = fileContent.replace(args.search, args.replace);
|
|
208
|
+
writeFileSync(args.path, fileContent, 'utf-8');
|
|
209
|
+
output = `Edited ${args.path}`;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
case 'list_dir': {
|
|
213
|
+
const entries = readdirSync(args.path, { withFileTypes: true });
|
|
214
|
+
output = entries.map(e => `${e.isDirectory() ? '📁' : '📄'} ${e.name}`).join('\n');
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
case 'search_files': {
|
|
218
|
+
const results = [];
|
|
219
|
+
const search = (dir, depth) => {
|
|
220
|
+
if (depth > 5)
|
|
221
|
+
return;
|
|
222
|
+
try {
|
|
223
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
224
|
+
if (e.name.startsWith('.') || e.name === 'node_modules' || e.name === 'dist')
|
|
225
|
+
continue;
|
|
226
|
+
const fp = join(dir, e.name);
|
|
227
|
+
if (e.isDirectory())
|
|
228
|
+
search(fp, depth + 1);
|
|
229
|
+
else if (new RegExp(args.pattern.replace(/\*/g, '.*'), 'i').test(e.name))
|
|
230
|
+
results.push(relative(args.cwd || process.cwd(), fp));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch { }
|
|
234
|
+
};
|
|
235
|
+
search(args.cwd || process.cwd(), 0);
|
|
236
|
+
output = results.join('\n') || 'No files found';
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
case 'grep': {
|
|
240
|
+
const content = readFileSync(args.path, 'utf-8');
|
|
241
|
+
const lines = content.split('\n');
|
|
242
|
+
const regex = new RegExp(args.pattern, 'gi');
|
|
243
|
+
output = lines.map((l, i) => regex.test(l) ? `${i + 1}: ${l}` : null).filter(Boolean).join('\n') || 'No matches';
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
case 'git': {
|
|
247
|
+
output = execSync(`git ${args.args}`, { cwd: args.cwd || process.cwd(), encoding: 'utf-8', maxBuffer: 1024 * 1024 }) || '(ok)';
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
case 'npm': {
|
|
251
|
+
const pm = existsSync(join(args.cwd || process.cwd(), 'bun.lockb')) ? 'bun' : existsSync(join(args.cwd || process.cwd(), 'yarn.lock')) ? 'yarn' : 'npm';
|
|
252
|
+
output = execSync(`${pm} ${args.args}`, { cwd: args.cwd || process.cwd(), encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 120000 }) || '(ok)';
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
case 'deploy': {
|
|
256
|
+
output = execSync('npx vercel deploy --prod --yes', { cwd: args.cwd || process.cwd(), encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 300000 }) || '(deployed)';
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
default:
|
|
260
|
+
output = `Unknown tool: ${toolName}`;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch (e) {
|
|
264
|
+
output = `Error: ${e.message}`;
|
|
265
|
+
}
|
|
266
|
+
currentMessages.push({ role: 'tool', content: output.slice(0, 2000), tool_call_id: tc.id });
|
|
267
|
+
setMessages([...currentMessages]);
|
|
268
|
+
scrollRef.current++;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (iterations >= maxIterations)
|
|
272
|
+
setStatus('Reached max iterations');
|
|
273
|
+
}
|
|
274
|
+
catch (e) {
|
|
275
|
+
setMessages(prev => [...prev, { role: 'assistant', content: `⚠ Error: ${e.message}` }]);
|
|
276
|
+
setStatus('Error');
|
|
277
|
+
}
|
|
278
|
+
setThinking(false);
|
|
279
|
+
}, [input, thinking, messages, config]);
|
|
280
|
+
useInput((input, key) => {
|
|
281
|
+
if (key.ctrl && input === 'c')
|
|
282
|
+
exit();
|
|
283
|
+
if (key.ctrl && input === 'd')
|
|
284
|
+
exit();
|
|
285
|
+
if (key.return) {
|
|
286
|
+
handleSubmit(input);
|
|
287
|
+
}
|
|
288
|
+
else if (key.backspace || key.delete) {
|
|
289
|
+
setInput(v => v.slice(0, -1));
|
|
290
|
+
}
|
|
291
|
+
else if (!key.ctrl && !key.meta) {
|
|
292
|
+
setInput(v => v + input);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
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) => {
|
|
300
|
+
if (m.role === 'user') {
|
|
301
|
+
return React.createElement(Box, { key: i, marginTop: 1 }, React.createElement(Text, { color: '#7C5CFC', bold: true }, '> '), React.createElement(Text, { color: '#FAFAFA' }, m.content));
|
|
302
|
+
}
|
|
303
|
+
if (m.role === 'tool') {
|
|
304
|
+
return React.createElement(Box, { key: i, marginTop: 0, paddingLeft: 2 }, React.createElement(Text, { color: '#52525B' }, ' ' + m.content.slice(0, 200)));
|
|
305
|
+
}
|
|
306
|
+
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: () => { } });
|
|
45
318
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const { render } = await import('ink');
|
|
49
|
-
const React = await import('react');
|
|
50
|
-
render(React.createElement(TUIApp, { prompt, config, effort, autoApprove: opts.yes || false, cwd: opts.cwd }));
|
|
51
|
-
});
|
|
52
|
-
// Config commands: lumina config, lumina config set <key> <value>
|
|
53
|
-
const configCmd = program.command('config').description('Manage configuration');
|
|
54
|
-
configCmd.action(async () => {
|
|
55
|
-
const config = await ensureConfig();
|
|
56
|
-
console.log('\n Lumina Code Configuration\n');
|
|
57
|
-
console.log(' Config:', CONFIG_FILE);
|
|
58
|
-
console.log(' API Key:', config.openrouterKey ? 'Set (' + config.openrouterKey.slice(0, 8) + '...)' : 'NOT SET');
|
|
59
|
-
console.log(' Default Effort:', config.defaultEffort || 'normal');
|
|
60
|
-
console.log('\n Commands:');
|
|
61
|
-
console.log(' lumina config set openrouter-key <key>');
|
|
62
|
-
console.log(' lumina config set default-effort <quick|normal|beast>\n');
|
|
63
|
-
});
|
|
64
|
-
configCmd.command('set <key> <value>')
|
|
65
|
-
.description('Set a config value')
|
|
66
|
-
.action(async (key, value) => {
|
|
67
|
-
const config = await ensureConfig();
|
|
68
|
-
config[key] = value;
|
|
69
|
-
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
70
|
-
console.log(' Set', key, '=', value);
|
|
71
|
-
});
|
|
72
|
-
program.parse();
|
|
319
|
+
return React.createElement(ChatScreen, { config });
|
|
320
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lumina-code-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Lumina Code - AI coding agent",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -10,16 +10,12 @@
|
|
|
10
10
|
"engines": { "node": ">=18.0.0" },
|
|
11
11
|
"scripts": { "build": "tsc --noEmit false", "prepublishOnly": "npm run build" },
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"commander": "^12.1.0",
|
|
14
13
|
"ink": "^5.1.0",
|
|
15
14
|
"react": "^18.3.1",
|
|
16
|
-
"chalk": "^5.3.0"
|
|
17
|
-
"cross-spawn": "^7.0.3",
|
|
18
|
-
"node-fetch": "^3.3.2"
|
|
15
|
+
"chalk": "^5.3.0"
|
|
19
16
|
},
|
|
20
17
|
"devDependencies": {
|
|
21
|
-
"@types/
|
|
22
|
-
"@types/cross-spawn": "^6.0.6",
|
|
18
|
+
"@types/react": "^18.3.12",
|
|
23
19
|
"typescript": "^5.7.2"
|
|
24
20
|
}
|
|
25
21
|
}
|