lumina-code-agent 1.3.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.
Files changed (2) hide show
  1. package/dist/index.js +108 -67
  2. package/package.json +7 -18
package/dist/index.js CHANGED
@@ -2,10 +2,9 @@
2
2
  // @ts-nocheck
3
3
  import React, { useState, useCallback, useRef } from 'react';
4
4
  import { Box, Text, useInput, useApp, Spacer } from 'ink';
5
- import TextInput from 'ink-text-input';
6
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
5
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs';
7
6
  import { homedir } from 'os';
8
- import { join } from 'path';
7
+ import { join, dirname, resolve, relative } from 'path';
9
8
  const CONFIG_DIR = join(homedir(), '.lumina');
10
9
  const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
11
10
  function loadConfig() {
@@ -23,12 +22,27 @@ function saveConfig(config) {
23
22
  mkdirSync(CONFIG_DIR, { recursive: true });
24
23
  writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
25
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
+ }
26
40
  // ── Onboarding Screen ───────────────────────────────────────────────
27
41
  function Onboarding({ onComplete }) {
28
42
  const [step, setStep] = useState(0);
29
43
  const [apiKey, setApiKey] = useState('');
30
44
  const [name, setName] = useState('');
31
- useInput((input, key) => {
45
+ const handleKey = useCallback((input, key) => {
32
46
  if (key.return) {
33
47
  if (step === 0) {
34
48
  setStep(1);
@@ -37,70 +51,60 @@ function Onboarding({ onComplete }) {
37
51
  setStep(2);
38
52
  }
39
53
  else if (step === 2) {
40
- saveConfig({ openrouterKey: apiKey.trim(), userName: name.trim() || 'User', defaultEffort: 'normal' });
54
+ saveConfig({ openrouterKey: apiKey.trim(), userName: name.trim() || 'User' });
41
55
  onComplete();
42
56
  }
43
57
  }
44
- });
45
- 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 — Better than Claude Code')), 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(TextInput, { value: apiKey, onChange: setApiKey, placeholder: 'sk-or-...' })), 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(TextInput, { value: name, onChange: setName, placeholder: 'Your name' })), React.createElement(Text, { color: '#3F3F46', marginTop: 1 }, ' Press Enter to start coding!')));
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!')));
46
73
  }
47
74
  // ── Chat Screen ─────────────────────────────────────────────────────
48
75
  function ChatScreen({ config }) {
49
76
  const { exit } = useApp();
50
- const [messages, setMessages] = useState([
51
- { role: 'system', content: `You are LUMINA CODE — an elite AI coding agent running locally.
52
-
53
- MODEL: openrouter/owl-alpha (1M+ context, best reasoning)
54
-
55
- WORKFLOW:
56
- 1. PLAN: Analyze the task. Create a brief plan.
57
- 2. ACT: Execute tools step by step. Read before writing.
58
- 3. VERIFY: Run builds, check for errors.
59
- 4. FIX: If something fails, debug and fix.
60
- 5. DEPLOY: If requested, deploy automatically.
61
-
62
- QUALITY: Production-grade code. No placeholders. No TODOs. No emoji in code.
63
- Handle errors. Use TypeScript. Responsive design. Accessible.
64
-
65
- TOOLS: run_command, read_file, write_file, edit_file, list_dir, search_files, grep, git, npm, deploy, detect_project, get_file_tree
66
-
67
- When using a tool, output ONLY:
68
- TOOL: <name>
69
- PARAMS: <json>
70
-
71
- Working directory: ${process.cwd()}` },
72
- ]);
77
+ const [messages, setMessages] = useState([]);
73
78
  const [input, setInput] = useState('');
74
79
  const [status, setStatus] = useState('Ready');
75
80
  const [thinking, setThinking] = useState(false);
76
81
  const scrollRef = useRef(0);
77
- const visibleMessages = messages.filter(m => m.role !== 'system').slice(-50);
78
- const handleSubmit = useCallback(async () => {
79
- const trimmed = input.trim();
82
+ const visibleMessages = messages.slice(-50);
83
+ const handleSubmit = useCallback(async (text) => {
84
+ const trimmed = text.trim();
80
85
  if (!trimmed || thinking)
81
86
  return;
82
87
  setInput('');
83
88
  setThinking(true);
84
89
  setStatus('Thinking...');
90
+ scrollRef.current++;
85
91
  const userMsg = { role: 'user', content: trimmed };
86
92
  const newMessages = [...messages, userMsg];
87
93
  setMessages(newMessages);
88
- scrollRef.current++;
89
94
  try {
90
95
  const OPENROUTER_URL = 'https://openrouter.ai/api/v1/chat/completions';
91
96
  const tools = [
92
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'] } } },
93
- { type: 'function', function: { name: 'read_file', description: 'Read file contents', parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } } },
94
- { 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'] } } },
95
- { 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'] } } },
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'] } } },
96
101
  { type: 'function', function: { name: 'list_dir', description: 'List directory contents', parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } } },
97
- { type: 'function', function: { name: 'search_files', description: 'Find files by pattern', parameters: { type: 'object', properties: { pattern: { type: 'string' }, cwd: { type: 'string' } }, required: ['pattern'] } } },
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'] } } },
98
103
  { type: 'function', function: { name: 'grep', description: 'Search file contents', parameters: { type: 'object', properties: { pattern: { type: 'string' }, path: { type: 'string' } }, required: ['pattern', 'path'] } } },
99
104
  { type: 'function', function: { name: 'git', description: 'Run git commands', parameters: { type: 'object', properties: { args: { type: 'string' }, cwd: { type: 'string' } }, required: ['args'] } } },
100
- { type: 'function', function: { name: 'npm', description: 'Run npm/yarn/pnpm 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'] } } },
101
106
  { type: 'function', function: { name: 'deploy', description: 'Deploy to Vercel', parameters: { type: 'object', properties: { target: { type: 'string' }, cwd: { type: 'string' } } } } },
102
107
  ];
103
- let assistantContent = '';
104
108
  let iterations = 0;
105
109
  const maxIterations = 30;
106
110
  let currentMessages = [...newMessages];
@@ -110,7 +114,47 @@ Working directory: ${process.cwd()}` },
110
114
  const res = await fetch(OPENROUTER_URL, {
111
115
  method: 'POST',
112
116
  headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${config.openrouterKey}`, 'HTTP-Referer': 'https://luminaai.co.in', 'X-Title': 'Lumina Code' },
113
- body: JSON.stringify({ model: 'openrouter/owl-alpha', messages: currentMessages, tools, stream: false, max_tokens: 32000, temperature: 0.1 }),
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
+ }),
114
158
  });
115
159
  if (!res.ok) {
116
160
  const err = await res.text().catch(() => '');
@@ -122,10 +166,10 @@ Working directory: ${process.cwd()}` },
122
166
  throw new Error('No response from model');
123
167
  const content = choice.message?.content || '';
124
168
  const toolCalls = choice.message?.tool_calls || [];
125
- assistantContent = content;
126
- currentMessages.push({ role: 'assistant', content, ...(toolCalls.length ? { tool_calls: toolCalls } : {}) });
169
+ currentMessages.push({ role: 'assistant', content });
170
+ setMessages([...currentMessages]);
171
+ scrollRef.current++;
127
172
  if (toolCalls.length === 0) {
128
- setMessages([...currentMessages]);
129
173
  setStatus('Done');
130
174
  break;
131
175
  }
@@ -137,9 +181,9 @@ Working directory: ${process.cwd()}` },
137
181
  scrollRef.current++;
138
182
  let output = '';
139
183
  try {
184
+ const { execSync } = await import('child_process');
140
185
  switch (toolName) {
141
186
  case 'run_command': {
142
- const { execSync } = await import('child_process');
143
187
  const cwd = args.cwd || process.cwd();
144
188
  output = execSync(args.command, { cwd, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: args.timeout || 120000, shell: true }) || '(no output)';
145
189
  break;
@@ -149,7 +193,7 @@ Working directory: ${process.cwd()}` },
149
193
  break;
150
194
  }
151
195
  case 'write_file': {
152
- const dir = join(args.path, '..');
196
+ const dir = dirname(resolve(args.path));
153
197
  if (!existsSync(dir))
154
198
  mkdirSync(dir, { recursive: true });
155
199
  writeFileSync(args.path, args.content, 'utf-8');
@@ -171,21 +215,19 @@ Working directory: ${process.cwd()}` },
171
215
  break;
172
216
  }
173
217
  case 'search_files': {
174
- const { readdirSync } = await import('fs');
175
- const { join: pathJoin, relative: pathRelative } = await import('path');
176
218
  const results = [];
177
219
  const search = (dir, depth) => {
178
220
  if (depth > 5)
179
221
  return;
180
222
  try {
181
223
  for (const e of readdirSync(dir, { withFileTypes: true })) {
182
- if (e.name.startsWith('.') || e.name === 'node_modules')
224
+ if (e.name.startsWith('.') || e.name === 'node_modules' || e.name === 'dist')
183
225
  continue;
184
- const fp = pathJoin(dir, e.name);
226
+ const fp = join(dir, e.name);
185
227
  if (e.isDirectory())
186
228
  search(fp, depth + 1);
187
- else if (new RegExp(pattern.replace(/\*/g, '.*'), 'i').test(e.name))
188
- results.push(pathRelative(cwd, fp));
229
+ else if (new RegExp(args.pattern.replace(/\*/g, '.*'), 'i').test(e.name))
230
+ results.push(relative(args.cwd || process.cwd(), fp));
189
231
  }
190
232
  }
191
233
  catch { }
@@ -202,18 +244,15 @@ Working directory: ${process.cwd()}` },
202
244
  break;
203
245
  }
204
246
  case 'git': {
205
- const { execSync } = await import('child_process');
206
247
  output = execSync(`git ${args.args}`, { cwd: args.cwd || process.cwd(), encoding: 'utf-8', maxBuffer: 1024 * 1024 }) || '(ok)';
207
248
  break;
208
249
  }
209
250
  case 'npm': {
210
- const { execSync } = await import('child_process');
211
251
  const pm = existsSync(join(args.cwd || process.cwd(), 'bun.lockb')) ? 'bun' : existsSync(join(args.cwd || process.cwd(), 'yarn.lock')) ? 'yarn' : 'npm';
212
252
  output = execSync(`${pm} ${args.args}`, { cwd: args.cwd || process.cwd(), encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 120000 }) || '(ok)';
213
253
  break;
214
254
  }
215
255
  case 'deploy': {
216
- const { execSync } = await import('child_process');
217
256
  output = execSync('npx vercel deploy --prod --yes', { cwd: args.cwd || process.cwd(), encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 300000 }) || '(deployed)';
218
257
  break;
219
258
  }
@@ -229,9 +268,8 @@ Working directory: ${process.cwd()}` },
229
268
  scrollRef.current++;
230
269
  }
231
270
  }
232
- if (iterations >= maxIterations) {
271
+ if (iterations >= maxIterations)
233
272
  setStatus('Reached max iterations');
234
- }
235
273
  }
236
274
  catch (e) {
237
275
  setMessages(prev => [...prev, { role: 'assistant', content: `⚠ Error: ${e.message}` }]);
@@ -239,11 +277,20 @@ Working directory: ${process.cwd()}` },
239
277
  }
240
278
  setThinking(false);
241
279
  }, [input, thinking, messages, config]);
242
- useInput((_input, key) => {
243
- if (key.ctrl && _input === 'c')
280
+ useInput((input, key) => {
281
+ if (key.ctrl && input === 'c')
244
282
  exit();
245
- if (key.ctrl && _input === 'd')
283
+ if (key.ctrl && input === 'd')
246
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
+ }
247
294
  });
248
295
  return React.createElement(Box, { flexDirection: 'column', height: '100%' },
249
296
  // Header
@@ -256,16 +303,10 @@ Working directory: ${process.cwd()}` },
256
303
  if (m.role === 'tool') {
257
304
  return React.createElement(Box, { key: i, marginTop: 0, paddingLeft: 2 }, React.createElement(Text, { color: '#52525B' }, ' ' + m.content.slice(0, 200)));
258
305
  }
259
- // Assistant
260
306
  return React.createElement(Box, { key: i, marginTop: 1, paddingLeft: 2 }, React.createElement(Text, { color: '#A1A1AA' }, m.content.slice(0, 500)));
261
307
  }), thinking && React.createElement(Text, { color: '#F59E0B' }, ' ⏳ thinking...')),
