lumina-code-agent 1.6.2 → 1.6.4

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 +168 -225
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  // @ts-nocheck
3
- import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs';
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
4
4
  import { homedir } from 'os';
5
- import { join, dirname, resolve, relative } from 'path';
5
+ import { join, dirname, resolve } from 'path';
6
6
  import { createInterface } from 'readline';
7
7
  import { execSync } from 'child_process';
8
8
  const CONFIG_DIR = join(homedir(), '.lumina');
@@ -23,125 +23,54 @@ function saveConfig(config) {
23
23
  writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
24
24
  }
25
25
  const rl = createInterface({ input: process.stdin, output: process.stdout });
26
- function ask(question) {
27
- return new Promise(resolve => rl.question(question, resolve));
28
- }
26
+ function ask(q) { return new Promise(r => rl.question(q, r)); }
29
27
  async function onboarding() {
30
- console.log('');
31
- console.log(' ⚡ LUMINA CODE — AI Coding Agent');
32
- console.log('');
28
+ console.log('\n ⚡ LUMINA CODE — AI Coding Agent\n');
33
29
  const apiKey = await ask(' Enter your OpenRouter API key: ');
34
30
  if (!apiKey.trim() || apiKey.trim().length < 10) {
35
- console.log(' ❌ Invalid API key.');
31
+ console.log(' ❌ Invalid key.');
36
32
  process.exit(1);
37
33
  }
38
34
  const name = await ask(' Your name (optional): ');
39
35
  saveConfig({ openrouterKey: apiKey.trim(), userName: name.trim() || 'User' });
40
- console.log(' ✓ Ready!');
41
- console.log('');
36
+ console.log(' ✓ Ready!\n');
42
37
  }
