galaxy-code 0.1.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.
package/dist/app.js ADDED
@@ -0,0 +1,596 @@
1
+ /**
2
+ * @author Bùi Trọng Hiếu
3
+ * @email kevinbui210191@gmail.com
4
+ * @create 2024-10-08
5
+ * @modify 2024-10-13
6
+ * @desc Main Ink interface with config-based agent routing.
7
+ */
8
+ import process from 'node:process';
9
+ import React, { useMemo, useState, useEffect } from 'react';
10
+ import { Box, Static, Text, useApp, useInput } from 'ink';
11
+ import TextInput from 'ink-text-input';
12
+ import Spinner from 'ink-spinner';
13
+ import figlet from 'figlet';
14
+ import { ClaudeAgent, GeminiAgent, OllamaAgent, } from '@workspace/agent-core/providers';
15
+ import { formatMessage, configManager } from '@workspace/agent-core';
16
+ import { toolEventEmitter } from '@workspace/agent-core/tools';
17
+ const cliVersion = '2.0.0';
18
+ const getTerminalWidth = () => {
19
+ const width = process.stdout?.columns;
20
+ return typeof width === 'number' && Number.isFinite(width) ? width : 80;
21
+ };
22
+ const centerLine = (text, width) => {
23
+ const trimmed = text.replace(/\s+$/u, '');
24
+ if (!trimmed)
25
+ return '';
26
+ const padding = Math.max(0, Math.floor((width - trimmed.length) / 2));
27
+ return `${' '.repeat(padding)}${trimmed}`;
28
+ };
29
+ const getInforLines = (config) => {
30
+ const width = getTerminalWidth();
31
+ const agentStr = config.model;
32
+ const lines = [
33
+ { id: 'version', text: cliVersion },
34
+ {
35
+ id: 'hints',
36
+ text: 'ENTER to send • \\ + ENTER for a new line • @ to mention files',
37
+ },
38
+ { id: 'cwd', text: `Current folder: ${process.cwd()}` },
39
+ {
40
+ id: 'config',
41
+ text: `Agent=${agentStr} Git=${config.git ? 'ON' : 'OFF'} Test=${config.test ? 'ON' : 'OFF'}`,
42
+ },
43
+ ];
44
+ return lines.map(line => ({
45
+ id: line.id,
46
+ text: centerLine(line.text, width - 50),
47
+ }));
48
+ };
49
+ // Generate ASCII art banner once
50
+ const bannerText = figlet.textSync('GALAXY', {
51
+ font: 'Standard',
52
+ horizontalLayout: 'default',
53
+ verticalLayout: 'default',
54
+ });
55
+ export default function App({ config }) {
56
+ const { exit } = useApp();
57
+ const [input, setInput] = useState('');
58
+ const [messages, setMessages] = useState([
59
+ { id: 'banner' },
60
+ // Show config error as system message if exists
61
+ ...(config.error
62
+ ? [
63
+ {
64
+ id: 'config-error',
65
+ author: 'system',
66
+ content: `⚠️ ${config.error}`,
67
+ timestamp: Date.now(),
68
+ },
69
+ ]
70
+ : []),
71
+ ]);
72
+ const [isProcessing, setIsProcessing] = useState(false);
73
+ const [commandSuggestions, setCommandSuggestions] = useState([]);
74
+ const [planning, setPlanning] = useState([]);
75
+ const [inputHistory, setInputHistory] = useState([]);
76
+ const [historyIndex, setHistoryIndex] = useState(-1);
77
+ const [suggestionIndex, setSuggestionIndex] = useState(0);
78
+ const [currentConfig, setCurrentConfig] = useState(config);
79
+ const infoLines = getInforLines(currentConfig);
80
+ // Initialize agent connection based on config
81
+ const agentConnection = useMemo(() => {
82
+ // Manual mode = Use Ollama with specific config
83
+ if (currentConfig.model === 'manual') {
84
+ // Find manual agent config to get apiKey
85
+ const manualAgentConfig = currentConfig.agent.find(value => value.type === 'manual');
86
+ return new OllamaAgent({
87
+ type: 'ollama',
88
+ model: 'qwen3-coder:480b-cloud',
89
+ host: 'https://ollama.com',
90
+ apiKey: manualAgentConfig?.apiKey,
91
+ });
92
+ }
93
+ // Find agent config by model type
94
+ const agentConfig = currentConfig.agent.find(value => value.type === currentConfig.model);
95
+ if (!agentConfig) {
96
+ return null;
97
+ }
98
+ // Direct agent mode (claude, gemini, ollama)
99
+ switch (currentConfig.model) {
100
+ case 'claude':
101
+ return new ClaudeAgent(agentConfig);
102
+ case 'ollama':
103
+ return new OllamaAgent(agentConfig);
104
+ case 'gemini':
105
+ return new GeminiAgent(agentConfig);
106
+ default:
107
+ return null;
108
+ }
109
+ }, [currentConfig.model, currentConfig.agent]);
110
+ const commandCatalog = useMemo(() => [
111
+ { command: '/help', description: 'Hiển thị danh sách lệnh' },
112
+ { command: '/exit', description: 'Thoát khỏi Galaxy CLI' },
113
+ { command: '/clear', description: 'Xóa lịch sử hội thoại hiện tại' },
114
+ { command: '/history', description: 'Xem danh sách 10 input gần nhất' },
115
+ { command: '/pwd', description: 'Hiển thị thư mục làm việc hiện tại' },
116
+ { command: '/information', description: 'Hiển thị thông tin hệ thống' },
117
+ {
118
+ command: '/git',
119
+ description: 'Bật/tắt git operations (/git true|false)',
120
+ },
121
+ {
122
+ command: '/test',
123
+ description: 'Bật/tắt test planning (/test true|false)',
124
+ },
125
+ {
126
+ command: '/mode',
127
+ description: 'Chọn agent mode (/mode claude|gemini|ollama|manual)',
128
+ },
129
+ { command: '/config', description: 'Mở thư mục chứa file cấu hình' },
130
+ ], []);
131
+ // Subscribe to tool execution events
132
+ useEffect(() => {
133
+ const unsubscribe = toolEventEmitter.onToolExecution((event) => {
134
+ // Handle plan_task - Create planning list
135
+ if (event.toolName === 'plan_task' &&
136
+ event.status === 'success' &&
137
+ event.toolInfo?.result) {
138
+ try {
139
+ const plan = event.toolInfo.result;
140
+ if (plan.steps && Array.isArray(plan.steps)) {
141
+ const planningItems = plan.steps.map((step, index) => ({
142
+ step: step.step,
143
+ action: step.action,
144
+ featureName: step.featureName || '',
145
+ featureDescription: step.featureDescription || '',
146
+ priority: step.priority || '',
147
+ reasoning: step.reasoning || '',
148
+ status: index === 0 ? 'in-progress' : 'pending',
149
+ }));
150
+ setPlanning(planningItems);
151
+ }
152
+ }
153
+ catch (error) { }
154
+ }
155
+ // Handle progress_reporter - Update planning status
156
+ if (event.toolName === 'progress_reporter') {
157
+ try {
158
+ const progressData = JSON.parse(event.content);
159
+ const stepNumber = progressData.step;
160
+ const status = progressData.status; // 'success' | 'error' | 'in_progress'
161
+ // Map progress_reporter status to PlanningItem status
162
+ const mappedStatus = status === 'success'
163
+ ? 'completed'
164
+ : status === 'error'
165
+ ? 'failed'
166
+ : 'in-progress';
167
+ // Update planning state
168
+ setPlanning(prev => {
169
+ return prev.map(item => {
170
+ // Update current step status
171
+ if (item.step === stepNumber) {
172
+ return {
173
+ ...item,
174
+ status: mappedStatus,
175
+ };
176
+ }
177
+ // If current step succeeded, mark next step as in-progress
178
+ if (status === 'success' &&
179
+ item.step === stepNumber + 1 &&
180
+ item.status === 'pending') {
181
+ return {
182
+ ...item,
183
+ status: 'in-progress',
184
+ };
185
+ }
186
+ return item;
187
+ });
188
+ });
189
+ }
190
+ catch (error) { }
191
+ }
192
+ // Add tool execution message to conversation
193
+ const message = {
194
+ id: `tool-${Date.now()}-${Math.random()}`,
195
+ author: 'tool',
196
+ toolName: event.toolName,
197
+ content: event.content,
198
+ toolInfo: event.toolInfo,
199
+ timestamp: event.timestamp,
200
+ };
201
+ setMessages(prev => [...prev, message]);
202
+ });
203
+ // Cleanup subscription on unmount
204
+ return () => {
205
+ unsubscribe();
206
+ };
207
+ }, []);
208
+ const handleCommand = (raw) => {
209
+ const [cmd, ...args] = raw.slice(1).split(/\s+/);
210
+ switch (cmd) {
211
+ case 'exit':
212
+ appendMessage('system', 'Goodbye!');
213
+ exit();
214
+ return;
215
+ case 'help':
216
+ appendMessage('system', [
217
+ 'Danh sách lệnh:',
218
+ ...commandCatalog.map(cmd => `${cmd.command.padEnd(15, ' ')} ${cmd.description}`),
219
+ ].join('\n'));
220
+ return;
221
+ case 'clear':
222
+ setMessages([{ id: 'banner' }]);
223
+ setPlanning([]);
224
+ // Clear agent conversation history
225
+ if (agentConnection) {
226
+ if ('clearHistory' in agentConnection) {
227
+ agentConnection.clearHistory();
228
+ }
229
+ }
230
+ appendMessage('system', '✅ Cleared conversation and progress');
231
+ return;
232
+ case 'history':
233
+ if (inputHistory.length === 0) {
234
+ appendMessage('system', 'No history yet.');
235
+ return;
236
+ }
237
+ {
238
+ const recent = [...inputHistory].slice(-10).reverse();
239
+ const lines = recent.map((item, idx) => `${idx + 1}. ${item.replace(/\n/g, ' ')}`);
240
+ appendMessage('system', ['Recent inputs:', ...lines].join('\n'));
241
+ }
242
+ return;
243
+ case 'pwd':
244
+ appendMessage('system', `Working directory: ${process.cwd()}`);
245
+ return;
246
+ case 'information':
247
+ {
248
+ const agentStr = currentConfig.model;
249
+ appendMessage('system', [
250
+ '📊 System Information:',
251
+ `Name: Galaxy CLI`,
252
+ `Version: ${cliVersion}`,
253
+ `Current Project: ${process.cwd()}`,
254
+ `Agent Mode: ${agentStr}`,
255
+ `Git Operations: ${currentConfig.git ? 'ON' : 'OFF'}`,
256
+ `Testing: ${currentConfig.test ? 'ON' : 'OFF'}`,
257
+ ].join('\n'));
258
+ }
259
+ return;
260
+ case 'mode':
261
+ {
262
+ // No arguments - show current mode
263
+ if (args.length === 0) {
264
+ const agentStr = currentConfig.model;
265
+ if (currentConfig.model === 'manual') {
266
+ appendMessage('system', `🤖 Agent Mode: ${agentStr}\n\nUsing Orchestrator Agent with full tool access.`);
267
+ }
268
+ else {
269
+ appendMessage('system', `🤖 Agent Mode: ${agentStr}\n\nDirect chat mode - no tools available.`);
270
+ }
271
+ return;
272
+ }
273
+ // Has arguments - switch agent mode
274
+ const newMode = args[0]?.toLowerCase() ?? '';
275
+ const validModes = ['claude', 'gemini', 'ollama', 'manual'];
276
+ // Type guard function
277
+ const isValidMode = (mode) => {
278
+ return validModes.includes(mode);
279
+ };
280
+ if (!isValidMode(newMode)) {
281
+ appendMessage('system', `❌ Invalid mode: ${newMode}\nValid modes: ${validModes.join(', ')}`);
282
+ return;
283
+ }
284
+ // Check if agent config exists (newMode is now ValidMode type)
285
+ const agentConfig = currentConfig.agent.find(value => value.type === newMode);
286
+ if (!agentConfig) {
287
+ appendMessage('system', `❌ Agent config for "${newMode}" not found in config file.\nPlease add it using /config command.`);
288
+ return;
289
+ }
290
+ // Switch to new mode
291
+ setCurrentConfig(prev => ({ ...prev, model: newMode }));
292
+ // Clear conversation when switching agents
293
+ setMessages([{ id: 'banner' }]);
294
+ setPlanning([]);
295
+ appendMessage('system', `✅ Switched to ${newMode} mode\n🔄 Conversation cleared`);
296
+ }
297
+ return;
298
+ case 'git':
299
+ if (args.length === 0) {
300
+ appendMessage('system', `Git operations are currently ${currentConfig.git ? 'ON' : 'OFF'}`);
301
+ return;
302
+ }
303
+ {
304
+ const value = args[0]?.toLowerCase() ?? '';
305
+ if (value === 'true' || value === 'on') {
306
+ setCurrentConfig(prev => ({ ...prev, git: true }));
307
+ appendMessage('system', '✅ Git operations enabled');
308
+ }
309
+ else if (value === 'false' || value === 'off') {
310
+ setCurrentConfig(prev => ({ ...prev, git: false }));
311
+ appendMessage('system', '❌ Git operations disabled');
312
+ }
313
+ else {
314
+ appendMessage('system', 'Usage: /git true|false');
315
+ }
316
+ }
317
+ return;
318
+ case 'test':
319
+ if (args.length === 0) {
320
+ appendMessage('system', `Testing is currently ${currentConfig.test ? 'ON' : 'OFF'}`);
321
+ return;
322
+ }
323
+ {
324
+ const value = args[0]?.toLowerCase() ?? '';
325
+ if (value === 'true' || value === 'on') {
326
+ setCurrentConfig(prev => ({ ...prev, test: true }));
327
+ appendMessage('system', '✅ Test planning enabled');
328
+ }
329
+ else if (value === 'false' || value === 'off') {
330
+ setCurrentConfig(prev => ({ ...prev, test: false }));
331
+ appendMessage('system', '❌ Test planning disabled');
332
+ }
333
+ else {
334
+ appendMessage('system', 'Usage: /test true|false');
335
+ }
336
+ }
337
+ return;
338
+ case 'config':
339
+ {
340
+ const configPath = configManager.getConfigFilePath();
341
+ configManager.openConfigFolder();
342
+ appendMessage('system', `📂 Config directory opened\n📄 Config file: ${configPath}`);
343
+ }
344
+ return;
345
+ default:
346
+ appendMessage('system', `Unknown command: /${cmd}${args.length ? ' ' + args.join(' ') : ''}`);
347
+ }
348
+ };
349
+ const handleSubmit = async (value) => {
350
+ if (isProcessing)
351
+ return;
352
+ const trimmed = value.trim();
353
+ if (!trimmed)
354
+ return;
355
+ if (trimmed.startsWith('/')) {
356
+ setInput('');
357
+ handleCommand(trimmed);
358
+ return;
359
+ }
360
+ appendMessage('user', trimmed);
361
+ setIsProcessing(true);
362
+ setInput('');
363
+ setInputHistory(prev => [...prev, trimmed]);
364
+ setHistoryIndex(-1);
365
+ try {
366
+ let responseContent = '';
367
+ // Route based on agent type
368
+ // if (currentConfig.model === 'manual') {
369
+ // // Use orchestrator with tools
370
+ // const response = await orchestratorAgent.handleUserInput(
371
+ // trimmed,
372
+ // {
373
+ // gitEnabled: currentConfig.git,
374
+ // testEnabled: currentConfig.test,
375
+ // },
376
+ // appendMessage,
377
+ // );
378
+ // responseContent = response.content;
379
+ // } else {
380
+ // Direct chat with specific agent (no tools)
381
+ if (!agentConnection) {
382
+ throw new Error('Agent connection not initialized');
383
+ }
384
+ const response = await agentConnection.handleUserInput(trimmed, {
385
+ gitEnabled: currentConfig.git,
386
+ testEnabled: currentConfig.test,
387
+ }, appendMessage);
388
+ responseContent = response.content;
389
+ // }
390
+ if (responseContent) {
391
+ // Only parse step completion in manual mode
392
+ // if (currentConfig.model === 'manual') {
393
+ // parseStepCompletion(responseContent);
394
+ // }
395
+ appendMessage('assistant', responseContent);
396
+ }
397
+ }
398
+ catch (error) {
399
+ appendMessage('system', `❌ Error: ${error instanceof Error ? error.message : String(error)}`);
400
+ }
401
+ finally {
402
+ setIsProcessing(false);
403
+ }
404
+ };
405
+ // const parseStepCompletion = (content: string) => {
406
+ // const jsonMatch = content.match(
407
+ // /\{step:\s*(\d+),\s*status:\s*['"]completed['"]\}/,
408
+ // );
409
+ // if (jsonMatch) {
410
+ // const stepNum = parseInt(jsonMatch[1] || '0', 10);
411
+ // updateStepStatus(stepNum, 'completed');
412
+ // return;
413
+ // }
414
+ // const checkboxMatch = content.match(/☒\s*Step\s+(\d+):/);
415
+ // if (checkboxMatch) {
416
+ // const stepNum = parseInt(checkboxMatch[1] || '0', 10);
417
+ // updateStepStatus(stepNum, 'completed');
418
+ // return;
419
+ // }
420
+ // const completedMatch = content.match(/Step\s+(\d+):.*?[✅✓]\s*Completed/i);
421
+ // if (completedMatch) {
422
+ // const stepNum = parseInt(completedMatch[1] || '0', 10);
423
+ // updateStepStatus(stepNum, 'completed');
424
+ // }
425
+ // };
426
+ // const updateStepStatus = (stepNum: number, status: 'completed') => {
427
+ // setPlanning(prev => {
428
+ // const updated: PlanningItem[] = prev.map(item => {
429
+ // if (item.step === stepNum) {
430
+ // return {...item, status: 'completed' as const};
431
+ // }
432
+ // if (item.step === stepNum + 1 && item.status === 'pending') {
433
+ // return {...item, status: 'in-progress' as const};
434
+ // }
435
+ // return item;
436
+ // });
437
+ // // Update progress tracker
438
+ // return updated;
439
+ // });
440
+ // };
441
+ useInput((inputKey, key) => {
442
+ if (key.ctrl && inputKey === 'c') {
443
+ exit();
444
+ }
445
+ if (commandSuggestions.length > 0 && input.startsWith('/')) {
446
+ if (key.upArrow || (key.shift && key.tab)) {
447
+ setSuggestionIndex(prev => (prev - 1 + commandSuggestions.length) % commandSuggestions.length);
448
+ }
449
+ else if (key.downArrow || key.tab) {
450
+ setSuggestionIndex(prev => (prev + 1) % commandSuggestions.length);
451
+ }
452
+ else if (key.escape || key.return) {
453
+ setCommandSuggestions([]);
454
+ setSuggestionIndex(0);
455
+ }
456
+ return;
457
+ }
458
+ if (inputHistory.length > 0 && !input.startsWith('/')) {
459
+ if (key.upArrow) {
460
+ const newIndex = historyIndex === -1
461
+ ? inputHistory.length - 1
462
+ : Math.max(0, historyIndex - 1);
463
+ setHistoryIndex(newIndex);
464
+ setInput(inputHistory[newIndex] || '');
465
+ }
466
+ else if (key.downArrow) {
467
+ if (historyIndex === -1)
468
+ return;
469
+ const newIndex = historyIndex + 1;
470
+ if (newIndex >= inputHistory.length) {
471
+ setHistoryIndex(-1);
472
+ setInput('');
473
+ }
474
+ else {
475
+ setHistoryIndex(newIndex);
476
+ setInput(inputHistory[newIndex] || '');
477
+ }
478
+ }
479
+ }
480
+ });
481
+ const appendMessage = (author, text, toolName, toolInfo) => {
482
+ const message = {
483
+ id: `${Date.now()}-${author}-${Math.random().toString(16).slice(2)}`,
484
+ author,
485
+ content: text,
486
+ toolName,
487
+ toolInfo,
488
+ timestamp: Date.now(),
489
+ };
490
+ if (author === 'tool' && toolName === 'plan_task') {
491
+ const planningItems = text.map((item, index) => ({
492
+ ...item,
493
+ status: (index === 0
494
+ ? 'in-progress'
495
+ : 'pending'),
496
+ }));
497
+ setPlanning(planningItems);
498
+ // Initialize progress tracker (only in manual mode)
499
+ if (currentConfig.model === 'manual') {
500
+ }
501
+ }
502
+ else {
503
+ setMessages(prev => [...prev, message]);
504
+ }
505
+ };
506
+ const currentStep = planning.find(p => p.status === 'in-progress');
507
+ return (React.createElement(React.Fragment, null,
508
+ React.createElement(Static, { items: messages }, msg => {
509
+ if (msg.author === 'user') {
510
+ return (React.createElement(Box, { key: msg.id, flexDirection: "row", marginTop: 1 },
511
+ React.createElement(Text, { color: "red" }, `> ${msg.content}`)));
512
+ }
513
+ if (msg.author === 'assistant') {
514
+ return (React.createElement(Box, { key: msg.id, flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: "blue", paddingX: 1, paddingY: 0 },
515
+ React.createElement(Text, null, msg.content)));
516
+ }
517
+ if (msg.author === 'tool') {
518
+ return (React.createElement(Box, { key: msg.id, flexDirection: "row", marginTop: 1 }, formatMessage(msg.content || '', msg.toolName, msg.toolInfo)));
519
+ }
520
+ if (msg.author === 'system') {
521
+ return (React.createElement(Box, { key: msg.id, flexDirection: "row", marginTop: 1 },
522
+ React.createElement(Text, { color: "gray" }, `⚙ ${msg.content}`)));
523
+ }
524
+ return (React.createElement(Box, { borderColor: "cyan", borderStyle: "round", flexDirection: "row", key: 'banner-box', width: process.stdout.columns },
525
+ React.createElement(Box, { flexDirection: "column" },
526
+ React.createElement(Text, { color: "cyan" }, bannerText)),
527
+ React.createElement(Box, { borderLeft: true, borderLeftColor: "cyan", flexDirection: "column", marginLeft: 2, marginTop: 1 }, infoLines.map(line => (React.createElement(Text, { key: line.id, dimColor: true }, line.text))))));
528
+ }),
529
+ currentConfig.model === 'manual' && planning.length > 0 && (React.createElement(Box, { flexDirection: "column", paddingTop: 1, paddingBottom: 1 },
530
+ currentStep && (React.createElement(Box, { marginBottom: 1 },
531
+ React.createElement(Text, { bold: true, color: "cyan" },
532
+ "\u26A1 ",
533
+ currentStep.action))),
534
+ planning.map(item => {
535
+ const checkbox = item.status === 'completed'
536
+ ? '☑'
537
+ : item.status === 'in-progress'
538
+ ? '⚙'
539
+ : item.status === 'failed'
540
+ ? '❌'
541
+ : '☐';
542
+ const color = item.status === 'completed'
543
+ ? 'green'
544
+ : item.status === 'in-progress'
545
+ ? 'cyan'
546
+ : item.status === 'failed'
547
+ ? 'red'
548
+ : 'gray';
549
+ return (React.createElement(Box, { key: item.step },
550
+ React.createElement(Text, { color: color },
551
+ checkbox,
552
+ " Step ",
553
+ item.step,
554
+ ": ",
555
+ item.action)));
556
+ }))),
557
+ React.createElement(Box, { flexDirection: "row", paddingTop: 1, width: process.stdout.columns }, isProcessing ? (React.createElement(Text, null,
558
+ React.createElement(Text, { color: "cyan" },
559
+ React.createElement(Spinner, { type: "dots" })),
560
+ ' Thinking...')) : null),
561
+ React.createElement(Box, { borderColor: "green", borderStyle: "round", flexDirection: "column", marginTop: 1 },
562
+ React.createElement(Box, { alignItems: "center", flexDirection: "row" },
563
+ React.createElement(Text, { color: "green" }, "> "),
564
+ React.createElement(TextInput, { value: input, showCursor: !isProcessing, onChange: isProcessing
565
+ ? () => { }
566
+ : value => {
567
+ setInput(value);
568
+ if (value.startsWith('/')) {
569
+ const partial = value.toLowerCase();
570
+ const matches = commandCatalog.filter(cmd => cmd.command.toLowerCase().startsWith(partial));
571
+ setCommandSuggestions(matches);
572
+ setSuggestionIndex(0);
573
+ }
574
+ else {
575
+ setCommandSuggestions([]);
576
+ setSuggestionIndex(0);
577
+ }
578
+ }, onSubmit: isProcessing
579
+ ? () => { }
580
+ : rawValue => {
581
+ const trimmed = rawValue.trim();
582
+ if (trimmed.startsWith('/') &&
583
+ commandSuggestions.length > 0) {
584
+ const selected = commandSuggestions[suggestionIndex] ??
585
+ commandSuggestions[0];
586
+ handleSubmit(selected?.command || '');
587
+ return;
588
+ }
589
+ handleSubmit(rawValue);
590
+ }, placeholder: isProcessing
591
+ ? 'Processing... (input disabled)'
592
+ : 'Type and press Enter to send' }))),
593
+ commandSuggestions.length > 0 && (React.createElement(Box, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: "gray" }, commandSuggestions.map((cmd, idx) => (React.createElement(Box, { key: cmd.command, paddingX: 1, paddingY: 0, flexDirection: "row" },
594
+ React.createElement(Text, { color: idx === suggestionIndex ? 'cyan' : undefined }, cmd.command.padEnd(15, ' ')),
595
+ React.createElement(Text, { dimColor: true }, cmd.description))))))));
596
+ }
@@ -0,0 +1,21 @@
1
+ export interface UpdateResult {
2
+ success: boolean;
3
+ error?: string;
4
+ needsRestart?: boolean;
5
+ }
6
+ /**
7
+ * Thực hiện update bằng npm install
8
+ */
9
+ export declare function performUpdate(version: string): Promise<UpdateResult>;
10
+ /**
11
+ * Restart CLI với arguments hiện tại
12
+ */
13
+ export declare function restartCLI(): void;
14
+ /**
15
+ * Hiển thị thông báo update thành công
16
+ */
17
+ export declare function showUpdateSuccess(version: string): void;
18
+ /**
19
+ * Hiển thị thông báo update failed
20
+ */
21
+ export declare function showUpdateError(error: string): void;