lumina-code-agent 1.5.0 → 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.
Files changed (2) hide show
  1. package/dist/index.js +89 -165
  2. package/package.json +3 -7
package/dist/index.js CHANGED
@@ -1,30 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  // @ts-nocheck
3
- // Check if we have a proper TTY for the TUI
4
- const hasTTY = process.stdin.isTTY && process.stdout.isTTY;
5
- if (!hasTTY) {
6
- // Non-TUI mode: just show help
7
- console.log('');
8
- console.log(' ⚡ LUMINA CODE — AI Coding Agent');
9
- console.log('');
10
- console.log(' Lumina Code requires a proper terminal (TTY) to run.');
11
- console.log(' Please open one of these and type: lumina');
12
- console.log('');
13
- console.log(' • Windows Terminal (recommended)');
14
- console.log(' • PowerShell');
15
- console.log(' • Command Prompt (cmd.exe)');
16
- console.log(' • iTerm2 / Terminal.app (macOS)');
17
- console.log(' • Any Linux terminal');
18
- console.log('');
19
- console.log(' If you\'re in Git Bash, try running from Windows Terminal instead.');
20
- console.log('');
21
- process.exit(0);
22
- }
23
- import React, { useState, useCallback, useRef } from 'react';
24
- import { Box, Text, useInput, useApp, Spacer } from 'ink';
25
3
  import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs';
26
4
  import { homedir } from 'os';
27
5
  import { join, dirname, resolve, relative } from 'path';
6
+ import { createInterface } from 'readline';
7
+ import { execSync } from 'child_process';
28
8
  const CONFIG_DIR = join(homedir(), '.lumina');
29
9
  const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
