osai-agent 4.2.42 → 4.2.44

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osai-agent",
3
- "version": "4.2.42",
3
+ "version": "4.2.44",
4
4
  "type": "module",
5
5
  "description": "OS AI Agent - YOUR AI AGENT",
6
6
  "main": "src/index.js",
@@ -386,7 +386,7 @@ export default {
386
386
  const header = `Todos for ${scopeLine}`;
387
387
  return {
388
388
  success: true,
389
- output: `${header}\n${''.repeat(header.length)}\n${items.length === 0 ? 'No todos.' : items.map(t => `[${t.status === 'done' ? 'x' : ' '}] #${t.id} [${t.priority}] ${t.text}`).join('\n')}`,
389
+ output: `${header}\n${'-'.repeat(header.length)}\n${items.length === 0 ? 'No todos.' : items.map(t => `[${t.status === 'done' ? 'x' : ' '}] #${t.id} [${t.priority}] ${t.text}`).join('\n')}`,
390
390
  todos: items,
391
391
  stats: todoStore.getStats(toolCall.mode || this.mode),
392
392
  };
@@ -3,7 +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, WARN } from '../utils/unicode.js';
6
+ import { isUnicode, DASH, WARN, ARROW_LEFT } from '../utils/unicode.js';
7
7
  import { toHttpUrl } from '../services/server-url.js';
8
8
  import { encrypt, decrypt, deriveKey } from '../services/crypto.js';
9
9
  import pkg from 'node-machine-id';
