@tiendung-36/openrouter-cli 1.0.0 → 1.0.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.
- package/README.md +1 -1
- package/package.json +2 -2
- package/src/api.js +77 -1
- package/src/config.js +195 -1
- package/src/index.js +518 -1
- package/src/preferences.js +36 -1
- package/src/tools.js +265 -1
- package/src/ui.js +233 -1
- package/src/utils.js +55 -1
package/src/tools.js
CHANGED
|
@@ -1 +1,265 @@
|
|
|
1
|
-
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { glob } from 'glob';
|
|
4
|
+
import { exec } from 'child_process';
|
|
5
|
+
import util from 'util';
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { ui } from './ui.js';
|
|
9
|
+
import { preferences } from './preferences.js';
|
|
10
|
+
|
|
11
|
+
const execPromise = util.promisify(exec);
|
|
12
|
+
|
|
13
|
+
// Security Lists
|
|
14
|
+
const WHITELIST = ['ls', 'dir', 'git status', 'echo', 'pwd', 'cd', 'ver', 'vol'];
|
|
15
|
+
const BLACKLIST = ['rm', 'del', 'format', 'shutdown', 'restart', 'rd'];
|
|
16
|
+
|
|
17
|
+
export const tools = {
|
|
18
|
+
definitions: [
|
|
19
|
+
{
|
|
20
|
+
name: "listFiles",
|
|
21
|
+
description: "List all files in the current directory (recursively, ignoring node_modules/.git)",
|
|
22
|
+
parameters: { type: "object", properties: {}, required: [] }
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "readFile",
|
|
26
|
+
description: "Read content of a specific file. IMPORTANT: For huge files (> 2000 lines), use startLine/endLine to read chunks.",
|
|
27
|
+
parameters: {
|
|
28
|
+
type: "object",
|
|
29
|
+
properties: {
|
|
30
|
+
path: { type: "string" },
|
|
31
|
+
startLine: { type: "number", description: "Optional: Start line number (1-based)" },
|
|
32
|
+
endLine: { type: "number", description: "Optional: End line number" }
|
|
33
|
+
},
|
|
34
|
+
required: ["path"]
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "readMultipleFiles",
|
|
39
|
+
description: "Read content of multiple files at once",
|
|
40
|
+
parameters: {
|
|
41
|
+
type: "object",
|
|
42
|
+
properties: { paths: { type: "array", items: { type: "string" } } },
|
|
43
|
+
required: ["paths"]
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "searchFiles",
|
|
48
|
+
description: "Search for files matching a glob pattern (e.g., 'src/*.js')",
|
|
49
|
+
parameters: {
|
|
50
|
+
type: "object",
|
|
51
|
+
properties: { query: { type: "string" } },
|
|
52
|
+
required: ["query"]
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "writeFile",
|
|
57
|
+
description: "Write or Overwrite content to a file",
|
|
58
|
+
parameters: {
|
|
59
|
+
type: "object",
|
|
60
|
+
properties: {
|
|
61
|
+
path: { type: "string" },
|
|
62
|
+
content: { type: "string" }
|
|
63
|
+
},
|
|
64
|
+
required: ["path", "content"]
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "searchContent",
|
|
69
|
+
description: "Search for text content within files (Grep-like). Use this to find code definitions or usage.",
|
|
70
|
+
parameters: {
|
|
71
|
+
type: "object",
|
|
72
|
+
properties: {
|
|
73
|
+
query: { type: "string" },
|
|
74
|
+
path: { type: "string", description: "Optional: Directory or file to search in (default: current dir)" }
|
|
75
|
+
},
|
|
76
|
+
required: ["query"]
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: "executeCommand",
|
|
81
|
+
description: "Execute a shell command",
|
|
82
|
+
parameters: {
|
|
83
|
+
type: "object",
|
|
84
|
+
properties: { command: { type: "string" } },
|
|
85
|
+
required: ["command"]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
|
|
90
|
+
async listFiles() {
|
|
91
|
+
try {
|
|
92
|
+
const files = await glob('**/*', {
|
|
93
|
+
ignore: ['node_modules/**', '.git/**', 'dist/**', 'coverage/**'],
|
|
94
|
+
nodir: true
|
|
95
|
+
});
|
|
96
|
+
return JSON.stringify(files.slice(0, 50)); // Limit to 50 files to save context
|
|
97
|
+
} catch (error) {
|
|
98
|
+
return `Error listing files: ${error.message}`;
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
async readFile({ path: filePath, startLine, endLine }) {
|
|
103
|
+
try {
|
|
104
|
+
if (!fs.existsSync(filePath)) return `Error: File ${filePath} does not exist.`;
|
|
105
|
+
|
|
106
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
107
|
+
const lines = content.split('\n');
|
|
108
|
+
const totalLines = lines.length;
|
|
109
|
+
|
|
110
|
+
// Pagination Logic
|
|
111
|
+
if (startLine || endLine) {
|
|
112
|
+
const start = (startLine || 1) - 1;
|
|
113
|
+
const end = endLine || totalLines;
|
|
114
|
+
const chunk = lines.slice(start, end).join('\n');
|
|
115
|
+
return `--- Reading ${path.basename(filePath)} (Lines ${start + 1} to ${Math.min(end, totalLines)} of ${totalLines}) ---\n${chunk}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Auto-Safety for Huge Files
|
|
119
|
+
// Since you said "tokens don't matter", we set a very generous limit (2000 lines ~ 100kb+).
|
|
120
|
+
const MAX_LINES = 2000;
|
|
121
|
+
if (totalLines > MAX_LINES) {
|
|
122
|
+
const chunk = lines.slice(0, MAX_LINES).join('\n');
|
|
123
|
+
return `--- WARNING: File ${path.basename(filePath)} is HUGE (${totalLines} lines). Showing first ${MAX_LINES} lines. ---\n` +
|
|
124
|
+
`--- SYSTEM INSTRUCTION: To read the rest, you MUST call 'readFile' again with startLine=${MAX_LINES + 1} and endLine=${Math.min(MAX_LINES * 2, totalLines)}. ---\n\n` +
|
|
125
|
+
chunk +
|
|
126
|
+
`\n\n... [TRUNCATED - USE PAGINATION TO READ MORE]`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return content;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
return `Error reading file: ${error.message}`;
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
async readMultipleFiles({ paths }) {
|
|
136
|
+
try {
|
|
137
|
+
const results = [];
|
|
138
|
+
for (const p of paths) {
|
|
139
|
+
if (fs.existsSync(p)) {
|
|
140
|
+
results.push(`--- ${p} ---\n${fs.readFileSync(p, 'utf-8')}`);
|
|
141
|
+
} else {
|
|
142
|
+
results.push(`--- ${p} ---\n(File not found)`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return results.join('\n\n');
|
|
146
|
+
} catch (error) {
|
|
147
|
+
return `Error reading multiple files: ${error.message}`;
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
async searchFiles({ query }) {
|
|
152
|
+
try {
|
|
153
|
+
const files = await glob(query, {
|
|
154
|
+
ignore: ['node_modules/**', '.git/**']
|
|
155
|
+
});
|
|
156
|
+
return JSON.stringify(files);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
return `Error searching files: ${error.message}`;
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
async searchContent({ query, path: searchPath = '.' }) {
|
|
163
|
+
try {
|
|
164
|
+
// Check if 'grep' is available (Linux/Mac/Git Bash)
|
|
165
|
+
// On Windows simple CMD, grep might not exist. We can try 'findstr' or fallback to node-based search.
|
|
166
|
+
// Let's implement a Node.js based grep to be cross-platform safe.
|
|
167
|
+
const files = await glob('**/*', {
|
|
168
|
+
cwd: searchPath,
|
|
169
|
+
ignore: ['node_modules/**', '.git/**', 'dist/**', 'coverage/**'],
|
|
170
|
+
nodir: true,
|
|
171
|
+
absolute: true
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const results = [];
|
|
175
|
+
for (const file of files) {
|
|
176
|
+
try {
|
|
177
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
178
|
+
const lines = content.split('\n');
|
|
179
|
+
lines.forEach((line, index) => {
|
|
180
|
+
if (line.includes(query)) {
|
|
181
|
+
results.push(`${path.relative(process.cwd(), file)}:${index + 1}: ${line.trim()}`);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
if (results.length > 100) break; // Limit results
|
|
185
|
+
} catch (e) {
|
|
186
|
+
// Ignore binary files or read errors
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (results.length === 0) return "No matches found.";
|
|
191
|
+
return results.slice(0, 100).join('\n') + (results.length > 100 ? '\n... (More results truncated)' : '');
|
|
192
|
+
} catch (error) {
|
|
193
|
+
return `Error searching content: ${error.message}`;
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
async writeFile({ path: filePath, content }) {
|
|
198
|
+
try {
|
|
199
|
+
// Create dirname if not exists
|
|
200
|
+
const dir = path.dirname(filePath);
|
|
201
|
+
if (!fs.existsSync(dir)) {
|
|
202
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
203
|
+
}
|
|
204
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
205
|
+
return `Successfully wrote to ${filePath}`;
|
|
206
|
+
} catch (error) {
|
|
207
|
+
return `Error writing file: ${error.message}`;
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
async executeCommand({ command }) {
|
|
212
|
+
// 1. Check Mode
|
|
213
|
+
const mode = preferences.get('mode') || 'ask';
|
|
214
|
+
if (mode === 'plan') return `(Skipped execution: Client is in Plan Only mode). Command would be: ${command}`;
|
|
215
|
+
|
|
216
|
+
// 2. Check Blacklist
|
|
217
|
+
const cmdBase = command.split(' ')[0].toLowerCase();
|
|
218
|
+
const isBlacklisted = BLACKLIST.some(b => cmdBase.includes(b)) || BLACKLIST.some(b => command.includes(b + ' '));
|
|
219
|
+
|
|
220
|
+
// 3. Check Whitelist or Auto-Run Mode
|
|
221
|
+
// If mode is 'always' AND not blacklisted => Auto run
|
|
222
|
+
// If mode is 'always' AND blacklisted => Still ask (Safety measure, or should we allow?)
|
|
223
|
+
// Let's say 'always' enables auto-run for everything except strictly blocked stuff?
|
|
224
|
+
// User said "Code luon" (Code always). Let's trust user but keep blacklist output warning.
|
|
225
|
+
|
|
226
|
+
const isSafe = (mode === 'always') || (!isBlacklisted && (WHITELIST.includes(cmdBase) || WHITELIST.some(w => command.startsWith(w))));
|
|
227
|
+
|
|
228
|
+
if (!isSafe) {
|
|
229
|
+
// 3. Ask for confirmation (Simple)
|
|
230
|
+
ui.warn(`Command: ${chalk.bold(command)}`);
|
|
231
|
+
|
|
232
|
+
const { confirm } = await inquirer.prompt([{
|
|
233
|
+
type: 'confirm',
|
|
234
|
+
name: 'confirm',
|
|
235
|
+
message: 'Allow execution?',
|
|
236
|
+
default: false
|
|
237
|
+
}]);
|
|
238
|
+
|
|
239
|
+
if (!confirm) {
|
|
240
|
+
return "User denied execution.";
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
const { stdout, stderr } = await execPromise(command);
|
|
246
|
+
|
|
247
|
+
// Optimization: Truncate Huge Outputs
|
|
248
|
+
const MAX_OUTPUT = 10000; // 10kb
|
|
249
|
+
let finalStdout = stdout || "(Command executed with no output)";
|
|
250
|
+
let finalStderr = stderr || "";
|
|
251
|
+
|
|
252
|
+
if (finalStdout.length > MAX_OUTPUT) {
|
|
253
|
+
finalStdout = finalStdout.slice(0, MAX_OUTPUT) + `\n\n... [OUTPUT TRUNCATED - ${finalStdout.length - MAX_OUTPUT} chars remaining]`;
|
|
254
|
+
}
|
|
255
|
+
if (finalStderr.length > MAX_OUTPUT) {
|
|
256
|
+
finalStderr = finalStderr.slice(0, MAX_OUTPUT) + `\n\n... [STDERR TRUNCATED - ${finalStderr.length - MAX_OUTPUT} chars remaining]`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (finalStderr) return `Stderr: ${finalStderr}\nStdout: ${finalStdout}`;
|
|
260
|
+
return finalStdout;
|
|
261
|
+
} catch (error) {
|
|
262
|
+
return `Execution Error: ${error.message}`;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
};
|
package/src/ui.js
CHANGED
|
@@ -1 +1,233 @@
|
|
|
1
|
-
const a5_0xeab874=a5_0xc153;(function(_0x5e4b51,_0x451aff){const _0x1c91e2=a5_0xc153,_0x374915=_0x5e4b51();while(!![]){try{const _0x56ff9b=parseInt(_0x1c91e2(0x25e))/0x1+-parseInt(_0x1c91e2(0x269))/0x2+-parseInt(_0x1c91e2(0x243))/0x3*(parseInt(_0x1c91e2(0x208))/0x4)+-parseInt(_0x1c91e2(0x215))/0x5*(-parseInt(_0x1c91e2(0x1de))/0x6)+parseInt(_0x1c91e2(0x23e))/0x7*(-parseInt(_0x1c91e2(0x1ae))/0x8)+parseInt(_0x1c91e2(0x217))/0x9*(parseInt(_0x1c91e2(0x1fa))/0xa)+parseInt(_0x1c91e2(0x201))/0xb;if(_0x56ff9b===_0x451aff)break;else _0x374915['push'](_0x374915['shift']());}catch(_0x51fc74){_0x374915['push'](_0x374915['shift']());}}}(a5_0x2b40,0x8fd67));import a5_0x270795 from'chalk';import a5_0x19acff from'ora';export const themes={'dark':{'name':'Dark\x20(Mặc\x20'+a5_0xeab874(0x242),'description':'Nền\x20tối,\x20c'+a5_0xeab874(0x26d)+'dễ\x20đọc\x20ban'+a5_0xeab874(0x225),'border':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x21c)),'ai':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x1fe)),'user':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x1f8)),'info':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x1ce)),'warn':a5_0x270795['hex']('#FFEAA7'),'error':a5_0x270795[a5_0xeab874(0x1d7)]('#FF7675'),'dim':a5_0x270795[a5_0xeab874(0x1d7)]('#636E72'),'accent':a5_0x270795['hex'](a5_0xeab874(0x1df)),'bg':'dark'},'light':{'name':a5_0xeab874(0x1a4),'description':a5_0xeab874(0x1da)+a5_0xeab874(0x1fc)+'dễ\x20đọc\x20ban'+a5_0xeab874(0x204),'border':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x24a)),'ai':a5_0x270795[a5_0xeab874(0x1d7)]('#2980B9'),'user':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x268)),'info':a5_0x270795[a5_0xeab874(0x1d7)]('#16A085'),'warn':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x253)),'error':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x24f)),'dim':a5_0x270795['hex'](a5_0xeab874(0x1a8)),'accent':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x238)),'bg':a5_0xeab874(0x1e2)},'ocean':{'name':a5_0xeab874(0x207),'description':a5_0xeab874(0x26e)+a5_0xeab874(0x200)+a5_0xeab874(0x264),'border':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x1d8)),'ai':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x1ce)),'user':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x262)),'info':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x1fe)),'warn':a5_0x270795['hex'](a5_0xeab874(0x1b5)),'error':a5_0x270795['hex'](a5_0xeab874(0x23a)),'dim':a5_0x270795['hex'](a5_0xeab874(0x1ea)),'accent':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x255)),'bg':a5_0xeab874(0x23b)},'fire':{'name':a5_0xeab874(0x20d),'description':a5_0xeab874(0x1d0)+a5_0xeab874(0x1c0)+'t','border':a5_0x270795['hex'](a5_0xeab874(0x21c)),'ai':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x209)),'user':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x1dd)),'info':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x213)),'warn':a5_0x270795['hex'](a5_0xeab874(0x1b5)),'error':a5_0x270795['hex'](a5_0xeab874(0x1d5)),'dim':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x1af)),'accent':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x22e)),'bg':a5_0xeab874(0x23b)},'matrix':{'name':'Matrix\x20Gre'+'en','description':a5_0xeab874(0x26e)+a5_0xeab874(0x1a0)+a5_0xeab874(0x1ff),'border':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x24d)),'ai':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x19b)),'user':a5_0x270795['hex'](a5_0xeab874(0x219)),'info':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x19b)),'warn':a5_0x270795[a5_0xeab874(0x1d7)]('#FFEAA7'),'error':a5_0x270795['hex'](a5_0xeab874(0x21c)),'dim':a5_0x270795[a5_0xeab874(0x1d7)](a5_0xeab874(0x1ea)),'accent':a5_0x270795['hex'](a5_0xeab874(0x1d8)),'bg':a5_0xeab874(0x23b)}};function a5_0xc153(_0x4a8843,_0x1aacd3){_0x4a8843=_0x4a8843-0x19a;const _0x2b4069=a5_0x2b40();let _0xc1530e=_0x2b4069[_0x4a8843];return _0xc1530e;}let currentTheme=themes[a5_0xeab874(0x23b)];export function setTheme(_0x25759e){if(themes[_0x25759e])return currentTheme=themes[_0x25759e],!![];return![];}export function getTheme(){return currentTheme;}export function getThemeName(){const _0x5878b2=a5_0xeab874,_0x4f52d4={'pxCDJ':'dark'};for(const [_0x3b7dda,_0x279f27]of Object['entries'](themes)){if(_0x279f27===currentTheme)return _0x3b7dda;}return _0x4f52d4[_0x5878b2(0x1cb)];}export function previewTheme(_0x14bd01){const _0x39a687=a5_0xeab874,_0x294504={'QAafo':function(_0x12da8d,_0x325006){return _0x12da8d+_0x325006;},'LMLBg':'\x20\x20\x20','iRyaS':_0x39a687(0x1f1),'BARRB':_0x39a687(0x239),'RSQWS':_0x39a687(0x271),'JrbEa':_0x39a687(0x261)+'\x20','oMOUc':_0x39a687(0x1d6)+_0x39a687(0x1cf),'oaWti':function(_0x56248f,_0x1240a6){return _0x56248f+_0x1240a6;},'SYOSW':_0x39a687(0x248)+'\x20','BWRrA':_0x39a687(0x249)+_0x39a687(0x247),'ZNajU':_0x39a687(0x1a1)+':\x20','RaMtg':_0x39a687(0x21e)},_0x45a1a9=themes[_0x14bd01];if(!_0x45a1a9)return;console[_0x39a687(0x25a)]('\x0a'+_0x45a1a9[_0x39a687(0x1f2)]('═'['repeat'](0x32))),console[_0x39a687(0x25a)](_0x294504[_0x39a687(0x22f)](_0x45a1a9[_0x39a687(0x1f2)](_0x294504[_0x39a687(0x1a3)]),a5_0x270795[_0x39a687(0x1c8)]['white'](_0x45a1a9[_0x39a687(0x218)]))),console[_0x39a687(0x25a)](_0x45a1a9['border'](_0x294504[_0x39a687(0x1a3)])+_0x45a1a9[_0x39a687(0x228)](_0x45a1a9['descriptio'+'n'])),console['log'](_0x45a1a9[_0x39a687(0x1f2)]('─'[_0x39a687(0x1ee)](0x32))),console[_0x39a687(0x25a)](_0x294504[_0x39a687(0x22f)](_0x45a1a9[_0x39a687(0x26c)](_0x294504[_0x39a687(0x1f0)]),_0x294504['BARRB'])),console[_0x39a687(0x25a)](_0x45a1a9['ai'](_0x294504[_0x39a687(0x1f9)])+(_0x39a687(0x250)+_0x39a687(0x1aa)+_0x39a687(0x1b4))),console[_0x39a687(0x25a)](_0x294504['QAafo'](_0x45a1a9[_0x39a687(0x1ad)](_0x294504[_0x39a687(0x1bd)]),_0x294504[_0x39a687(0x1b2)])),console[_0x39a687(0x25a)](_0x294504[_0x39a687(0x19f)](_0x45a1a9[_0x39a687(0x20b)](_0x294504[_0x39a687(0x1c6)]),_0x294504[_0x39a687(0x1e4)])),console[_0x39a687(0x25a)](_0x45a1a9[_0x39a687(0x235)](_0x294504[_0x39a687(0x240)])+_0x294504[_0x39a687(0x210)]),console[_0x39a687(0x25a)](_0x294504[_0x39a687(0x19f)](_0x45a1a9[_0x39a687(0x1f2)]('═'['repeat'](0x32)),'\x0a'));}export const ui={'user':_0x16e691=>console[a5_0xeab874(0x25a)](currentTheme[a5_0xeab874(0x26c)](a5_0xeab874(0x24e)+_0x16e691)),'ai':_0x149f7d=>console[a5_0xeab874(0x25a)](currentTheme['ai'](a5_0xeab874(0x1f4)+_0x149f7d)),'warn':_0x59c714=>console[a5_0xeab874(0x25a)](currentTheme[a5_0xeab874(0x20b)]('⚠\x20'+_0x59c714)),'info':_0x554d4f=>console[a5_0xeab874(0x25a)](currentTheme[a5_0xeab874(0x1ad)]('ℹ\x20'+_0x554d4f)),'error':_0x3923a7=>console[a5_0xeab874(0x25a)](currentTheme[a5_0xeab874(0x235)]('✖\x20'+_0x3923a7)),'spinner':_0x3de814=>a5_0x19acff({'text':_0x3de814,'color':a5_0xeab874(0x205)}),'toolOutput':_0x21c052=>console['log'](currentTheme[a5_0xeab874(0x228)]('>\x20Tool:\x20'+_0x21c052[a5_0xeab874(0x206)](0x0,0x64)+(_0x21c052[a5_0xeab874(0x1e9)]>0x64?a5_0xeab874(0x214):''))),'showBanner':(_0x294416=a5_0xeab874(0x1b6),_0x91275b='',_0x34b646=0x0)=>{const _0x2f54d5=a5_0xeab874,_0x1ab46b={'jCwQt':function(_0x35695d,_0x465c27){return _0x35695d(_0x465c27);},'kQqNY':function(_0x3abbdd,_0x1a10b9){return _0x3abbdd+_0x1a10b9;},'LybGT':function(_0x14db8c,_0x17e2f9){return _0x14db8c-_0x17e2f9;},'DzTml':function(_0x33154b,_0x2642d6){return _0x33154b-_0x2642d6;},'lVeCq':_0x2f54d5(0x254),'YjhQC':'Use\x20/model'+'\x20to\x20switch'+'\x20models','iVhHs':function(_0x121493,_0x43c58e){return _0x121493(_0x43c58e);},'oWhkH':_0x2f54d5(0x20c)+'\x20to\x20custom'+'ize\x20look','QoeYX':function(_0x1db33c,_0x2aa9f8){return _0x1db33c(_0x2aa9f8);},'KfEOl':_0x2f54d5(0x203)+_0x2f54d5(0x1cc),'aYuem':function(_0x2ce04c,_0x3531e6){return _0x2ce04c(_0x3531e6);},'rcMOt':_0x2f54d5(0x233)+_0x2f54d5(0x20a)+_0x2f54d5(0x1ba),'DjbyE':function(_0x92c148,_0x2ac61a){return _0x92c148<_0x2ac61a;},'hZGiv':function(_0x64bb82,_0xf6903){return _0x64bb82-_0xf6903;},'wKvwP':function(_0x2e682a,_0x5f437a){return _0x2e682a+_0x5f437a;},'PbHhG':function(_0x49a03b,_0x30dce5){return _0x49a03b+_0x30dce5;},'ybRMh':function(_0x4042d7,_0x20b0fb){return _0x4042d7+_0x20b0fb;},'NlmBF':function(_0x3db34f,_0x36626d){return _0x3db34f+_0x36626d;},'NkvZj':function(_0x118220,_0x1c384d){return _0x118220-_0x1c384d;},'ZMJIQ':function(_0x1a0eeb,_0xfa83cf){return _0x1a0eeb-_0xfa83cf;},'iSeNv':function(_0x35e8e3,_0xc35742){return _0x35e8e3+_0xc35742;},'ZXcIH':function(_0x11190a,_0x15cd33){return _0x11190a+_0x15cd33;},'ciKSz':function(_0x28429d,_0x251325){return _0x28429d-_0x251325;}},_0x1650b5=currentTheme[_0x2f54d5(0x1f2)],_0xecf211=currentTheme[_0x2f54d5(0x228)],_0x450dfc=currentTheme[_0x2f54d5(0x26c)],_0x14f692=0x50,_0x4ded0b=0x23;console[_0x2f54d5(0x25a)](),console['log'](_0x1ab46b['jCwQt'](_0x1650b5,_0x1ab46b[_0x2f54d5(0x257)](_0x1ab46b[_0x2f54d5(0x257)]('╭'+'─'[_0x2f54d5(0x1ee)](0xf),_0x2f54d5(0x1b9)+_0x2f54d5(0x236)+_0x294416+'\x20')+'─'['repeat'](_0x1ab46b[_0x2f54d5(0x21f)](_0x1ab46b[_0x2f54d5(0x1d4)](_0x14f692,0xf)-('\x20OpenRoute'+_0x2f54d5(0x236)+_0x294416+'\x20')['length'],0x2)),'╮')));const _0x1e78c0=(_0x10185b,_0x108a6a)=>{const _0x10031a=_0x2f54d5,_0x43c2db=_0x10185b[_0x10031a(0x1c3)](_0x4ded0b)[_0x10031a(0x206)](0x0,_0x4ded0b);},_0x89808c=[_0x2f54d5(0x1f6)+_0x2f54d5(0x1a5),_0x2f54d5(0x211)+_0x2f54d5(0x22a),_0x2f54d5(0x1f5)+_0x2f54d5(0x265),'\x20\x20\x20\x20█░░░░░'+_0x2f54d5(0x21b),_0x2f54d5(0x1c1)+_0x2f54d5(0x270),_0x2f54d5(0x24c)+'▀\x20\x20\x20\x20\x20\x20'],_0x18d930=['',_0x2f54d5(0x1dc)+_0x2f54d5(0x25f),'',..._0x89808c[_0x2f54d5(0x1e5)](_0x22fea8=>_0x1650b5(_0x22fea8)),'',_0x2f54d5(0x1bf)+':\x20'+_0x1ab46b[_0x2f54d5(0x1b7)](_0x450dfc,_0x1ab46b[_0x2f54d5(0x257)](_0x34b646,_0x1ab46b[_0x2f54d5(0x26f)])),'\x20'+process['cwd']()['slice'](0x0,0x1e)+'...'],_0x21b3db=[_0x1ab46b['jCwQt'](_0x1650b5,'Tips\x20for\x20g'+_0x2f54d5(0x241)+'rted'),_0xecf211(_0x2f54d5(0x19d)+_0x2f54d5(0x23f)+_0x2f54d5(0x216)),_0x1ab46b[_0x2f54d5(0x1b7)](_0xecf211,_0x1ab46b[_0x2f54d5(0x19a)]),_0x1ab46b[_0x2f54d5(0x1a2)](_0xecf211,_0x1ab46b['oWhkH']),'',_0x1ab46b['QoeYX'](_0x1650b5,_0x1ab46b['KfEOl']),_0x1ab46b[_0x2f54d5(0x231)](_0xecf211,_0x1ab46b[_0x2f54d5(0x19e)])],_0x1834e1=Math[_0x2f54d5(0x1c9)](_0x18d930['length'],_0x21b3db[_0x2f54d5(0x1e9)]);for(let _0x5cc811=0x0;_0x1ab46b[_0x2f54d5(0x1db)](_0x5cc811,_0x1834e1);_0x5cc811++){let _0x50af8a=_0x18d930[_0x5cc811]||'',_0x3c915a=_0x21b3db[_0x5cc811]||'';const _0xe60bd8=_0x50af8a[_0x2f54d5(0x237)](/\x1b\[[0-9;]*m/g,'')['length'],_0xa7bef9='\x20'[_0x2f54d5(0x1ee)](Math['max'](0x0,_0x1ab46b[_0x2f54d5(0x1c5)](_0x4ded0b,_0xe60bd8)));console['log'](_0x1ab46b[_0x2f54d5(0x21d)](_0x1ab46b[_0x2f54d5(0x234)](_0x1ab46b[_0x2f54d5(0x20f)](_0x1ab46b['kQqNY'](_0x1ab46b['NlmBF'](_0x1650b5('│'),_0x50af8a),_0xa7bef9),_0x1ab46b[_0x2f54d5(0x231)](_0x1650b5,'│\x20'))+_0x3c915a[_0x2f54d5(0x1c3)](0x0),'\x20'['repeat'](Math['max'](0x0,_0x1ab46b[_0x2f54d5(0x1e7)](_0x1ab46b[_0x2f54d5(0x1ef)](_0x14f692-_0x4ded0b,0x3),_0x3c915a['replace'](/\x1b\[[0-9;]*m/g,'')[_0x2f54d5(0x1e9)])))),_0x1ab46b[_0x2f54d5(0x231)](_0x1650b5,'│')));}console[_0x2f54d5(0x25a)](_0x1650b5(_0x1ab46b[_0x2f54d5(0x221)](_0x1ab46b[_0x2f54d5(0x1e8)]('╰','─'[_0x2f54d5(0x1ee)](_0x1ab46b[_0x2f54d5(0x1e0)](_0x14f692,0x2))),'╯'))),console[_0x2f54d5(0x25a)]();},'showHelp':()=>{const _0x66d70=a5_0xeab874,_0x28d8f1={'XwTxf':_0x66d70(0x1ec),'uLJKz':_0x66d70(0x1e1)+_0x66d70(0x1b8),'qxkkK':_0x66d70(0x246)+_0x66d70(0x1cd)+_0x66d70(0x223),'AIVcm':'ctrl\x20+\x20shi'+'ft\x20+\x20-\x20to\x20'+_0x66d70(0x1a7),'UKBpB':_0x66d70(0x1fd)+'ands','nhkSx':'shift\x20+\x20ta'+'b\x20to\x20auto-'+_0x66d70(0x224)+'ts','NWBiK':_0x66d70(0x273)+_0x66d70(0x19c)+_0x66d70(0x202),'cDknP':_0x66d70(0x263)+_0x66d70(0x212),'Aueno':_0x66d70(0x226)+'o\x20stash\x20pr'+_0x66d70(0x1c2),'BQPsH':'\x0a\x20Default\x20'+'Commands','OGKZw':_0x66d70(0x1c7)+_0x66d70(0x22d),'iJybc':_0x66d70(0x1ed)+_0x66d70(0x244),'MSkKa':_0x66d70(0x252)+_0x66d70(0x1e3),'LmcOr':_0x66d70(0x22c),'ehCzy':'Check\x20curr'+_0x66d70(0x24b)+_0x66d70(0x1bb)+_0x66d70(0x1ab),'mqZPB':_0x66d70(0x1b1)+_0x66d70(0x1d3)+'mode\x20(Ask/'+'Auto)','AkgRR':_0x66d70(0x1a6),'urNHf':'/clear','FHylE':_0x66d70(0x259)+_0x66d70(0x22b)+_0x66d70(0x272),'QcBEz':_0x66d70(0x230),'nfIyU':_0x66d70(0x1d2)+'LI'},_0x17f95c=currentTheme[_0x66d70(0x1f2)];console[_0x66d70(0x25a)](a5_0x270795[_0x66d70(0x1c8)][_0x66d70(0x26a)](_0x66d70(0x229)+_0x66d70(0x1eb)+_0x66d70(0x1b0))),console[_0x66d70(0x25a)](a5_0x270795['bold'](_0x28d8f1['XwTxf'])),console[_0x66d70(0x25a)](currentTheme[_0x66d70(0x228)](_0x66d70(0x1c7)));const _0x4d9595=[[_0x28d8f1[_0x66d70(0x1bc)],_0x28d8f1[_0x66d70(0x1be)],_0x28d8f1[_0x66d70(0x251)]],[_0x28d8f1[_0x66d70(0x1c4)],_0x28d8f1[_0x66d70(0x25d)],_0x28d8f1['NWBiK']],[_0x28d8f1[_0x66d70(0x25b)],_0x66d70(0x266)+_0x66d70(0x222)+'os',_0x28d8f1[_0x66d70(0x256)]],[_0x66d70(0x1e6)+_0x66d70(0x1f7),'backslash\x20'+_0x66d70(0x1ac)+'rn\x20for\x20new'+'line',_0x66d70(0x258)+_0x66d70(0x1ca)+_0x66d70(0x20e)]];_0x4d9595[_0x66d70(0x25c)](_0x2cccc7=>{const _0x23d078=_0x66d70,_0x3720c9=_0x2cccc7[0x0][_0x23d078(0x1c3)](0x19),_0x5889d3=_0x2cccc7[0x1][_0x23d078(0x1c3)](0x23),_0xfa263d=_0x2cccc7[0x2];console[_0x23d078(0x25a)]('\x20'+currentTheme['user'](_0x3720c9)+currentTheme[_0x23d078(0x1ad)](_0x5889d3)+currentTheme[_0x23d078(0x228)](_0xfa263d));}),console[_0x66d70(0x25a)](a5_0x270795[_0x66d70(0x1c8)](_0x28d8f1[_0x66d70(0x220)])),console['log'](currentTheme[_0x66d70(0x228)](_0x28d8f1[_0x66d70(0x23c)]));const _0x567985=[{'cmd':_0x66d70(0x26b),'desc':_0x28d8f1[_0x66d70(0x1a9)]},{'cmd':'/theme','desc':_0x28d8f1[_0x66d70(0x260)]},{'cmd':_0x28d8f1[_0x66d70(0x21a)],'desc':_0x28d8f1[_0x66d70(0x267)]},{'cmd':'/settings','desc':_0x28d8f1[_0x66d70(0x1fb)]},{'cmd':_0x28d8f1[_0x66d70(0x1f3)],'desc':_0x66d70(0x1d9)+_0x66d70(0x232)+_0x66d70(0x1b3)},{'cmd':_0x28d8f1['urNHf'],'desc':_0x28d8f1[_0x66d70(0x245)]},{'cmd':_0x28d8f1[_0x66d70(0x1d1)],'desc':_0x28d8f1[_0x66d70(0x227)]}];_0x567985['forEach'](({cmd:_0x51e970,desc:_0x49d327})=>{const _0x54c347=_0x66d70;console[_0x54c347(0x25a)]('\x20'+currentTheme[_0x54c347(0x23d)](_0x51e970[_0x54c347(0x1c3)](0xf))+'\x20'+currentTheme[_0x54c347(0x228)](_0x49d327));}),console[_0x66d70(0x25a)]();}};function a5_0x2b40(){const _0x49e575=['info','87944KXzMBk','#7F8FA6','yle\x20Help\x0a','Configure\x20','oMOUc','\x20file','\x20giúp\x20gì?','#FDCB6E','1.0.0','jCwQt','\x20mode','\x20OpenRoute','yet)','cost\x20&\x20tok','uLJKz','JrbEa','qxkkK','\x20API\x20Usage','m\x20-\x20nổi\x20bậ','\x20\x20\x20\x20▀▄░░░░','ompt','padEnd','UKBpB','hZGiv','SYOSW','\x20─────────','bold','max','gs\x20to\x20cust','pxCDJ','ivity','\x20esc\x20to\x20cl','#81ECEC','ng\x20tin','Tone\x20đỏ\x20ca','QcBEz','Exit\x20the\x20C','execution\x20','DzTml','#EE5A24','Đây\x20là\x20thô','hex','#00CEC9','Save\x20chat\x20','Nền\x20sáng,\x20','DjbyE','\x20\x20\x20Welcome','#FF6348','32448SgHJNu','#A29BFE','ciKSz','!\x20for\x20bash','light','CLI\x20theme','BWRrA','map','&\x20for\x20back','NkvZj','ZXcIH','length','#636E72','ode\x20CLI\x20St','\x20Shortcuts','Switch\x20usa','repeat','ZMJIQ','iRyaS','\x20\x20\x20You\x20>\x20','border','AkgRR','AI:\x20','\x20\x20\x20\x20\x20▄▀░░░','\x20\x20\x20\x20\x20\x20\x20▄▀▄','ground','#55EFC4','RSQWS','182810WRDYeL','mqZPB','chữ\x20đậm\x20-\x20','/\x20for\x20comm','#74B9FF','r\x20style','dương\x20-\x20má','2772880vIMZMj','odel','Recent\x20act','\x20ngày','cyan','slice','Ocean\x20Blue','892XDojSE','#FFA502','activity\x20(','warn','Use\x20/theme','Fire\x20Red','omize','ybRMh','RaMtg','\x20\x20\x20\x20\x20\x20▄█░█','\x20paths','#FFEAA7','...','960TyOoZJ','mands','54wBstlP','name','#2ED573','LmcOr','░░█\x20\x20\x20\x20','#FF6B6B','wKvwP','Đây\x20là\x20lỗi','LybGT','BQPsH','iSeNv','o\x20show\x20tod','ear\x20input','accept\x20edi','\x20đêm','ctrl\x20+\x20s\x20t','nfIyU','dim','\x0a\x20Claude\x20C','▄\x20\x20\x20\x20\x20\x20','ersation\x20c','/cost','───────','#E056FD','QAafo','/exit','aYuem','history\x20to','No\x20recent\x20','PbHhG','error','r\x20CLI\x20v','replace','#8E44AD','Xin\x20chào!','#FF7675','dark','OGKZw','accent','749PtYhhQ','to\x20see\x20com','ZNajU','etting\x20sta','định)','2883Funlvq','ge\x20model','FHylE','double\x20tap','h\x20báo','\x20\x20\x20⚠\x20Warn:','Đây\x20là\x20cản','#2C3E50','ent\x20usage\x20','\x20\x20\x20\x20\x20\x20▀▄▄▄','#00FF00','You:\x20','#C0392B','Chào\x20bạn!\x20','AIVcm','Customize\x20','#D35400','\x20reqs','#6C5CE7','Aueno','kQqNY','/keybindin','Clear\x20conv','log','cDknP','forEach','nhkSx','714936vWoyQZ','\x20back!','MSkKa','\x20\x20\x20ℹ\x20Info:','#00B894','@\x20for\x20file','t\x20mẻ','▀▄\x20\x20\x20\x20\x20','ctrl\x20+\x20t\x20t','ehCzy','#27AE60','270650kDOElx','white','/model','user','hữ\x20sáng\x20-\x20','Tone\x20xanh\x20','lVeCq','░▄▀\x20\x20\x20\x20','\x20\x20\x20AI\x20\x20>\x20','ontext','meta\x20+\x20p\x20t','YjhQC','#7BED9F','o\x20switch\x20m','Run\x20/help\x20','rcMOt','oaWti','lá\x20-\x20hacke','\x20\x20\x20✖\x20Error','iVhHs','LMLBg','Light','\x20\x20\x20\x20\x20\x20\x20','/history','undo','#7F8C8D','iJybc','Tôi\x20có\x20thể','ens','(\x5c)\x20+\x20retu'];a5_0x2b40=function(){return _0x49e575;};return a5_0x2b40();}
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
|
|
4
|
+
// Theme definitions - optimized for readability
|
|
5
|
+
export const themes = {
|
|
6
|
+
dark: {
|
|
7
|
+
name: 'Dark (Mặc định)',
|
|
8
|
+
description: 'Nền tối, chữ sáng - dễ đọc ban đêm',
|
|
9
|
+
border: chalk.hex('#FF6B6B'),
|
|
10
|
+
ai: chalk.hex('#74B9FF'), // Light blue - AI response
|
|
11
|
+
user: chalk.hex('#55EFC4'), // Mint green - user input
|
|
12
|
+
info: chalk.hex('#81ECEC'), // Cyan - info
|
|
13
|
+
warn: chalk.hex('#FFEAA7'), // Yellow - warning
|
|
14
|
+
error: chalk.hex('#FF7675'), // Red - error
|
|
15
|
+
dim: chalk.hex('#636E72'), // Gray - dim text
|
|
16
|
+
accent: chalk.hex('#A29BFE'), // Purple - accent
|
|
17
|
+
bg: 'dark'
|
|
18
|
+
},
|
|
19
|
+
light: {
|
|
20
|
+
name: 'Light',
|
|
21
|
+
description: 'Nền sáng, chữ đậm - dễ đọc ban ngày',
|
|
22
|
+
border: chalk.hex('#2C3E50'),
|
|
23
|
+
ai: chalk.hex('#2980B9'), // Blue
|
|
24
|
+
user: chalk.hex('#27AE60'), // Green
|
|
25
|
+
info: chalk.hex('#16A085'), // Teal
|
|
26
|
+
warn: chalk.hex('#D35400'), // Orange
|
|
27
|
+
error: chalk.hex('#C0392B'), // Red
|
|
28
|
+
dim: chalk.hex('#7F8C8D'), // Gray
|
|
29
|
+
accent: chalk.hex('#8E44AD'), // Purple
|
|
30
|
+
bg: 'light'
|
|
31
|
+
},
|
|
32
|
+
ocean: {
|
|
33
|
+
name: 'Ocean Blue',
|
|
34
|
+
description: 'Tone xanh dương - mát mẻ',
|
|
35
|
+
border: chalk.hex('#00CEC9'),
|
|
36
|
+
ai: chalk.hex('#81ECEC'), // Light cyan
|
|
37
|
+
user: chalk.hex('#00B894'), // Green
|
|
38
|
+
info: chalk.hex('#74B9FF'), // Light blue
|
|
39
|
+
warn: chalk.hex('#FDCB6E'), // Yellow
|
|
40
|
+
error: chalk.hex('#FF7675'), // Red
|
|
41
|
+
dim: chalk.hex('#636E72'), // Gray
|
|
42
|
+
accent: chalk.hex('#6C5CE7'), // Purple
|
|
43
|
+
bg: 'dark'
|
|
44
|
+
},
|
|
45
|
+
fire: {
|
|
46
|
+
name: 'Fire Red',
|
|
47
|
+
description: 'Tone đỏ cam - nổi bật',
|
|
48
|
+
border: chalk.hex('#FF6B6B'),
|
|
49
|
+
ai: chalk.hex('#FFA502'), // Orange
|
|
50
|
+
user: chalk.hex('#FF6348'), // Coral
|
|
51
|
+
info: chalk.hex('#FFEAA7'), // Light yellow
|
|
52
|
+
warn: chalk.hex('#FDCB6E'), // Yellow
|
|
53
|
+
error: chalk.hex('#EE5A24'), // Deep orange
|
|
54
|
+
dim: chalk.hex('#7F8FA6'), // Gray
|
|
55
|
+
accent: chalk.hex('#E056FD'), // Pink
|
|
56
|
+
bg: 'dark'
|
|
57
|
+
},
|
|
58
|
+
matrix: {
|
|
59
|
+
name: 'Matrix Green',
|
|
60
|
+
description: 'Tone xanh lá - hacker style',
|
|
61
|
+
border: chalk.hex('#00FF00'),
|
|
62
|
+
ai: chalk.hex('#7BED9F'), // Light green
|
|
63
|
+
user: chalk.hex('#2ED573'), // Green
|
|
64
|
+
info: chalk.hex('#7BED9F'), // Light green
|
|
65
|
+
warn: chalk.hex('#FFEAA7'), // Yellow
|
|
66
|
+
error: chalk.hex('#FF6B6B'), // Red
|
|
67
|
+
dim: chalk.hex('#636E72'), // Gray
|
|
68
|
+
accent: chalk.hex('#00CEC9'), // Cyan
|
|
69
|
+
bg: 'dark'
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Current theme
|
|
74
|
+
let currentTheme = themes.dark;
|
|
75
|
+
|
|
76
|
+
export function setTheme(themeName) {
|
|
77
|
+
if (themes[themeName]) {
|
|
78
|
+
currentTheme = themes[themeName];
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function getTheme() {
|
|
85
|
+
return currentTheme;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function getThemeName() {
|
|
89
|
+
for (const [key, theme] of Object.entries(themes)) {
|
|
90
|
+
if (theme === currentTheme) return key;
|
|
91
|
+
}
|
|
92
|
+
return 'dark';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Preview a theme
|
|
96
|
+
export function previewTheme(themeName) {
|
|
97
|
+
const theme = themes[themeName];
|
|
98
|
+
if (!theme) return;
|
|
99
|
+
|
|
100
|
+
console.log('\n' + theme.border('═'.repeat(50)));
|
|
101
|
+
console.log(theme.border(' ') + chalk.bold.white(theme.name));
|
|
102
|
+
console.log(theme.border(' ') + theme.dim(theme.description));
|
|
103
|
+
console.log(theme.border('─'.repeat(50)));
|
|
104
|
+
console.log(theme.user(' You > ') + 'Xin chào!');
|
|
105
|
+
console.log(theme.ai(' AI > ') + 'Chào bạn! Tôi có thể giúp gì?');
|
|
106
|
+
console.log(theme.info(' ℹ Info: ') + 'Đây là thông tin');
|
|
107
|
+
console.log(theme.warn(' ⚠ Warn: ') + 'Đây là cảnh báo');
|
|
108
|
+
console.log(theme.error(' ✖ Error: ') + 'Đây là lỗi');
|
|
109
|
+
console.log(theme.border('═'.repeat(50)) + '\n');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const ui = {
|
|
113
|
+
// Styles based on current theme
|
|
114
|
+
user: (text) => console.log(currentTheme.user(`You: ${text}`)),
|
|
115
|
+
ai: (text) => console.log(currentTheme.ai(`AI: ${text}`)),
|
|
116
|
+
warn: (text) => console.log(currentTheme.warn(`⚠ ${text}`)),
|
|
117
|
+
info: (text) => console.log(currentTheme.info(`ℹ ${text}`)),
|
|
118
|
+
error: (text) => console.log(currentTheme.error(`✖ ${text}`)),
|
|
119
|
+
|
|
120
|
+
// Spinner
|
|
121
|
+
spinner: (text) => ora({ text, color: 'cyan' }),
|
|
122
|
+
|
|
123
|
+
// Format tool output to look distinct
|
|
124
|
+
toolOutput: (text) => console.log(currentTheme.dim(`> Tool: ${text.slice(0, 100)}${text.length > 100 ? '...' : ''}`)),
|
|
125
|
+
|
|
126
|
+
showBanner: (version = '1.0.0', username = '', requestCount = 0) => {
|
|
127
|
+
const c = currentTheme.border; // Color for border/accent
|
|
128
|
+
const t = currentTheme.dim; // Text color
|
|
129
|
+
const h = currentTheme.user; // Highlight/User color
|
|
130
|
+
|
|
131
|
+
const width = 80;
|
|
132
|
+
const leftWidth = 35;
|
|
133
|
+
// Right width = width - leftWidth - 3 (borders)
|
|
134
|
+
|
|
135
|
+
console.log();
|
|
136
|
+
console.log(c('╭' + '─'.repeat(15) + ` OpenRouter CLI v${version} ` + '─'.repeat(width - 15 - ` OpenRouter CLI v${version} `.length - 2) + '╮'));
|
|
137
|
+
|
|
138
|
+
// Helper to format rows
|
|
139
|
+
const row = (left, right) => {
|
|
140
|
+
const l = left.padEnd(leftWidth).slice(0, leftWidth); // Truncate if too long (simple)
|
|
141
|
+
// Color codes mess up length calculation for padding. We need to measure valid length.
|
|
142
|
+
// But strict column layout with colors is tricky.
|
|
143
|
+
// Simplified approach: Render layout line by line.
|
|
144
|
+
|
|
145
|
+
// To properly pad colored strings, we need stripAnsi-like function, but here we manually align.
|
|
146
|
+
// Let's assume left and right are arrays of lines to be printed.
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const logo = [
|
|
150
|
+
' ▄▀▄ ',
|
|
151
|
+
' ▄█░█▄ ',
|
|
152
|
+
' ▄▀░░░▀▄ ',
|
|
153
|
+
' █░░░░░░░█ ',
|
|
154
|
+
' ▀▄░░░░░▄▀ ',
|
|
155
|
+
' ▀▄▄▄▀ '
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
const leftContent = [
|
|
159
|
+
'',
|
|
160
|
+
` Welcome back!`,
|
|
161
|
+
'',
|
|
162
|
+
...logo.map(l => c(l)),
|
|
163
|
+
'',
|
|
164
|
+
` API Usage: ${h(requestCount + ' reqs')}`,
|
|
165
|
+
` ${process.cwd().slice(0, 30)}...`
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
const rightContent = [
|
|
169
|
+
c('Tips for getting started'),
|
|
170
|
+
t('Run /help to see commands'),
|
|
171
|
+
t('Use /model to switch models'),
|
|
172
|
+
t('Use /theme to customize look'),
|
|
173
|
+
'',
|
|
174
|
+
c('Recent activity'),
|
|
175
|
+
t('No recent activity (yet)')
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
const maxLines = Math.max(leftContent.length, rightContent.length);
|
|
179
|
+
|
|
180
|
+
for (let i = 0; i < maxLines; i++) {
|
|
181
|
+
let left = leftContent[i] || '';
|
|
182
|
+
let right = rightContent[i] || '';
|
|
183
|
+
|
|
184
|
+
// Pad left column. strip ANSI to count length.
|
|
185
|
+
const leftLen = left.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
186
|
+
const padding = ' '.repeat(Math.max(0, leftWidth - leftLen));
|
|
187
|
+
|
|
188
|
+
console.log(c('│') + left + padding + c('│ ') + right.padEnd(0) + ' '.repeat(Math.max(0, width - leftWidth - 3 - right.replace(/\x1b\[[0-9;]*m/g, '').length)) + c('│'));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log(c('╰' + '─'.repeat(width - 2) + '╯'));
|
|
192
|
+
console.log();
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
showHelp: () => {
|
|
196
|
+
const border = currentTheme.border;
|
|
197
|
+
console.log(chalk.bold.white('\n Claude Code CLI Style Help\n'));
|
|
198
|
+
|
|
199
|
+
console.log(chalk.bold(' Shortcuts'));
|
|
200
|
+
console.log(currentTheme.dim(' ─────────'));
|
|
201
|
+
const shortcuts = [
|
|
202
|
+
['! for bash mode', 'double tap esc to clear input', 'ctrl + shift + - to undo'],
|
|
203
|
+
['/ for commands', 'shift + tab to auto-accept edits', 'meta + p to switch model'],
|
|
204
|
+
['@ for file paths', 'ctrl + t to show todos', 'ctrl + s to stash prompt'],
|
|
205
|
+
['& for background', 'backslash (\\) + return for newline', '/keybindings to customize']
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
shortcuts.forEach(row => {
|
|
209
|
+
const col1 = row[0].padEnd(25);
|
|
210
|
+
const col2 = row[1].padEnd(35);
|
|
211
|
+
const col3 = row[2];
|
|
212
|
+
console.log(` ${currentTheme.user(col1)}${currentTheme.info(col2)}${currentTheme.dim(col3)}`);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
console.log(chalk.bold('\n Default Commands'));
|
|
216
|
+
console.log(currentTheme.dim(' ────────────────'));
|
|
217
|
+
|
|
218
|
+
const commands = [
|
|
219
|
+
{ cmd: '/model', desc: 'Switch usage model' },
|
|
220
|
+
{ cmd: '/theme', desc: 'Customize CLI theme' },
|
|
221
|
+
{ cmd: '/cost', desc: 'Check current usage cost & tokens' },
|
|
222
|
+
{ cmd: '/settings', desc: 'Configure execution mode (Ask/Auto)' },
|
|
223
|
+
{ cmd: '/history', desc: 'Save chat history to file' },
|
|
224
|
+
{ cmd: '/clear', desc: 'Clear conversation context' },
|
|
225
|
+
{ cmd: '/exit', desc: 'Exit the CLI' }
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
commands.forEach(({ cmd, desc }) => {
|
|
229
|
+
console.log(` ${currentTheme.accent(cmd.padEnd(15))} ${currentTheme.dim(desc)}`);
|
|
230
|
+
});
|
|
231
|
+
console.log();
|
|
232
|
+
}
|
|
233
|
+
};
|
package/src/utils.js
CHANGED
|
@@ -1 +1,55 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Estimate token count from a string (rough: 1 token ~= 4 chars for English, 2 chars for CJK)
|
|
3
|
+
*/
|
|
4
|
+
function estimateTokens(text) {
|
|
5
|
+
if (!text) return 0;
|
|
6
|
+
return Math.ceil(text.length / 3); // Conservative estimate
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Truncates the message history to fit within a safe token limit.
|
|
11
|
+
* - Keeps system prompt always.
|
|
12
|
+
* - Aggressively truncates tool output content.
|
|
13
|
+
* - Removes oldest messages when over limit.
|
|
14
|
+
* @param {Array} messages - Message history
|
|
15
|
+
* @param {number} maxTokens - Maximum token budget (default 100000 for safety margin)
|
|
16
|
+
* @returns {Array} - Truncated message list
|
|
17
|
+
*/
|
|
18
|
+
export function truncateContext(messages, maxTokens = 100000) {
|
|
19
|
+
const systemMessage = messages.find(m => m.role === 'system');
|
|
20
|
+
let otherMessages = messages.filter(m => m.role !== 'system');
|
|
21
|
+
|
|
22
|
+
// First pass: Truncate large tool outputs in-place
|
|
23
|
+
otherMessages = otherMessages.map(m => {
|
|
24
|
+
if (m.role === 'tool' && m.content && m.content.length > 200000) {
|
|
25
|
+
return { ...m, content: m.content.slice(0, 200000) + '\n... [TRUNCATED]' };
|
|
26
|
+
}
|
|
27
|
+
if (m.role === 'assistant' && m.content && m.content.length > 200000) {
|
|
28
|
+
return { ...m, content: m.content.slice(0, 200000) + '\n... [TRUNCATED]' };
|
|
29
|
+
}
|
|
30
|
+
return m;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Calculate current token usage
|
|
34
|
+
let totalTokens = estimateTokens(systemMessage?.content || '');
|
|
35
|
+
for (const m of otherMessages) {
|
|
36
|
+
totalTokens += estimateTokens(typeof m.content === 'string' ? m.content : JSON.stringify(m.content || ''));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Second pass: Remove oldest messages until under limit
|
|
40
|
+
while (totalTokens > maxTokens && otherMessages.length > 4) {
|
|
41
|
+
const removed = otherMessages.shift();
|
|
42
|
+
totalTokens -= estimateTokens(typeof removed.content === 'string' ? removed.content : JSON.stringify(removed.content || ''));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (systemMessage) {
|
|
46
|
+
return [systemMessage, ...otherMessages];
|
|
47
|
+
}
|
|
48
|
+
return otherMessages;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Safe sleep function
|
|
53
|
+
* @param {number} ms
|
|
54
|
+
*/
|
|
55
|
+
export const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|