lumina-code-agent 1.3.0 → 1.5.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 +111 -75
  2. package/package.json +7 -18
package/dist/index.js CHANGED
@@ -1,11 +1,30 @@
1
1
  #!/usr/bin/env node
2
2
  // @ts-nocheck
3
+ // Check if we have a proper TTY for the TUI
4
+ const hasTTY = process.stdin.isTTY && process.stdout.isTTY;
5
+ if (!hasTTY) {
6
+ // Non-TUI mode: just show help
7
+ console.log('');
8
+ console.log(' ⚡ LUMINA CODE — AI Coding Agent');
9
+ console.log('');
10
+ console.log(' Lumina Code requires a proper terminal (TTY) to run.');
11
+ console.log(' Please open one of these and type: lumina');
12
+ console.log('');
13
+ console.log(' • Windows Terminal (recommended)');
14
+ console.log(' • PowerShell');
15
+ console.log(' • Command Prompt (cmd.exe)');
16
+ console.log(' • iTerm2 / Terminal.app (macOS)');
17
+ console.log(' • Any Linux terminal');
18
+ console.log('');
19
+ console.log(' If you\'re in Git Bash, try running from Windows Terminal instead.');
20
+ console.log('');
21
+ process.exit(0);
22
+ }
3
23
  import React, { useState, useCallback, useRef } from 'react';
4
24
  import { Box, Text, useInput, useApp, Spacer } from 'ink';
5
- import TextInput from 'ink-text-input';
6
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
25
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs';
7
26
  import { homedir } from 'os';
8
- import { join } from 'path';
27
+ import { join, dirname, resolve, relative } from 'path';
9
28
  const CONFIG_DIR = join(homedir(), '.lumina');
10
29
  const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
11
30
  function loadConfig() {
@@ -37,70 +56,59 @@ function Onboarding({ onComplete }) {
37
56
  setStep(2);
38
57
  }
39
58
  else if (step === 2) {
40
- saveConfig({ openrouterKey: apiKey.trim(), userName: name.trim() || 'User', defaultEffort: 'normal' });
59
+ saveConfig({ openrouterKey: apiKey.trim(), userName: name.trim() || 'User' });
41
60
  onComplete();
42
61
  }
43
62
  }
63
+ else if (key.backspace || key.delete) {
64
+ if (step === 1)
65
+ setApiKey(v => v.slice(0, -1));
66
+ if (step === 2)
67
+ setName(v => v.slice(0, -1));
68
+ }
69
+ else if (!key.ctrl && !key.meta && input) {
70
+ if (step === 1)
71
+ setApiKey(v => v + input);
72
+ if (step === 2)
73
+ setName(v => v + input);
74
+ }
44
75
  });
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!')));
76
+ 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
77
  }
47
78
  // ── Chat Screen ─────────────────────────────────────────────────────
48
79
  function ChatScreen({ config }) {
49
80
  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
- ]);
81
+ const [messages, setMessages] = useState([]);
73
82
  const [input, setInput] = useState('');
74
83
  const [status, setStatus] = useState('Ready');
75
84
  const [thinking, setThinking] = useState(false);
76
85
  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();
