lumina-code-agent 1.6.0 → 1.6.2

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 +192 -117
  2. package/package.json +15 -5
package/dist/index.js CHANGED
@@ -22,33 +22,126 @@ function saveConfig(config) {
22
22
  mkdirSync(CONFIG_DIR, { recursive: true });
23
23
  writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
24
24
  }
25
- const rl = createInterface({ input: process.stdin, output: stdout });
25
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
26
26
  function ask(question) {
27
27
  return new Promise(resolve => rl.question(question, resolve));
28
28
  }
29
29
  async function onboarding() {
30
30
  console.log('');
31
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
32
  console.log('');
36
33
  const apiKey = await ask(' Enter your OpenRouter API key: ');
37
34
  if (!apiKey.trim() || apiKey.trim().length < 10) {
38
- console.log(' ❌ Invalid API key. Please try again.');
35
+ console.log(' ❌ Invalid API key.');
39
36
  process.exit(1);
40
37
  }
41
- const name = await ask(' What should I call you? (optional): ');
38
+ const name = await ask(' Your name (optional): ');
42
39
  saveConfig({ openrouterKey: apiKey.trim(), userName: name.trim() || 'User' });
43
- console.log('');
44
- console.log(' ✓ Setup complete! Let\'s build something amazing.');
40
+ console.log(' ✓ Ready!');
45
41
  console.log('');
46
42
  }
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}`;
131
+ }
132
+ }
133
+ catch (e) {
134
+ output = `Error: ${e.message}`;
135
+ }
136
+ return output;
137
+ }
138
+ // ── Chat Loop ───────────────────────────────────────────────────────
47
139
  async function chat(config) {
48
140
  console.log(' Type what you want to build. I\'ll handle the rest.');
49
- console.log(' Commands: /help /clear /exit');
141
+ console.log(' Commands: /help /clear /files /exit');
50
142
  console.log('');
51
143
  let messages = [];
144
+ const createdFiles = new Set();
52
145
  while (true) {
53
146
  const input = await ask(' > ');
54
147
  const trimmed = input.trim();
@@ -61,61 +154,97 @@ async function chat(config) {
61
154
  console.log(' ✓ Cleared.');
62
155
  continue;
63
156
  }
157
+ if (trimmed === '/files') {
158
+ if (createdFiles.size === 0) {
159
+ console.log(' No files created yet.');
160
+ }
161
+ else {
162
+ console.log(' Created files:');
163
+ for (const f of createdFiles)
164
+ console.log(` ${f}`);
165
+ }
166
+ continue;
167
+ }
64
168
  if (trimmed === '/help') {
65
- console.log(' Commands: /help /clear /exit');
169
+ console.log(' Commands: /help /clear /files /exit');
66
170
  continue;
67
171
  }
68
172
  messages.push({ role: 'user', content: trimmed });
69
173
  console.log('');
70
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.
182
+
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
193
+
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
202
+
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.
206
+
207
+ Working directory: ${process.cwd()}
208
+
209
+ When using a tool, output ONLY:
210
+ TOOL: <tool_name>
211
+ PARAMS: {"key": "value"}`;
71
212
  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'] } } },
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'] } } },
73
214
  { 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'] } } },
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'] } } },
76
217
  { 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'] } } },
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'] } } },
78
219
  { type: 'function', function: { name: 'grep', description: 'Search file contents', parameters: { type: 'object', properties: { pattern: { type: 'string' }, path: { type: 'string' } }, required: ['pattern', 'path'] } } },
79
220
  { type: 'function', function: { name: 'git', description: 'Run git commands', parameters: { type: 'object', properties: { args: { type: 'string' }, cwd: { type: 'string' } }, required: ['args'] } } },
80
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'] } } },
81
222
  { type: 'function', function: { name: 'deploy', description: 'Deploy to Vercel', parameters: { type: 'object', properties: { target: { type: 'string' }, cwd: { type: 'string' } } } } },
82
223
  ];
83
224
  let iterations = 0;
84
- const maxIterations = 30;
225
+ const maxIterations = 50;
85
226
  let currentMessages = [...messages];
227
+ let lastAssistantContent = '';
86
228
  while (iterations < maxIterations) {
87
229
  iterations++;
88
- process.stdout.write(` ⏳ Working (step ${iterations})...\r`);
230
+ process.stdout.write(` ⏳ Thinking (step ${iterations})...\r`);
231
+ const controller = new AbortController();
232
+ const timeout = setTimeout(() => controller.abort(), 120000);
89
233
  const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
90
234
  method: 'POST',
91
235
  headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${config.openrouterKey}`, 'HTTP-Referer': 'https://luminaai.co.in', 'X-Title': 'Lumina Code' },
92
236
  body: JSON.stringify({
93
237
  model: 'openrouter/owl-alpha',
94
- messages: [{ role: 'system', content: `You are LUMINA CODE — an elite AI coding agent.
95
-
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.
102
-
103
- QUALITY: Production-grade code. No placeholders. No TODOs. No emoji in code.
104
- Handle errors. TypeScript. Responsive. Accessible. Clean architecture.
105
-
106
- FORBIDDEN: lorem ipsum, TODO comments, emoji in code, var keyword, any type, skipping error handling, hardcoded secrets.
107
-
108
- When using a tool, output ONLY:
109
- TOOL: <name>
110
- PARAMS: <json>
111
-
112
- Working directory: ${process.cwd()}` }, ...currentMessages],
238
+ messages: [{ role: 'system', content: systemPrompt }, ...currentMessages],
113
239
  tools,
240
+ tool_choice: 'auto',
114
241
  stream: false,
115
- max_tokens: 32000,
242
+ max_tokens: 8000,
116
243
  temperature: 0.1,
117
244
  }),
245
+ signal: controller.signal,
118
246
  });
247
+ clearTimeout(timeout);
119
248
  if (!res.ok) {
120
249
  const err = await res.text().catch(() => '');
121
250
  throw new Error(`API error ${res.status}: ${err.slice(0, 200)}`);
@@ -126,98 +255,44 @@ Working directory: ${process.cwd()}` }, ...currentMessages],
126
255
  throw new Error('No response from model');
