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.
Files changed (2) hide show
  1. package/dist/index.js +34 -47
  2. 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
- import React, { useState, useCallback, useRef } from 'react';
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
- // ── 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' }, '▌'));
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 Onboarding({ onComplete }) {
50
+ function OnboardingScreen({ onComplete }) {
42
51
  const [step, setStep] = useState(0);
43
52
  const [apiKey, setApiKey] = useState('');
44
53
  const [name, setName] = useState('');
45
- const handleKey = useCallback((input, key) => {
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
- }, [step, apiKey, name, onComplete]);
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. 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'] } } },
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 glob pattern', parameters: { type: 'object', properties: { pattern: { type: 'string' }, cwd: { type: 'string' } }, required: ['pattern'] } } },
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.4.0",
3
+ "version": "1.5.1",
4
4
  "description": "Lumina Code - AI coding agent",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
- "bin": { "lumina": "dist/index.js" },
7
+ "bin": {
8
+ "lumina": "dist/index.js"
9
+ },
8
10
  "main": "dist/index.js",
9
- "files": ["dist/", "README.md"],
10
- "engines": { "node": ">=18.0.0" },
11
- "scripts": { "build": "tsc --noEmit false", "prepublishOnly": "npm run build" },
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",