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/LICENSE +21 -0
- package/README.md +679 -0
- package/dist/app.d.ts +7 -0
- package/dist/app.js +596 -0
- package/dist/auto-updater.d.ts +21 -0
- package/dist/auto-updater.js +144 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +159 -0
- package/dist/env.d.ts +1 -0
- package/dist/env.js +29 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.js +8 -0
- package/dist/update-checker.d.ts +22 -0
- package/dist/update-checker.js +85 -0
- package/package.json +102 -0
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;
|