30
10
  function loadConfig() {
@@ -42,68 +22,59 @@ function saveConfig(config) {
42
22
  mkdirSync(CONFIG_DIR, { recursive: true });
43
23
  writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
44
24
  }
45
- // ── Onboarding Screen ───────────────────────────────────────────────
46
- function Onboarding({ onComplete }) {
47
- const [step, setStep] = useState(0);
48
- const [apiKey, setApiKey] = useState('');
49
- const [name, setName] = useState('');
50
- useInput((input, key) => {
51
- if (key.return) {
52
- if (step === 0) {
53
- setStep(1);
54
- }
55
- else if (step === 1 && apiKey.trim().length > 10) {
56
- setStep(2);
57
- }
58
- else if (step === 2) {
59
- saveConfig({ openrouterKey: apiKey.trim(), userName: name.trim() || 'User' });
60
- onComplete();
61
- }
62
- }
63
- else if (key.backspace || key.delete) {
64
- if (step === 1)
65
- setApiKey(v => v.slice(0, -1));
66
- if (step === 2)
67
- setName(v => v.slice(0, -1));
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);
40
+ }
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('');
46
+ }
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;
68
63
  }
69
- else if (!key.ctrl && !key.meta && input) {
70
- if (step === 1)
71
- setApiKey(v => v + input);
72
- if (step === 2)
73
- setName(v => v + input);
64
+ if (trimmed === '/help') {
65
+ console.log(' Commands: /help /clear /exit');
66
+ continue;
74
67
  }
75
- });
76
- 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!')));
77
- }
78
- // ── Chat Screen ─────────────────────────────────────────────────────
79
- function ChatScreen({ config }) {
80
- const { exit } = useApp();
81
- const [messages, setMessages] = useState([]);
82
- const [input, setInput] = useState('');
83
- const [status, setStatus] = useState('Ready');
84
- const [thinking, setThinking] = useState(false);
85
- const scrollRef = useRef(0);
86
- const visibleMessages = messages.slice(-50);
87
- const handleSubmit = useCallback(async (text) => {
88
- const trimmed = text.trim();
89
- if (!trimmed || thinking)
90
- return;
91
- setInput('');
92
- setThinking(true);
93
- setStatus('Thinking...');
94
- scrollRef.current++;
95
- const userMsg = { role: 'user', content: trimmed };
96
- const newMessages = [...messages, userMsg];
97
- setMessages(newMessages);
68
+ messages.push({ role: 'user', content: trimmed });
69
+ console.log('');
98
70
  try {
99
- const OPENROUTER_URL = 'https://openrouter.ai/api/v1/chat/completions';
100
71
  const tools = [
101
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'] } } },
102
- { type: 'function', function: { name: 'read_file', description: 'Read file contents. ALWAYS read before editing.', parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } } },
103
- { 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'] } } },
104
- { 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'] } } },
73
+ { type: 'function', function: { name: 'read_file', description: 'Read file contents', parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } } },
74
+ { 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'] } } },
75
+ { 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'] } } },
105
76
  { type: 'function', function: { name: 'list_dir', description: 'List directory contents', parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } } },
106
- { type: 'function', function: { name: 'search_files', description: 'Find files by glob pattern', parameters: { type: 'object', properties: { pattern: { type: 'string' }, cwd: { type: 'string' } }, required: ['pattern'] } } },
77
+ { type: 'function', function: { name: 'search_files', description: 'Find files by pattern', parameters: { type: 'object', properties: { pattern: { type: 'string' }, cwd: { type: 'string' } }, required: ['pattern'] } } },
107
78
  { type: 'function', function: { name: 'grep', description: 'Search file contents', parameters: { type: 'object', properties: { pattern: { type: 'string' }, path: { type: 'string' } }, required: ['pattern', 'path'] } } },
108
79
  { type: 'function', function: { name: 'git', description: 'Run git commands', parameters: { type: 'object', properties: { args: { type: 'string' }, cwd: { type: 'string' } }, required: ['args'] } } },
109
80
  { type: 'function', function: { name: 'npm', description: 'Run npm/yarn/pnpm/bun commands', parameters: { type: 'object', properties: { args: { type: 'string' }, cwd: { type: 'string' } }, required: ['args'] } } },
@@ -111,19 +82,17 @@ function ChatScreen({ config }) {
111
82
  ];
112
83
  let iterations = 0;
113
84
  const maxIterations = 30;
114
- let currentMessages = [...newMessages];
85
+ let currentMessages = [...messages];
115
86
  while (iterations < maxIterations) {
116
87
  iterations++;
117
- setStatus(`Working (step ${iterations})...`);
118
- 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', {
119
90
  method: 'POST',
120
91
  headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${config.openrouterKey}`, 'HTTP-Referer': 'https://luminaai.co.in', 'X-Title': 'Lumina Code' },
121
92
  body: JSON.stringify({
122
93
  model: 'openrouter/owl-alpha',
123
94
  messages: [{ role: 'system', content: `You are LUMINA CODE — an elite AI coding agent.
124
95
 
125
- MODEL: openrouter/owl-alpha (1M+ context, best reasoning)
126
-
127
96
  WORKFLOW:
128
97
  1. PLAN: Analyze the task. Create a brief plan.
129
98
  2. ACT: Execute tools step by step. Read before writing.
@@ -131,23 +100,10 @@ WORKFLOW:
131
100
  4. FIX: If something fails, debug and fix immediately.
132
101
  5. DEPLOY: If requested, deploy automatically.
133
102
 
134
- QUALITY STANDARDS:
135
- - Production-grade code, always
136
- - TypeScript with proper types (never use 'any')
137
- - Error handling everywhere
138
- - Responsive design (320px to 2560px)
139
- - Accessible (semantic HTML, ARIA, keyboard navigation)
140
- - Clean architecture, modern patterns
141
- - 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.
142
105
 
143
- FORBIDDEN:
144
- - Lorem ipsum or placeholder content
145
- - TODO/FIXME comments in production code
146
- - Emoji in code or UI
147
- - var keyword (always let/const)
148
- - any type in TypeScript
149
- - Skipping error handling
150
- - Hardcoded secrets
106
+ FORBIDDEN: lorem ipsum, TODO comments, emoji in code, var keyword, any type, skipping error handling, hardcoded secrets.
151
107
 
152
108
  When using a tool, output ONLY:
153
109
  TOOL: <name>
@@ -171,30 +127,29 @@ Working directory: ${process.cwd()}` }, ...currentMessages],
171
127
  const content = choice.message?.content || '';
172
128
  const toolCalls = choice.message?.tool_calls || [];
173
129
  currentMessages.push({ role: 'assistant', content });
174
- setMessages([...currentMessages]);
175
- scrollRef.current++;
176
130
  if (toolCalls.length === 0) {
177
- setStatus('Done');
131
+ messages = currentMessages;
132
+ console.log(' ✓ ' + content.slice(0, 500));
133
+ if (content.length > 500)
134
+ console.log(' ...');
135
+ console.log('');
178
136
  break;
179
137
  }
180
138
  for (const tc of toolCalls) {
181
139
  const args = JSON.parse(tc.function.arguments || '{}');
182
140
  const toolName = tc.function.name;
183
- setStatus(`Running: ${toolName}...`);
184
- scrollRef.current++;
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,76 +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]);
271
- scrollRef.current++;
218
+ console.log(` ✓ ${output.slice(0, 100)}`);
272
219
  }
273
220
  }
274
- if (iterations >= maxIterations)
275
- setStatus('Reached max iterations');
276
221
  }
277
222
  catch (e) {
278
- setMessages(prev => [...prev, { role: 'assistant', content: `⚠ Error: ${e.message}` }]);
279
- setStatus('Error');
280
- }
281
- setThinking(false);
282
- }, [input, thinking, messages, config]);
283
- useInput((input, key) => {
284
- if (key.ctrl && input === 'c')
285
- exit();
286
- if (key.ctrl && input === 'd')
287
- exit();
288
- if (key.return) {
289
- handleSubmit(input);
290
- }
291
- else if (key.backspace || key.delete) {
292
- setInput(v => v.slice(0, -1));
293
- }
294
- else if (!key.ctrl && !key.meta) {
295
- setInput(v => v + input);
223
+ console.log(` ⚠ Error: ${e.message}`);
224
+ console.log('');
296
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));
297
235
  });
298
- 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) => {
299
- if (m.role === 'user') {
300
- return React.createElement(Box, { key: i, marginTop: 1 }, React.createElement(Text, { color: '#7C5CFC', bold: true }, '> '), React.createElement(Text, { color: '#FAFAFA' }, m.content));
301
- }
302
- if (m.role === 'tool') {
303
- return React.createElement(Box, { key: i, marginTop: 0, paddingLeft: 2 }, React.createElement(Text, { color: '#52525B' }, ' ' + m.content.slice(0, 200)));
304
- }
305
- return React.createElement(Box, { key: i, marginTop: 1, paddingLeft: 2 }, React.createElement(Text, { color: '#A1A1AA' }, m.content.slice(0, 500)));
306
- }), 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 ')));
307
236
  }
308
- // ── Main App ────────────────────────────────────────────────────────
309
- export default function App() {
310
- const config = loadConfig();
311
- if (!config?.openrouterKey) {
312
- return React.createElement(Onboarding, { onComplete: () => { } });
313
- }
314
- return React.createElement(ChatScreen, { config });
237
+ else {
238
+ chat(config).then(() => process.exit(0));
315
239
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumina-code-agent",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Lumina Code - AI coding agent",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,13 +9,9 @@
9
9
  "files": ["dist/", "README.md"],
10
10
  "engines": { "node": ">=18.0.0" },
11
11
  "scripts": { "build": "tsc --noEmit false", "prepublishOnly": "npm run build" },
12
- "dependencies": {
13
- "ink": "^5.1.0",
14
- "react": "^18.3.1",
15
- "chalk": "^5.3.0"
16
- },
12
+ "dependencies": {},
17
13
  "devDependencies": {
18
- "@types/react": "^18.3.12",
14
+ "@types/node": "^22.10.0",
19
15
  "typescript": "^5.7.2"
20
16
  }
21
17
  }