lumina-code-agent 1.5.1 → 1.6.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.
Files changed (2) hide show
  1. package/dist/index.js +84 -152
  2. 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
- // ── 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 });
25
+ const rl = createInterface({ input: process.stdin, output: process.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
- return React.createElement(ChatScreen, { config });
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
- // ── Onboarding Screen ───────────────────────────────────────────────
50
- function OnboardingScreen({ onComplete }) {
51
- const [step, setStep] = useState(0);
52
- const [apiKey, setApiKey] = useState('');
53
- const [name, setName] = useState('');
54
- useInput((input, key) => {
55
- if (key.return) {
56
- if (step === 0) {
57
- setStep(1);
58
- }
59
- else if (step === 1 && apiKey.trim().length > 10) {
60
- setStep(2);
61
- }
62
- else if (step === 2) {
63
- saveConfig({ openrouterKey: apiKey.trim(), userName: name.trim() || 'User' });
64
- onComplete();
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
- else if (!key.ctrl && !key.meta && input) {
74
- if (step === 1)
75
- setApiKey(v => v + input);
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
- 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!')));
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 = [...newMessages];
85
+ let currentMessages = [...messages];
117
86
  while (iterations < maxIterations) {
118
87
  iterations++;
119
- setStatus(`Working (step ${iterations})...`);
120
- const res = await fetch(OPENROUTER_URL, {
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 STANDARDS:
137
- - Production-grade code, always
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
- setStatus('Done');
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
- setStatus(`Running: ${toolName}...`);
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 fileContent = readFileSync(args.path, 'utf-8');
208
- if (!fileContent.includes(args.search))
209
- throw new Error(`String not found: "${args.search.slice(0, 50)}"`);
210
- fileContent = fileContent.replace(args.search, args.replace);
211
- writeFileSync(args.path, fileContent, 'utf-8');
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
- const entries = readdirSync(args.path, { withFileTypes: true });
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' || e.name === 'dist')
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 content = readFileSync(args.path, 'utf-8');
244
- const lines = content.split('\n');
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
- setMessages([...currentMessages]);
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
- setMessages(prev => [...prev, { role: 'assistant', content: `⚠ Error: ${e.message}` }]);
278
- setStatus('Error');
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
- export default App;
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.5.1",
3
+ "version": "1.6.1",
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
- "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
- },
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/react": "^18.3.12",
14
+ "@types/node": "^22.10.0",
29
15
  "typescript": "^5.7.2"
30
16
  }
31
17
  }