@@ -113,7 +113,7 @@ export const listProviders = async () => {
113
113
  console.log(chalk.hex('#565f89')(' ' + DASH.repeat(48)));
114
114
 
115
115
  for (const p of catalog) {
116
- const activeMark = p.active ? chalk.green(' ' + (isUnicode ? '◀' : '<')) : '';
116
+ const activeMark = p.active ? chalk.green(' ' + ARROW_LEFT) : '';
117
117
  const freeMark = p.free_tier ? chalk.green('Yes') : chalk.red('No ');
118
118
  const name = p.id === active?.id ? chalk.white.bold(p.name) : chalk.white(p.name);
119
119
  const sdk = p.sdk_type === 'anthropic' ? 'native ' : 'OpenAI ';
@@ -395,7 +395,7 @@ export const showProvider = async () => {
395
395
  const all = getAllLocalProviders();
396
396
  console.log();
397
397
  console.log(chalk.hex('#7aa2f7').bold(' Local Provider Configuration'));
398
- console.log(chalk.hex('#565f89')(' '.repeat(40)));
398
+ console.log(chalk.hex('#565f89')(' ' + DASH.repeat(40)));
399
399
  if (all.length === 0) {
400
400
  console.log(` ${chalk.gray('No providers configured.')}`);
401
401
  console.log(` ${chalk.gray(' Use: osai-agent provider set <type> --key <key> --local')}`);
@@ -424,7 +424,7 @@ export const showProvider = async () => {
424
424
 
425
425
  console.log();
426
426
  console.log(chalk.hex('#7aa2f7').bold(' Provider Configuration'));
427
- console.log(chalk.hex('#565f89')(' '.repeat(32)));
427
+ console.log(chalk.hex('#565f89')(' ' + DASH.repeat(32)));
428
428
 
429
429
  if (data.type === 'osai') {
430
430
  console.log(` ${chalk.white('Current provider :')} ${chalk.green('OS AI Agent')}`);
@@ -1,7 +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
+ import { DOT, EMPTY, TICK } from '../utils/unicode.js';
5
5
  import { showConfig } from './config.js';
6
6
  import { clearScreen, printError, printPanel } from '../ui/terminal.js';
7
7
  import { logger } from '../utils/logger.js';
@@ -50,7 +50,7 @@ export const stopSubagent = async ({ server }) => {
50
50
  }
51
51
  const result = await response.json();
52
52
  spinner.succeeded(result.message || 'Subagent stop requested');
53
- console.log(chalk.green('✓ Subagent stop command sent successfully'));
53
+ console.log(chalk.green(`${TICK} Subagent stop command sent successfully`));
54
54
  } catch (error) {
55
55
  spinner.fail(`Failed to stop subagent: ${error.message}`);
56
56
  console.error(chalk.red(`Error: ${error.message}`));
@@ -8,7 +8,7 @@ import os from 'os';
8
8
  import { DEFAULTS, TOOLS } from '../utils/constants.js';
9
9
  import { logger } from '../utils/logger.js';
10
10
  import { searchDDG, searchSerpAPI, searchTavily, searchDDGHttp, formatSearchOutput } from './search-providers.js';
11
- import { TREE_BRANCH, TREE_LAST, TREE_VLINE, TREE_SPACE } from '../utils/unicode.js';
11
+ import { TREE_BRANCH, TREE_LAST, TREE_VLINE, TREE_SPACE, TICK } from '../utils/unicode.js';
12
12
  import { extractExports, extractLocalImports } from '../parser/dependencies.js';
13
13
 
14
14
  const execAsync = promisify(exec);
@@ -1274,7 +1274,7 @@ export const diagPostEdit = async (targetPath, lang = 'auto') => {
1274
1274
  const output = [stdout, stderr].filter(Boolean).join('\n').trim();
1275
1275
 
1276
1276
  if (!output || output.includes('Found 0 errors')) {
1277
- return { success: true, output: `[DIAG:${detected.toUpperCase()}] No errors detected.` };
1277
+ return { success: true, output: `[DIAG:${detected.toUpperCase()}] ${TICK} No errors detected.` };
1278
1278
  }
1279
1279
 
1280
1280
  const lines = output.split('\n').slice(0, 30);
@@ -3,7 +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
+ import { isUnicode, CROSS, ELLIPSIS } from '../../utils/unicode.js';
7
7
 
8
8
  const C = {
9
9
  removedBg: '#2d1010',
@@ -141,7 +141,7 @@ export function NewFileDiff({ filePath, content }) {
141
141
  ),
142
142
  h(Box, { flexDirection: 'column', paddingLeft: 2, borderStyle: 'round', borderColor: C.border },
143
143
  ...Array.from({ length: shown }, (_, i) => {
144
- const raw = lines[i].length > innerW ? lines[i].slice(0, innerW - 1) + '…' : lines[i];
144
+ const raw = lines[i].length > innerW ? lines[i].slice(0, innerW - 1) + ELLIPSIS : lines[i];
145
145
  return h(Box, { key: i, paddingLeft: 1 },
146
146
  h(Text, { color: C.lineNumFg }, `${String(i + 1).padStart(lnW)} `),
147
147
  h(Box, { backgroundColor: C.addedBg },
@@ -173,7 +173,7 @@ export function AppendFileDiff({ filePath, content }) {
173
173
  ),
174
174
  h(Box, { flexDirection: 'column', paddingLeft: 2, borderStyle: 'round', borderColor: C.border },
175
175
  ...shown.map((line, i) => {
176
- const raw = line.length > innerW ? line.slice(0, innerW - 1) + '…' : line;
176
+ const raw = line.length > innerW ? line.slice(0, innerW - 1) + ELLIPSIS : line;
177
177
  return h(Box, { key: i, paddingLeft: 1 },
178
178
  h(Text, { color: C.lineNumFg }, `${String(i + 1).padStart(lnW)} `),
179
179
  h(Box, { backgroundColor: C.addedBg },
@@ -1,6 +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
+ import { isUnicode, DOT, EMPTY, HEADER_LOGO_L, HEADER_LOGO_M, HEADER_LOGO_R, BAR, BOX_TL, BOX_TR, BOX_BL, BOX_BR, BOX_H, ELLIPSIS } from '../../utils/unicode.js';
4
4
  import { ENABLE_UI_ANIMATIONS, useAnimationFrame } from '../animation.js';
5
5
 
6
6
  const chunkLength = (chunks) => chunks.reduce((sum, chunk) => sum + String(chunk.text || '').length, 0);
@@ -9,7 +9,7 @@ const makeItem = (chunks) => ({ chunks, width: chunkLength(chunks) });
9
9
 
10
10
  function truncateChunks(chunks, maxWidth) {
11
11
  if (chunkLength(chunks) <= maxWidth) return chunks;
12
- if (maxWidth <= 1) return [{ text: '…', color: chunks[0]?.color || 'white' }];
12
+ if (maxWidth <= 1) return [{ text: ELLIPSIS, color: chunks[0]?.color || 'white' }];
13
13
 
14
14
  const out = [];
15
15
  let remaining = maxWidth;
@@ -21,7 +21,7 @@ function truncateChunks(chunks, maxWidth) {
21
21
  remaining -= text.length;
22
22
  continue;
23
23
  }
24
- out.push({ ...chunk, text: text.slice(0, Math.max(0, remaining - 1)) + '…' });
24
+ out.push({ ...chunk, text: text.slice(0, Math.max(0, remaining - 1)) + ELLIPSIS });
25
25
  remaining = 0;
26
26
  }
27
27
  return out;
@@ -128,7 +128,7 @@ export function Header({ mode, device, isConnected, isLocal, provider, execution
128
128
  const padding = ' '.repeat(Math.max(0, contentWidth - lineWidth));
129
129
 
130
130
  return h(Box, { key: `header_line_${lineIndex}`, flexDirection: 'row', width: '100%' },
131
- h(Text, { color: 'white' }, '│'),
131
+ h(Text, { color: 'white' }, BAR),
132
132
  lineIndex > 0 ? h(Text, { color: '#3b4261' }, ' ') : null,
133
133
  line.map((item, itemIndex) =>
134
134
  h(Text, { key: `header_item_${lineIndex}_${itemIndex}` },
@@ -142,7 +142,7 @@ export function Header({ mode, device, isConnected, isLocal, provider, execution
142
142
  )
143
143
  ),
144
144
  h(Text, {}, padding),
145
- h(Text, { color: 'white' }, '│')
145
+ h(Text, { color: 'white' }, BAR)
146
146
  );
147
147
  };
148
148
 
@@ -151,8 +151,8 @@ export function Header({ mode, device, isConnected, isLocal, provider, execution
151
151
  width: '100%',
152
152
  flexDirection: 'column',
153
153
  },
154
- h(Text, { color: 'white' }, `┌${'─'.repeat(contentWidth)}┐`),
154
+ h(Text, { color: 'white' }, `${BOX_TL}${BOX_H.repeat(contentWidth)}${BOX_TR}`),
155
155
  lines.map(renderLine),
156
- h(Text, { color: 'white' }, `└${'─'.repeat(contentWidth)}┘`)
156
+ h(Text, { color: 'white' }, `${BOX_BL}${BOX_H.repeat(contentWidth)}${BOX_BR}`)
157
157
  );
158
158
  }
@@ -3,7 +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 { ARROW, CLOUD } from '../../utils/unicode.js';
6
+ import { ARROW, CLOUD, DASH } from '../../utils/unicode.js';
7
7
 
8
8
  export function HistoryPicker({ sessions, visible, onSelect, onCancel }) {
9
9
  const [cursor, setCursor] = useState(0);
@@ -80,7 +80,7 @@ export function HistoryPicker({ sessions, visible, onSelect, onCancel }) {
80
80
  }
81
81
  const visibleItems = filtered.slice(startIdx, startIdx + maxVis);
82
82
 
83
- const separator = '─'.repeat(52);
83
+ const separator = DASH.repeat(52);
84
84
 
85
85
  return h(
86
86
  Box,
@@ -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
- import { isUnicode, TICK, DOT, EMPTY, ARROW, SPINNER_BRAILLE } from '../../utils/unicode.js';
9
+ import { isUnicode, TICK, CROSS, DOT, EMPTY, ARROW, SPINNER_BRAILLE, BULLET, ELLIPSIS, BAR, DASH, BQ_LINE, LOADING } 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
 
@@ -342,8 +342,8 @@ const ToolSpinner = React.memo(({ name, toolCall, done, animate = true }) => {
342
342
  if (writeLabel) {
343
343
  const dots = !done && animate && ENABLE_UI_ANIMATIONS ? WRITING_DOTS[frame % WRITING_DOTS.length] : '';
344
344
  return h(Box, { paddingLeft: 2, paddingY: 0 },
345
- h(Text, { color: done ? '#9ece6a' : '#7aa2f7' }, done ? ' ' : (animate && ENABLE_UI_ANIMATIONS ? ` ${SPINNER_FRAMES[frame % SPINNER_FRAMES.length]} ` : ' ')),
346
- h(Text, { color: '#ffffff' }, ' '),
345
+ h(Text, { color: done ? '#9ece6a' : '#7aa2f7' }, done ? ' ' + TICK + ' ' : (animate && ENABLE_UI_ANIMATIONS ? ` ${SPINNER_FRAMES[frame % SPINNER_FRAMES.length]} ` : ' ' + ELLIPSIS + ' ')),
346
+ h(Text, { color: '#ffffff' }, BULLET + ' '),
347
347
  h(Text, { color, bold: true }, writeLabel),
348
348
  h(Text, { color: '#565f89' }, done ? '' : dots)
349
349
  );
@@ -352,16 +352,16 @@ const ToolSpinner = React.memo(({ name, toolCall, done, animate = true }) => {
352
352
  if (readLabel) {
353
353
  const dots = !done && animate && ENABLE_UI_ANIMATIONS ? READING_DOTS[frame % READING_DOTS.length] : '';
354
354
  return h(Box, { paddingLeft: 2, paddingY: 0 },
355
- h(Text, { color: done ? '#9ece6a' : '#7aa2f7' }, done ? ' ' : (animate && ENABLE_UI_ANIMATIONS ? ` ${SPINNER_FRAMES[frame % SPINNER_FRAMES.length]} ` : ' ')),
356
- h(Text, { color: '#ffffff' }, ' '),
355
+ h(Text, { color: done ? '#9ece6a' : '#7aa2f7' }, done ? ' ' + TICK + ' ' : (animate && ENABLE_UI_ANIMATIONS ? ` ${SPINNER_FRAMES[frame % SPINNER_FRAMES.length]} ` : ' ' + ELLIPSIS + ' ')),
356
+ h(Text, { color: '#ffffff' }, BULLET + ' '),
357
357
  h(Text, { color, bold: true }, readLabel),
358
358
  h(Text, { color: '#ffffff' }, done ? '' : dots)
359
359
  );
360
360
  }
361
361
 
362
362
  return h(Box, { paddingLeft: 2, paddingY: 0 },
363
- h(Text, { color: done ? '#9ece6a' : '#7aa2f7' }, done ? ' ' : (animate && ENABLE_UI_ANIMATIONS ? ` ${SPINNER_FRAMES[frame % SPINNER_FRAMES.length]} ` : ' ')),
364
- h(Text, { color: '#ffffff' }, ' '),
363
+ h(Text, { color: done ? '#9ece6a' : '#7aa2f7' }, done ? ' ' + TICK + ' ' : (animate && ENABLE_UI_ANIMATIONS ? ` ${SPINNER_FRAMES[frame % SPINNER_FRAMES.length]} ` : ' ' + ELLIPSIS + ' ')),
364
+ h(Text, { color: '#ffffff' }, BULLET + ' '),
365
365
  h(Text, { color, bold: true }, name),
366
366
  target.text ? h(Text, { color: '#565f89' }, ': ') : null,
367
367
  target.text
@@ -378,8 +378,8 @@ const ContextSummarySpinner = React.memo(({ summaryEvent, done, animate = true }
378
378
  const keepRecent = Number(summaryEvent?.keepRecent || 0);
379
379
 
380
380
  return h(Box, { paddingLeft: 2, paddingY: 0 },
381
- h(Text, { color: done ? '#9ece6a' : '#7aa2f7', bold: true }, done ? ' ' : (animate && ENABLE_UI_ANIMATIONS ? ` ${SPINNER_FRAMES[frame % SPINNER_FRAMES.length]} ` : ' ')),
382
- h(Text, { color: '#ffffff' }, ' '),
381
+ h(Text, { color: done ? '#9ece6a' : '#7aa2f7', bold: true }, done ? ' ' + TICK + ' ' : (animate && ENABLE_UI_ANIMATIONS ? ` ${SPINNER_FRAMES[frame % SPINNER_FRAMES.length]} ` : ' ' + ELLIPSIS + ' ')),
382
+ h(Text, { color: '#ffffff' }, BULLET + ' '),
383
383
  h(Text, { color: '#7dcfff', bold: true }, 'CONTEXT_SUMMARY'),
384
384
  h(Text, { color: '#565f89' }, done
385
385
  ? `: summarized ${summarizeCount} messages, kept ${keepRecent} recent`
@@ -392,8 +392,8 @@ const ContextSummaryResult = React.memo(({ doneEvent }) => {
392
392
  const summarized = Number(doneEvent?.summarizedMessages || 0);
393
393
  const remaining = Number(doneEvent?.remainingMessages || 0);
394
394
  return h(Box, { paddingLeft: 2, paddingY: 0 },
395
- h(Text, { color: '#9ece6a', bold: true }, ' '),
396
- h(Text, { color: '#ffffff' }, ' '),
395
+ h(Text, { color: '#9ece6a', bold: true }, ' ' + TICK + ' '),
396
+ h(Text, { color: '#ffffff' }, BULLET + ' '),
397
397
  h(Text, { color: '#7dcfff', bold: true }, 'CONTEXT_SUMMARY'),
398
398
  h(Text, { color: '#565f89' }, `: completed (${summarized} summarized, ${remaining} messages now in context)`)
399
399
  );
@@ -422,7 +422,7 @@ const SearchResults = React.memo(({ results }) => {
422
422
 
423
423
  const ToolResultEvent = React.memo(({ name, success, output, toolCall, results, outputIndex, expandedOutputIndexes }) => {
424
424
  const color = getToolColor(name);
425
- const label = success ? '✓' : '✗';
425
+ const label = success ? TICK : CROSS;
426
426
  const labelColor = success ? '#9ece6a' : '#f7768e';
427
427
  const target = formatTarget(toolCall);
428
428
  const writeLabel = WRITE_TOOLS.has(name) ? getWriteActionLabel(name, toolCall, success) : null;
@@ -469,7 +469,7 @@ const ToolResultEvent = React.memo(({ name, success, output, toolCall, results,
469
469
  return h(Box, { flexDirection: 'column', paddingLeft: 2, paddingY: 0 },
470
470
  h(Box,
471
471
  h(Text, { color: labelColor, bold: true }, ` ${label} `),
472
- h(Text, { color: '#ffffff' }, ' '),
472
+ h(Text, { color: '#ffffff' }, BULLET + ' '),
473
473
  writeLabel
474
474
  ? h(Text, { color }, writeLabel)
475
475
  : readLabel
@@ -697,7 +697,7 @@ function renderContent(text) {
697
697
  type: 'list',
698
698
  ordered: false,
699
699
  level: Math.floor((bulletMatch[1] || '').length / 2),
700
- marker: '•',
700
+ marker: BULLET,
701
701
  content: bulletMatch[2] || ''
702
702
  });
703
703
  continue;
@@ -763,7 +763,7 @@ const TableBlock = React.memo(({ rows }) => {
763
763
  const displayLen = visibleLen(value);
764
764
  if (displayLen <= width) return value + ' '.repeat(Math.max(0, width - displayLen));
765
765
  const plain = stripAnsi(value);
766
- const cut = plain.slice(0, Math.max(1, width - 1)) + '…';
766
+ const cut = plain.slice(0, Math.max(1, width - 1)) + ELLIPSIS;
767
767
  return cut + ' '.repeat(Math.max(0, width - cut.length));
768
768
  };
769
769
 
@@ -772,17 +772,17 @@ const TableBlock = React.memo(({ rows }) => {
772
772
  if (row.raw.startsWith('__HIDDEN_')) {
773
773
  const hidden = Number(row.raw.match(/__HIDDEN_(\d+)__/)?.[1] || 0);
774
774
  return h(Text, { key: `tbl_hidden_${idx}`, color: '#565f89', italic: true },
775
- ` ${hidden} ligne(s) cachée(s) pour fluidité du terminal …`);
775
+ ` ${ELLIPSIS} ${hidden} ligne(s) cachée(s) pour fluidité du terminal ${ELLIPSIS}`);
776
776
  }
777
777
  if (isTableSeparator(row.raw)) {
778
- const sep = widths.map(w => '─'.repeat(w)).join('─┼─');
778
+ const sep = widths.map(w => DASH.repeat(w)).join(DASH + '+' + DASH);
779
779
  return h(Text, { key: `tbl_sep_${idx}`, color: '#2a2e3f' }, ` ${sep}`);
780
780
  }
781
781
  const content = row.cells.map((cell, i) => {
782
782
  const rendered = renderCellInline(cell || '');
783
783
  const styled = idx === 0 ? chalk.bold.hex('#7dcfff')(rendered) : chalk.hex('#c0caf5')(rendered);
784
784
  return pad(styled, widths[i]);
785
- }).join(' ');
785
+ }).join(' ' + BAR + ' ');
786
786
  return h(Text, { key: `tbl_row_${idx}`, ansi: true }, ` ${content}`);
787
787
  })
788
788
  );
@@ -795,8 +795,8 @@ const AnimatedToolIndicator = React.memo(({ tool, content, color, events, animat
795
795
  const frame = useAnimationFrame(isAnimating);
796
796
 
797
797
  return h(Box, { paddingLeft: 2 },
798
- h(Text, { color: isAnimating ? '#7aa2f7' : '#9ece6a', bold: true }, isAnimating ? (ENABLE_UI_ANIMATIONS ? ` ${SPINNER_FRAMES[frame % SPINNER_FRAMES.length]} ` : ' ') : ' '),
799
- h(Text, { color: '#ffffff' }, ' '),
798
+ h(Text, { color: isAnimating ? '#7aa2f7' : '#9ece6a', bold: true }, isAnimating ? (ENABLE_UI_ANIMATIONS ? ` ${SPINNER_FRAMES[frame % SPINNER_FRAMES.length]} ` : ' ' + ELLIPSIS + ' ') : ' ' + TICK + ' '),
799
+ h(Text, { color: '#ffffff' }, BULLET + ' '),
800
800
  h(Text, { color, bold: true }, tool),
801
801
  content ? h(Text, { color: '#565f89' }, `: ${content}`) : null
802
802
  );
@@ -841,10 +841,10 @@ const TextContent = React.memo(({ content, events, animate = true }) => {
841
841
  h(Text, { color: '#9aa5ce', bold: true }, `#### ${part.content}`)
842
842
  );
843
843
  case 'hr':
844
- return h(Box, { key: i, paddingLeft: 2 }, h(Text, { color: '#2a2e3f' }, '────────────────────────────────────────'));
844
+ return h(Box, { key: i, paddingLeft: 2 }, h(Text, { color: '#2a2e3f' }, DASH.repeat(40)));
845
845
  case 'quote':
846
846
  return h(Box, { key: i, paddingLeft: 2 },
847
- h(Text, { color: '#565f89' }, ' '),
847
+ h(Text, { color: '#565f89' }, BQ_LINE + ' '),
848
848
  h(InlineText, { text: part.content })
849
849
  );
850
850
  case 'list':
@@ -880,7 +880,7 @@ function hasMatchingContextSummaryEnd(events, startIndex, summaryEvent) {
880
880
 
881
881
  function EventSeparator() {
882
882
  return h(Box, { flexDirection: 'column', paddingY: 0 },
883
- h(Text, { color: '#2a2e3f' }, ' o─────────────────────────────────────────o')
883
+ h(Text, { color: '#2a2e3f' }, ' o' + DASH.repeat(41) + 'o')
884
884
  );
885
885
  }
886
886
 
@@ -982,14 +982,14 @@ 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 ? TICK : isProgress ? (isUnicode ? '⟳' : '[.]') : EMPTY;
985
+ const icon = isDone ? TICK : isProgress ? LOADING : 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} `),
989
989
  h(Text, { color: '#c0caf5' }, t.text || t.description || ''),
990
990
  );
991
991
  }),
992
- more > 0 ? h(Text, { key: 'more', color: '#565f89', dimColor: true }, ` and ${more} more`) : null,
992
+ more > 0 ? h(Text, { key: 'more', color: '#565f89', dimColor: true }, ` ${ELLIPSIS} and ${more} more`) : null,
993
993
  );
994
994
  }
995
995
  case 'subagent': {
@@ -3,7 +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
+ import { MODE_ICONS, ARROW, ARROW_LEFT, DOT, DASH } from '../../utils/unicode.js';
7
7
 
8
8
  const ALL_MODES = [
9
9
  { name: 'GENERAL', desc: 'System administration mode', value: 'GENERAL' },
@@ -115,7 +115,7 @@ export function ModePicker({ visible, onSelect, onCancel, currentMode, currentEx
115
115
  const isCurrent = m.value === currentMode || m.value === currentExecutionMode;
116
116
  const icon = MODE_ICONS[m.value] || DOT;
117
117
  const prefix = isHighlighted ? h(Text, { color: '#9ece6a' }, ' ' + ARROW + ' ') : h(Text, { color: '#3b3f52' }, ' ');
118
- const currentMark = isCurrent ? ' current' : '';
118
+ const currentMark = isCurrent ? ` ${ARROW_LEFT} current` : '';
119
119
 
120
120
  return h(
121
121
  Box,
@@ -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 { DASH } from '../../utils/unicode.js';
6
7
 
7
8
  const MAIN_ACTIONS = [
8
9
  { name: 'Select Provider', desc: 'Choose from the provider catalog', action: 'catalog' },
@@ -729,7 +730,7 @@ export function ProviderMenu({ visible, onSelect, onCancel, serverUrl, token, cu
729
730
 
730
731
  if (!visible) return null;
731
732
 
732
- const separator = '─'.repeat(52);
733
+ const separator = DASH.repeat(52);
733
734
  const { rows } = useWindowSize();
734
735
 
735
736
  // SETTING UP VIEW
@@ -2,7 +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
+ import { isUnicode, ARROW, CROSS, CLOUD, DASH } from '../../utils/unicode.js';
6
6
 
7
7
  const OPTIONS = [
8
8
  { name: 'Local', desc: 'Save to local storage', value: 'local' },
@@ -82,7 +82,7 @@ export function SavePicker({ visible, onSelect, onCancel, hasCloud }) {
82
82
  ...filtered.map((o, i) => {
83
83
  const isHL = i === cursor;
84
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
+ const icon = o.value === 'local' ? ' ' : o.value === 'cloud' ? `${CLOUD} ` : CROSS + ' ';
86
86
 
87
87
  return h(Box, { key: i },
88
88
  prefix,
@@ -3,7 +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 { ARROW } from '../../utils/unicode.js';
6
+ import { ARROW, ARROW_LEFT, DASH } from '../../utils/unicode.js';
7
7
 
8
8
  /**
9
9
  * Reusable interactive menu with:
@@ -119,7 +119,7 @@ export function SelectMenu({
119
119
  }
120
120
  const visibleItems = filtered.slice(startIdx, startIdx + maxVisibleItems);
121
121
 
122
- const separator = '─'.repeat(50);
122
+ const separator = DASH.repeat(50);
123
123
 
124
124
  return h(
125
125
  Box,
@@ -147,7 +147,7 @@ export function SelectMenu({
147
147
  const icon = isHighlighted ? h(Text, { color: '#9ece6a' }, ` ${ARROW} `) : h(Text, { color: '#3b3f52' }, ' ');
148
148
  const label = item.label || item.cmd || item.name || '';
149
149
  const desc = item.desc ? ` ${item.desc}` : '';
150
- const mark = item.active ? ' ◀' : '';
150
+ const mark = item.active ? ` ${ARROW_LEFT}` : '';
151
151
 
152
152
  return h(
153
153
  Box,
@@ -3,7 +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 { ARROW } from '../../utils/unicode.js';
6
+ import { ARROW, DASH } from '../../utils/unicode.js';
7
7
 
8
8
  const COMMANDS = [
9
9
  { cmd: '/clear', desc: 'Clear displayed history', action: 'clear' },
@@ -98,7 +98,7 @@ export function SlashMenu({ visible, onSelect, onCancel }) {
98
98
  }
99
99
  const visibleItems = filtered.slice(startIdx, startIdx + maxVisible);
100
100
 
101
- const separator = '─'.repeat(52);
101
+ const separator = DASH.repeat(52);
102
102
 
103
103
  return h(
104
104
  Box,
@@ -1,7 +1,7 @@
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
+ import { TICK, CROSS, SPINNER_CIRCLE, DOT, DASH, TREE_MID, ELLIPSIS } from '../../utils/unicode.js';
5
5
 
6
6
  const SPINNER = SPINNER_CIRCLE;
7
7
 
@@ -15,7 +15,7 @@ function formatDuration(ms) {
15
15
  function truncate(value, max = 50) {
16
16
  const s = String(value || '');
17
17
  if (s.length <= max) return s;
18
- return s.slice(0, max - 1) + '…';
18
+ return s.slice(0, max - 1) + ELLIPSIS;
19
19
  }
20
20
 
21
21
  function statusLabel(state) {
@@ -90,7 +90,7 @@ export function SubagentPanel({ state, events, isExpanded }) {
90
90
  ),
91
91
  isRunning && state.lastTool
92
92
  ? h(Box, { paddingLeft: 2 },
93
- h(Text, { color: '#565f89' }, '⎿ '),
93
+ h(Text, { color: '#565f89' }, `${DASH} `),
94
94
  h(Text, { color: getToolColor(state.lastTool), bold: true }, state.lastTool),
95
95
  state.lastTarget ? h(Text, { color: '#565f89' }, `: ${truncate(state.lastTarget, 80)}`) : null,
96
96
  )
@@ -98,13 +98,13 @@ export function SubagentPanel({ state, events, isExpanded }) {
98
98
  isExpanded
99
99
  ? h(Box, { flexDirection: 'column', paddingLeft: 2 },
100
100
  hiddenEvents > 0
101
- ? h(Text, { color: '#565f89', dimColor: true }, ` ${hiddenEvents} earlier subagent events hidden`)
101
+ ? h(Text, { color: '#565f89', dimColor: true }, ` ${ELLIPSIS} ${hiddenEvents} earlier subagent events hidden`)
102
102
  : null,
103
103
  visibleEvents.length > 0
104
104
  ? visibleEvents.map((ev, i) => {
105
105
  if (ev.type === 'tool_start') {
106
106
  return h(Box, { key: i },
107
- h(Text, { color: '#7aa2f7' }, ' ├─ '),
107
+ h(Text, { color: '#7aa2f7' }, ` ${TREE_MID}${DASH} `),
108
108
  h(Text, { color: getToolColor(ev.name), bold: true }, ev.name),
109
109
  formatTarget(ev) ? h(Text, { color: '#565f89' }, `: ${truncate(formatTarget(ev), 80)}`) : null,
110
110
  h(Text, { color: '#565f89' }, ' ' + DOT),
@@ -112,7 +112,7 @@ export function SubagentPanel({ state, events, isExpanded }) {
112
112
  }
113
113
  if (ev.type === 'tool_end') {
114
114
  return h(Box, { key: i },
115
- h(Text, { color: ev.success ? '#9ece6a' : '#f7768e' }, ' ├─ '),
115
+ h(Text, { color: ev.success ? '#9ece6a' : '#f7768e' }, ` ${TREE_MID}${DASH} `),
116
116
  h(Text, { color: getToolColor(ev.name), bold: true }, ev.name),
117
117
  formatTarget(ev) ? h(Text, { color: '#565f89' }, `: ${truncate(formatTarget(ev), 80)}`) : null,
118
118
  h(Text, { color: '#565f89' }, ev.success ? ' ' + TICK : ' ' + CROSS),
@@ -3,7 +3,7 @@ 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
+ import { SPINNER_BRAILLE, TICK, CROSS, ELLIPSIS } from '../../utils/unicode.js';
7
7
 
8
8
  const SPINNER_FRAMES = SPINNER_BRAILLE;
9
9
 
@@ -171,7 +171,7 @@ export function ToolItem({ tool }) {
171
171
  const prefix = tool.name === 'TASK' ? 'Subagent' : isSubagent ? 'Subagent tool' : null;
172
172
 
173
173
  return h(Box, { paddingLeft: 2, paddingY: 0 },
174
- h(Text, { color: '#7aa2f7' }, ENABLE_UI_ANIMATIONS ? ` ${SPINNER_FRAMES[frame % SPINNER_FRAMES.length]} ` : ' '),
174
+ h(Text, { color: '#7aa2f7' }, ENABLE_UI_ANIMATIONS ? ` ${SPINNER_FRAMES[frame % SPINNER_FRAMES.length]} ` : ' ' + ELLIPSIS + ' '),
175
175
  prefix ? h(Text, { color: '#7dcfff' }, `${prefix} `) : null,
176
176
  h(Text, { color, bold: true }, tool.name),
177
177
  target.text ? h(Text, { color: '#565f89' }, ': ') : null,
package/src/ui/diff.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { highlightCode } from '../parser/markdown.js';
3
+ import { BOX_TL, BOX_TR, BOX_BL, BOX_BR, BOX_V, BOX_H, ELLIPSIS, CROSS } from '../utils/unicode.js';
3
4
 
4
5
  export const langFromPath = (fp) => {
5
6
  if (!fp) return '';
@@ -115,22 +116,22 @@ export const printNewFile = (filePath, content) => {
115
116
  console.log();
116
117
  const header = ` + ${filePath} (new file, ${lines.length} lines) `;
117
118
  const headerPad = Math.max(0, w - 2 - header.length);
118
- console.log(bc('╭') + chalk.bgHex(c.headerBg).hex(c.headerFg)(header) + chalk.hex(c.headerBg)('─'.repeat(headerPad)) + bc('╮'));
119
+ console.log(bc(BOX_TL) + chalk.bgHex(c.headerBg).hex(c.headerFg)(header) + chalk.hex(c.headerBg)(BOX_H.repeat(headerPad)) + bc(BOX_TR));
119
120
 
120
121
  const shown = Math.min(lines.length, 50);
121
122
  for (let i = 0; i < shown; i++) {
122
123
  const lineNum = chalk.hex(c.lineNumFg)(String(i + 1).padStart(lnW));
123
- const raw = lines[i].length > innerW ? lines[i].slice(0, innerW - 1) + '…' : lines[i];
124
+ const raw = lines[i].length > innerW ? lines[i].slice(0, innerW - 1) + ELLIPSIS : lines[i];
124
125
  const colored = highlightCode(raw, lang);
125
126
  const prefix = chalk.bgHex(c.addedBg).hex(c.addedFg)('+');
126
127
  const padLen = Math.max(0, innerW - stripAnsi(colored).length);
127
- console.log(bc('│') + ' ' + lineNum + ' ' + prefix + ' ' + colored + ' '.repeat(padLen) + bc('│'));
128
+ console.log(bc(BOX_V) + ' ' + lineNum + ' ' + prefix + ' ' + colored + ' '.repeat(padLen) + bc(BOX_V));
128
129
  }
129
130
  if (lines.length > 50) {
130
131
  const msg = ` ··· ${lines.length - 50} more lines`;
131
- console.log(bc('│') + chalk.hex(c.dim)(msg) + ' '.repeat(Math.max(0, w - 2 - msg.length)) + bc('│'));
132
+ console.log(bc(BOX_V) + chalk.hex(c.dim)(msg) + ' '.repeat(Math.max(0, w - 2 - msg.length)) + bc(BOX_V));
132
133
  }
133
- console.log(bc('╰' + '─'.repeat(w - 2) + '╯'));
134
+ console.log(bc(BOX_BL + BOX_H.repeat(w - 2) + BOX_BR));
134
135
  console.log();
135
136
  };
136
137
 
@@ -150,13 +151,13 @@ export const printEditDiff = (filePath, find, replace) => {
150
151
  console.log();
151
152
  const header = ` ✎ ${filePath} `;
152
153
  const headerPad = Math.max(0, w - 2 - header.length);
153
- console.log(bc('╭') + chalk.bgHex(c.headerBg).hex(c.headerFg)(header) + chalk.hex(c.headerBg)('─'.repeat(headerPad)) + bc('╮'));
154
+ console.log(bc(BOX_TL) + chalk.bgHex(c.headerBg).hex(c.headerFg)(header) + chalk.hex(c.headerBg)(BOX_H.repeat(headerPad)) + bc(BOX_TR));
154
155
 
155
156
  for (const hunk of collapsed) {
156
157
  if (hunk.type === 'skip') {
157
158
  const msg = ` ··· ${hunk.count} unchanged line${hunk.count > 1 ? 's' : ''}`;
158
159
  const pad = Math.max(0, w - 2 - msg.length);
159
- console.log(bc('│') + chalk.hex(c.dim)(msg) + ' '.repeat(pad) + bc('│'));
160
+ console.log(bc(BOX_V) + chalk.hex(c.dim)(msg) + ' '.repeat(pad) + bc(BOX_V));
160
161
  continue;
161
162
  }
162
163
 
@@ -171,7 +172,7 @@ export const printEditDiff = (filePath, find, replace) => {
171
172
  const nums = idx === 0 ? lineNums : lineNumsCont;
172
173
  const styled = chalk.hex(c.ctxFg)(' ' + chunk);
173
174
  const pad = Math.max(0, innerW + 1 - visLen(styled));
174
- console.log(bc('│') + ' ' + nums + ' ' + styled + ' '.repeat(pad) + bc('│'));
175
+ console.log(bc(BOX_V) + ' ' + nums + ' ' + styled + ' '.repeat(pad) + bc(BOX_V));
175
176
  });
176
177
  } else if (hunk.type === 'del') {
177
178
  const chunks = wrapPlain(hunk.line, innerW);
@@ -180,7 +181,7 @@ export const printEditDiff = (filePath, find, replace) => {
180
181
  const sign = idx === 0 ? '-' : ' ';
181
182
  const colored = chalk.bgHex(c.removedBg).hex(c.removedFg)(sign) + highlightCode(chunk, lang);
182
183
  const pad = Math.max(0, innerW + 1 - stripAnsi(colored).length);
183
- console.log(bc('│') + ' ' + nums + ' ' + colored + ' '.repeat(pad) + bc('│'));
184
+ console.log(bc(BOX_V) + ' ' + nums + ' ' + colored + ' '.repeat(pad) + bc(BOX_V));
184
185
  });
185
186
  } else if (hunk.type === 'add') {
186
187
  const chunks = wrapPlain(hunk.line, innerW);
@@ -189,7 +190,7 @@ export const printEditDiff = (filePath, find, replace) => {
189
190
  const sign = idx === 0 ? '+' : ' ';
190
191
  const colored = chalk.bgHex(c.addedBg).hex(c.addedFg)(sign) + highlightCode(chunk, lang);
191
192
  const pad = Math.max(0, innerW + 1 - stripAnsi(colored).length);
192
- console.log(bc('│') + ' ' + nums + ' ' + colored + ' '.repeat(pad) + bc('│'));
193
+ console.log(bc(BOX_V) + ' ' + nums + ' ' + colored + ' '.repeat(pad) + bc(BOX_V));
193
194
  });
194
195
  }
195
196
  }
@@ -198,8 +199,8 @@ export const printEditDiff = (filePath, find, replace) => {
198
199
  const adds = hunks.filter(h => h.type === 'add').length;
199
200
  const summary = ` ${chalk.hex(c.addedFg)('+' + adds)} ${chalk.hex(c.removedFg)('-' + dels)} `;
200
201
  const summaryPad = Math.max(0, w - 2 - stripAnsi(summary).length);
201
- console.log(bc('│') + summary + ' '.repeat(summaryPad) + bc('│'));
202
- console.log(bc('╰' + '─'.repeat(w - 2) + '╯'));
202
+ console.log(bc(BOX_V) + summary + ' '.repeat(summaryPad) + bc(BOX_V));
203
+ console.log(bc(BOX_BL + BOX_H.repeat(w - 2) + BOX_BR));
203
204
  console.log();
204
205
  };
205
206
 
@@ -215,22 +216,22 @@ export const printAppendDiff = (filePath, content) => {
215
216
  console.log();
216
217
  const header = ` + ${filePath} (appending ${lines.length} line${lines.length > 1 ? 's' : ''}) `;
217
218
  const headerPad = Math.max(0, w - 2 - header.length);
218
- console.log(bc('╭') + chalk.bgHex(c.headerBg).hex(c.addedFg)(header) + chalk.hex(c.headerBg)('─'.repeat(headerPad)) + bc('╮'));
219
+ console.log(bc(BOX_TL) + chalk.bgHex(c.headerBg).hex(c.addedFg)(header) + chalk.hex(c.headerBg)(BOX_H.repeat(headerPad)) + bc(BOX_TR));
219
220
 
220
221
  const skip = lines.length > 30;
221
222
  const shown = skip ? lines.slice(0, 15) : lines;
222
223
  shown.forEach((line, i) => {
223
224
  const lineNum = chalk.hex(c.lineNumFg)(String(i + 1).padStart(lnW));
224
- const raw = line.length > innerW ? line.slice(0, innerW - 1) + '…' : line;
225
+ const raw = line.length > innerW ? line.slice(0, innerW - 1) + ELLIPSIS : line;
225
226
  const colored = highlightCode(raw, lang);
226
227
  const pad = Math.max(0, innerW - stripAnsi(colored).length);
227
- console.log(bc('│') + ' ' + lineNum + ' ' + chalk.bgHex(c.addedBg).hex(c.addedFg)('+') + ' ' + colored + ' '.repeat(pad) + bc('│'));
228
+ console.log(bc(BOX_V) + ' ' + lineNum + ' ' + chalk.bgHex(c.addedBg).hex(c.addedFg)('+') + ' ' + colored + ' '.repeat(pad) + bc(BOX_V));
228
229
  });
229
230
  if (skip) {
230
231
  const msg = ` ··· ${lines.length - 15} more lines`;
231
- console.log(bc('│') + chalk.hex(c.dim)(msg) + ' '.repeat(Math.max(0, w - 2 - msg.length)) + bc('│'));
232
+ console.log(bc(BOX_V) + chalk.hex(c.dim)(msg) + ' '.repeat(Math.max(0, w - 2 - msg.length)) + bc(BOX_V));
232
233
  }
233
- console.log(bc('╰' + '─'.repeat(w - 2) + '╯'));
234
+ console.log(bc(BOX_BL + BOX_H.repeat(w - 2) + BOX_BR));
234
235
  console.log();
235
236
  };
236
237
 
@@ -240,10 +241,10 @@ export const printAppendDiff = (filePath, content) => {
240
241
  export const printDeleteFile = (filePath) => {
241
242
  const w = getWidth();
242
243
  const bc = chalk.hex(c.border);
243
- const msg = ` ${filePath} (deleted) `;
244
+ const msg = ` ${CROSS} ${filePath} (deleted) `;
244
245
  const pad = Math.max(0, w - 2 - msg.length);
245
246
  console.log();
246
- console.log(bc('╭') + chalk.bgHex(c.removedBg).hex(c.removedFg)(msg) + chalk.hex(c.removedBg)('─'.repeat(pad)) + bc('╮'));
247
- console.log(bc('╰' + '─'.repeat(w - 2) + '╯'));
247
+ console.log(bc(BOX_TL) + chalk.bgHex(c.removedBg).hex(c.removedFg)(msg) + chalk.hex(c.removedBg)(BOX_H.repeat(pad)) + bc(BOX_TR));
248
+ console.log(bc(BOX_BL + BOX_H.repeat(w - 2) + BOX_BR));
248
249
  console.log();
249
250
  };
@@ -8,7 +8,7 @@ export const ARROW = isUnicode ? '▸' : '>';
8
8
  export const DASH = isUnicode ? '─' : '-';
9
9
  export const BAR = isUnicode ? '│' : '|';
10
10
  export const BULLET = isUnicode ? '•' : '*';
11
- export const ELLIPSIS = '…';
11
+ export const ELLIPSIS = isUnicode ? '…' : '...';
12
12
  export const SPINNER_BRAILLE = isUnicode
13
13
  ? ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
14
14
  : ['|', '/', '-', '\\'];
@@ -44,9 +44,12 @@ export const WARN = isUnicode ? '⚠' : '!';
44
44
  export const CLOUD = isUnicode ? '☁' : '~';
45
45
  export const TRIANGLE = isUnicode ? '▲' : '^';
46
46
  export const TREE_BRANCH = isUnicode ? '├── ' : '|-- ';
47
+ export const TREE_MID = isUnicode ? '├' : '|';
47
48
  export const TREE_LAST = isUnicode ? '└── ' : '`-- ';
48
49
  export const TREE_VLINE = isUnicode ? '│ ' : '| ';
49
50
  export const TREE_SPACE = ' ';
51
+ export const ARROW_LEFT = isUnicode ? '◀' : '<';
52
+ export const LOADING = isUnicode ? '⟳' : '[.]';
50
53
  export const LOADING_DOTS = isUnicode
51
54
  ? ['', '∘', '∘∘', '∘∘∘']
52
55
  : ['', '.', '..', '...'];