clarity-ai 1.3.0 → 1.3.1
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 +1 -1
- package/src/commands/index.js +2 -0
- package/src/ui/chatbox.js +127 -72
- package/src/ui/prompt.js +11 -0
package/package.json
CHANGED
package/src/commands/index.js
CHANGED
|
@@ -267,6 +267,7 @@ const commandRegistry = {
|
|
|
267
267
|
}]);
|
|
268
268
|
settings.set('defaultModel', model);
|
|
269
269
|
blocks.success('Model Set', `Default model: ${model}`);
|
|
270
|
+
return { resetRL: true };
|
|
270
271
|
},
|
|
271
272
|
|
|
272
273
|
async provider(args) {
|
|
@@ -300,6 +301,7 @@ const commandRegistry = {
|
|
|
300
301
|
} else {
|
|
301
302
|
blocks.warn('No Models', `No models listed for ${PROVIDER_NAMES[provider]}`);
|
|
302
303
|
}
|
|
304
|
+
return { resetRL: true };
|
|
303
305
|
},
|
|
304
306
|
|
|
305
307
|
config(args) {
|
package/src/ui/chatbox.js
CHANGED
|
@@ -15,10 +15,15 @@ import { readFileSync } from 'fs';
|
|
|
15
15
|
|
|
16
16
|
const PKG = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url)));
|
|
17
17
|
const VERSION = PKG.version;
|
|
18
|
+
|
|
18
19
|
const RESET = '\x1b[0m';
|
|
20
|
+
const GREY_BG = '\x1b[48;5;236m';
|
|
21
|
+
const PURPLE_BG = '\x1b[48;5;53m';
|
|
22
|
+
const FILL = '░';
|
|
19
23
|
|
|
20
24
|
let rl = null;
|
|
21
25
|
let conversation = [];
|
|
26
|
+
let cmdBuffer = '';
|
|
22
27
|
|
|
23
28
|
const SYSTEM_PROMPT = `You are CLARITY, an autonomous AI agent running in the user's terminal (Termux on Android).
|
|
24
29
|
|
|
@@ -34,20 +39,26 @@ Current environment: ${process.platform} ${process.arch}, Node ${process.version
|
|
|
34
39
|
Directory: ${process.cwd()}
|
|
35
40
|
Termux: ${isTermux()}`;
|
|
36
41
|
|
|
37
|
-
|
|
42
|
+
function getW() {
|
|
43
|
+
return process.stdout.columns || 80;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function fill(w) {
|
|
47
|
+
return FILL.repeat(w);
|
|
48
|
+
}
|
|
38
49
|
|
|
39
50
|
function renderPromptBar() {
|
|
40
51
|
const model = settings.get('defaultModel') || 'groq/llama-3.3-70b-versatile';
|
|
41
|
-
const w =
|
|
52
|
+
const w = getW();
|
|
42
53
|
const line = '━'.repeat(w - 4);
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const
|
|
54
|
+
const left = ` ${model} `;
|
|
55
|
+
const right = `v${VERSION} /help `;
|
|
56
|
+
const gap = Math.max(0, w - 4 - left.length - right.length);
|
|
46
57
|
|
|
47
58
|
console.log(c.accent(` ┏${line}┓`));
|
|
48
|
-
console.log(c.accent(` ┃${GREY_BG}${
|
|
49
|
-
console.log(c.accent(` ┃${GREY_BG}
|
|
50
|
-
console.log(c.accent(` ┃${GREY_BG}${
|
|
59
|
+
console.log(c.accent(` ┃${GREY_BG}${fill(w - 4)}${RESET}${c.accent('┃')}`));
|
|
60
|
+
console.log(c.accent(` ┃${GREY_BG}${c.muted(left)}${fill(gap)}${c.muted(right)}${RESET}${c.accent('┃')}`));
|
|
61
|
+
console.log(c.accent(` ┃${GREY_BG}${fill(w - 4)}${RESET}${c.accent('┃')}`));
|
|
51
62
|
console.log(c.accent(` ┗${line}┛`));
|
|
52
63
|
}
|
|
53
64
|
|
|
@@ -55,6 +66,61 @@ function showPrompt() {
|
|
|
55
66
|
process.stdout.write(` ${c.accent('◆')} `);
|
|
56
67
|
}
|
|
57
68
|
|
|
69
|
+
function attachReadlineHandlers() {
|
|
70
|
+
rl.removeAllListeners('line');
|
|
71
|
+
rl.removeAllListeners('close');
|
|
72
|
+
rl.removeAllListeners('SIGINT');
|
|
73
|
+
|
|
74
|
+
rl.on('line', onLine);
|
|
75
|
+
rl.on('close', () => {
|
|
76
|
+
console.log(c.muted('\nGoodbye!'));
|
|
77
|
+
process.exit(0);
|
|
78
|
+
});
|
|
79
|
+
rl.on('SIGINT', () => {
|
|
80
|
+
console.log(c.muted('\nUse /exit to quit'));
|
|
81
|
+
renderPromptBar();
|
|
82
|
+
showPrompt();
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function onLine(line) {
|
|
87
|
+
const input = line.trim();
|
|
88
|
+
if (!input) { renderPromptBar(); showPrompt(); return; }
|
|
89
|
+
cmdBuffer = '';
|
|
90
|
+
addToHistory(input);
|
|
91
|
+
|
|
92
|
+
if (input.startsWith('/')) {
|
|
93
|
+
const result = await commandRegistry.execute(input, { rl, conversation, showPrompt });
|
|
94
|
+
if (result?.exit) { closeChat(); return; }
|
|
95
|
+
if (result?.resetRL) {
|
|
96
|
+
rl.close();
|
|
97
|
+
rl = createPrompt();
|
|
98
|
+
attachReadlineHandlers();
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
conversation.push({ role: 'user', content: input });
|
|
102
|
+
renderUserMsg(input);
|
|
103
|
+
await handleAIResponse();
|
|
104
|
+
}
|
|
105
|
+
renderPromptBar();
|
|
106
|
+
showPrompt();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function renderUserMsg(text) {
|
|
110
|
+
const w = getW();
|
|
111
|
+
const line = '─'.repeat(w - 4);
|
|
112
|
+
console.log();
|
|
113
|
+
console.log(c.primary(` ┌${line}┐`));
|
|
114
|
+
console.log(c.primary(` │${GREY_BG}${fill(w - 4)}${RESET}${c.primary('│')}`));
|
|
115
|
+
text.split('\n').forEach(l => {
|
|
116
|
+
const clean = l.replace(/\x1b\[[0-9;]*m/g, '');
|
|
117
|
+
console.log(c.primary(` │${GREY_BG} ${c.user(l)}${fill(Math.max(0, w - 6 - clean.length))}${RESET}${c.primary('│')}`));
|
|
118
|
+
});
|
|
119
|
+
console.log(c.primary(` │${GREY_BG}${fill(w - 4)}${RESET}${c.primary('│')}`));
|
|
120
|
+
console.log(c.primary(` └${line}┘`));
|
|
121
|
+
console.log();
|
|
122
|
+
}
|
|
123
|
+
|
|
58
124
|
function startChat() {
|
|
59
125
|
if (!hasAnyKeys()) {
|
|
60
126
|
blocks.warn('No API Keys', 'Run /init to configure API keys first.');
|
|
@@ -68,64 +134,40 @@ function startChat() {
|
|
|
68
134
|
renderPromptBar();
|
|
69
135
|
showPrompt();
|
|
70
136
|
rl = createPrompt();
|
|
137
|
+
attachReadlineHandlers();
|
|
71
138
|
|
|
72
|
-
|
|
139
|
+
process.stdin.on('keypress', onKeypress);
|
|
140
|
+
}
|
|
73
141
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
cmdBuffer = '/';
|
|
77
|
-
const input = rl.line || '';
|
|
78
|
-
readline.clearLine(process.stdout, 0);
|
|
79
|
-
readline.cursorTo(process.stdout, 0);
|
|
80
|
-
suggestCommands(input);
|
|
81
|
-
rl._refreshLine();
|
|
82
|
-
}
|
|
83
|
-
if (key && key.name === 'escape') {
|
|
84
|
-
if (cmdBuffer) { cmdBuffer = ''; }
|
|
85
|
-
}
|
|
86
|
-
});
|
|
142
|
+
function onKeypress(char, key) {
|
|
143
|
+
if (!key) return;
|
|
87
144
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
145
|
+
if (key.name === 'slash' && !cmdBuffer) {
|
|
146
|
+
cmdBuffer = '/';
|
|
147
|
+
showCmdList();
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (key.name === 'escape') {
|
|
91
151
|
cmdBuffer = '';
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (input.startsWith('/')) {
|
|
95
|
-
const result = await commandRegistry.execute(input, { rl, conversation, showPrompt });
|
|
96
|
-
if (result?.exit) { closeChat(); return; }
|
|
97
|
-
} else {
|
|
98
|
-
conversation.push({ role: 'user', content: input });
|
|
99
|
-
await handleAIResponse();
|
|
100
|
-
}
|
|
101
|
-
renderPromptBar();
|
|
102
|
-
showPrompt();
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
rl.on('close', () => {
|
|
106
|
-
console.log(c.muted('\nGoodbye!'));
|
|
107
|
-
process.exit(0);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
rl.on('SIGINT', () => {
|
|
111
|
-
console.log(c.muted('\nUse /exit to quit'));
|
|
112
|
-
renderPromptBar();
|
|
113
|
-
showPrompt();
|
|
114
|
-
});
|
|
152
|
+
}
|
|
115
153
|
}
|
|
116
154
|
|
|
117
|
-
function
|
|
118
|
-
const w =
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
155
|
+
function showCmdList() {
|
|
156
|
+
const w = getW();
|
|
157
|
+
readline.clearLine(process.stdout, 0);
|
|
158
|
+
readline.cursorTo(process.stdout, 0);
|
|
159
|
+
console.log(c.accent(` ┌${'─'.repeat(w - 6)}┐`));
|
|
160
|
+
console.log(c.accent(` │${GREY_BG}${fill(w - 6)}${RESET}${c.accent('│')}`));
|
|
161
|
+
const top = ALL_COMMANDS.slice(0, 8);
|
|
124
162
|
top.forEach(([cmd, desc], i) => {
|
|
125
|
-
const tag =
|
|
126
|
-
console.log(` ${tag} ${c.muted(desc)}`);
|
|
163
|
+
const tag = i === 0 ? c.primary('/' + cmd) : c.accent('/' + cmd);
|
|
164
|
+
console.log(c.accent(` │${GREY_BG} ${tag}${fill(Math.max(0, w - 10 - cmd.length - desc.length))}${c.muted(desc)} ${RESET}${c.accent('│')}`));
|
|
127
165
|
});
|
|
128
|
-
|
|
166
|
+
if (ALL_COMMANDS.length > 8) {
|
|
167
|
+
console.log(c.accent(` │${GREY_BG} ${c.muted('... and ' + (ALL_COMMANDS.length - 8) + ' more — Tab to complete')}${fill(Math.max(0, w - 12 - 28))}${RESET}${c.accent('│')}`));
|
|
168
|
+
}
|
|
169
|
+
console.log(c.accent(` │${GREY_BG}${fill(w - 6)}${RESET}${c.accent('│')}`));
|
|
170
|
+
console.log(c.accent(` └${'─'.repeat(w - 6)}┘`));
|
|
129
171
|
}
|
|
130
172
|
|
|
131
173
|
async function handleAIResponse() {
|
|
@@ -134,40 +176,51 @@ async function handleAIResponse() {
|
|
|
134
176
|
const apiKey = getKey(provider);
|
|
135
177
|
|
|
136
178
|
if (!apiKey) {
|
|
137
|
-
blocks.error('Key Missing', `No
|
|
179
|
+
blocks.error('Key Missing', `No key for ${PROVIDER_NAMES[provider] || provider}`);
|
|
138
180
|
return;
|
|
139
181
|
}
|
|
140
182
|
|
|
141
|
-
const systemMsg = {
|
|
183
|
+
const systemMsg = {
|
|
184
|
+
role: 'system',
|
|
185
|
+
content: SYSTEM_PROMPT + '\n\nUser memory: ' + (memory.show().filter(m => m.role === 'system').map(m => m.content).join('; ') || 'none')
|
|
186
|
+
};
|
|
142
187
|
const messages = [systemMsg, ...conversation];
|
|
143
188
|
|
|
144
|
-
spin.start('
|
|
189
|
+
spin.start('thinking...');
|
|
145
190
|
|
|
146
191
|
try {
|
|
147
192
|
const stream = sendMessage(apiKey, messages, model, settings.get('stream'));
|
|
193
|
+
const streaming = settings.get('stream');
|
|
148
194
|
|
|
149
|
-
if (
|
|
195
|
+
if (streaming) {
|
|
150
196
|
spin.stop();
|
|
151
|
-
const w =
|
|
197
|
+
const w = getW();
|
|
152
198
|
const line = '━'.repeat(w - 4);
|
|
153
|
-
|
|
199
|
+
|
|
154
200
|
console.log(c.accent(` ┏${line}┓`));
|
|
155
|
-
console.log(c.accent(` ┃${PURPLE_BG}${
|
|
156
|
-
process.stdout.write(c.accent(` ┃${PURPLE_BG} ${c.ai('CLARITY')}
|
|
201
|
+
console.log(c.accent(` ┃${PURPLE_BG}${fill(w - 4)}${RESET}${c.accent('┃')}`));
|
|
202
|
+
process.stdout.write(c.accent(` ┃${PURPLE_BG} ${c.ai('CLARITY')} `));
|
|
157
203
|
let full = '';
|
|
204
|
+
|
|
158
205
|
try {
|
|
159
206
|
for await (const chunk of stream) {
|
|
160
207
|
full += chunk;
|
|
161
|
-
process.stdout.write(
|
|
208
|
+
process.stdout.write(chunk);
|
|
162
209
|
}
|
|
163
210
|
} catch (streamErr) {
|
|
164
|
-
if (full) process.stdout.write(c.warning('\n\n[
|
|
211
|
+
if (full) process.stdout.write(c.warning('\n\n[interrupted]'));
|
|
165
212
|
else throw streamErr;
|
|
166
213
|
}
|
|
214
|
+
|
|
215
|
+
const clean = full.replace(/\x1b\[[0-9;]*m/g, '');
|
|
216
|
+
const lastLine = clean.includes('\n') ? clean.split('\n').pop() : clean;
|
|
217
|
+
const remain = Math.max(0, w - 8 - lastLine.length);
|
|
218
|
+
process.stdout.write(`${fill(remain)}${RESET}${c.accent('┃')}`);
|
|
167
219
|
console.log();
|
|
168
|
-
console.log(c.accent(` ┃${PURPLE_BG}${
|
|
220
|
+
console.log(c.accent(` ┃${PURPLE_BG}${fill(w - 4)}${RESET}${c.accent('┃')}`));
|
|
169
221
|
console.log(c.accent(` ┗${line}┛`));
|
|
170
222
|
console.log();
|
|
223
|
+
|
|
171
224
|
if (full.trim()) {
|
|
172
225
|
conversation.push({ role: 'assistant', content: full });
|
|
173
226
|
memory.add(conversation);
|
|
@@ -186,9 +239,9 @@ async function handleAIResponse() {
|
|
|
186
239
|
}
|
|
187
240
|
|
|
188
241
|
if (settings.get('showTokens')) {
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
console.log(c.dim(` tokens: ${
|
|
242
|
+
const inTok = Math.ceil(conversation.reduce((s, m) => s + m.content.length, 0) / 4);
|
|
243
|
+
const outTok = Math.ceil(conversation.filter(m => m.role === 'assistant').reduce((s, m) => s + m.content.length, 0) / 4);
|
|
244
|
+
console.log(c.dim(` tokens: ${inTok} in / ${outTok} out`));
|
|
192
245
|
}
|
|
193
246
|
} catch (err) {
|
|
194
247
|
spin.fail('Error');
|
|
@@ -197,7 +250,9 @@ async function handleAIResponse() {
|
|
|
197
250
|
}
|
|
198
251
|
|
|
199
252
|
function closeChat() {
|
|
253
|
+
process.stdin.removeListener('keypress', onKeypress);
|
|
200
254
|
if (rl) rl.close();
|
|
255
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
201
256
|
console.log(c.muted('\nGoodbye!'));
|
|
202
257
|
process.exit(0);
|
|
203
258
|
}
|
package/src/ui/prompt.js
CHANGED
|
@@ -108,6 +108,17 @@ function createPrompt() {
|
|
|
108
108
|
historySize: MAX_HISTORY,
|
|
109
109
|
terminal: true,
|
|
110
110
|
prompt: '',
|
|
111
|
+
completer: (line) => {
|
|
112
|
+
if (line.startsWith('/')) {
|
|
113
|
+
const hits = ALL_COMMANDS.filter(([cmd]) => {
|
|
114
|
+
const fullCmd = '/' + cmd;
|
|
115
|
+
return fullCmd.startsWith(line);
|
|
116
|
+
}).map(([cmd]) => '/' + cmd);
|
|
117
|
+
if (hits.length === 0) return [[], line];
|
|
118
|
+
return [hits, line];
|
|
119
|
+
}
|
|
120
|
+
return [[], line];
|
|
121
|
+
}
|
|
111
122
|
});
|
|
112
123
|
return rl;
|
|
113
124
|
}
|