43
- // ── Tool Execution ──────────────────────────────────────────────────
44
- async function executeTool(name, args, cwd) {
45
- const workDir = cwd || process.cwd();
46
- let output = '';
47
- try {
48
- switch (name) {
49
- case 'run_command': {
50
- const cmdCwd = args.cwd || workDir;
51
- output = execSync(args.command, { cwd: cmdCwd, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: args.timeout || 120000 }) || '(ok)';
52
- break;
53
- }
54
- case 'read_file': {
55
- const p = args.path.startsWith('/') ? args.path : join(workDir, args.path);
56
- if (!existsSync(p)) {
57
- output = `File not found: ${args.path}`;
58
- break;
59
- }
60
- output = readFileSync(p, 'utf-8');
61
- break;
62
- }
63
- case 'write_file': {
64
- const p = args.path.startsWith('/') ? args.path : join(workDir, args.path);
65
- const dir = dirname(resolve(p));
66
- if (!existsSync(dir))
67
- mkdirSync(dir, { recursive: true });
68
- writeFileSync(p, args.content, 'utf-8');
69
- output = `✓ Created ${args.path} (${args.content.length} chars)`;
70
- break;
71
- }
72
- case 'edit_file': {
73
- const p = args.path.startsWith('/') ? args.path : join(workDir, args.path);
74
- let fc = readFileSync(p, 'utf-8');
75
- if (!fc.includes(args.search))
76
- throw new Error(`Not found: "${args.search.slice(0, 50)}"`);
77
- fc = fc.replace(args.search, args.replace);
78
- writeFileSync(p, fc, 'utf-8');
79
- output = `✓ Edited ${args.path}`;
80
- break;
81
- }
82
- case 'list_dir': {
83
- const p = args.path || workDir;
84
- const entries = readdirSync(p, { withFileTypes: true });
85
- output = entries.slice(0, 50).map(e => `${e.isDirectory() ? '📁' : '📄'} ${e.name}`).join('\n');
86
- break;
87
- }
88
- case 'search_files': {
89
- const results = [];
90
- const search = (dir, depth) => {
91
- if (depth > 5)
92
- return;
93
- try {
94
- for (const e of readdirSync(dir, { withFileTypes: true })) {
95
- if (e.name.startsWith('.') || e.name === 'node_modules' || e.name === 'dist' || e.name === '.git')
96
- continue;
97
- const fp = join(dir, e.name);
98
- if (e.isDirectory())
99
- search(fp, depth + 1);
100
- else if (new RegExp(args.pattern.replace(/\*/g, '.*').replace(/\?/g, '.'), 'i').test(e.name))
101
- results.push(relative(workDir, fp));
102
- }
103
- }
104
- catch { }
105
- };
106
- search(args.cwd || workDir, 0);
107
- output = results.join('\n') || 'No files found';
108
- break;
109
- }
110
- case 'grep': {
111
- const p = args.path.startsWith('/') ? args.path : join(workDir, args.path);
112
- const c = readFileSync(p, 'utf-8');
113
- output = c.split('\n').map((l, i) => new RegExp(args.pattern, 'gi').test(l) ? `${i + 1}: ${l}` : null).filter(Boolean).join('\n') || 'No matches';
114
- break;
115
- }
116
- case 'git': {
117
- output = execSync(`git ${args.args}`, { cwd: args.cwd || workDir, encoding: 'utf-8', maxBuffer: 1024 * 1024 }) || '(ok)';
118
- break;
119
- }
120
- case 'npm': {
121
- const pm = existsSync(join(args.cwd || workDir, 'bun.lockb')) ? 'bun' : existsSync(join(args.cwd || workDir, 'yarn.lock')) ? 'yarn' : 'npm';
122
- output = execSync(`${pm} ${args.args}`, { cwd: args.cwd || workDir, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 120000 }) || '(ok)';
123
- break;
124
- }
125
- case 'deploy': {
126
- output = execSync('npx vercel deploy --prod --yes', { cwd: args.cwd || workDir, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 300000 }) || '(deployed)';
127
- break;
128
- }
129
- default:
130
- output = `Unknown tool: ${name}`;
38
+ // ── Parse code blocks from model output ─────────────────────────────
39
+ function parseCodeBlocks(text) {
40
+ const files = [];
41
+ // Match ```filename ... ``` blocks
42
+ const blockRegex = /```([\w./\-_]+)?\s*\n([\s\S]*?)```/g;
43
+ let match;
44
+ while ((match = blockRegex.exec(text)) !== null) {
45
+ const filename = match[1]?.trim();
46
+ const content = match[2].trim();
47
+ if (filename && content) {
48
+ files.push({ path: filename, content });
131
49
  }
132
50
  }
133
- catch (e) {
134
- output = `Error: ${e.message}`;
51
+ // Also match FILE: path ... END FILE blocks
52
+ const fileRegex = /FILE:\s*([\w./\-_]+)\n([\s\S]*?)END FILE/g;
53
+ while ((match = fileRegex.exec(text)) !== null) {
54
+ files.push({ path: match[1].trim(), content: match[2].trim() });
135
55
  }
136
- return output;
56
+ return files;
57
+ }
58
+ // ── Execute shell commands from model output ────────────────────────
59
+ function extractCommands(text) {
60
+ const commands = [];
61
+ const cmdRegex = /COMMAND:\s*(.+)/g;
62
+ let match;
63
+ while ((match = cmdRegex.exec(text)) !== null) {
64
+ commands.push(match[1].trim());
65
+ }
66
+ return commands;
137
67
  }
138
68
  // ── Chat Loop ───────────────────────────────────────────────────────
139
69
  async function chat(config) {
140
70
  console.log(' Type what you want to build. I\'ll handle the rest.');
141
- console.log(' Commands: /help /clear /files /exit');
142
- console.log('');
143
- let messages = [];
71
+ console.log(' Commands: /help /clear /files /run /exit\n');
144
72
  const createdFiles = new Set();
73
+ let conversationHistory = [];
145
74
  while (true) {
146
75
  const input = await ask(' > ');
147
76
  const trimmed = input.trim();
@@ -150,153 +79,170 @@ async function chat(config) {
150
79
  if (trimmed === '/exit' || trimmed === '/quit')
151
80
  break;
152
81
  if (trimmed === '/clear') {
153
- messages = [];
154
- console.log(' ✓ Cleared.');
82
+ conversationHistory = [];
83
+ console.log(' ✓ Cleared.\n');
155
84
  continue;
156
85
  }
157
86
  if (trimmed === '/files') {
158
87
  if (createdFiles.size === 0) {
159
- console.log(' No files created yet.');
88
+ console.log(' No files created yet.\n');
160
89
  }
161
90
  else {
162
91
  console.log(' Created files:');
163
92
  for (const f of createdFiles)
164
93
  console.log(` ${f}`);
94
+ console.log('');
165
95
  }
166
96
  continue;
167
97
  }
168
98
  if (trimmed === '/help') {
169
- console.log(' Commands: /help /clear /files /exit');
99
+ console.log(' Commands: /help /clear /files /run <cmd> /exit\n');
170
100
  continue;
171
101
  }
172
- messages.push({ role: 'user', content: trimmed });
173
- console.log('');
174
- try {
175
- const systemPrompt = `You are LUMINA CODE — an elite AI coding agent.
176
-
177
- WORKFLOW:
178
- 1. PLAN: Think about what files you need to create.
179
- 2. ACT: Use write_file to create each file. Use run_command to install packages, run builds.
180
- 3. VERIFY: Run builds to check for errors.
181
- 4. FIX: If something fails, debug and fix immediately.
102
+ if (trimmed.startsWith('/run ')) {
103
+ const cmd = trimmed.slice(5);
104
+ try {
105
+ console.log(` Running: ${cmd}`);
106
+ const out = execSync(cmd, { cwd: process.cwd(), encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 120000 });
107
+ console.log(` ${out.slice(0, 500)}`);
108
+ }
109
+ catch (e) {
110
+ console.log(` Error: ${e.message}`);
111
+ }
112
+ console.log('');
113
+ continue;
114
+ }
115
+ // Build the prompt
116
+ const systemPrompt = `You are LUMINA CODE — an elite AI coding agent. You create COMPLETE, production-grade websites and apps.
182
117
 
183
- QUALITY STANDARDS:
184
- - Production-grade code, always
185
- - TypeScript with proper types (never use 'any')
186
- - Error handling everywhere
187
- - Responsive design (320px to 2560px)
188
- - Accessible (semantic HTML, ARIA, keyboard navigation)
189
- - Clean architecture, modern patterns
190
- - Beautiful UI (consistent spacing, typography, color)
191
- - Use modern CSS (grid, flexbox, custom properties)
192
- - Use Three.js or CSS 3D for 3D effects
118
+ CRITICAL RULES:
119
+ 1. Output COMPLETE files using this exact format:
120
+ FILENAME: path/to/file.ext
121
+ [complete file content]
122
+ END FILE
193
123
 
194
- FORBIDDEN:
195
- - Lorem ipsum or placeholder content
196
- - TODO/FIXME comments in production code
197
- - Emoji in code or UI
198
- - var keyword (always let/const)
199
- - any type in TypeScript
200
- - Skipping error handling
201
- - Hardcoded secrets
124
+ 2. Output shell commands using:
125
+ COMMAND: npm install something
126
+ COMMAND: npm run build
202
127
 
203
- IMPORTANT: When you need to create a file, use the write_file tool IMMEDIATELY.
204
- Don't just describe what you'll do actually DO it.
205
- Create ALL necessary files for a complete, working project.
128
+ 3. Create ALL necessary files for a complete, working project
129
+ 4. Use modern HTML/CSS/JS, Three.js for 3D, CSS animations
130
+ 5. No placeholders, no TODOs, no lorem ipsum
131
+ 6. Beautiful, cinematic, production-quality code
132
+ 7. Each file should be COMPLETE — not snippets
206
133
 
207
134
  Working directory: ${process.cwd()}
208
135
 
209
- When using a tool, output ONLY:
210
- TOOL: <tool_name>
211
- PARAMS: {"key": "value"}`;
212
- const tools = [
213
- { type: 'function', function: { name: 'run_command', description: 'Run any shell command (npm, git, build, etc.)', parameters: { type: 'object', properties: { command: { type: 'string' }, cwd: { type: 'string' }, timeout: { type: 'number' } }, required: ['command'] } } },
214
- { type: 'function', function: { name: 'read_file', description: 'Read file contents', parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } } },
215
- { type: 'function', function: { name: 'write_file', description: 'Create or overwrite a file with exact content', parameters: { type: 'object', properties: { path: { type: 'string' }, content: { type: 'string' } }, required: ['path', 'content'] } } },
216
- { type: 'function', function: { name: 'edit_file', description: 'Edit a file by replacing exact text', parameters: { type: 'object', properties: { path: { type: 'string' }, search: { type: 'string' }, replace: { type: 'string' } }, required: ['path', 'search', 'replace'] } } },
217
- { type: 'function', function: { name: 'list_dir', description: 'List directory contents', parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } } },
218
- { type: 'function', function: { name: 'search_files', description: 'Find files by glob pattern', parameters: { type: 'object', properties: { pattern: { type: 'string' }, cwd: { type: 'string' } }, required: ['pattern'] } } },
219
- { type: 'function', function: { name: 'grep', description: 'Search file contents', parameters: { type: 'object', properties: { pattern: { type: 'string' }, path: { type: 'string' } }, required: ['pattern', 'path'] } } },
220
- { type: 'function', function: { name: 'git', description: 'Run git commands', parameters: { type: 'object', properties: { args: { type: 'string' }, cwd: { type: 'string' } }, required: ['args'] } } },
221
- { type: 'function', function: { name: 'npm', description: 'Run npm/yarn/pnpm/bun commands', parameters: { type: 'object', properties: { args: { type: 'string' }, cwd: { type: 'string' } }, required: ['args'] } } },
222
- { type: 'function', function: { name: 'deploy', description: 'Deploy to Vercel', parameters: { type: 'object', properties: { target: { type: 'string' }, cwd: { type: 'string' } } } } },
223
- ];
224
- let iterations = 0;
225
- const maxIterations = 50;
226
- let currentMessages = [...messages];
227
- let lastAssistantContent = '';
228
- while (iterations < maxIterations) {
229
- iterations++;
230
- process.stdout.write(` ⏳ Thinking (step ${iterations})...\r`);
231
- const controller = new AbortController();
232
- const timeout = setTimeout(() => controller.abort(), 120000);
233
- const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
234
- method: 'POST',
235
- headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${config.openrouterKey}`, 'HTTP-Referer': 'https://luminaai.co.in', 'X-Title': 'Lumina Code' },
236
- body: JSON.stringify({
237
- model: 'openrouter/owl-alpha',
238
- messages: [{ role: 'system', content: systemPrompt }, ...currentMessages],
239
- tools,
240
- tool_choice: 'auto',
241
- stream: false,
242
- max_tokens: 8000,
243
- temperature: 0.1,
244
- }),
245
- signal: controller.signal,
246
- });
247
- clearTimeout(timeout);
248
- if (!res.ok) {
249
- const err = await res.text().catch(() => '');
250
- throw new Error(`API error ${res.status}: ${err.slice(0, 200)}`);
251
- }
252
- const data = await res.json();
253
- const choice = data.choices?.[0];
254
- if (!choice)
255
- throw new Error('No response from model');
256
- const content = choice.message?.content || '';
257
- const toolCalls = choice.message?.tool_calls || [];
258
- lastAssistantContent = content;
259
- currentMessages.push({ role: 'assistant', content, ...(toolCalls.length ? { tool_calls: toolCalls } : {}) });
260
- if (toolCalls.length === 0) {
261
- messages = currentMessages;
262
- if (content)
263
- console.log(` ${content.slice(0, 300)}`);
264
- console.log('');
265
- break;
266
- }
267
- for (const tc of toolCalls) {
268
- let args = {};
136
+ Example output:
137
+ FILENAME: index.html
138
+ <!DOCTYPE html>
139
+ <html>
140
+ ...
141
+ </html>
142
+ END FILE
143
+
144
+ FILENAME: style.css
145
+ :root { --bg: #0a0a0f; }
146
+ ...
147
+ END FILE
148
+
149
+ FILENAME: script.js
150
+ import * as THREE from 'three';
151
+ ...
152
+ END FILE
153
+
154
+ COMMAND: npm install three
155
+ COMMAND: npm run build`;
156
+ const messages = [
157
+ { role: 'system', content: systemPrompt },
158
+ ...conversationHistory,
159
+ { role: 'user', content: trimmed },
160
+ ];
161
+ console.log(' ⏳ Generating...\n');
162
+ try {
163
+ const controller = new AbortController();
164
+ const timeout = setTimeout(() => controller.abort(), 180000);
165
+ const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
166
+ method: 'POST',
167
+ headers: {
168
+ 'Content-Type': 'application/json',
169
+ Authorization: `Bearer ${config.openrouterKey}`,
170
+ 'HTTP-Referer': 'https://luminaai.co.in',
171
+ 'X-Title': 'Lumina Code',
172
+ },
173
+ body: JSON.stringify({
174
+ model: 'openrouter/owl-alpha',
175
+ messages,
176
+ stream: false,
177
+ max_tokens: 16000,
178
+ temperature: 0.2,
179
+ }),
180
+ signal: controller.signal,
181
+ });
182
+ clearTimeout(timeout);
183
+ if (!res.ok) {
184
+ const err = await res.text().catch(() => '');
185
+ throw new Error(`API error ${res.status}: ${err.slice(0, 200)}`);
186
+ }
187
+ const data = await res.json();
188
+ const content = data.choices?.[0]?.message?.content || '';
189
+ if (!content) {
190
+ console.log(' ⚠ No response from model. Try again.\n');
191
+ continue;
192
+ }
193
+ // Parse and create files
194
+ const files = parseCodeBlocks(content);
195
+ const commands = extractCommands(content);
196
+ if (files.length > 0) {
197
+ console.log(` 📁 Creating ${files.length} file(s)...\n`);
198
+ for (const file of files) {
269
199
  try {
270
- args = JSON.parse(tc.function.arguments || '{}');
200
+ const filePath = join(process.cwd(), file.path);
201
+ const dir = dirname(resolve(filePath));
202
+ if (!existsSync(dir))
203
+ mkdirSync(dir, { recursive: true });
204
+ writeFileSync(filePath, file.content, 'utf-8');
205
+ createdFiles.add(file.path);
206
+ console.log(` ✓ ${file.path} (${file.content.length} chars)`);
271
207
  }
272
- catch { }
273
- const toolName = tc.function.name;
274
- console.log(` ⚙ ${toolName}(${JSON.stringify(args).slice(0, 100)})`);
275
- const output = await executeTool(toolName, args, process.cwd());
276
- // Track created files
277
- if (toolName === 'write_file' && args.path) {
278
- createdFiles.add(args.path);
279
- console.log(` ✓ Created: ${args.path}`);
208
+ catch (e) {
209
+ console.log(` ✗ ${file.path}: ${e.message}`);
280
210
  }
281
- else {
282
- const shortOut = output.slice(0, 150).replace(/\n/g, ' ');
283
- console.log(` ✓ ${shortOut}`);
284
- }
285
- currentMessages.push({ role: 'tool', content: output.slice(0, 3000), tool_call_id: tc.id });
286
211
  }
287
212
  }
288
- if (iterations >= maxIterations) {
289
- console.log(' ⚠ Reached max iterations. Partial result:');
290
- console.log(` ${lastAssistantContent.slice(0, 300)}`);
213
+ // Execute commands
214
+ if (commands.length > 0) {
215
+ console.log(`\n ⚙ Running ${commands.length} command(s)...\n`);
216
+ for (const cmd of commands) {
217
+ try {
218
+ console.log(` $ ${cmd}`);
219
+ const out = execSync(cmd, { cwd: process.cwd(), encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 120000 });
220
+ console.log(` ✓ ${out.slice(0, 200)}`);
221
+ }
222
+ catch (e) {
223
+ console.log(` ✗ ${e.message}`);
224
+ }
225
+ }
291
226
  }
292
- if (createdFiles.size > 0) {
293
- console.log(` 📁 Created ${createdFiles.size} file(s)`);
227
+ // Show any text output from the model
228
+ const textOnly = content
229
+ .replace(/FILENAME:[\s\S]*?END FILE/g, '')
230
+ .replace(/COMMAND:.+/g, '')
231
+ .trim();
232
+ if (textOnly) {
233
+ console.log(`\n 💬 ${textOnly.slice(0, 500)}`);
294
234
  }
235
+ console.log(`\n 📊 Total files created: ${createdFiles.size}`);
295
236
  console.log('');
237
+ // Save conversation
238
+ conversationHistory.push({ role: 'user', content: trimmed });
239
+ conversationHistory.push({ role: 'assistant', content });
240
+ // Keep last 10 messages
241
+ if (conversationHistory.length > 20)
242
+ conversationHistory = conversationHistory.slice(-20);
296
243
  }
297
244
  catch (e) {
298
- console.log(` ⚠ Error: ${e.message}`);
299
- console.log('');
245
+ console.log(` ⚠ Error: ${e.message}\n`);
300
246
  }
301
247
  }
302
248
  rl.close();
@@ -304,10 +250,7 @@ PARAMS: {"key": "value"}`;
304
250
  // ── Main ────────────────────────────────────────────────────────────
305
251
  const config = loadConfig();
306
252
  if (!config?.openrouterKey) {
307
- onboarding().then(() => {
308
- const newConfig = loadConfig();
309
- chat(newConfig).then(() => process.exit(0));
310
- });
253
+ onboarding().then(() => chat(loadConfig()).then(() => process.exit(0)));
311
254
  }
312
255
  else {
313
256
  chat(config).then(() => process.exit(0));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumina-code-agent",
3
- "version": "1.6.2",
3
+ "version": "1.6.4",
4
4
  "description": "Lumina Code - AI coding agent",
5
5
  "license": "MIT",
6
6
  "type": "module",