lumina-code-agent 1.6.1 → 1.6.3

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 +187 -169
  2. package/package.json +15 -5
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,32 +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(' Better than Claude Code. Better than Codex.');
33
- console.log('');
34
- console.log(' Welcome! Let\'s get you set up.');
35
- console.log('');
28
+ console.log('\n ⚡ LUMINA CODE — AI Coding Agent\n');
36
29
  const apiKey = await ask(' Enter your OpenRouter API key: ');
37
30
  if (!apiKey.trim() || apiKey.trim().length < 10) {
38
- console.log(' ❌ Invalid API key. Please try again.');
31
+ console.log(' ❌ Invalid key.');
39
32
  process.exit(1);
40
33
  }
41
- const name = await ask(' What should I call you? (optional): ');
34
+ const name = await ask(' Your name (optional): ');
42
35
  saveConfig({ openrouterKey: apiKey.trim(), userName: name.trim() || 'User' });
43
- console.log('');
44
- console.log(' ✓ Setup complete! Let\'s build something amazing.');
45
- console.log('');
36
+ console.log(' ✓ Ready!\n');
37
+ }
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 });
49
+ }
50
+ }
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() });
55
+ }
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;
46
67
  }
68
+ // ── Chat Loop ───────────────────────────────────────────────────────
47
69
  async function chat(config) {
48
70
  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 = [];
71
+ console.log(' Commands: /help /clear /files /run /exit\n');
72
+ const createdFiles = new Set();
73
+ let conversationHistory = [];
52
74
  while (true) {
53
75
  const input = await ask(' > ');
54
76
  const trimmed = input.trim();
@@ -57,171 +79,170 @@ async function chat(config) {
57
79
  if (trimmed === '/exit' || trimmed === '/quit')
58
80
  break;
59
81
  if (trimmed === '/clear') {
60
- messages = [];
61
- console.log(' ✓ Cleared.');
82
+ conversationHistory = [];
83
+ console.log(' ✓ Cleared.\n');
84
+ continue;
85
+ }
86
+ if (trimmed === '/files') {
87
+ if (createdFiles.size === 0) {
88
+ console.log(' No files created yet.\n');
89
+ }
90
+ else {
91
+ console.log(' Created files:');
92
+ for (const f of createdFiles)
93
+ console.log(` ${f}`);
94
+ console.log('');
95
+ }
62
96
  continue;
63
97
  }
64
98
  if (trimmed === '/help') {
65
- console.log(' Commands: /help /clear /exit');
99
+ console.log(' Commands: /help /clear /files /run <cmd> /exit\n');
66
100
  continue;
67
101
  }
68
- messages.push({ role: 'user', content: trimmed });
69
- console.log('');
70
- try {
71
- const tools = [
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'] } } },
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'] } } },
76
- { type: 'function', function: { name: 'list_dir', description: 'List directory contents', parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } } },
77
- { type: 'function', function: { name: 'search_files', description: 'Find files by pattern', parameters: { type: 'object', properties: { pattern: { type: 'string' }, cwd: { type: 'string' } }, required: ['pattern'] } } },
78
- { type: 'function', function: { name: 'grep', description: 'Search file contents', parameters: { type: 'object', properties: { pattern: { type: 'string' }, path: { type: 'string' } }, required: ['pattern', 'path'] } } },
79
- { type: 'function', function: { name: 'git', description: 'Run git commands', parameters: { type: 'object', properties: { args: { type: 'string' }, cwd: { type: 'string' } }, required: ['args'] } } },
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'] } } },
81
- { type: 'function', function: { name: 'deploy', description: 'Deploy to Vercel', parameters: { type: 'object', properties: { target: { type: 'string' }, cwd: { type: 'string' } } } } },
82
- ];
83
- let iterations = 0;
84
- const maxIterations = 30;
85
- let currentMessages = [...messages];
86
- while (iterations < maxIterations) {
87
- iterations++;
88
- process.stdout.write(` ⏳ Working (step ${iterations})...\r`);
89
- const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
90
- method: 'POST',
91
- headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${config.openrouterKey}`, 'HTTP-Referer': 'https://luminaai.co.in', 'X-Title': 'Lumina Code' },
92
- body: JSON.stringify({
93
- model: 'openrouter/owl-alpha',
94
- messages: [{ role: 'system', content: `You are LUMINA CODE — an elite AI coding agent.
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.
95
117
 
96
- WORKFLOW:
97
- 1. PLAN: Analyze the task. Create a brief plan.
98
- 2. ACT: Execute tools step by step. Read before writing.
99
- 3. VERIFY: Run builds, check for errors.
100
- 4. FIX: If something fails, debug and fix immediately.
101
- 5. DEPLOY: If requested, deploy automatically.
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
102
123
 
103
- QUALITY: Production-grade code. No placeholders. No TODOs. No emoji in code.
104
- Handle errors. TypeScript. Responsive. Accessible. Clean architecture.
124
+ 2. Output shell commands using:
125
+ COMMAND: npm install something
126
+ COMMAND: npm run build
105
127
 
106
- FORBIDDEN: lorem ipsum, TODO comments, emoji in code, var keyword, any type, skipping error handling, hardcoded secrets.
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
107
133
 
108
- When using a tool, output ONLY:
109
- TOOL: <name>
110
- PARAMS: <json>
134
+ Working directory: ${process.cwd()}
111
135
 
112
- Working directory: ${process.cwd()}` }, ...currentMessages],
113
- tools,
114
- stream: false,
115
- max_tokens: 32000,
116
- temperature: 0.1,
117
- }),
118
- });
119
- if (!res.ok) {
120
- const err = await res.text().catch(() => '');
121
- throw new Error(`API error ${res.status}: ${err.slice(0, 200)}`);
122
- }
123
- const data = await res.json();
124
- const choice = data.choices?.[0];
125
- if (!choice)
126
- throw new Error('No response from model');
127
- const content = choice.message?.content || '';
128
- const toolCalls = choice.message?.tool_calls || [];
129
- currentMessages.push({ role: 'assistant', content });
130
- if (toolCalls.length === 0) {
131
- messages = currentMessages;
132
- console.log(' ✓ ' + content.slice(0, 500));
133
- if (content.length > 500)
134
- console.log(' ...');
135
- console.log('');
136
- break;
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) {
199
+ try {
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)`);
207
+ }
208
+ catch (e) {
209
+ console.log(` ✗ ${file.path}: ${e.message}`);
210
+ }
137
211
  }
138
- for (const tc of toolCalls) {
139
- const args = JSON.parse(tc.function.arguments || '{}');
140
- const toolName = tc.function.name;
141
- console.log(` ⚙ ${toolName}(${JSON.stringify(args).slice(0, 80)})`);
142
- let output = '';
212
+ }
213
+ // Execute commands
214
+ if (commands.length > 0) {
215
+ console.log(`\nRunning ${commands.length} command(s)...\n`);
216
+ for (const cmd of commands) {
143
217
  try {
144
- switch (toolName) {
145
- case 'run_command': {
146
- const cwd = args.cwd || process.cwd();
147
- output = execSync(args.command, { cwd, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: args.timeout || 120000, shell: true }) || '(no output)';
148
- break;
149
- }
150
- case 'read_file':
151
- output = readFileSync(args.path, 'utf-8');
152
- break;
153
- case 'write_file': {
154
- const dir = dirname(resolve(args.path));
155
- if (!existsSync(dir))
156
- mkdirSync(dir, { recursive: true });
157
- writeFileSync(args.path, args.content, 'utf-8');
158
- output = `Wrote ${args.content.length} chars to ${args.path}`;
159
- break;
160
- }
161
- case 'edit_file': {
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');
167
- output = `Edited ${args.path}`;
168
- break;
169
- }
170
- case 'list_dir':
171
- output = readdirSync(args.path, { withFileTypes: true }).map(e => `${e.isDirectory() ? '📁' : '📄'} ${e.name}`).join('\n');
172
- break;
173
- case 'search_files': {
174
- const results = [];
175
- const search = (dir, depth) => {
176
- if (depth > 5)
177
- return;
178
- try {
179
- for (const e of readdirSync(dir, { withFileTypes: true })) {
180
- if (e.name.startsWith('.') || e.name === 'node_modules')
181
- continue;
182
- const fp = join(dir, e.name);
183
- if (e.isDirectory())
184
- search(fp, depth + 1);
185
- else if (new RegExp(args.pattern.replace(/\*/g, '.*'), 'i').test(e.name))
186
- results.push(relative(args.cwd || process.cwd(), fp));
187
- }
188
- }
189
- catch { }
190
- };
191
- search(args.cwd || process.cwd(), 0);
192
- output = results.join('\n') || 'No files found';
193
- break;
194
- }
195
- case 'grep': {
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';
198
- break;
199
- }
200
- case 'git':
201
- output = execSync(`git ${args.args}`, { cwd: args.cwd || process.cwd(), encoding: 'utf-8', maxBuffer: 1024 * 1024 }) || '(ok)';
202
- break;
203
- case 'npm': {
204
- const pm = existsSync(join(args.cwd || process.cwd(), 'bun.lockb')) ? 'bun' : existsSync(join(args.cwd || process.cwd(), 'yarn.lock')) ? 'yarn' : 'npm';
205
- output = execSync(`${pm} ${args.args}`, { cwd: args.cwd || process.cwd(), encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 120000 }) || '(ok)';
206
- break;
207
- }
208
- case 'deploy':
209
- output = execSync('npx vercel deploy --prod --yes', { cwd: args.cwd || process.cwd(), encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 300000 }) || '(deployed)';
210
- break;
211
- default: output = `Unknown tool: ${toolName}`;
212
- }
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)}`);
213
221
  }
214
222
  catch (e) {
215
- output = `Error: ${e.message}`;
223
+ console.log(` ${e.message}`);
216
224
  }
217
- currentMessages.push({ role: 'tool', content: output.slice(0, 2000), tool_call_id: tc.id });
218
- console.log(` ✓ ${output.slice(0, 100)}`);
219
225
  }
220
226
  }
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)}`);
234
+ }
235
+ console.log(`\n 📊 Total files created: ${createdFiles.size}`);
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);
221
243
  }
222
244
  catch (e) {
223
- console.log(` ⚠ Error: ${e.message}`);
224
- console.log('');
245
+ console.log(` ⚠ Error: ${e.message}\n`);
225
246
  }
226
247
  }
227
248
  rl.close();
@@ -229,10 +250,7 @@ Working directory: ${process.cwd()}` }, ...currentMessages],
229
250
  // ── Main ────────────────────────────────────────────────────────────
230
251
  const config = loadConfig();
231
252
  if (!config?.openrouterKey) {
232
- onboarding().then(() => {
233
- const newConfig = loadConfig();
234
- chat(newConfig).then(() => process.exit(0));
235
- });
253
+ onboarding().then(() => chat(loadConfig()).then(() => process.exit(0)));
236
254
  }
237
255
  else {
238
256
  chat(config).then(() => process.exit(0));
package/package.json CHANGED
@@ -1,14 +1,24 @@
1
1
  {
2
2
  "name": "lumina-code-agent",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
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
  "devDependencies": {
14
24
  "@types/node": "^22.10.0",