86
+ const visibleMessages = messages.slice(-50);
87
+ const handleSubmit = useCallback(async (text) => {
88
+ const trimmed = text.trim();
80
89
  if (!trimmed || thinking)
81
90
  return;
82
91
  setInput('');
83
92
  setThinking(true);
84
93
  setStatus('Thinking...');
94
+ scrollRef.current++;
85
95
  const userMsg = { role: 'user', content: trimmed };
86
96
  const newMessages = [...messages, userMsg];
87
97
  setMessages(newMessages);
88
- scrollRef.current++;
89
98
  try {
90
99
  const OPENROUTER_URL = 'https://openrouter.ai/api/v1/chat/completions';
91
100
  const tools = [
92
101
  { 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'] } } },
102
+ { type: 'function', function: { name: 'read_file', description: 'Read file contents. ALWAYS read before editing.', parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } } },
103
+ { 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'] } } },
104
+ { 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
105
  { 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'] } } },
106
+ { 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
107
  { type: 'function', function: { name: 'grep', description: 'Search file contents', parameters: { type: 'object', properties: { pattern: { type: 'string' }, path: { type: 'string' } }, required: ['pattern', 'path'] } } },
99
108
  { 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'] } } },
109
+ { 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
110
  { type: 'function', function: { name: 'deploy', description: 'Deploy to Vercel', parameters: { type: 'object', properties: { target: { type: 'string' }, cwd: { type: 'string' } } } } },
102
111
  ];
103
- let assistantContent = '';
104
112
  let iterations = 0;
105
113
  const maxIterations = 30;
106
114
  let currentMessages = [...newMessages];
@@ -110,7 +118,47 @@ Working directory: ${process.cwd()}` },
110
118
  const res = await fetch(OPENROUTER_URL, {
111
119
  method: 'POST',
112
120
  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 }),
121
+ body: JSON.stringify({
122
+ model: 'openrouter/owl-alpha',
123
+ messages: [{ role: 'system', content: `You are LUMINA CODE — an elite AI coding agent.
124
+
125
+ MODEL: openrouter/owl-alpha (1M+ context, best reasoning)
126
+
127
+ WORKFLOW:
128
+ 1. PLAN: Analyze the task. Create a brief plan.
129
+ 2. ACT: Execute tools step by step. Read before writing.
130
+ 3. VERIFY: Run builds, check for errors.
131
+ 4. FIX: If something fails, debug and fix immediately.
132
+ 5. DEPLOY: If requested, deploy automatically.
133
+
134
+ QUALITY STANDARDS:
135
+ - Production-grade code, always
136
+ - TypeScript with proper types (never use 'any')
137
+ - Error handling everywhere
138
+ - Responsive design (320px to 2560px)
139
+ - Accessible (semantic HTML, ARIA, keyboard navigation)
140
+ - Clean architecture, modern patterns
141
+ - Beautiful UI (consistent spacing, typography, color)
142
+
143
+ FORBIDDEN:
144
+ - Lorem ipsum or placeholder content
145
+ - TODO/FIXME comments in production code
146
+ - Emoji in code or UI
147
+ - var keyword (always let/const)
148
+ - any type in TypeScript
149
+ - Skipping error handling
150
+ - Hardcoded secrets
151
+
152
+ When using a tool, output ONLY:
153
+ TOOL: <name>
154
+ PARAMS: <json>
155
+
156
+ Working directory: ${process.cwd()}` }, ...currentMessages],
157
+ tools,
158
+ stream: false,
159
+ max_tokens: 32000,
160
+ temperature: 0.1,
161
+ }),
114
162
  });
115
163
  if (!res.ok) {
116
164
  const err = await res.text().catch(() => '');
@@ -122,14 +170,13 @@ Working directory: ${process.cwd()}` },
122
170
  throw new Error('No response from model');
123
171
  const content = choice.message?.content || '';
124
172
  const toolCalls = choice.message?.tool_calls || [];
125
- assistantContent = content;
126
- currentMessages.push({ role: 'assistant', content, ...(toolCalls.length ? { tool_calls: toolCalls } : {}) });
173
+ currentMessages.push({ role: 'assistant', content });
174
+ setMessages([...currentMessages]);
175
+ scrollRef.current++;
127
176
  if (toolCalls.length === 0) {
128
- setMessages([...currentMessages]);
129
177
  setStatus('Done');
130
178
  break;
131
179
  }
132
- // Execute tools
133
180
  for (const tc of toolCalls) {
134
181
  const args = JSON.parse(tc.function.arguments || '{}');
135
182
  const toolName = tc.function.name;
@@ -137,9 +184,9 @@ Working directory: ${process.cwd()}` },
137
184
  scrollRef.current++;
138
185
  let output = '';
139
186
  try {
187
+ const { execSync } = await import('child_process');
140
188
  switch (toolName) {
141
189
  case 'run_command': {
142
- const { execSync } = await import('child_process');
143
190
  const cwd = args.cwd || process.cwd();
144
191
  output = execSync(args.command, { cwd, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: args.timeout || 120000, shell: true }) || '(no output)';
145
192
  break;
@@ -149,7 +196,7 @@ Working directory: ${process.cwd()}` },
149
196
  break;
150
197
  }
151
198
  case 'write_file': {
152
- const dir = join(args.path, '..');
199
+ const dir = dirname(resolve(args.path));
153
200
  if (!existsSync(dir))
154
201
  mkdirSync(dir, { recursive: true });
155
202
  writeFileSync(args.path, args.content, 'utf-8');
@@ -171,21 +218,19 @@ Working directory: ${process.cwd()}` },
171
218
  break;
172
219
  }
173
220
  case 'search_files': {
174
- const { readdirSync } = await import('fs');
175
- const { join: pathJoin, relative: pathRelative } = await import('path');
176
221
  const results = [];
177
222
  const search = (dir, depth) => {
178
223
  if (depth > 5)
179
224
  return;
180
225
  try {
181
226
  for (const e of readdirSync(dir, { withFileTypes: true })) {
182
- if (e.name.startsWith('.') || e.name === 'node_modules')
227
+ if (e.name.startsWith('.') || e.name === 'node_modules' || e.name === 'dist')
183
228
  continue;
184
- const fp = pathJoin(dir, e.name);
229
+ const fp = join(dir, e.name);
185
230
  if (e.isDirectory())
186
231
  search(fp, depth + 1);
187
- else if (new RegExp(pattern.replace(/\*/g, '.*'), 'i').test(e.name))
188
- results.push(pathRelative(cwd, fp));
232
+ else if (new RegExp(args.pattern.replace(/\*/g, '.*'), 'i').test(e.name))
233
+ results.push(relative(args.cwd || process.cwd(), fp));
189
234
  }
190
235
  }
191
236
  catch { }
@@ -202,18 +247,15 @@ Working directory: ${process.cwd()}` },
202
247
  break;
203
248
  }
204
249
  case 'git': {
205
- const { execSync } = await import('child_process');
206
250
  output = execSync(`git ${args.args}`, { cwd: args.cwd || process.cwd(), encoding: 'utf-8', maxBuffer: 1024 * 1024 }) || '(ok)';
207
251
  break;
208
252
  }
209
253
  case 'npm': {
210
- const { execSync } = await import('child_process');
211
254
  const pm = existsSync(join(args.cwd || process.cwd(), 'bun.lockb')) ? 'bun' : existsSync(join(args.cwd || process.cwd(), 'yarn.lock')) ? 'yarn' : 'npm';
212
255
  output = execSync(`${pm} ${args.args}`, { cwd: args.cwd || process.cwd(), encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 120000 }) || '(ok)';
213
256
  break;
214
257
  }
215
258
  case 'deploy': {
216
- const { execSync } = await import('child_process');
217
259
  output = execSync('npx vercel deploy --prod --yes', { cwd: args.cwd || process.cwd(), encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 300000 }) || '(deployed)';
218
260
  break;
219
261
  }
@@ -229,9 +271,8 @@ Working directory: ${process.cwd()}` },
229
271
  scrollRef.current++;
230
272
  }
231
273
  }
232
- if (iterations >= maxIterations) {
274
+ if (iterations >= maxIterations)
233
275
  setStatus('Reached max iterations');
234
- }
235
276
  }
236
277
  catch (e) {
237
278
  setMessages(prev => [...prev, { role: 'assistant', content: `⚠ Error: ${e.message}` }]);
@@ -239,35 +280,30 @@ Working directory: ${process.cwd()}` },
239
280
  }
240
281
  setThinking(false);
241
282
  }, [input, thinking, messages, config]);
242
- useInput((_input, key) => {
243
- if (key.ctrl && _input === 'c')
283
+ useInput((input, key) => {
284
+ if (key.ctrl && input === 'c')
244
285
  exit();
245
- if (key.ctrl && _input === 'd')
286
+ if (key.ctrl && input === 'd')
246
287
  exit();
288
+ if (key.return) {
289
+ handleSubmit(input);
290
+ }
291
+ else if (key.backspace || key.delete) {
292
+ setInput(v => v.slice(0, -1));
293
+ }
294
+ else if (!key.ctrl && !key.meta) {
295
+ setInput(v => v + input);
296
+ }
247
297
  });
248
- return React.createElement(Box, { flexDirection: 'column', height: '100%' },
249
- // Header
250
- 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)),
251
- // Messages
252
- React.createElement(Box, { flexDirection: 'column', flexGrow: 1, paddingX: 2, paddingY: 1, overflowY: 'hidden' }, visibleMessages.map((m, i) => {
298
+ 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) => {
253
299
  if (m.role === 'user') {
254
300
  return React.createElement(Box, { key: i, marginTop: 1 }, React.createElement(Text, { color: '#7C5CFC', bold: true }, '> '), React.createElement(Text, { color: '#FAFAFA' }, m.content));
255
301
  }
256
302
  if (m.role === 'tool') {
257
303
  return React.createElement(Box, { key: i, marginTop: 0, paddingLeft: 2 }, React.createElement(Text, { color: '#52525B' }, ' ' + m.content.slice(0, 200)));
258
304
  }
259
- // Assistant
260
305
  return React.createElement(Box, { key: i, marginTop: 1, paddingLeft: 2 }, React.createElement(Text, { color: '#A1A1AA' }, m.content.slice(0, 500)));
261
- }), thinking && React.createElement(Text, { color: '#F59E0B' }, ' ⏳ thinking...')),
262
- // 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
- })),
269
- // Footer
270
- React.createElement(Box, { paddingX: 2 }, React.createElement(Text, { color: '#3F3F46' }, ' Ctrl+C to exit | OWL-Alpha ')));
306
+ }), 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 ')));
271
307
  }
272
308
  // ── Main App ────────────────────────────────────────────────────────
273
309
  export default function App() {
package/package.json CHANGED
@@ -1,29 +1,18 @@
1
1
  {
2
2
  "name": "lumina-code-agent",
3
- "version": "1.3.0",
3
+ "version": "1.5.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",