osai-agent 4.2.37 → 4.2.41
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/package.json +4 -3
- package/src/commands/account.js +2 -1
- package/src/commands/mcp.js +5 -4
- package/src/commands/provider.js +5 -4
- package/src/commands/search.js +3 -2
- package/src/commands/stop-subagent.js +2 -1
- package/src/parser/markdown.js +41 -28
- package/src/ui/App.js +1 -1
- package/src/ui/animation.js +1 -1
- package/src/ui/components/DiffView.js +3 -2
- package/src/ui/components/Header.js +13 -12
- package/src/ui/components/MessageHistory.js +8 -8
- package/src/ui/components/ModePicker.js +4 -12
- package/src/ui/components/SavePicker.js +4 -3
- package/src/ui/components/SubagentPanel.js +6 -5
- package/src/ui/components/ToolExecution.js +2 -1
- package/src/ui/terminal.js +2 -1
- package/src/utils/unicode.js +42 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "osai-agent",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.41",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OS AI Agent - YOUR AI AGENT",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
],
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@cfworker/json-schema": "^4.1.1",
|
|
42
|
+
"@google/generative-ai": "^0.24.1",
|
|
42
43
|
"@modelcontextprotocol/client": "^2.0.0-alpha.3",
|
|
43
44
|
"@tavily/core": "^0.7.3",
|
|
44
45
|
"boxen": "^8.0.1",
|
|
@@ -47,17 +48,17 @@
|
|
|
47
48
|
"conf": "^12.0.0",
|
|
48
49
|
"duck-duck-scrape": "^2.2.7",
|
|
49
50
|
"fast-glob": "^3.3.3",
|
|
50
|
-
"@google/generative-ai": "^0.24.1",
|
|
51
|
-
"openai": "^6.42.0",
|
|
52
51
|
"ink": "^7.0.3",
|
|
53
52
|
"ink-scroll-view": "^0.3.7",
|
|
54
53
|
"ink-spinner": "^5.0.0",
|
|
55
54
|
"ink-text-input": "^6.0.0",
|
|
56
55
|
"inquirer": "^13.4.2",
|
|
56
|
+
"is-unicode-supported": "^2.1.0",
|
|
57
57
|
"marked": "^15.0.12",
|
|
58
58
|
"marked-terminal": "^7.3.0",
|
|
59
59
|
"minimist": "^1.2.8",
|
|
60
60
|
"node-machine-id": "^1.1.12",
|
|
61
|
+
"openai": "^6.42.0",
|
|
61
62
|
"ora": "^9.4.0",
|
|
62
63
|
"react": "^19.2.6",
|
|
63
64
|
"serpapi": "^2.2.1",
|
package/src/commands/account.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Conf from 'conf';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import boxen from 'boxen';
|
|
4
|
+
import { DOT, EMPTY } from '../utils/unicode.js';
|
|
4
5
|
import ora from 'ora';
|
|
5
6
|
import { createInterface } from 'node:readline';
|
|
6
7
|
import { rmSync, existsSync } from 'node:fs';
|
|
@@ -20,7 +21,7 @@ export const deleteAccount = async ({ server: serverArg }) => {
|
|
|
20
21
|
'',
|
|
21
22
|
` ${chalk.hex('#8ab4d8')('Your AI sysadmin, network engineer & senior developer')}`,
|
|
22
23
|
'',
|
|
23
|
-
` ${token ? chalk.green(
|
|
24
|
+
` ${token ? chalk.green(DOT) : chalk.red(EMPTY)} ${chalk.hex('#8ab4d8')('Auth:')} ${chalk.white(token ? 'Authenticated' : 'Not authenticated')}`,
|
|
24
25
|
].join('\n');
|
|
25
26
|
|
|
26
27
|
console.log(boxen(headerContent, {
|
package/src/commands/mcp.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Conf from 'conf';
|
|
2
2
|
import { mcpClientManager } from '../tools/mcp-client.js';
|
|
3
|
+
import { TICK, CROSS } from '../utils/unicode.js';
|
|
3
4
|
|
|
4
5
|
const config = new Conf({ projectName: 'osai-agent' });
|
|
5
6
|
|
|
@@ -211,12 +212,12 @@ async function reloadServers() {
|
|
|
211
212
|
|
|
212
213
|
if (results.connected.length > 0) {
|
|
213
214
|
for (const r of results.connected) {
|
|
214
|
-
console.log(` \x1b[32m
|
|
215
|
+
console.log(` \x1b[32m${TICK}\x1b[0m ${r.name}: ${r.toolCount} tools`);
|
|
215
216
|
}
|
|
216
217
|
}
|
|
217
218
|
if (results.failed.length > 0) {
|
|
218
219
|
for (const r of results.failed) {
|
|
219
|
-
console.log(` \x1b[31m
|
|
220
|
+
console.log(` \x1b[31m${CROSS}\x1b[0m ${r.name}: ${r.error}`);
|
|
220
221
|
}
|
|
221
222
|
}
|
|
222
223
|
}
|
|
@@ -240,7 +241,7 @@ async function testServer(args) {
|
|
|
240
241
|
|
|
241
242
|
try {
|
|
242
243
|
const tools = await mcpClientManager.addServer(name, cfg);
|
|
243
|
-
console.log(`\x1b[32m
|
|
244
|
+
console.log(`\x1b[32m${TICK} Connected\x1b[0m — ${tools.length} tools:`);
|
|
244
245
|
for (const tool of tools) {
|
|
245
246
|
console.log(` - ${tool.name}: ${tool.description || '(no description)'}`);
|
|
246
247
|
if (tool.inputSchema) {
|
|
@@ -253,6 +254,6 @@ async function testServer(args) {
|
|
|
253
254
|
}
|
|
254
255
|
await mcpClientManager.removeServer(name);
|
|
255
256
|
} catch (err) {
|
|
256
|
-
console.log(`\x1b[31m
|
|
257
|
+
console.log(`\x1b[31m${CROSS} Connection failed\x1b[0m: ${err.message}`);
|
|
257
258
|
}
|
|
258
259
|
}
|
package/src/commands/provider.js
CHANGED
|
@@ -3,6 +3,7 @@ import Conf from 'conf';
|
|
|
3
3
|
import inquirer from 'inquirer';
|
|
4
4
|
import ora from 'ora';
|
|
5
5
|
import { printError, printSuccess, printInfo, printNotLoggedIn } from '../ui/terminal.js';
|
|
6
|
+
import { isUnicode, DASH } from '../utils/unicode.js';
|
|
6
7
|
import { toHttpUrl } from '../services/server-url.js';
|
|
7
8
|
import { encrypt, decrypt, deriveKey } from '../services/crypto.js';
|
|
8
9
|
import pkg from 'node-machine-id';
|
|
@@ -107,18 +108,18 @@ export const listProviders = async () => {
|
|
|
107
108
|
const active = catalog.find(p => p.active);
|
|
108
109
|
console.log();
|
|
109
110
|
console.log(chalk.hex('#7aa2f7').bold(' Provider Catalog'));
|
|
110
|
-
console.log(chalk.hex('#565f89')('
|
|
111
|
+
console.log(chalk.hex('#565f89')(' ' + DASH.repeat(48)));
|
|
111
112
|
console.log(chalk.hex('#565f89')(` ${'Provider'.padEnd(20)} SDK Type Free Models`));
|
|
112
|
-
console.log(chalk.hex('#565f89')('
|
|
113
|
+
console.log(chalk.hex('#565f89')(' ' + DASH.repeat(48)));
|
|
113
114
|
|
|
114
115
|
for (const p of catalog) {
|
|
115
|
-
const activeMark = p.active ? chalk.green(' ◀') : '';
|
|
116
|
+
const activeMark = p.active ? chalk.green(' ' + (isUnicode ? '◀' : '<')) : '';
|
|
116
117
|
const freeMark = p.free_tier ? chalk.green('Yes') : chalk.red('No ');
|
|
117
118
|
const name = p.id === active?.id ? chalk.white.bold(p.name) : chalk.white(p.name);
|
|
118
119
|
const sdk = p.sdk_type === 'anthropic' ? 'native ' : 'OpenAI ';
|
|
119
120
|
console.log(` ${(name + activeMark).padEnd(22)} ${sdk} ${freeMark} ${chalk.gray(p.models_count + ' models')}`);
|
|
120
121
|
}
|
|
121
|
-
console.log(chalk.hex('#565f89')('
|
|
122
|
+
console.log(chalk.hex('#565f89')(' ' + DASH.repeat(48)));
|
|
122
123
|
console.log(chalk.gray(` ${active?.name || 'OS AI Agent'} is currently active\n`));
|
|
123
124
|
} catch (err) {
|
|
124
125
|
spinner.fail('Network error');
|
package/src/commands/search.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import Conf from 'conf';
|
|
3
3
|
import { printError, printSuccess, printInfo } from '../ui/terminal.js';
|
|
4
|
+
import { isUnicode, DASH } from '../utils/unicode.js';
|
|
4
5
|
|
|
5
6
|
const getConfig = () => new Conf({ projectName: 'osai-agent' });
|
|
6
7
|
|
|
@@ -44,7 +45,7 @@ export const searchList = async () => {
|
|
|
44
45
|
|
|
45
46
|
console.log();
|
|
46
47
|
console.log(chalk.hex('#7aa2f7').bold(' Search Providers'));
|
|
47
|
-
console.log(chalk.hex('#565f89')('
|
|
48
|
+
console.log(chalk.hex('#565f89')(' ' + DASH.repeat(32)));
|
|
48
49
|
|
|
49
50
|
if (Object.keys(searchKeys).length === 0) {
|
|
50
51
|
console.log(chalk.hex('#565f89')(' No search API keys configured.'));
|
|
@@ -58,7 +59,7 @@ export const searchList = async () => {
|
|
|
58
59
|
console.log(` ${chalk.white(provider.padEnd(20))} ${chalk.gray(maskKey(key))}`);
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
console.log(chalk.hex('#565f89')('
|
|
62
|
+
console.log(chalk.hex('#565f89')(' ' + DASH.repeat(32)));
|
|
62
63
|
console.log(chalk.gray(' DuckDuckGo is always available (no key needed)'));
|
|
63
64
|
console.log();
|
|
64
65
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Conf from 'conf';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import boxen from 'boxen';
|
|
4
|
+
import { DOT, EMPTY } from '../utils/unicode.js';
|
|
4
5
|
import { showConfig } from './config.js';
|
|
5
6
|
import { clearScreen, printError, printPanel } from '../ui/terminal.js';
|
|
6
7
|
import { logger } from '../utils/logger.js';
|
|
@@ -15,7 +16,7 @@ export const stopSubagent = async ({ server }) => {
|
|
|
15
16
|
const token = config.get('token');
|
|
16
17
|
const serverUrl = server || config.get('server') || 'https://agent.osai.dev';
|
|
17
18
|
// Default from env
|
|
18
|
-
const authDot = token ? chalk.green(
|
|
19
|
+
const authDot = token ? chalk.green(DOT) : chalk.red(EMPTY);
|
|
19
20
|
const authLabel = token ? 'Authenticated' : 'Not authenticated';
|
|
20
21
|
const serverHost = serverUrl.replace(/^ws(s?):\/\//, '');
|
|
21
22
|
const headerContent = [
|
package/src/parser/markdown.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Markdown renderer — buffered + streaming
|
|
2
2
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
|
-
|
|
4
|
+
import { isUnicode } from '../utils/unicode.js';
|
|
5
5
|
// ─── Tokyo Night color tokens ─────────────────────────────────
|
|
6
6
|
const C = {
|
|
7
7
|
text: '#c0caf5',
|
|
@@ -22,10 +22,23 @@ const C = {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
const ELLIPSIS = '…';
|
|
25
|
-
const
|
|
25
|
+
const FORCE_ASCII =
|
|
26
26
|
process.env.OSAI_TABLE_ASCII === '1' ||
|
|
27
27
|
process.env.NO_UNICODE_TABLES === '1' ||
|
|
28
|
-
(process.env.TERM || '').toLowerCase() === 'dumb'
|
|
28
|
+
(process.env.TERM || '').toLowerCase() === 'dumb' ||
|
|
29
|
+
!isUnicode;
|
|
30
|
+
|
|
31
|
+
const S = FORCE_ASCII ? {
|
|
32
|
+
boxTL: '+', boxH: '-', boxV: '|', boxBL: '+',
|
|
33
|
+
h1: '==', h2: '>', h3: 'o', h4: '>',
|
|
34
|
+
hr: '-', bq: '|', dot: 'o',
|
|
35
|
+
ul0: '*', ul1: '-', ul2: '>',
|
|
36
|
+
} : {
|
|
37
|
+
boxTL: '╭', boxH: '─', boxV: '│', boxBL: '╰',
|
|
38
|
+
h1: '━━', h2: '◆', h3: '●', h4: '›',
|
|
39
|
+
hr: '─', bq: '┃', dot: '●',
|
|
40
|
+
ul0: '•', ul1: '◦', ul2: '▸',
|
|
41
|
+
};
|
|
29
42
|
|
|
30
43
|
// ─── Tool names (for stripping tool JSON from output) ────────
|
|
31
44
|
const ALL_TOOLS = 'LOCAL_CMD|SSH_CMD|READ_FILE|WRITE_FILE|EDIT_FILE|APPEND_FILE|DELETE_FILE|LIST_DIR|SEARCH_FILE|CREATE_DIR|TREE_VIEW|RUN_SCRIPT|MOVE_FILE|COPY_FILE|FILE_INFO|FETCH_URL|WEB_SEARCH|TODO_ADD|TODO_COMPLETE|TODO_UPDATE|TODO_LIST|TODO_CLEAR|POWERSHELL|GLOB|GREP|GIT|DIAG_POST_EDIT|ASK_USER|PLAN_MODE|SKILL_LIST|LOAD_SKILL|CREATE_SKILL|TASK';
|
|
@@ -114,7 +127,7 @@ const renderTableRows = (rows, maxWidth = 96) => {
|
|
|
114
127
|
return out;
|
|
115
128
|
});
|
|
116
129
|
|
|
117
|
-
const useUnicode = !
|
|
130
|
+
const useUnicode = !FORCE_ASCII;
|
|
118
131
|
const colSep = useUnicode ? ' │ ' : ' | ';
|
|
119
132
|
const rowSep = useUnicode ? '─┼─' : '-+-';
|
|
120
133
|
const rowFill = useUnicode ? '─' : '-';
|
|
@@ -235,12 +248,12 @@ export const renderMarkdown = (text) => {
|
|
|
235
248
|
if (!inCode) {
|
|
236
249
|
inCode = true; codeLang = codeMatch[1] || ''; codeLines = [];
|
|
237
250
|
const label = codeLang ? chalk.bgHex(C.border).hex(C.cyan)(` ${codeLang} `) : '';
|
|
238
|
-
output.push(chalk.hex(C.border)('
|
|
251
|
+
output.push(chalk.hex(C.border)(' ' + S.boxTL + S.boxH) + (label || chalk.hex(C.border)(S.boxH)));
|
|
239
252
|
} else {
|
|
240
253
|
const highlighted = highlightCode(codeLines.join('\n'), codeLang);
|
|
241
254
|
for (const cl of highlighted.split('\n'))
|
|
242
|
-
output.push(chalk.hex(C.border)('
|
|
243
|
-
output.push(chalk.hex(C.border)('
|
|
255
|
+
output.push(chalk.hex(C.border)(' ' + S.boxV + ' ') + cl);
|
|
256
|
+
output.push(chalk.hex(C.border)(' ' + S.boxBL + S.boxH.repeat(4)));
|
|
244
257
|
inCode = false; codeLines = []; codeLang = '';
|
|
245
258
|
}
|
|
246
259
|
continue;
|
|
@@ -257,32 +270,32 @@ export const renderMarkdown = (text) => {
|
|
|
257
270
|
if (bigDot) {
|
|
258
271
|
const text = bigDot[1].trim();
|
|
259
272
|
output.push('');
|
|
260
|
-
output.push(chalk.bold.hex(C.bright)('
|
|
273
|
+
output.push(chalk.bold.hex(C.bright)(' ' + S.dot) + (text ? ' ' + chalk.hex(C.bright)(renderInline(text)) : ''));
|
|
261
274
|
continue;
|
|
262
275
|
}
|
|
263
276
|
|
|
264
277
|
// Headings
|
|
265
278
|
const h1 = rawLine.match(/^# ?(.+)/);
|
|
266
|
-
if (h1) { output.push(''); output.push(chalk.bold.hex(C.blue)('
|
|
279
|
+
if (h1) { output.push(''); output.push(chalk.bold.hex(C.blue)(' ' + S.h1 + ' ') + chalk.bold.hex(C.bright)(renderInline(h1[1]))); output.push(chalk.hex(C.border)(' ' + S.hr.repeat(Math.min(w - 4, 58)))); continue; }
|
|
267
280
|
const h2 = rawLine.match(/^## ?(.+)/);
|
|
268
|
-
if (h2) { output.push(''); output.push(chalk.bold.hex(C.cyan)('
|
|
281
|
+
if (h2) { output.push(''); output.push(chalk.bold.hex(C.cyan)(' ' + S.h2 + ' ') + chalk.bold.hex(C.text)(renderInline(h2[1]))); continue; }
|
|
269
282
|
const h3 = rawLine.match(/^### ?(.+)/);
|
|
270
|
-
if (h3) { output.push(''); output.push(chalk.bold.hex(C.teal)('
|
|
283
|
+
if (h3) { output.push(''); output.push(chalk.bold.hex(C.teal)(' ' + S.h3 + ' ') + chalk.bold.hex(C.soft)(renderInline(h3[1]))); continue; }
|
|
271
284
|
const h4 = rawLine.match(/^#### ?(.+)/);
|
|
272
|
-
if (h4) { output.push(chalk.hex(C.subtle)('
|
|
285
|
+
if (h4) { output.push(chalk.hex(C.subtle)(' ' + S.h4 + ' ') + chalk.italic.hex(C.soft)(renderInline(h4[1]))); continue; }
|
|
273
286
|
|
|
274
287
|
// HR
|
|
275
|
-
if (rawLine.match(/^[-*_]{3,}\s*$/)) { output.push(chalk.hex(C.border)(' ' +
|
|
288
|
+
if (rawLine.match(/^[-*_]{3,}\s*$/)) { output.push(chalk.hex(C.border)(' ' + S.hr.repeat(Math.min(w - 4, 60)))); continue; }
|
|
276
289
|
|
|
277
290
|
// Blockquote
|
|
278
291
|
const bq = rawLine.match(/^>\s*(.*)/);
|
|
279
|
-
if (bq) { output.push(chalk.hex(C.border)('
|
|
292
|
+
if (bq) { output.push(chalk.hex(C.border)(' ' + S.bq + ' ') + chalk.italic.hex(C.soft)(renderInline(bq[1]))); continue; }
|
|
280
293
|
|
|
281
294
|
// Lists
|
|
282
295
|
const num = rawLine.match(/^(\s*)(\d+)\.\s+(.*)/);
|
|
283
296
|
if (num) { const [, ind, n, c] = num; const lv = Math.floor(ind.length / 2); output.push(' '.repeat(lv + 1) + chalk.hex(C.blue)(n + '.') + ' ' + renderInline(c)); continue; }
|
|
284
297
|
const ul = rawLine.match(/^(\s*)[-*•]\s+(.*)/);
|
|
285
|
-
if (ul) { const [, ind, c] = ul; const lv = Math.floor(ind.length / 2); const b = lv === 0 ? chalk.hex(C.emerald)(
|
|
298
|
+
if (ul) { const [, ind, c] = ul; const lv = Math.floor(ind.length / 2); const b = lv === 0 ? chalk.hex(C.emerald)(S.ul0) : lv === 1 ? chalk.hex(C.cyan)(S.ul1) : chalk.hex(C.subtle)(S.ul2); output.push(' '.repeat(lv + 1) + b + ' ' + renderInline(c)); continue; }
|
|
286
299
|
|
|
287
300
|
// Empty line
|
|
288
301
|
if (!rawLine.trim()) { output.push(''); continue; }
|
|
@@ -293,8 +306,8 @@ export const renderMarkdown = (text) => {
|
|
|
293
306
|
|
|
294
307
|
if (inTable) flushTable();
|
|
295
308
|
if (inCode && codeLines.length > 0) {
|
|
296
|
-
for (const cl of codeLines) output.push(chalk.hex(C.border)('
|
|
297
|
-
output.push(chalk.hex(C.border)('
|
|
309
|
+
for (const cl of codeLines) output.push(chalk.hex(C.border)(' ' + S.boxV + ' ') + chalk.hex(C.text)(cl));
|
|
310
|
+
output.push(chalk.hex(C.border)(' ' + S.boxBL + S.boxH.repeat(4)));
|
|
298
311
|
}
|
|
299
312
|
|
|
300
313
|
return output.join('\n');
|
|
@@ -425,12 +438,12 @@ export class StreamMarkdown {
|
|
|
425
438
|
if (!this._inCode) {
|
|
426
439
|
this._inCode = true; this._codeLang = codeMatch[1] || ''; this._codeLines = [];
|
|
427
440
|
const label = this._codeLang ? chalk.bgHex(C.border).hex(C.cyan)(` ${this._codeLang} `) : '';
|
|
428
|
-
return chalk.hex(C.border)('
|
|
441
|
+
return chalk.hex(C.border)(' ' + S.boxTL + S.boxH) + (label || chalk.hex(C.border)(S.boxH)) + '\n';
|
|
429
442
|
} else {
|
|
430
443
|
const highlighted = highlightCode(this._codeLines.join('\n'), this._codeLang);
|
|
431
444
|
let out = '';
|
|
432
|
-
for (const cl of highlighted.split('\n')) out += chalk.hex(C.border)('
|
|
433
|
-
out += chalk.hex(C.border)('
|
|
445
|
+
for (const cl of highlighted.split('\n')) out += chalk.hex(C.border)(' ' + S.boxV + ' ') + cl + '\n';
|
|
446
|
+
out += chalk.hex(C.border)(' ' + S.boxBL + S.boxH.repeat(4)) + '\n';
|
|
434
447
|
this._inCode = false; this._codeLines = []; this._codeLang = '';
|
|
435
448
|
return out;
|
|
436
449
|
}
|
|
@@ -465,26 +478,26 @@ export class StreamMarkdown {
|
|
|
465
478
|
const bigDot = line.match(/(?:^\s*-{3,}\s*#{3}|^__BIGDOT__)\s*(.*)/);
|
|
466
479
|
if (bigDot) {
|
|
467
480
|
const text = bigDot[1].trim();
|
|
468
|
-
return '\n' + chalk.bold.hex(C.bright)('
|
|
481
|
+
return '\n' + chalk.bold.hex(C.bright)(' ' + S.dot) + (text ? ' ' + chalk.hex(C.bright)(renderInline(text)) : '') + '\n';
|
|
469
482
|
}
|
|
470
483
|
|
|
471
484
|
// Headings — support both "## Title" and "##Title" (no space)
|
|
472
|
-
const h1 = line.match(/^# ?(.+)/); if (h1) return '\n' + chalk.bold.hex(C.blue)('
|
|
473
|
-
const h2 = line.match(/^## ?(.+)/); if (h2) return '\n' + chalk.bold.hex(C.cyan)('
|
|
474
|
-
const h3 = line.match(/^### ?(.+)/); if (h3) return '\n' + chalk.bold.hex(C.teal)('
|
|
475
|
-
const h4 = line.match(/^#### ?(.+)/); if (h4) return chalk.hex(C.subtle)('
|
|
485
|
+
const h1 = line.match(/^# ?(.+)/); if (h1) return '\n' + chalk.bold.hex(C.blue)(' ' + S.h1 + ' ') + chalk.bold.hex(C.bright)(renderInline(h1[1])) + '\n' + chalk.hex(C.border)(' ' + S.hr.repeat(56));
|
|
486
|
+
const h2 = line.match(/^## ?(.+)/); if (h2) return '\n' + chalk.bold.hex(C.cyan)(' ' + S.h2 + ' ') + chalk.bold(renderInline(h2[1]));
|
|
487
|
+
const h3 = line.match(/^### ?(.+)/); if (h3) return '\n' + chalk.bold.hex(C.teal)(' ' + S.h3 + ' ') + chalk.hex(C.soft)(renderInline(h3[1]));
|
|
488
|
+
const h4 = line.match(/^#### ?(.+)/); if (h4) return chalk.hex(C.subtle)(' ' + S.h4 + ' ') + chalk.italic.hex(C.soft)(renderInline(h4[1]));
|
|
476
489
|
|
|
477
490
|
// HR
|
|
478
|
-
if (line.match(/^[-*_]{3,}\s*$/)) return chalk.hex(C.border)(' ' +
|
|
491
|
+
if (line.match(/^[-*_]{3,}\s*$/)) return chalk.hex(C.border)(' ' + S.hr.repeat(56));
|
|
479
492
|
|
|
480
493
|
// Blockquote
|
|
481
|
-
const bq = line.match(/^>\s*(.*)/); if (bq) return chalk.hex(C.border)('
|
|
494
|
+
const bq = line.match(/^>\s*(.*)/); if (bq) return chalk.hex(C.border)(' ' + S.bq + ' ') + chalk.italic.hex(C.soft)(renderInline(bq[1]));
|
|
482
495
|
|
|
483
496
|
// Lists
|
|
484
497
|
const num = line.match(/^(\s*)(\d+)\.\s+(.*)/);
|
|
485
498
|
if (num) { const [, ind, n, c] = num; const lv = Math.floor(ind.length / 2); return ' '.repeat(lv + 1) + chalk.hex(C.blue)(n + '.') + ' ' + renderInline(c); }
|
|
486
499
|
const ul = line.match(/^(\s*)[-*•]\s+(.*)/);
|
|
487
|
-
if (ul) { const [, ind, c] = ul; const lv = Math.floor(ind.length / 2); const b = lv === 0 ? chalk.hex(C.emerald)(
|
|
500
|
+
if (ul) { const [, ind, c] = ul; const lv = Math.floor(ind.length / 2); const b = lv === 0 ? chalk.hex(C.emerald)(S.ul0) : lv === 1 ? chalk.hex(C.cyan)(S.ul1) : chalk.hex(C.subtle)(S.ul2); return ' '.repeat(lv + 1) + b + ' ' + renderInline(c); }
|
|
488
501
|
|
|
489
502
|
return ' ' + renderInline(line);
|
|
490
503
|
}
|
package/src/ui/App.js
CHANGED
|
@@ -70,7 +70,7 @@ function formatDuration(seconds) {
|
|
|
70
70
|
const remainMins = mins % 60;
|
|
71
71
|
return `${hours}h ${remainMins}m ${Math.floor(secs)}s`;
|
|
72
72
|
}
|
|
73
|
-
const defaultRenderThrottle = process.
|
|
73
|
+
const defaultRenderThrottle = process.stdout.isTTY ? '250' : '150';
|
|
74
74
|
const requestedRenderThrottle = Number.parseInt(process.env.OSAI_UI_RENDER_THROTTLE_MS || defaultRenderThrottle, 10);
|
|
75
75
|
const UI_RENDER_THROTTLE_MS = Number.isFinite(requestedRenderThrottle)
|
|
76
76
|
? Math.max(100, requestedRenderThrottle)
|
package/src/ui/animation.js
CHANGED
|
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
|
|
|
2
2
|
|
|
3
3
|
export const ENABLE_UI_ANIMATIONS = process.env.OSAI_UI_ANIMATIONS !== '0';
|
|
4
4
|
|
|
5
|
-
const defaultInterval = process.
|
|
5
|
+
const defaultInterval = process.stdout.isTTY ? '800' : '400';
|
|
6
6
|
const requestedInterval = Number.parseInt(process.env.OSAI_UI_ANIMATION_INTERVAL_MS || defaultInterval, 10);
|
|
7
7
|
const TICK_INTERVAL_MS = Number.isFinite(requestedInterval)
|
|
8
8
|
? Math.max(300, requestedInterval)
|
|
@@ -3,6 +3,7 @@ import { Box, Text } from 'ink';
|
|
|
3
3
|
import { h } from '../h.js';
|
|
4
4
|
import { buildDiff, collapseContext, langFromPath } from '../diff.js';
|
|
5
5
|
import { highlightCode } from '../../parser/markdown.js';
|
|
6
|
+
import { isUnicode, CROSS } from '../../utils/unicode.js';
|
|
6
7
|
|
|
7
8
|
const C = {
|
|
8
9
|
removedBg: '#2d1010',
|
|
@@ -109,7 +110,7 @@ export function EditFileDiff({ filePath, find, replace }) {
|
|
|
109
110
|
|
|
110
111
|
return h(Box, { flexDirection: 'column', marginY: 1 },
|
|
111
112
|
h(Box, { paddingLeft: 2, paddingY: 0 },
|
|
112
|
-
h(Text, { color: C.headerFg, bold: true }, ' ✎ '),
|
|
113
|
+
h(Text, { color: C.headerFg, bold: true }, ' ' + (isUnicode ? '✎' : '[edit]') + ' '),
|
|
113
114
|
h(Text, { color: C.headerFg }, filePath || '')
|
|
114
115
|
),
|
|
115
116
|
h(Box, { flexDirection: 'column', paddingLeft: 2, borderStyle: 'round', borderColor: C.border },
|
|
@@ -191,7 +192,7 @@ export function AppendFileDiff({ filePath, content }) {
|
|
|
191
192
|
export function DeleteFileDiff({ filePath }) {
|
|
192
193
|
return h(Box, { flexDirection: 'column', marginY: 1 },
|
|
193
194
|
h(Box, { paddingLeft: 2, paddingY: 0 },
|
|
194
|
-
h(Text, { color: C.removedLabel, bold: true }, '
|
|
195
|
+
h(Text, { color: C.removedLabel, bold: true }, ' ' + CROSS + ' '),
|
|
195
196
|
h(Text, { color: C.removedLabel }, `${filePath} (deleted)`)
|
|
196
197
|
),
|
|
197
198
|
h(Box, { paddingLeft: 2, borderStyle: 'round', borderColor: C.border },
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Box, Text } from 'ink';
|
|
2
2
|
import { h } from '../h.js';
|
|
3
|
+
import { isUnicode, DOT, EMPTY, HEADER_LOGO_L, HEADER_LOGO_M, HEADER_LOGO_R, BAR } from '../../utils/unicode.js';
|
|
3
4
|
import { ENABLE_UI_ANIMATIONS, useAnimationFrame } from '../animation.js';
|
|
4
5
|
|
|
5
6
|
const chunkLength = (chunks) => chunks.reduce((sum, chunk) => sum + String(chunk.text || '').length, 0);
|
|
@@ -71,18 +72,18 @@ export function Header({ mode, device, isConnected, isLocal, provider, execution
|
|
|
71
72
|
: null;
|
|
72
73
|
|
|
73
74
|
const statusChunks = isLocal
|
|
74
|
-
? [{ text: '
|
|
75
|
-
: [{ text: isConnected ?
|
|
75
|
+
? [{ text: DOT + ' Local', color: '#73daca', bold: true }]
|
|
76
|
+
: [{ text: (isConnected ? DOT : EMPTY) + ' Connected', color: isConnected ? '#9ece6a' : '#f7768e', bold: true }];
|
|
76
77
|
|
|
77
78
|
const items = [
|
|
78
79
|
makeItem([
|
|
79
|
-
{ text:
|
|
80
|
-
{ text:
|
|
81
|
-
{ text: '
|
|
80
|
+
{ text: HEADER_LOGO_L, color: '#7aa2f7' },
|
|
81
|
+
{ text: HEADER_LOGO_M, color: '#4a9eff', bold: true },
|
|
82
|
+
{ text: HEADER_LOGO_R + ' ', color: '#7aa2f7' },
|
|
82
83
|
{ text: 'OS AI AGENT ', color: '#c0caf5', bold: true },
|
|
83
84
|
]),
|
|
84
85
|
makeItem([
|
|
85
|
-
{ text: '
|
|
86
|
+
{ text: ' ' + BAR + ' ', color: '#3b4261' },
|
|
86
87
|
{ text: 'Provider: ', color: '#565f89' },
|
|
87
88
|
{
|
|
88
89
|
text: isDefault ? (isLocal ? 'not configured ' : 'osai/auto ') : `${providerType}${providerModel ? '/' + providerModel : ''} `,
|
|
@@ -91,31 +92,31 @@ export function Header({ mode, device, isConnected, isLocal, provider, execution
|
|
|
91
92
|
},
|
|
92
93
|
]),
|
|
93
94
|
makeItem([
|
|
94
|
-
{ text: '
|
|
95
|
+
{ text: ' ' + BAR + ' ', color: '#3b4261' },
|
|
95
96
|
{ text: 'Mode: ', color: '#565f89' },
|
|
96
97
|
{ text: `${mode}/${executionMode || 'EXEC'} `, color: '#c0caf5' },
|
|
97
98
|
]),
|
|
98
99
|
makeItem([
|
|
99
|
-
{ text: '
|
|
100
|
+
{ text: ' ' + BAR + ' ', color: '#3b4261' },
|
|
100
101
|
{ text: 'Device: ', color: '#565f89' },
|
|
101
102
|
{ text: `${device || 'local'} `, color: '#c0caf5' },
|
|
102
103
|
]),
|
|
103
104
|
makeItem([
|
|
104
|
-
{ text: '
|
|
105
|
+
{ text: ' ' + BAR + ' ', color: '#3b4261' },
|
|
105
106
|
...statusChunks,
|
|
106
107
|
]),
|
|
107
108
|
];
|
|
108
109
|
|
|
109
110
|
if (subagentActive) {
|
|
110
111
|
items.push(makeItem([
|
|
111
|
-
{ text: '
|
|
112
|
-
{ text: '
|
|
112
|
+
{ text: ' ' + BAR + ' ', color: '#3b4261' },
|
|
113
|
+
{ text: DOT + ' Subagent', color: subagentDim ? '#1a5fad' : '#4a9eff', bold: !subagentDim },
|
|
113
114
|
]));
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
if (tokenLabel) {
|
|
117
118
|
items.push(makeItem([
|
|
118
|
-
{ text: '
|
|
119
|
+
{ text: ' ' + BAR + ' ', color: '#3b4261' },
|
|
119
120
|
{ text: tokenLabel, color: '#e0af68' },
|
|
120
121
|
]));
|
|
121
122
|
}
|
|
@@ -6,7 +6,7 @@ import { highlightCode } from '../../parser/markdown.js';
|
|
|
6
6
|
import { EditFileDiff, NewFileDiff, AppendFileDiff, DeleteFileDiff } from './DiffView.js';
|
|
7
7
|
import { ENABLE_UI_ANIMATIONS, useAnimationFrame } from '../animation.js';
|
|
8
8
|
import { SubagentPanel } from './SubagentPanel.js';
|
|
9
|
-
|
|
9
|
+
import { isUnicode, TICK, DOT, EMPTY, ARROW, SPINNER_BRAILLE } from '../../utils/unicode.js';
|
|
10
10
|
const stripAnsi = (s) => (s || '').replace(/\x1b\[[0-9;]*m/g, '').replace(/\x1b\]8;;[^\x1b]*\x1b\\/g, '');
|
|
11
11
|
const visibleLen = (s) => stripAnsi(s).length;
|
|
12
12
|
|
|
@@ -26,12 +26,12 @@ const renderCellInline = (text) => {
|
|
|
26
26
|
);
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
const SPINNER_FRAMES =
|
|
29
|
+
const SPINNER_FRAMES = SPINNER_BRAILLE;
|
|
30
30
|
const THINKING_DOTS = ['', '.', '..', '...'];
|
|
31
|
-
const WRITING_DOTS = ['', '·', '··', '···'];
|
|
31
|
+
const WRITING_DOTS = !isUnicode ? ['', '.', '..', '...'] : ['', '·', '··', '···'];
|
|
32
32
|
|
|
33
33
|
const WRITE_TOOLS = new Set(['WRITE_FILE', 'EDIT_FILE', 'APPEND_FILE', 'DELETE_FILE', 'MOVE_FILE', 'COPY_FILE', 'CREATE_DIR']);
|
|
34
|
-
const READING_DOTS = ['', '∘', '∘∘', '∘∘∘'];
|
|
34
|
+
const READING_DOTS = !isUnicode ? ['', '.', '..', '...'] : ['', '∘', '∘∘', '∘∘∘'];
|
|
35
35
|
const READ_TOOLS = new Set(['READ_FILE', 'LIST_DIR', 'TREE_VIEW', 'FILE_INFO']);
|
|
36
36
|
|
|
37
37
|
function formatDuration(seconds) {
|
|
@@ -837,7 +837,7 @@ const TextContent = React.memo(({ content, events, animate = true }) => {
|
|
|
837
837
|
return h(Box, { key: i, paddingLeft: 2 },
|
|
838
838
|
part.level === 1 ? h(Text, { color: '#7aa2f7', bold: true }, `# ${part.content}`) :
|
|
839
839
|
part.level === 2 ? h(Text, { color: '#7dcfff', bold: true }, `## ${part.content}`) :
|
|
840
|
-
part.level === 3 ? h(Text, { color: '#ffffff', bold: true },
|
|
840
|
+
part.level === 3 ? h(Text, { color: '#ffffff', bold: true }, DOT + ' ' + part.content) :
|
|
841
841
|
h(Text, { color: '#9aa5ce', bold: true }, `#### ${part.content}`)
|
|
842
842
|
);
|
|
843
843
|
case 'hr':
|
|
@@ -906,13 +906,13 @@ const CollapsibleThought = React.memo(function CollapsibleThought({ id, content,
|
|
|
906
906
|
if (streaming) {
|
|
907
907
|
const dots = animate && ENABLE_UI_ANIMATIONS ? THINKING_DOTS[frame % THINKING_DOTS.length].padEnd(3, ' ') : '...';
|
|
908
908
|
return h(Box, { key: `thought_${id}`, paddingLeft: 2, paddingY: 0 },
|
|
909
|
-
h(Text, { color: '#565f89', bold: true }, '
|
|
909
|
+
h(Text, { color: '#565f89', bold: true }, ARROW + ' Thinking'),
|
|
910
910
|
h(Text, { color: '#565f89' }, dots),
|
|
911
911
|
h(Text, { color: '#2a2e3f' }, ' (Ctrl+O to expand)')
|
|
912
912
|
);
|
|
913
913
|
}
|
|
914
914
|
return h(Box, { key: `thought_${id}`, paddingLeft: 2, paddingY: 0 },
|
|
915
|
-
h(Text, { color: '#565f89' }, '
|
|
915
|
+
h(Text, { color: '#565f89' }, ARROW + ' Thought'),
|
|
916
916
|
h(Text, { color: '#2a2e3f' }, ' (Ctrl+O to expand)')
|
|
917
917
|
);
|
|
918
918
|
});
|
|
@@ -982,7 +982,7 @@ function renderEvent(ev, i, events, expandedOutputIndexes, thoughtStreaming, exp
|
|
|
982
982
|
...items.map((t, j) => {
|
|
983
983
|
const isDone = t.status === 'done' || t.status === 'completed';
|
|
984
984
|
const isProgress = t.status === 'in_progress';
|
|
985
|
-
const icon = isDone ?
|
|
985
|
+
const icon = isDone ? TICK : isProgress ? (isUnicode ? '⟳' : '[.]') : EMPTY;
|
|
986
986
|
const color = isDone ? '#73daca' : isProgress ? '#e0af68' : '#565f89';
|
|
987
987
|
return h(Box, { key: j, paddingLeft: 2 },
|
|
988
988
|
h(Text, { color }, `${icon} `),
|
|
@@ -3,6 +3,7 @@ import { Box, Text, useInput, useWindowSize } from 'ink';
|
|
|
3
3
|
import { h } from '../h.js';
|
|
4
4
|
import { InputShell } from './InputShell.js';
|
|
5
5
|
import { isOnlySgrMouseInput } from '../mouse-scroll.js';
|
|
6
|
+
import { MODE_ICONS, ARROW, DOT, DASH } from '../../utils/unicode.js';
|
|
6
7
|
|
|
7
8
|
const ALL_MODES = [
|
|
8
9
|
{ name: 'GENERAL', desc: 'System administration mode', value: 'GENERAL' },
|
|
@@ -13,15 +14,6 @@ const ALL_MODES = [
|
|
|
13
14
|
{ name: 'EXEC', desc: 'Exec mode — file modifications allowed', value: 'EXEC' },
|
|
14
15
|
];
|
|
15
16
|
|
|
16
|
-
const MODE_ICONS = {
|
|
17
|
-
GENERAL: '⚙',
|
|
18
|
-
CODING: '⟨/⟩',
|
|
19
|
-
NETWORK: '◈',
|
|
20
|
-
SSH: '⇌',
|
|
21
|
-
PLAN: '◷',
|
|
22
|
-
EXEC: '▶',
|
|
23
|
-
};
|
|
24
|
-
|
|
25
17
|
export function ModePicker({ visible, onSelect, onCancel, currentMode, currentExecutionMode, hiddenModes }) {
|
|
26
18
|
const [query, setQuery] = useState('');
|
|
27
19
|
const [cursor, setCursor] = useState(0);
|
|
@@ -100,7 +92,7 @@ export function ModePicker({ visible, onSelect, onCancel, currentMode, currentEx
|
|
|
100
92
|
|
|
101
93
|
if (!visible) return null;
|
|
102
94
|
|
|
103
|
-
const separator =
|
|
95
|
+
const separator = DASH.repeat(48);
|
|
104
96
|
|
|
105
97
|
return h(
|
|
106
98
|
Box,
|
|
@@ -121,8 +113,8 @@ export function ModePicker({ visible, onSelect, onCancel, currentMode, currentEx
|
|
|
121
113
|
...filtered.map((m, i) => {
|
|
122
114
|
const isHighlighted = i === cursor;
|
|
123
115
|
const isCurrent = m.value === currentMode || m.value === currentExecutionMode;
|
|
124
|
-
const icon = MODE_ICONS[m.value] ||
|
|
125
|
-
const prefix = isHighlighted ? h(Text, { color: '#9ece6a' }, '
|
|
116
|
+
const icon = MODE_ICONS[m.value] || DOT;
|
|
117
|
+
const prefix = isHighlighted ? h(Text, { color: '#9ece6a' }, ' ' + ARROW + ' ') : h(Text, { color: '#3b3f52' }, ' ');
|
|
126
118
|
const currentMark = isCurrent ? ' ◀ current' : '';
|
|
127
119
|
|
|
128
120
|
return h(
|
|
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useMemo } from 'react';
|
|
|
2
2
|
import { Box, Text, useInput } from 'ink';
|
|
3
3
|
import { h } from '../h.js';
|
|
4
4
|
import { isOnlySgrMouseInput } from '../mouse-scroll.js';
|
|
5
|
+
import { isUnicode, ARROW, CROSS, DASH } from '../../utils/unicode.js';
|
|
5
6
|
|
|
6
7
|
const OPTIONS = [
|
|
7
8
|
{ name: 'Local', desc: 'Save to local storage', value: 'local' },
|
|
@@ -68,7 +69,7 @@ export function SavePicker({ visible, onSelect, onCancel, hasCloud }) {
|
|
|
68
69
|
|
|
69
70
|
if (!visible) return null;
|
|
70
71
|
|
|
71
|
-
const separator =
|
|
72
|
+
const separator = DASH.repeat(44);
|
|
72
73
|
|
|
73
74
|
return h(
|
|
74
75
|
Box,
|
|
@@ -80,8 +81,8 @@ export function SavePicker({ visible, onSelect, onCancel, hasCloud }) {
|
|
|
80
81
|
// Items
|
|
81
82
|
...filtered.map((o, i) => {
|
|
82
83
|
const isHL = i === cursor;
|
|
83
|
-
const prefix = isHL ? h(Text, { color: '#9ece6a' }, '
|
|
84
|
-
const icon = o.value === 'local' ? ' ' : o.value === 'cloud' ? '☁ ' : '
|
|
84
|
+
const prefix = isHL ? h(Text, { color: '#9ece6a' }, ' ' + ARROW + ' ') : h(Text, { color: '#3b3f52' }, ' ');
|
|
85
|
+
const icon = o.value === 'local' ? ' ' : o.value === 'cloud' ? (isUnicode ? '☁ ' : '[c] ') : CROSS + ' ';
|
|
85
86
|
|
|
86
87
|
return h(Box, { key: i },
|
|
87
88
|
prefix,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Box, Text } from 'ink';
|
|
2
2
|
import { h } from '../h.js';
|
|
3
3
|
import { ENABLE_UI_ANIMATIONS, useAnimationFrame } from '../animation.js';
|
|
4
|
+
import { TICK, CROSS, SPINNER_CIRCLE, DOT } from '../../utils/unicode.js';
|
|
4
5
|
|
|
5
|
-
const SPINNER =
|
|
6
|
+
const SPINNER = SPINNER_CIRCLE;
|
|
6
7
|
|
|
7
8
|
function formatDuration(ms) {
|
|
8
9
|
if (!ms || ms < 0) return '0s';
|
|
@@ -59,8 +60,8 @@ export function SubagentPanel({ state, events, isExpanded }) {
|
|
|
59
60
|
|
|
60
61
|
const isRunning = state.status === 'running';
|
|
61
62
|
const isFailed = state.status === 'failed';
|
|
62
|
-
const spinner = ENABLE_UI_ANIMATIONS && isRunning ? SPINNER[frame % SPINNER.length] :
|
|
63
|
-
const icon = isRunning ? spinner : isFailed ?
|
|
63
|
+
const spinner = ENABLE_UI_ANIMATIONS && isRunning ? SPINNER[frame % SPINNER.length] : DOT;
|
|
64
|
+
const icon = isRunning ? spinner : isFailed ? CROSS : TICK;
|
|
64
65
|
const iconColor = isRunning ? '#7dcfff' : isFailed ? '#f7768e' : '#9ece6a';
|
|
65
66
|
const elapsed = state.elapsed || (state.startedAt ? Date.now() - state.startedAt : 0);
|
|
66
67
|
const desc = truncate(state.description || 'Exploration task', 50);
|
|
@@ -106,7 +107,7 @@ export function SubagentPanel({ state, events, isExpanded }) {
|
|
|
106
107
|
h(Text, { color: '#7aa2f7' }, ' ├─ '),
|
|
107
108
|
h(Text, { color: getToolColor(ev.name), bold: true }, ev.name),
|
|
108
109
|
formatTarget(ev) ? h(Text, { color: '#565f89' }, `: ${truncate(formatTarget(ev), 80)}`) : null,
|
|
109
|
-
h(Text, { color: '#565f89' }, '
|
|
110
|
+
h(Text, { color: '#565f89' }, ' ' + DOT),
|
|
110
111
|
);
|
|
111
112
|
}
|
|
112
113
|
if (ev.type === 'tool_end') {
|
|
@@ -114,7 +115,7 @@ export function SubagentPanel({ state, events, isExpanded }) {
|
|
|
114
115
|
h(Text, { color: ev.success ? '#9ece6a' : '#f7768e' }, ' ├─ '),
|
|
115
116
|
h(Text, { color: getToolColor(ev.name), bold: true }, ev.name),
|
|
116
117
|
formatTarget(ev) ? h(Text, { color: '#565f89' }, `: ${truncate(formatTarget(ev), 80)}`) : null,
|
|
117
|
-
h(Text, { color: '#565f89' }, ev.success ? '
|
|
118
|
+
h(Text, { color: '#565f89' }, ev.success ? ' ' + TICK : ' ' + CROSS),
|
|
118
119
|
);
|
|
119
120
|
}
|
|
120
121
|
return null;
|
|
@@ -3,8 +3,9 @@ import { Box, Text } from 'ink';
|
|
|
3
3
|
import { h } from '../h.js';
|
|
4
4
|
import { highlightCode } from '../../parser/markdown.js';
|
|
5
5
|
import { ENABLE_UI_ANIMATIONS, useAnimationFrame } from '../animation.js';
|
|
6
|
+
import { SPINNER_BRAILLE, TICK, CROSS } from '../../utils/unicode.js';
|
|
6
7
|
|
|
7
|
-
const SPINNER_FRAMES =
|
|
8
|
+
const SPINNER_FRAMES = SPINNER_BRAILLE;
|
|
8
9
|
|
|
9
10
|
const TOOL_COLORS = {
|
|
10
11
|
READ_FILE: '#9ece6a',
|
package/src/ui/terminal.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import boxen from 'boxen';
|
|
3
|
+
import { TICK } from '../utils/unicode.js';
|
|
3
4
|
|
|
4
5
|
export const printInfo = (msg) => console.log(chalk.blue('i ') + msg);
|
|
5
6
|
export const printError = (msg) => console.error(chalk.red('x ') + msg);
|
|
6
|
-
export const printSuccess = (msg) => console.log(chalk.green('
|
|
7
|
+
export const printSuccess = (msg) => console.log(chalk.green(TICK + ' ') + msg);
|
|
7
8
|
export const printHeader = () => {};
|
|
8
9
|
export const printAuthHeader = (title, subtitle = '') => {
|
|
9
10
|
console.log(boxen(
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import isUnicodeSupported from 'is-unicode-supported';
|
|
2
|
+
export const isUnicode = isUnicodeSupported();
|
|
3
|
+
export const TICK = isUnicode ? '✓' : 'v';
|
|
4
|
+
export const CROSS = isUnicode ? '✗' : 'x';
|
|
5
|
+
export const DOT = isUnicode ? '●' : 'o';
|
|
6
|
+
export const EMPTY = isUnicode ? '○' : 'o';
|
|
7
|
+
export const ARROW = isUnicode ? '▸' : '>';
|
|
8
|
+
export const DASH = isUnicode ? '─' : '-';
|
|
9
|
+
export const BAR = isUnicode ? '│' : '|';
|
|
10
|
+
export const BULLET = isUnicode ? '•' : '*';
|
|
11
|
+
export const ELLIPSIS = '…';
|
|
12
|
+
export const SPINNER_BRAILLE = isUnicode
|
|
13
|
+
? ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
|
14
|
+
: ['|', '/', '-', '\\'];
|
|
15
|
+
export const SPINNER_CIRCLE = isUnicode
|
|
16
|
+
? ['◐', '◓', '◑', '◒']
|
|
17
|
+
: ['[', '-', 'o', ')'];
|
|
18
|
+
export const SPINNER_DOTS = isUnicode
|
|
19
|
+
? ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
|
20
|
+
: ['.', '..', '...', ''];
|
|
21
|
+
export const BOX_TL = isUnicode ? '╭' : '+';
|
|
22
|
+
export const BOX_TR = isUnicode ? '╮' : '+';
|
|
23
|
+
export const BOX_BL = isUnicode ? '╰' : '+';
|
|
24
|
+
export const BOX_BR = isUnicode ? '╯' : '+';
|
|
25
|
+
export const BOX_H = isUnicode ? '─' : '-';
|
|
26
|
+
export const BOX_V = isUnicode ? '│' : '|';
|
|
27
|
+
export const H1_LINE = isUnicode ? '━━' : '==';
|
|
28
|
+
export const H2_BULLET = isUnicode ? '◆' : '>';
|
|
29
|
+
export const H3_BULLET = isUnicode ? '●' : 'o';
|
|
30
|
+
export const H4_ARROW = isUnicode ? '›' : '>';
|
|
31
|
+
export const BQ_LINE = isUnicode ? '┃' : '|';
|
|
32
|
+
export const HR_LINE = isUnicode ? '─' : '-';
|
|
33
|
+
export const UL_BULLET = (level) => {
|
|
34
|
+
if (!isUnicode) return '*';
|
|
35
|
+
return level === 0 ? '•' : level === 1 ? '◦' : '▸';
|
|
36
|
+
};
|
|
37
|
+
export const MODE_ICONS = isUnicode
|
|
38
|
+
? { GENERAL: '⚙', CODING: '⟨/⟩', NETWORK: '◈', SSH: '⇌', PLAN: '◷', EXEC: '▶' }
|
|
39
|
+
: { GENERAL: '[.]', CODING: '</>', NETWORK: '(#)', SSH: '<->', PLAN: '(o)', EXEC: '>' };
|
|
40
|
+
export const HEADER_LOGO_L = isUnicode ? '▐' : '[';
|
|
41
|
+
export const HEADER_LOGO_M = isUnicode ? '△' : '^';
|
|
42
|
+
export const HEADER_LOGO_R = isUnicode ? '▌' : ']';
|