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/CHANGELOG.md +71 -0
- package/package.json +1 -1
- package/src/agents/loop.js +277 -66
- package/src/commands/index.js +120 -56
- package/src/commands/model.js +19 -4
- package/src/main.js +144 -66
- package/src/providers/index.js +10 -2
- package/src/ui/blocks.js +115 -48
- package/src/ui/input.js +14 -45
- package/src/ui/prompt.js +127 -0
- package/src/ui/spinner.js +98 -23
package/src/main.js
CHANGED
|
@@ -1,90 +1,168 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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 =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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 (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
showPrompt();
|
|
118
|
+
if (key.name === 'backspace') {
|
|
119
|
+
currentInput = currentInput.slice(0, -1);
|
|
120
|
+
drawPromptLine();
|
|
50
121
|
return;
|
|
51
122
|
}
|
|
52
123
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
160
|
+
if (str && !key.ctrl && !key.meta) {
|
|
161
|
+
currentInput += str;
|
|
162
|
+
drawPromptLine();
|
|
163
|
+
}
|
|
89
164
|
});
|
|
165
|
+
|
|
166
|
+
console.log(top);
|
|
167
|
+
drawPromptLine();
|
|
90
168
|
}
|
package/src/providers/index.js
CHANGED
|
@@ -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
|
|
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(
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
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
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
90
|
+
export function renderInfo(msg) {
|
|
91
|
+
return chalk.hex('#54A0FF')('\u2139 ') + chalk.hex('#54A0FF')(msg);
|
|
92
|
+
}
|
|
38
93
|
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
error: (msg
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
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:
|
|
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 === '
|
|
22
|
-
process.stdout.write('\
|
|
23
|
-
process.
|
|
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
|
+
}
|
package/src/ui/prompt.js
ADDED
|
@@ -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
|
+
}
|