262
308
  // Input
263
- React.createElement(Box, { borderStyle: 'single', borderColor: '#3F3F46', paddingX: 1 }, React.createElement(Text, { color: '#7C5CFC' }, ' > '), React.createElement(TextInput, {
264
- value: input,
265
- onChange: setInput,
266
- onSubmit: handleSubmit,
267
- placeholder: 'What do you want to build?',
268
- })),
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' }, '▌')),
269
310
  // Footer
270
311
  React.createElement(Box, { paddingX: 2 }, React.createElement(Text, { color: '#3F3F46' }, ' Ctrl+C to exit | OWL-Alpha ')));
271
312
  }
package/package.json CHANGED
@@ -1,29 +1,18 @@
1
1
  {
2
2
  "name": "lumina-code-agent",
3
- "version": "1.3.0",
3
+ "version": "1.4.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
- "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
- },
9
+ "files": ["dist/", "README.md"],
10
+ "engines": { "node": ">=18.0.0" },
11
+ "scripts": { "build": "tsc --noEmit false", "prepublishOnly": "npm run build" },
22
12
  "dependencies": {
23
- "chalk": "^5.3.0",
24
13
  "ink": "^5.1.0",
25
- "ink-text-input": "^5.0.1",
26
- "react": "^18.3.1"
14
+ "react": "^18.3.1",
15
+ "chalk": "^5.3.0"
27
16
  },
28
17
  "devDependencies": {
29
18
  "@types/react": "^18.3.12",