clarity-ai 3.0.2 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/main.js CHANGED
@@ -1,90 +1,168 @@
1
- import readline from 'readline';
2
- import { clr } from './ui/colors.js';
3
- import { blocks } from './ui/blocks.js';
1
+ import chalk from 'chalk';
2
+ import { renderUser, renderAI, divider } from './ui/blocks.js';
3
+ import { getPrompt, drawPromptBox, SlashPalette } from './ui/prompt.js';
4
4
  import { dispatchCommand } from './commands/index.js';
5
- import { callProvider } from './providers/index.js';
6
- import { loadHistory, saveHistory, addMessage } from './core/history.js';
7
5
  import { agentLoop } from './agents/loop.js';
6
+ import { showBanner } from './ui/banner.js';
8
7
 
9
8
  export async function startChat(config) {
10
- const history = loadHistory();
11
-
12
- console.log(blocks.info(
13
- 'CLARITY-AI v' + config.version + ' interactive session started.\n' +
14
- 'Type /help for commands. Ctrl+C to exit.\n' +
15
- 'Provider: ' + config.provider + ' | Model: ' + config.model,
16
- ' Ready ── ' + new Date().toLocaleTimeString()
17
- ));
9
+ const history = [];
10
+ const palette = new SlashPalette();
11
+ let slashMode = false;
12
+ let slashBuffer = '';
13
+
14
+ const { top } = drawPromptBox(config.provider, config.model, config.agentMode);
15
+ console.log(chalk.hex('#00FF9F')('\u2714 ') + chalk.white('CLARITY-AI v' + config.version + ' interactive session started.') +
16
+ chalk.dim(' Type /help for commands. Ctrl+C to exit.'));
17
+ console.log(divider());
18
18
  console.log();
19
19
 
20
- const rl = readline.createInterface({
21
- input: process.stdin,
22
- output: process.stdout,
23
- terminal: true,
24
- historySize: 100,
25
- });
20
+ const { emitKeypressEvents } = await import('readline');
21
+ emitKeypressEvents(process.stdin);
22
+ if (process.stdin.isTTY) process.stdin.setRawMode(true);
23
+
24
+ let currentInput = '';
25
+
26
+ function drawPromptLine() {
27
+ process.stdout.write('\r\x1b[2K');
28
+ process.stdout.write(getPrompt(config.provider, config.model));
29
+ if (currentInput) process.stdout.write(currentInput);
30
+ }
26
31
 
27
- function showPrompt() {
28
- const pre = clr.primary('❯ ') + clr.dim(config.provider + '/' + config.model) + clr.primary(' ❯ ');
29
- rl.setPrompt(pre);
30
- rl.prompt();
32
+ function clearAndPrompt() {
33
+ console.log();
34
+ drawPromptLine();
31
35
  }
32
36
 
33
- process.stdin.on('keypress', (str, key) => {
34
- if (key && key.ctrl && key.name === 'l') {
37
+ process.stdin.on('keypress', async (str, key) => {
38
+ if (!key) return;
39
+
40
+ if (key.ctrl && key.name === 'c') {
41
+ if (slashMode) {
42
+ palette.hide();
43
+ slashMode = false;
44
+ slashBuffer = '';
45
+ currentInput = '';
46
+ drawPromptLine();
47
+ return;
48
+ }
49
+ console.log('\n' + chalk.hex('#00FF9F')('\u2714 ') + chalk.dim('Session ended. Goodbye.'));
50
+ process.exit(0);
51
+ }
52
+
53
+ if (key.ctrl && key.name === 'l') {
35
54
  process.stdout.write('\x1Bc');
36
- showPrompt();
55
+ await showBanner(config.version, config.provider, config.model);
56
+ drawPromptLine();
57
+ return;
58
+ }
59
+
60
+ if (key.ctrl && key.name === 'u') {
61
+ currentInput = '';
62
+ slashMode = false;
63
+ slashBuffer = '';
64
+ drawPromptLine();
65
+ return;
37
66
  }
38
- });
39
67
 
40
- showPrompt();
68
+ if (key.name === 'escape') {
69
+ if (slashMode) {
70
+ palette.hide();
71
+ slashMode = false;
72
+ slashBuffer = '';
73
+ currentInput = '';
74
+ drawPromptLine();
75
+ }
76
+ return;
77
+ }
41
78
 
42
- rl.on('line', async (input) => {
43
- input = input.trim();
44
- if (!input) { showPrompt(); return; }
79
+ if (slashMode) {
80
+ if (key.name === 'up') {
81
+ palette.selectPrev();
82
+ return;
83
+ }
84
+ if (key.name === 'down') {
85
+ palette.selectNext();
86
+ return;
87
+ }
88
+ if (key.name === 'return') {
89
+ const selected = palette.getSelected();
90
+ palette.hide();
91
+ slashMode = false;
92
+ currentInput = '';
93
+ drawPromptLine();
94
+ if (selected) {
95
+ console.log();
96
+ await dispatchCommand(selected.name, config, history);
97
+ }
98
+ clearAndPrompt();
99
+ return;
100
+ }
101
+ if (key.name === 'backspace') {
102
+ slashBuffer = slashBuffer.slice(0, -1);
103
+ currentInput = '/' + slashBuffer;
104
+ palette.filter(slashBuffer);
105
+ drawPromptLine();
106
+ return;
107
+ }
108
+ if (str && !key.ctrl && !key.meta) {
109
+ slashBuffer += str;
110
+ currentInput = '/' + slashBuffer;
111
+ palette.filter(slashBuffer);
112
+ drawPromptLine();
113
+ return;
114
+ }
115
+ return;
116
+ }
45
117
 
46
- if (input.startsWith('/')) {
47
- const newConfig = await dispatchCommand(input, config, rl);
48
- if (newConfig) config = newConfig;
49
- showPrompt();
118
+ if (key.name === 'backspace') {
119
+ currentInput = currentInput.slice(0, -1);
120
+ drawPromptLine();
50
121
  return;
51
122
  }
52
123
 
53
- console.log('\n' + blocks.user(input) + '\n');
54
-
55
- if (config.agentMode !== false) {
56
- const result = await agentLoop(input, config, history);
57
- addMessage(history, 'user', input);
58
- addMessage(history, 'assistant', result);
59
- saveHistory(history);
60
- console.log('\n' + blocks.ai(result) + '\n');
61
- } else {
62
- addMessage(history, 'user', input);
63
- console.log(clr.dim('◆ Thinking...'));
64
-
65
- let fullResponse = '';
66
- const stream = callProvider(config, [
67
- { role: 'system', content: 'You are CLARITY-AI, a helpful AI assistant.' },
68
- ...history.slice(-10).map(m => ({ role: m.role === 'assistant' ? 'assistant' : 'user', content: m.content })),
69
- { role: 'user', content: input },
70
- ]);
71
-
72
- process.stdout.write(clr.ai('') + ' ');
73
- for await (const chunk of stream) {
74
- fullResponse += chunk;
75
- process.stdout.write(chunk);
124
+ if (key.name === 'return') {
125
+ const input = currentInput.trim();
126
+ currentInput = '';
127
+
128
+ if (!input) {
129
+ console.log();
130
+ drawPromptLine();
131
+ return;
76
132
  }
77
- console.log('\n');
78
133
 
79
- addMessage(history, 'assistant', fullResponse);
80
- saveHistory(history);
134
+ process.stdout.write('\r\x1b[2K');
135
+ console.log();
136
+ console.log(renderUser(input));
137
+ console.log();
138
+
139
+ if (input.startsWith('/')) {
140
+ await dispatchCommand(input, config, history);
141
+ clearAndPrompt();
142
+ return;
143
+ }
144
+
145
+ await agentLoop(input, config, history);
146
+ clearAndPrompt();
147
+ return;
81
148
  }
82
149
 
83
- showPrompt();
84
- });
150
+ if (str === '/' && currentInput === '') {
151
+ slashMode = true;
152
+ slashBuffer = '';
153
+ currentInput = '/';
154
+ console.log();
155
+ palette.show();
156
+ drawPromptLine();
157
+ return;
158
+ }
85
159
 
86
- rl.on('SIGINT', () => {
87
- console.log('\n' + blocks.info('Session ended. Goodbye.', '◆ EXIT'));
88
- process.exit(0);
160
+ if (str && !key.ctrl && !key.meta) {
161
+ currentInput += str;
162
+ drawPromptLine();
163
+ }
89
164
  });
165
+
166
+ console.log(top);
167
+ drawPromptLine();
90
168
  }
@@ -16,15 +16,23 @@ export const PROVIDERS = [
16
16
 
17
17
  const senders = { groq: groqSend, gemini: geminiSend, openrouter: openrouterSend, openai: openaiSend, anthropic: anthropicSend, deepseek: deepseekSend };
18
18
 
19
- export function callProvider(config, messages) {
19
+ export function streamProvider(config, messages) {
20
20
  const provider = config.provider || 'groq';
21
21
  const model = config.model || 'llama-3.3-70b-versatile';
22
22
  const apiKey = config.apiKeys?.[provider] || process.env[provider.toUpperCase() + '_API_KEY'] || '';
23
23
  const sender = senders[provider];
24
- if (!sender) throw new Error(`Unknown provider: ${provider}`);
24
+ if (!sender) throw new Error('Unknown provider: ' + provider);
25
25
  return sender(apiKey, messages, model, true);
26
26
  }
27
27
 
28
+ export async function callProvider(config, messages) {
29
+ let full = '';
30
+ for await (const chunk of streamProvider(config, messages)) {
31
+ full += chunk;
32
+ }
33
+ return full;
34
+ }
35
+
28
36
  export function getProvider(name) {
29
37
  return PROVIDERS.find(p => p.value === name);
30
38
  }
package/src/ui/blocks.js CHANGED
@@ -1,65 +1,132 @@
1
- // src/ui/blocks.js
2
1
  import chalk from 'chalk';
3
2
  import { clr } from './colors.js';
4
3
  import stripAnsi from 'strip-ansi';
5
4
 
6
- const W = process.stdout.columns || 80;
5
+ const W = () => process.stdout.columns || 80;
7
6
 
8
- const BORDERS = {
9
- round: { tl:'╭', tr:'╮', bl:'╰', br:'╯', h:'─', v:'│' },
10
- sharp: { tl:'┌', tr:'┐', bl:'', br:'┘', h:'─', v:'│' },
11
- double: { tl:'╔', tr:'╗', bl:'╚', br:'╝', h:'═', v:'║' },
12
- thick: { tl:'', tr:'▜', bl:'▙', br:'', h:'', v:'█' },
13
- };
7
+ export function renderUser(text) {
8
+ const w = Math.min(W(), 80);
9
+ const lines = String(text).split('\n');
10
+ const out = [];
11
+ out.push(chalk.hex('#9B59FF').bold('\u276f YOU ') + chalk.hex('#9B59FF')('\u2500'.repeat(Math.max(0, w - 7))));
12
+ for (const line of lines) {
13
+ const vis = stripAnsi(line).length;
14
+ const pad = Math.max(0, w - vis - 1);
15
+ out.push(chalk.hex('#C39BD3').bgHex('#2D1B4E')(line + ' '.repeat(pad)));
16
+ }
17
+ return out.join('\n');
18
+ }
19
+
20
+ export function renderAI(text) {
21
+ const w = Math.min(W(), 80);
22
+ const lines = String(text).split('\n');
23
+ const out = [];
24
+ out.push(chalk.hex('#9B59FF')('\u25c6 CLARITY ') + chalk.hex('#555555')('\u2500'.repeat(Math.max(0, w - 11))));
25
+ for (const line of lines) {
26
+ out.push(chalk.hex('#7B2FFF')('\u2502') + ' ' + chalk.white(line));
27
+ }
28
+ return out.join('\n');
29
+ }
14
30
 
15
- function box(content, opts = {}) {
16
- const {
17
- title = '',
18
- style = 'round',
19
- color = '#00FFFF',
20
- width = Math.min(W - 2, 78),
21
- padding = 1,
22
- } = opts;
31
+ export function renderEdit(filepath, hunks) {
32
+ const out = [];
33
+ const fname = filepath.split('/').pop();
34
+ out.push(
35
+ chalk.bgHex('#1A1A2E').hex('#74B9FF').bold(' \u270e ' + fname + ' ') +
36
+ chalk.dim(' ' + filepath)
37
+ );
38
+ out.push(chalk.hex('#333333')('\u2500'.repeat(Math.min(W(), 80))));
39
+ for (const h of hunks) {
40
+ if (h.type === 'add') {
41
+ out.push(
42
+ chalk.hex('#00FF9F')('+') +
43
+ chalk.dim(String(h.lineNum).padStart(4)) +
44
+ ' ' +
45
+ chalk.hex('#00FF9F')(h.line)
46
+ );
47
+ } else if (h.type === 'remove') {
48
+ out.push(
49
+ chalk.hex('#FF4757')('-') +
50
+ chalk.dim(String(h.lineNum).padStart(4)) +
51
+ ' ' +
52
+ chalk.hex('#FF4757').strikethrough(h.line)
53
+ );
54
+ } else {
55
+ out.push(
56
+ chalk.dim(' ') +
57
+ chalk.dim(String(h.lineNum).padStart(4)) +
58
+ ' ' +
59
+ chalk.hex('#888888')(h.line)
60
+ );
61
+ }
62
+ }
63
+ return out.join('\n');
64
+ }
23
65
 
24
- const b = BORDERS[style] || BORDERS.round;
25
- const colorFn = (t) => chalk.hex(color)(t);
26
- const inner = width - 2;
66
+ export function renderWrite(filepath, lineCount) {
67
+ const fname = filepath.split('/').pop();
68
+ return [
69
+ chalk.bgHex('#1A1A2E').hex('#00FF9F').bold(' + ' + fname + ' ') +
70
+ chalk.dim(' ' + filepath),
71
+ chalk.hex('#00FF9F')(' ' + lineCount + ' lines written'),
72
+ ].join('\n');
73
+ }
27
74
 
28
- const lines = String(content).split('\n');
29
- const padded = lines.map(l => {
30
- const vis = stripAnsi(l).length;
31
- const pad = Math.max(0, inner - padding * 2 - vis);
32
- return colorFn(b.v) + ' '.repeat(padding) + l + ' '.repeat(pad) + colorFn(b.v);
33
- });
75
+ export function renderTool(toolName, input, output) {
76
+ const out = [];
77
+ out.push(chalk.hex('#FFD700')('\u2699 ') + chalk.hex('#FFD700').bold(toolName) + chalk.dim(' \u00b7 ' + String(input).slice(0, 60)));
78
+ if (output) {
79
+ const lines = String(output).split('\n').slice(0, 8);
80
+ for (const l of lines) {
81
+ out.push(chalk.dim(' \u2502 ') + chalk.hex('#AAAAAA')(l));
82
+ }
83
+ if (String(output).split('\n').length > 8) {
84
+ out.push(chalk.dim(' \u2502 ...'));
85
+ }
86
+ }
87
+ return out.join('\n');
88
+ }
34
89
 
35
- const titleStr = title
36
- ? colorFn(b.h).repeat(2) + ' ' + chalk.bold(title) + ' ' + colorFn(b.h).repeat(Math.max(0, inner - title.length - 4))
37
- : colorFn(b.h).repeat(inner);
90
+ export function renderInfo(msg) {
91
+ return chalk.hex('#54A0FF')('\u2139 ') + chalk.hex('#54A0FF')(msg);
92
+ }
38
93
 
39
- const top = colorFn(b.tl) + titleStr + colorFn(b.tr);
40
- const bottom = colorFn(b.bl) + colorFn(b.h).repeat(inner) + colorFn(b.br);
94
+ export function renderError(msg) {
95
+ return chalk.hex('#FF4757')('\u2716 ') + chalk.hex('#FF4757')(msg);
96
+ }
97
+
98
+ export function renderSuccess(msg) {
99
+ return chalk.hex('#00FF9F')('\u2714 ') + chalk.hex('#00FF9F')(msg);
100
+ }
101
+
102
+ export function renderWarning(msg) {
103
+ return chalk.hex('#FFB800')('\u26a0 ') + chalk.hex('#FFB800')(msg);
104
+ }
105
+
106
+ export function divider() {
107
+ return chalk.hex('#333333')('\u2500'.repeat(Math.min(process.stdout.columns || 80, 80)));
108
+ }
41
109
 
42
- return [top, ...padded, bottom].join('\n');
110
+ export function renderPromptPrefix(provider, model) {
111
+ return (
112
+ chalk.hex('#00FFFF')('\u276f ') +
113
+ chalk.dim(provider + '/' + model) +
114
+ chalk.hex('#00FFFF')(' \u276f ')
115
+ );
43
116
  }
44
117
 
45
- // 12 named block types
46
118
  export const blocks = {
47
- info: (msg, title) => box(clr.info(msg), { title: title || 'ℹ INFO', color: '#54A0FF', style: 'round' }),
48
- success: (msg, title) => box(clr.success(msg), { title: title || '✔ SUCCESS', color: '#00FF9F', style: 'round' }),
49
- warning: (msg, title) => box(clr.warning(msg), { title: title || '⚠ WARNING', color: '#FFB800', style: 'sharp' }),
50
- error: (msg, title) => box(clr.error(msg), { title: title || '✖ ERROR', color: '#FF4757', style: 'sharp' }),
51
- code: (msg, title) => box(chalk.hex('#E8E8E8')(msg), { title: title || '◆ CODE', color: '#9B59FF', style: 'sharp' }),
52
- file: (msg, title) => box(clr.filepath(msg),{ title: title || '📄 FILE', color: '#74B9FF', style: 'round' }),
53
- ai: (msg, title) => box(clr.ai(msg), { title: title || '◆ CLARITY', color: '#9B59FF', style: 'round' }),
54
- user: (msg, title) => box(clr.user(msg), { title: title || '❯ YOU', color: '#00FFFF', style: 'round' }),
55
- tool: (msg, title) => box(clr.tool(msg), { title: title || '⚙ TOOL', color: '#00FF9F', style: 'double' }),
56
- task: (msg, title) => box(clr.gold(msg), { title: title || '▶ TASK', color: '#FFD700', style: 'double' }),
57
- divider: () => clr.muted(''.repeat(Math.min(W, 80))),
58
- progress: (pct, label) => {
59
- const filled = Math.round(pct / 100 * 30);
60
- const bar = chalk.hex('#00FFFF')('█'.repeat(filled)) + chalk.gray('░'.repeat(30 - filled));
61
- return `[${bar}] ${chalk.bold(pct + '%')} ${label || ''}`;
62
- },
119
+ ai: renderAI,
120
+ user: renderUser,
121
+ info: (msg) => renderInfo(msg),
122
+ error: (msg) => renderError(msg),
123
+ success: (msg) => renderSuccess(msg),
124
+ warning: (msg) => renderWarning(msg),
125
+ divider,
126
+ edit: renderEdit,
127
+ write: renderWrite,
128
+ tool: renderTool,
129
+ task: (msg) => chalk.hex('#FFD700')('\u25b6 ') + chalk.hex('#FFD700')(msg),
63
130
  };
64
131
 
65
132
  export default blocks;
package/src/ui/input.js CHANGED
@@ -1,60 +1,29 @@
1
1
  import readline from 'readline';
2
2
  import chalk from 'chalk';
3
- import { clr } from './colors.js';
4
3
 
5
- export function createPrompt(config) {
4
+ export function createInput(config) {
6
5
  const rl = readline.createInterface({
7
6
  input: process.stdin,
8
7
  output: process.stdout,
9
8
  terminal: true,
10
- historySize: 100,
11
- });
12
-
13
- const promptStr = clr.primary('❯ ') + clr.dim(config.provider + '/' + config.model) + clr.primary(' ❯ ');
14
-
15
- rl.on('SIGINT', () => {
16
- process.stdout.write('\n');
17
- process.exit(0);
9
+ historySize: 200,
18
10
  });
19
11
 
20
12
  process.stdin.on('keypress', (str, key) => {
21
- if (key.ctrl && key.name === 'd') {
22
- process.stdout.write('\n');
23
- process.exit(0);
24
- }
25
- if (key.ctrl && key.name === 'l') {
26
- console.clear();
27
- return rl._refreshLine();
28
- }
29
- if (key.ctrl && key.name === 'u') {
30
- const pos = rl.cursor;
31
- rl.line = rl.line.slice(pos);
32
- rl.cursor = 0;
33
- return rl._refreshLine();
34
- }
35
- if (key.ctrl && key.name === 'a') {
36
- rl.cursor = 0;
37
- return rl._refreshLine();
38
- }
39
- if (key.ctrl && key.name === 'e') {
40
- rl.cursor = rl.line.length;
41
- return rl._refreshLine();
42
- }
43
- if (key.ctrl && key.name === 'k') {
44
- rl.line = rl.line.slice(0, rl.cursor);
45
- return rl._refreshLine();
46
- }
47
- if (key.ctrl && key.name === 'w') {
48
- const before = rl.line.slice(0, rl.cursor);
49
- const after = rl.line.slice(rl.cursor);
50
- const trimmed = before.replace(/\S+\s*$/, '');
51
- rl.line = trimmed + after;
52
- rl.cursor = trimmed.length;
53
- return rl._refreshLine();
13
+ if (key && key.ctrl && key.name === 'l') {
14
+ process.stdout.write('\x1Bc');
15
+ process.stdout.write(chalk.hex('#00FFFF').bold('\n CLARITY v3.0\n\n'));
16
+ rl.prompt();
54
17
  }
55
18
  });
56
19
 
57
- rl.setPrompt(promptStr);
58
-
59
20
  return rl;
60
21
  }
22
+
23
+ export function getPromptString(provider, model) {
24
+ return (
25
+ chalk.hex('#00FFFF')('\u276f ') +
26
+ chalk.dim(provider + '/' + model) +
27
+ chalk.hex('#00FFFF')(' \u276f ')
28
+ );
29
+ }
@@ -0,0 +1,127 @@
1
+ import readline from 'readline';
2
+ import chalk from 'chalk';
3
+ import { ALL_COMMANDS } from '../commands/index.js';
4
+
5
+ const W = () => process.stdout.columns || 80;
6
+
7
+ export function drawPromptBox(provider, model, agentMode = true) {
8
+ const w = Math.min(W(), 80);
9
+ const inner = w - 2;
10
+ const top = chalk.hex('#333333')('\u250c') + chalk.hex('#333333')('\u2500'.repeat(inner)) + chalk.hex('#333333')('\u2510');
11
+ const bottom =
12
+ chalk.hex('#333333')('\u2514') +
13
+ chalk.hex('#333333')('\u2500 ') +
14
+ chalk.dim(provider + '/' + model) +
15
+ chalk.hex('#333333')(' ' + '\u2500'.repeat(Math.max(0, inner - (provider + '/' + model).length - 14))) +
16
+ chalk.hex('#00FF9F')(agentMode ? 'agent: ON ' : 'agent: OFF') +
17
+ chalk.hex('#333333')('\u2500\u2518');
18
+ return { top, bottom };
19
+ }
20
+
21
+ export function getPrompt(provider, model) {
22
+ return (
23
+ chalk.hex('#333333')('\u2502 ') +
24
+ chalk.hex('#00FFFF')('\u276f ') +
25
+ chalk.dim(provider + '/' + model) +
26
+ chalk.hex('#00FFFF')(' \u276f ')
27
+ );
28
+ }
29
+
30
+ export class SlashPalette {
31
+ constructor() {
32
+ this.visible = false;
33
+ this.query = '';
34
+ this.selected = 0;
35
+ this.commands = [];
36
+ this._lastLineCount = 0;
37
+ }
38
+
39
+ show() {
40
+ this.visible = true;
41
+ this.commands = ALL_COMMANDS;
42
+ this.selected = 0;
43
+ this.query = '';
44
+ this.render();
45
+ }
46
+
47
+ hide() {
48
+ this.visible = false;
49
+ this.query = '';
50
+ this.selected = 0;
51
+ const lines = this._lastLineCount;
52
+ this._lastLineCount = 0;
53
+ for (let i = 0; i < lines; i++) {
54
+ process.stdout.write('\x1b[A\x1b[2K');
55
+ }
56
+ }
57
+
58
+ filter(query) {
59
+ this.query = query;
60
+ this.selected = 0;
61
+ this.render();
62
+ }
63
+
64
+ selectNext() {
65
+ const filtered = this._filtered();
66
+ this.selected = Math.min(this.selected + 1, filtered.length - 1);
67
+ this.render();
68
+ }
69
+
70
+ selectPrev() {
71
+ this.selected = Math.max(this.selected - 1, 0);
72
+ this.render();
73
+ }
74
+
75
+ getSelected() {
76
+ const filtered = this._filtered();
77
+ return filtered[this.selected] || null;
78
+ }
79
+
80
+ _filtered() {
81
+ if (!this.query) return this.commands;
82
+ return this.commands.filter(c =>
83
+ c.name.includes(this.query) || c.description.toLowerCase().includes(this.query.toLowerCase())
84
+ );
85
+ }
86
+
87
+ render() {
88
+ const filtered = this._filtered();
89
+ const show = filtered.slice(0, 8);
90
+ const w = Math.min(W(), 80);
91
+
92
+ if (this._lastLineCount) {
93
+ for (let i = 0; i < this._lastLineCount; i++) {
94
+ process.stdout.write('\x1b[A\x1b[2K');
95
+ }
96
+ }
97
+
98
+ const lines = [];
99
+ lines.push(
100
+ chalk.bgHex('#1A1A2E').hex('#00FFFF').bold(' Commands ') +
101
+ chalk.bgHex('#1A1A2E').dim(' type to filter ') +
102
+ chalk.bgHex('#1A1A2E').dim('esc to close'.padEnd(w - 28, ' '))
103
+ );
104
+ lines.push(chalk.hex('#333333')('\u2500'.repeat(w)));
105
+
106
+ if (show.length === 0) {
107
+ lines.push(chalk.dim(' No commands match "' + this.query + '"'));
108
+ } else {
109
+ show.forEach((cmd, i) => {
110
+ const isSelected = i === this.selected;
111
+ const prefix = isSelected ? chalk.hex('#00FFFF')(' \u276f ') : ' ';
112
+ const name = isSelected
113
+ ? chalk.bgHex('#1A1A2E').hex('#00FFFF').bold((cmd.name).padEnd(18))
114
+ : chalk.hex('#9B59FF')((cmd.name).padEnd(18));
115
+ const desc = isSelected
116
+ ? chalk.white(cmd.description)
117
+ : chalk.dim(cmd.description);
118
+ lines.push(prefix + name + ' ' + desc);
119
+ });
120
+ }
121
+
122
+ lines.push(chalk.hex('#333333')('\u2500'.repeat(w)));
123
+
124
+ process.stdout.write(lines.join('\n') + '\n');
125
+ this._lastLineCount = lines.length;
126
+ }
127
+ }