127
256
  const content = choice.message?.content || '';
128
257
  const toolCalls = choice.message?.tool_calls || [];
129
- currentMessages.push({ role: 'assistant', content });
258
+ lastAssistantContent = content;
259
+ currentMessages.push({ role: 'assistant', content, ...(toolCalls.length ? { tool_calls: toolCalls } : {}) });
130
260
  if (toolCalls.length === 0) {
131
261
  messages = currentMessages;
132
- console.log(' ✓ ' + content.slice(0, 500));
133
- if (content.length > 500)
134
- console.log(' ...');
262
+ if (content)
263
+ console.log(` ${content.slice(0, 300)}`);
135
264
  console.log('');
136
265
  break;
137
266
  }
138
267
  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 = '';
268
+ let args = {};
143
269
  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
- }
270
+ args = JSON.parse(tc.function.arguments || '{}');
271
+ }
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}`);
213
280
  }
214
- catch (e) {
215
- output = `Error: ${e.message}`;
281
+ else {
282
+ const shortOut = output.slice(0, 150).replace(/\n/g, ' ');
283
+ console.log(` ✓ ${shortOut}`);
216
284
  }
217
- currentMessages.push({ role: 'tool', content: output.slice(0, 2000), tool_call_id: tc.id });
218
- console.log(` ✓ ${output.slice(0, 100)}`);
285
+ currentMessages.push({ role: 'tool', content: output.slice(0, 3000), tool_call_id: tc.id });
219
286
  }
220
287
  }
288
+ if (iterations >= maxIterations) {
289
+ console.log(' ⚠ Reached max iterations. Partial result:');
290
+ console.log(` ${lastAssistantContent.slice(0, 300)}`);
291
+ }
292
+ if (createdFiles.size > 0) {
293
+ console.log(` 📁 Created ${createdFiles.size} file(s)`);
294
+ }
295
+ console.log('');
221
296
  }
222
297
  catch (e) {
223
298
  console.log(` ⚠ Error: ${e.message}`);
package/package.json CHANGED
@@ -1,14 +1,24 @@
1
1
  {
2
2
  "name": "lumina-code-agent",
3
- "version": "1.6.0",
3
+ "version": "1.6.2",
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",