clarity-ai 1.1.1 → 1.3.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/package.json +2 -13
- package/scripts/postinstall.js +18 -17
- package/src/commands/index.js +67 -20
- package/src/config/settings.js +2 -2
- package/src/providers/groq.js +8 -4
- package/src/providers/index.js +6 -6
- package/src/ui/blocks.js +73 -53
- package/src/ui/chatbox.js +103 -30
- package/src/ui/prompt.js +69 -7
- package/src/utils/version-check.js +29 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clarity-ai",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "AI agent CLI for Termux and terminal — chat, code, automate",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -17,25 +17,14 @@
|
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"chalk": "^5.3.0",
|
|
19
19
|
"inquirer": "^9.2.12",
|
|
20
|
-
"node-fetch": "^3.3.2",
|
|
21
20
|
"conf": "^12.0.0",
|
|
22
21
|
"marked": "^12.0.0",
|
|
23
22
|
"marked-terminal": "^7.1.0",
|
|
24
|
-
"ora": "^8.0.1",
|
|
25
|
-
"boxen": "^7.1.1",
|
|
26
23
|
"figlet": "^1.7.0",
|
|
27
24
|
"gradient-string": "^2.0.2",
|
|
28
25
|
"cli-table3": "^0.6.3",
|
|
29
|
-
"commander": "^12.0.0",
|
|
30
|
-
"dotenv": "^16.4.5",
|
|
31
|
-
"axios": "^1.6.8",
|
|
32
|
-
"fs-extra": "^11.2.0",
|
|
33
|
-
"glob": "^10.3.12",
|
|
34
|
-
"open": "^10.1.0",
|
|
35
|
-
"update-notifier": "^7.0.0",
|
|
36
26
|
"semver": "^7.6.0",
|
|
37
|
-
"
|
|
38
|
-
"wrap-ansi": "^9.0.0"
|
|
27
|
+
"glob": "^10.3.12"
|
|
39
28
|
},
|
|
40
29
|
"keywords": [
|
|
41
30
|
"ai",
|
package/scripts/postinstall.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import figlet from 'figlet';
|
|
2
2
|
import gradient from 'gradient-string';
|
|
3
|
-
import boxen from 'boxen';
|
|
4
3
|
import chalk from 'chalk';
|
|
5
4
|
|
|
6
5
|
const GRADIENT = gradient(['#00d2ff', '#7b2ff7']);
|
|
@@ -14,40 +13,42 @@ try {
|
|
|
14
13
|
|
|
15
14
|
const nodeVersion = process.versions.node;
|
|
16
15
|
const [major] = nodeVersion.split('.').map(Number);
|
|
16
|
+
const W = process.stdout.columns || 80;
|
|
17
|
+
const line = '═'.repeat(W - 4);
|
|
17
18
|
|
|
18
19
|
if (major < 18) {
|
|
19
|
-
console.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
));
|
|
20
|
+
console.error();
|
|
21
|
+
console.error(chalk.hex('#ff4466')(` ╔${line}╗`));
|
|
22
|
+
console.error(chalk.hex('#ff4466')(` ║ ✗ Node.js 18+ required. You have v${nodeVersion}`));
|
|
23
|
+
console.error(chalk.hex('#ffcc00')(` ║ Termux: pkg install nodejs-lts`));
|
|
24
|
+
console.error(chalk.hex('#ff4466')(` ╚${line}╝`));
|
|
25
|
+
console.error();
|
|
24
26
|
process.exit(1);
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
const isTermux = process.env.PREFIX?.includes('com.termux');
|
|
28
30
|
|
|
29
|
-
console.log(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
));
|
|
34
|
-
|
|
35
|
-
console.log(chalk.hex('#666888')("Run 'clarity init' to configure your AI keys and get started."));
|
|
31
|
+
console.log();
|
|
32
|
+
console.log(chalk.hex('#00ff88')(` ╔${line}╗`));
|
|
33
|
+
console.log(chalk.hex('#00ff88')(` ║ ✓ CLARITY installed successfully!`));
|
|
34
|
+
console.log(chalk.hex('#00d2ff')(` ║ Run: clarity init`));
|
|
35
|
+
console.log(chalk.hex('#00ff88')(` ╚${line}╝`));
|
|
36
|
+
console.log();
|
|
37
|
+
console.log(chalk.hex('#666888')(" Run 'clarity init' to configure your AI keys and get started."));
|
|
36
38
|
|
|
37
39
|
const binDir = process.env.npm_config_prefix ? `${process.env.npm_config_prefix}/bin` : null;
|
|
38
40
|
if (isTermux && binDir) {
|
|
39
|
-
const { existsSync, writeFileSync, chmodSync, unlinkSync } = await import('fs');
|
|
41
|
+
const { existsSync, writeFileSync, chmodSync, unlinkSync, lstatSync } = await import('fs');
|
|
40
42
|
const { resolve } = await import('path');
|
|
41
43
|
const wrapperPath = resolve(binDir, 'clarity');
|
|
42
|
-
const targetPath = resolve(process.argv[1] || '.', '..', 'bin', 'clarity.js');
|
|
43
44
|
|
|
44
45
|
try {
|
|
45
|
-
const stat =
|
|
46
|
+
const stat = lstatSync(wrapperPath);
|
|
46
47
|
if (stat.isSymbolicLink()) {
|
|
47
48
|
unlinkSync(wrapperPath);
|
|
48
49
|
writeFileSync(wrapperPath, `#!/data/data/com.termux/files/usr/bin/bash\nexec node "${resolve(process.env.PREFIX || '/usr', 'lib/node_modules/clarity-ai/bin/clarity.js')}" "$@"\n`);
|
|
49
50
|
chmodSync(wrapperPath, '755');
|
|
50
|
-
console.log(chalk.hex('#00ff88')('✓ Termux wrapper created at:'), chalk.hex('#00d2ff')(wrapperPath));
|
|
51
|
+
console.log(chalk.hex('#00ff88')(' ✓ Termux wrapper created at:'), chalk.hex('#00d2ff')(wrapperPath));
|
|
51
52
|
}
|
|
52
53
|
} catch {}
|
|
53
54
|
}
|
package/src/commands/index.js
CHANGED
|
@@ -33,6 +33,7 @@ const commandRegistry = {
|
|
|
33
33
|
|
|
34
34
|
case '/keys': return this.keys(args);
|
|
35
35
|
case '/model': return this.model(args);
|
|
36
|
+
case '/provider': return this.provider(args);
|
|
36
37
|
case '/config': return this.config(args);
|
|
37
38
|
case '/theme': return this.theme(args);
|
|
38
39
|
|
|
@@ -147,7 +148,7 @@ const commandRegistry = {
|
|
|
147
148
|
|
|
148
149
|
const categories = {
|
|
149
150
|
'CHAT & AI': ['/chat', '/ask <q>', '/clear', '/history show|clear|export', '/memory show|add|clear'],
|
|
150
|
-
'CONFIGURATION': ['/init', '/keys set|list|remove|test', '/
|
|
151
|
+
'CONFIGURATION': ['/init', '/keys set|list|remove|test', '/provider', '/model', '/config show|reset', '/theme set|list'],
|
|
151
152
|
'FILES': ['/file create|read|delete|list|edit', '/bash <command>', '/code run|write|explain|fix|review|refactor|test|docs'],
|
|
152
153
|
'WEB & SEARCH': ['/web <url>', '/search <query>', '/translate <text>', '/summarize <file|url>'],
|
|
153
154
|
'AGENTS & TOOLS': ['/agent start|stop|list|logs', '/tools list|run'],
|
|
@@ -232,26 +233,72 @@ const commandRegistry = {
|
|
|
232
233
|
|
|
233
234
|
async model(args) {
|
|
234
235
|
const sub = args[0];
|
|
235
|
-
if (
|
|
236
|
+
if (sub === 'set') {
|
|
237
|
+
if (args.length < 2) { blocks.warn('Usage', '/model set <provider/model>'); return; }
|
|
238
|
+
settings.set('defaultModel', args[1]);
|
|
239
|
+
blocks.success('Model Set', `Default model: ${args[1]}`);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
236
242
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
243
|
+
const configured = Object.keys(listKeys());
|
|
244
|
+
if (configured.length === 0) { blocks.info('No Keys', 'Configure keys first with /init'); return; }
|
|
245
|
+
|
|
246
|
+
const { default: inquirer } = await import('inquirer');
|
|
247
|
+
const choices = [];
|
|
248
|
+
for (const p of configured) {
|
|
249
|
+
const models = listModels(p);
|
|
250
|
+
models.forEach(m => {
|
|
251
|
+
choices.push({ name: `${p}/${m}`, value: `${p}/${m}` });
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (choices.length === 0) { blocks.warn('No Models', 'No models available for configured providers.'); return; }
|
|
256
|
+
|
|
257
|
+
const current = settings.get('defaultModel') || 'groq/llama-3.3-70b-versatile';
|
|
258
|
+
const { model } = await inquirer.prompt([{
|
|
259
|
+
type: 'list',
|
|
260
|
+
name: 'model',
|
|
261
|
+
message: 'Select AI model:',
|
|
262
|
+
pageSize: 15,
|
|
263
|
+
choices: choices.map(c => ({
|
|
264
|
+
...c,
|
|
265
|
+
checked: c.value === current,
|
|
266
|
+
})),
|
|
267
|
+
}]);
|
|
268
|
+
settings.set('defaultModel', model);
|
|
269
|
+
blocks.success('Model Set', `Default model: ${model}`);
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
async provider(args) {
|
|
273
|
+
const configured = Object.keys(listKeys());
|
|
274
|
+
if (configured.length === 0) { blocks.info('No Keys', 'Configure keys first with /init'); return; }
|
|
275
|
+
|
|
276
|
+
const { default: inquirer } = await import('inquirer');
|
|
277
|
+
const { provider } = await inquirer.prompt([{
|
|
278
|
+
type: 'list',
|
|
279
|
+
name: 'provider',
|
|
280
|
+
message: 'Select AI provider:',
|
|
281
|
+
choices: configured.map(p => ({
|
|
282
|
+
name: PROVIDER_NAMES[p] || p,
|
|
283
|
+
value: p,
|
|
284
|
+
})),
|
|
285
|
+
}]);
|
|
286
|
+
|
|
287
|
+
const models = listModels(provider);
|
|
288
|
+
if (models.length > 0) {
|
|
289
|
+
const { model } = await inquirer.prompt([{
|
|
290
|
+
type: 'list',
|
|
291
|
+
name: 'model',
|
|
292
|
+
message: `Select ${PROVIDER_NAMES[provider]} model:`,
|
|
293
|
+
choices: models.map(m => ({
|
|
294
|
+
name: `${provider}/${m}`,
|
|
295
|
+
value: `${provider}/${m}`,
|
|
296
|
+
})),
|
|
297
|
+
}]);
|
|
298
|
+
settings.set('defaultModel', model);
|
|
299
|
+
blocks.success('Provider & Model Set', `${PROVIDER_NAMES[provider]} → ${model}`);
|
|
300
|
+
} else {
|
|
301
|
+
blocks.warn('No Models', `No models listed for ${PROVIDER_NAMES[provider]}`);
|
|
255
302
|
}
|
|
256
303
|
},
|
|
257
304
|
|
package/src/config/settings.js
CHANGED
|
@@ -3,7 +3,7 @@ import paths from './paths.js';
|
|
|
3
3
|
|
|
4
4
|
const schema = {
|
|
5
5
|
theme: { type: 'string', default: 'dark' },
|
|
6
|
-
|
|
6
|
+
defaultModel: { type: 'string', default: 'groq/llama-3.3-70b-versatile' },
|
|
7
7
|
stream: { type: 'boolean', default: true },
|
|
8
8
|
showTokens: { type: 'boolean', default: true },
|
|
9
9
|
saveHistory: { type: 'boolean', default: true },
|
|
@@ -17,7 +17,7 @@ const settings = new Conf({
|
|
|
17
17
|
cwd: paths.config,
|
|
18
18
|
defaults: {
|
|
19
19
|
theme: 'dark',
|
|
20
|
-
defaultModel: 'groq/
|
|
20
|
+
defaultModel: 'groq/llama-3.3-70b-versatile',
|
|
21
21
|
stream: true,
|
|
22
22
|
showTokens: true,
|
|
23
23
|
saveHistory: true,
|
package/src/providers/groq.js
CHANGED
|
@@ -8,15 +8,19 @@ const PROVIDER = {
|
|
|
8
8
|
|
|
9
9
|
async function* sendMessage(apiKey, messages, model = 'llama3-70b-8192', stream = true) {
|
|
10
10
|
const url = `${PROVIDER.baseURL}/chat/completions`;
|
|
11
|
-
const body = { model, messages, stream };
|
|
12
|
-
|
|
11
|
+
const body = { model, messages, stream, max_tokens: 4096 };
|
|
12
|
+
|
|
13
13
|
const res = await fetch(url, {
|
|
14
14
|
method: 'POST',
|
|
15
15
|
headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
|
|
16
16
|
body: JSON.stringify(body),
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
if (!res.ok)
|
|
19
|
+
if (!res.ok) {
|
|
20
|
+
let detail = '';
|
|
21
|
+
try { const e = await res.json(); detail = e.error?.message || JSON.stringify(e); } catch { detail = res.statusText; }
|
|
22
|
+
throw new Error(`Groq API error: ${res.status} — ${detail}`);
|
|
23
|
+
}
|
|
20
24
|
|
|
21
25
|
if (!stream) {
|
|
22
26
|
const data = await res.json();
|
|
@@ -34,7 +38,7 @@ async function* sendMessage(apiKey, messages, model = 'llama3-70b-8192', stream
|
|
|
34
38
|
buffer += decoder.decode(value, { stream: true });
|
|
35
39
|
const lines = buffer.split('\n');
|
|
36
40
|
buffer = lines.pop() || '';
|
|
37
|
-
|
|
41
|
+
|
|
38
42
|
for (const line of lines) {
|
|
39
43
|
if (line.startsWith('data: ')) {
|
|
40
44
|
const data = line.slice(6).trim();
|
package/src/providers/index.js
CHANGED
|
@@ -10,12 +10,12 @@ const providers = { groq, gemini, deepseek, openrouter, openai, anthropic: claud
|
|
|
10
10
|
const PRIORITY = ['groq', 'gemini', 'deepseek', 'openrouter', 'openai', 'anthropic'];
|
|
11
11
|
|
|
12
12
|
const capabilities = {
|
|
13
|
-
groq: { free: true, streaming: true, models: ['
|
|
14
|
-
gemini: { free: true, streaming: true, models: ['gemini-
|
|
15
|
-
deepseek: { free: false, cheap: true, streaming: true, models: ['deepseek-chat', 'deepseek-coder'], baseURL: 'https://api.deepseek.com/v1' },
|
|
16
|
-
openrouter: { free: true, streaming: true, models: ['meta-llama/llama-3.
|
|
17
|
-
anthropic: { free: false, streaming: true, models: ['claude-3-5-haiku-
|
|
18
|
-
openai: { free: false, streaming: true, models: ['gpt-4o-mini', 'gpt-4o', 'gpt-
|
|
13
|
+
groq: { free: true, streaming: true, models: ['llama-3.3-70b-versatile', 'llama-3.1-8b-instant', 'meta-llama/llama-4-scout-17b-16e-instruct', 'meta-llama/llama-4-maverick-17b-128e-instruct', 'mixtral-8x7b-32768', 'gemma2-9b-it', 'qwen/qwen3-32b', 'deepseek-r1-distill-llama-70b', 'moonshotai/kimi-k2-instruct'], baseURL: 'https://api.groq.com/openai/v1' },
|
|
14
|
+
gemini: { free: true, streaming: true, models: ['gemini-2.0-flash', 'gemini-2.5-pro-exp-03-25', 'gemini-1.5-flash', 'gemini-1.5-pro'], baseURL: 'https://generativelanguage.googleapis.com/v1beta' },
|
|
15
|
+
deepseek: { free: false, cheap: true, streaming: true, models: ['deepseek-chat', 'deepseek-coder', 'deepseek-reasoner'], baseURL: 'https://api.deepseek.com/v1' },
|
|
16
|
+
openrouter: { free: true, streaming: true, models: ['meta-llama/llama-3.3-70b-instruct:free', 'google/gemma-2-9b-it:free', 'mistralai/mistral-7b-instruct:free', 'deepseek/deepseek-chat:free', 'qwen/qwen-2.5-72b-instruct:free'], baseURL: 'https://openrouter.ai/api/v1' },
|
|
17
|
+
anthropic: { free: false, streaming: true, models: ['claude-3-5-haiku-latest', 'claude-3-5-sonnet-latest', 'claude-3-haiku'], baseURL: 'https://api.anthropic.com/v1' },
|
|
18
|
+
openai: { free: false, streaming: true, models: ['gpt-4o-mini', 'gpt-4o', 'gpt-4.1', 'o3-mini'], baseURL: 'https://api.openai.com/v1' },
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
function sendMessage(apiKey, messages, model, stream = true) {
|
package/src/ui/blocks.js
CHANGED
|
@@ -1,74 +1,82 @@
|
|
|
1
1
|
import cliTable3 from 'cli-table3';
|
|
2
2
|
import c from './colors.js';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
const w = process.stdout.columns || 80;
|
|
6
|
-
console.log(color(char.repeat(w)));
|
|
7
|
-
}
|
|
4
|
+
const W = () => process.stdout.columns || 80;
|
|
8
5
|
|
|
9
|
-
function
|
|
10
|
-
|
|
6
|
+
function mid(text) {
|
|
7
|
+
const w = W();
|
|
8
|
+
const pad = Math.max(0, Math.floor((w - text.length) / 2));
|
|
9
|
+
return ' '.repeat(pad) + text;
|
|
11
10
|
}
|
|
12
11
|
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
const FILL = '░';
|
|
13
|
+
const FILL_BG = '\x1b[48;5;17m';
|
|
14
|
+
const FILL_BG_PURPLE = '\x1b[48;5;53m';
|
|
15
|
+
const FILL_BG_RED = '\x1b[48;5;52m';
|
|
16
|
+
const FILL_BG_GREEN = '\x1b[48;5;22m';
|
|
17
|
+
const FILL_BG_YELLOW = '\x1b[48;5;58m';
|
|
18
|
+
const FILL_BG_BLUE = '\x1b[48;5;19m';
|
|
19
|
+
const RESET = '\x1b[0m';
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
},
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
console.log(c.warning(` └${'─'.repeat((process.stdout.columns || 80) - 4)}┘`));
|
|
35
|
-
gap();
|
|
36
|
-
},
|
|
21
|
+
function shadeBox(color, title, msg) {
|
|
22
|
+
const w = W();
|
|
23
|
+
const line = '━'.repeat(w - 4);
|
|
24
|
+
console.log();
|
|
25
|
+
console.log(color(` ┏${line}┓`));
|
|
26
|
+
console.log(color(` ┃${FILL_BG}${' '.repeat(w - 4)}${RESET}${color('┃')}`));
|
|
27
|
+
if (title) console.log(color(` ┃${FILL_BG} ${title}${' '.repeat(Math.max(0, w - 8 - title.length))}${RESET}${color('┃')}`));
|
|
28
|
+
msg.split('\n').forEach(l => console.log(color(` ┃${FILL_BG} ${c.white(l)}${' '.repeat(Math.max(0, w - 6 - l.length))}${RESET}${color('┃')}`)));
|
|
29
|
+
console.log(color(` ┃${FILL_BG}${' '.repeat(w - 4)}${RESET}${color('┃')}`));
|
|
30
|
+
console.log(color(` ┗${line}┛`));
|
|
31
|
+
console.log();
|
|
32
|
+
}
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
gap();
|
|
44
|
-
},
|
|
34
|
+
const blocks = {
|
|
35
|
+
info(title, msg) { shadeBox(c.info, `ℹ ${title}`, msg); },
|
|
36
|
+
success(title, msg) { shadeBox(c.success, `✓ ${title}`, msg); },
|
|
37
|
+
warn(title, msg) { shadeBox(c.warning, `⚠ ${title}`, msg); },
|
|
38
|
+
error(title, msg) { shadeBox(c.error, `✗ ${title}`, msg); },
|
|
45
39
|
|
|
46
40
|
code(lang, code) {
|
|
47
|
-
|
|
41
|
+
const w = W();
|
|
42
|
+
console.log(c.dim(` ┌─ ${lang} ${'─'.repeat(Math.max(0, w - lang.length - 8))}`));
|
|
48
43
|
code.split('\n').forEach(l => console.log(c.code(` │ ${l}`)));
|
|
49
|
-
console.log(c.dim(` └${'─'.repeat(
|
|
44
|
+
console.log(c.dim(` └${'─'.repeat(w - 4)}`));
|
|
50
45
|
},
|
|
51
46
|
|
|
52
47
|
file(path, content) {
|
|
48
|
+
const w = W();
|
|
53
49
|
console.log(c.filename(` ┌─ ${path}`));
|
|
54
50
|
content.split('\n').forEach(l => console.log(c.white(` │ ${l}`)));
|
|
55
|
-
console.log(c.dim(` └${'─'.repeat(
|
|
51
|
+
console.log(c.dim(` └${'─'.repeat(w - 4)}`));
|
|
56
52
|
},
|
|
57
53
|
|
|
58
|
-
ai(msg) {
|
|
59
|
-
const w =
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
console.log(c.accent(`
|
|
63
|
-
|
|
54
|
+
ai(msg, title = 'CLARITY') {
|
|
55
|
+
const w = W();
|
|
56
|
+
const line = '═'.repeat(w - 4);
|
|
57
|
+
console.log();
|
|
58
|
+
console.log(c.accent(` ╔${line}╗`));
|
|
59
|
+
console.log(c.accent(` ║${FILL_BG_PURPLE}${' '.repeat(w - 4)}${RESET}${c.accent('║')}`));
|
|
60
|
+
if (title) console.log(c.accent(` ║${FILL_BG_PURPLE} ${c.ai(title)}${' '.repeat(Math.max(0, w - 8 - title.length))}${RESET}${c.accent('║')}`));
|
|
61
|
+
msg.split('\n').forEach(l => {
|
|
62
|
+
const clean = l.replace(/\x1b\[[0-9;]*m/g, '');
|
|
63
|
+
console.log(c.accent(` ║${FILL_BG_PURPLE} ${c.white(l)}${' '.repeat(Math.max(0, w - 6 - clean.length))}${RESET}${c.accent('║')}`));
|
|
64
|
+
});
|
|
65
|
+
console.log(c.accent(` ║${FILL_BG_PURPLE}${' '.repeat(w - 4)}${RESET}${c.accent('║')}`));
|
|
66
|
+
console.log(c.accent(` ╚${line}╝`));
|
|
67
|
+
console.log();
|
|
64
68
|
},
|
|
65
69
|
|
|
66
70
|
user(msg) {
|
|
67
|
-
const w =
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
console.log(c.primary(`
|
|
71
|
-
|
|
71
|
+
const w = W();
|
|
72
|
+
const line = '─'.repeat(w - 4);
|
|
73
|
+
console.log();
|
|
74
|
+
console.log(c.primary(` ┌${line}┐`));
|
|
75
|
+
console.log(c.primary(` │${FILL_BG}${' '.repeat(w - 4)}${RESET}${c.primary('│')}`));
|
|
76
|
+
msg.split('\n').forEach(l => console.log(c.primary(` │${FILL_BG} ${c.user(l)}${' '.repeat(Math.max(0, w - 6 - l.length))}${RESET}${c.primary('│')}`)));
|
|
77
|
+
console.log(c.primary(` │${FILL_BG}${' '.repeat(w - 4)}${RESET}${c.primary('│')}`));
|
|
78
|
+
console.log(c.primary(` └${line}┘`));
|
|
79
|
+
console.log();
|
|
72
80
|
},
|
|
73
81
|
|
|
74
82
|
table(headers, rows) {
|
|
@@ -85,12 +93,12 @@ const blocks = {
|
|
|
85
93
|
},
|
|
86
94
|
|
|
87
95
|
divider(label) {
|
|
88
|
-
const w =
|
|
96
|
+
const w = W();
|
|
89
97
|
if (label) {
|
|
90
|
-
const side = Math.floor((w - label.length - 4) / 2);
|
|
98
|
+
const side = Math.max(0, Math.floor((w - label.length - 4) / 2));
|
|
91
99
|
console.log(c.dim(` ${'─'.repeat(side)} ${label} ${'─'.repeat(side)}`));
|
|
92
100
|
} else {
|
|
93
|
-
|
|
101
|
+
console.log(c.dim('─'.repeat(w)));
|
|
94
102
|
}
|
|
95
103
|
},
|
|
96
104
|
|
|
@@ -104,6 +112,18 @@ const blocks = {
|
|
|
104
112
|
const filled = Math.round(w * pct / 100);
|
|
105
113
|
const bar = c.primary('█'.repeat(filled)) + c.dim('░'.repeat(w - filled));
|
|
106
114
|
console.log(` ${c.muted(label)} ${bar} ${c.white(String(pct))}%`);
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
cmdsuggest(cmds) {
|
|
118
|
+
const w = W();
|
|
119
|
+
console.log();
|
|
120
|
+
console.log(c.dim(` ${'─'.repeat(w - 4)}`));
|
|
121
|
+
cmds.forEach(([cmd, desc], i) => {
|
|
122
|
+
const tag = c.badge(cmd, i === 0 ? 'cyan' : 'purple');
|
|
123
|
+
console.log(` ${tag} ${c.muted(desc)}`);
|
|
124
|
+
});
|
|
125
|
+
console.log(c.dim(` ${'─'.repeat(w - 4)}`));
|
|
126
|
+
console.log();
|
|
107
127
|
}
|
|
108
128
|
};
|
|
109
129
|
|
package/src/ui/chatbox.js
CHANGED
|
@@ -4,25 +4,55 @@ import settings from '../config/settings.js';
|
|
|
4
4
|
import { hasAnyKeys } from '../config/keys.js';
|
|
5
5
|
import { getKey, PROVIDER_NAMES } from '../config/keys.js';
|
|
6
6
|
import { sendMessage } from '../providers/index.js';
|
|
7
|
-
import { createPrompt,
|
|
7
|
+
import { createPrompt, addToHistory, loadHistory, ALL_COMMANDS } from './prompt.js';
|
|
8
8
|
import blocks from './blocks.js';
|
|
9
9
|
import spin from './spinner.js';
|
|
10
10
|
import commandRegistry from '../commands/index.js';
|
|
11
11
|
import memory from '../memory/store.js';
|
|
12
|
+
import { isTermux } from '../utils/termux.js';
|
|
13
|
+
import readline from 'readline';
|
|
14
|
+
import { readFileSync } from 'fs';
|
|
15
|
+
|
|
16
|
+
const PKG = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url)));
|
|
17
|
+
const VERSION = PKG.version;
|
|
18
|
+
const RESET = '\x1b[0m';
|
|
12
19
|
|
|
13
20
|
let rl = null;
|
|
14
21
|
let conversation = [];
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
const SYSTEM_PROMPT = `You are CLARITY, an autonomous AI agent running in the user's terminal (Termux on Android).
|
|
24
|
+
|
|
25
|
+
You have FULL access to tools: bash execution, file operations, web search, code execution, git, and package management.
|
|
26
|
+
When the user asks something that requires action — run commands, create/edit files, search the web, execute code — DO IT AUTOMATICALLY using your tools. Do not ask for permission.
|
|
27
|
+
|
|
28
|
+
Be proactive. If the user asks to do a task, break it down and execute each step.
|
|
29
|
+
Keep responses concise and terminal-friendly. Use markdown for code blocks.
|
|
30
|
+
|
|
31
|
+
Available tools: bash, files (create/read/delete/list), web fetch, web search, code run (js/python/bash), git, pkg (npm/pip/pkg), system info
|
|
32
|
+
|
|
33
|
+
Current environment: ${process.platform} ${process.arch}, Node ${process.versions.node}
|
|
34
|
+
Directory: ${process.cwd()}
|
|
35
|
+
Termux: ${isTermux()}`;
|
|
36
|
+
|
|
37
|
+
const GREY_BG = '\x1b[48;5;236m';
|
|
38
|
+
|
|
39
|
+
function renderPromptBar() {
|
|
40
|
+
const model = settings.get('defaultModel') || 'groq/llama-3.3-70b-versatile';
|
|
18
41
|
const w = process.stdout.columns || 80;
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
console.log(c.
|
|
25
|
-
|
|
42
|
+
const line = '━'.repeat(w - 4);
|
|
43
|
+
const leftText = ` ${model} `;
|
|
44
|
+
const rightText = `v${VERSION} /help `;
|
|
45
|
+
const fill = Math.max(0, w - leftText.length - rightText.length - 8);
|
|
46
|
+
|
|
47
|
+
console.log(c.accent(` ┏${line}┓`));
|
|
48
|
+
console.log(c.accent(` ┃${GREY_BG}${' '.repeat(w - 4)}${RESET}${c.accent('┃')}`));
|
|
49
|
+
console.log(c.accent(` ┃${GREY_BG} ${c.muted(leftText)}${' '.repeat(fill)}${c.muted(rightText)}${RESET}${c.accent('┃')}`));
|
|
50
|
+
console.log(c.accent(` ┃${GREY_BG}${' '.repeat(w - 4)}${RESET}${c.accent('┃')}`));
|
|
51
|
+
console.log(c.accent(` ┗${line}┛`));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function showPrompt() {
|
|
55
|
+
process.stdout.write(` ${c.accent('◆')} `);
|
|
26
56
|
}
|
|
27
57
|
|
|
28
58
|
function startChat() {
|
|
@@ -34,16 +64,31 @@ function startChat() {
|
|
|
34
64
|
loadHistory();
|
|
35
65
|
console.clear();
|
|
36
66
|
renderBanner();
|
|
37
|
-
renderHeader();
|
|
38
67
|
console.log();
|
|
39
|
-
|
|
40
|
-
rl = createPrompt();
|
|
68
|
+
renderPromptBar();
|
|
41
69
|
showPrompt();
|
|
70
|
+
rl = createPrompt();
|
|
71
|
+
|
|
72
|
+
let cmdBuffer = '';
|
|
73
|
+
|
|
74
|
+
process.stdin.on('keypress', (char, key) => {
|
|
75
|
+
if (key && key.name === 'slash' && !cmdBuffer) {
|
|
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
|
+
});
|
|
42
87
|
|
|
43
88
|
rl.on('line', async (line) => {
|
|
44
89
|
const input = line.trim();
|
|
45
|
-
if (!input) { showPrompt(); return; }
|
|
46
|
-
|
|
90
|
+
if (!input) { renderPromptBar(); showPrompt(); return; }
|
|
91
|
+
cmdBuffer = '';
|
|
47
92
|
addToHistory(input);
|
|
48
93
|
|
|
49
94
|
if (input.startsWith('/')) {
|
|
@@ -51,9 +96,9 @@ function startChat() {
|
|
|
51
96
|
if (result?.exit) { closeChat(); return; }
|
|
52
97
|
} else {
|
|
53
98
|
conversation.push({ role: 'user', content: input });
|
|
54
|
-
blocks.user(input);
|
|
55
99
|
await handleAIResponse();
|
|
56
100
|
}
|
|
101
|
+
renderPromptBar();
|
|
57
102
|
showPrompt();
|
|
58
103
|
});
|
|
59
104
|
|
|
@@ -64,12 +109,27 @@ function startChat() {
|
|
|
64
109
|
|
|
65
110
|
rl.on('SIGINT', () => {
|
|
66
111
|
console.log(c.muted('\nUse /exit to quit'));
|
|
112
|
+
renderPromptBar();
|
|
67
113
|
showPrompt();
|
|
68
114
|
});
|
|
69
115
|
}
|
|
70
116
|
|
|
117
|
+
function suggestCommands(partial) {
|
|
118
|
+
const w = process.stdout.columns || 80;
|
|
119
|
+
const p = partial.replace('/', '');
|
|
120
|
+
const matches = p ? ALL_COMMANDS.filter(([cmd]) => cmd.startsWith(p)) : ALL_COMMANDS;
|
|
121
|
+
const top = matches.slice(0, 8);
|
|
122
|
+
if (top.length === 0) return;
|
|
123
|
+
console.log(c.dim(` ${'─'.repeat(w - 4)}`));
|
|
124
|
+
top.forEach(([cmd, desc], i) => {
|
|
125
|
+
const tag = blocks.badge('/' + cmd, i === 0 ? 'cyan' : 'purple');
|
|
126
|
+
console.log(` ${tag} ${c.muted(desc)}`);
|
|
127
|
+
});
|
|
128
|
+
console.log(c.dim(` ${'─'.repeat(w - 4)}`));
|
|
129
|
+
}
|
|
130
|
+
|
|
71
131
|
async function handleAIResponse() {
|
|
72
|
-
const model = settings.get('defaultModel') || 'groq/
|
|
132
|
+
const model = settings.get('defaultModel') || 'groq/llama-3.3-70b-versatile';
|
|
73
133
|
const [provider] = model.split('/');
|
|
74
134
|
const apiKey = getKey(provider);
|
|
75
135
|
|
|
@@ -78,8 +138,8 @@ async function handleAIResponse() {
|
|
|
78
138
|
return;
|
|
79
139
|
}
|
|
80
140
|
|
|
81
|
-
const
|
|
82
|
-
const messages = [
|
|
141
|
+
const systemMsg = { role: 'system', content: SYSTEM_PROMPT + '\n\nUser memory: ' + (memory.show().filter(m => m.role === 'system').map(m => m.content).join('; ') || 'none') };
|
|
142
|
+
const messages = [systemMsg, ...conversation];
|
|
83
143
|
|
|
84
144
|
spin.start('CLARITY is thinking...');
|
|
85
145
|
|
|
@@ -89,27 +149,40 @@ async function handleAIResponse() {
|
|
|
89
149
|
if (settings.get('stream')) {
|
|
90
150
|
spin.stop();
|
|
91
151
|
const w = process.stdout.columns || 80;
|
|
92
|
-
|
|
93
|
-
|
|
152
|
+
const line = '━'.repeat(w - 4);
|
|
153
|
+
const PURPLE_BG = '\x1b[48;5;53m';
|
|
154
|
+
console.log(c.accent(` ┏${line}┓`));
|
|
155
|
+
console.log(c.accent(` ┃${PURPLE_BG}${' '.repeat(w - 4)}${RESET}${c.accent('┃')}`));
|
|
156
|
+
process.stdout.write(c.accent(` ┃${PURPLE_BG} ${c.ai('CLARITY')} ${RESET}${c.white('')}`));
|
|
94
157
|
let full = '';
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
158
|
+
try {
|
|
159
|
+
for await (const chunk of stream) {
|
|
160
|
+
full += chunk;
|
|
161
|
+
process.stdout.write(c.white(chunk));
|
|
162
|
+
}
|
|
163
|
+
} catch (streamErr) {
|
|
164
|
+
if (full) process.stdout.write(c.warning('\n\n[stream interrupted]'));
|
|
165
|
+
else throw streamErr;
|
|
98
166
|
}
|
|
99
167
|
console.log();
|
|
100
|
-
console.log(c.accent(`
|
|
168
|
+
console.log(c.accent(` ┃${PURPLE_BG}${' '.repeat(w - 4)}${RESET}${c.accent('┃')}`));
|
|
169
|
+
console.log(c.accent(` ┗${line}┛`));
|
|
101
170
|
console.log();
|
|
102
|
-
|
|
103
|
-
|
|
171
|
+
if (full.trim()) {
|
|
172
|
+
conversation.push({ role: 'assistant', content: full });
|
|
173
|
+
memory.add(conversation);
|
|
174
|
+
}
|
|
104
175
|
} else {
|
|
105
176
|
spin.stop();
|
|
106
177
|
let full = '';
|
|
107
178
|
for await (const chunk of stream) {
|
|
108
179
|
full += chunk;
|
|
109
180
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
181
|
+
if (full.trim()) {
|
|
182
|
+
blocks.ai(full);
|
|
183
|
+
conversation.push({ role: 'assistant', content: full });
|
|
184
|
+
memory.add(conversation);
|
|
185
|
+
}
|
|
113
186
|
}
|
|
114
187
|
|
|
115
188
|
if (settings.get('showTokens')) {
|
package/src/ui/prompt.js
CHANGED
|
@@ -2,9 +2,76 @@ import readline from 'readline';
|
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
3
3
|
import { dirname } from 'path';
|
|
4
4
|
import paths from '../config/paths.js';
|
|
5
|
-
import c from './colors.js';
|
|
6
5
|
|
|
7
6
|
const HISTORY_FILE = paths.history;
|
|
7
|
+
const ALL_COMMANDS = [
|
|
8
|
+
['help', 'Show all commands'],
|
|
9
|
+
['init', 'Setup wizard'],
|
|
10
|
+
['chat', 'Start chat session'],
|
|
11
|
+
['ask', 'One-shot question'],
|
|
12
|
+
['clear', 'Clear screen'],
|
|
13
|
+
['exit', 'Exit CLARITY'],
|
|
14
|
+
['keys set', 'Set API key'],
|
|
15
|
+
['keys list', 'List API keys'],
|
|
16
|
+
['keys remove', 'Remove API key'],
|
|
17
|
+
['keys test', 'Test API key'],
|
|
18
|
+
['provider', 'Switch AI provider'],
|
|
19
|
+
['model', 'Select AI model'],
|
|
20
|
+
['model set', 'Set default model'],
|
|
21
|
+
['model list', 'List models'],
|
|
22
|
+
['config show', 'Show config'],
|
|
23
|
+
['config reset', 'Reset config'],
|
|
24
|
+
['theme set', 'Set theme'],
|
|
25
|
+
['theme list', 'List themes'],
|
|
26
|
+
['file create', 'Create file'],
|
|
27
|
+
['file read', 'Read file'],
|
|
28
|
+
['file delete', 'Delete file'],
|
|
29
|
+
['file list', 'List directory'],
|
|
30
|
+
['file edit', 'Edit file'],
|
|
31
|
+
['bash', 'Run command'],
|
|
32
|
+
['code run', 'Run code file'],
|
|
33
|
+
['code write', 'AI write code'],
|
|
34
|
+
['code explain', 'Explain code'],
|
|
35
|
+
['code fix', 'Fix code'],
|
|
36
|
+
['code review', 'Review code'],
|
|
37
|
+
['code refactor', 'Refactor code'],
|
|
38
|
+
['code test', 'Generate tests'],
|
|
39
|
+
['code docs', 'Generate docs'],
|
|
40
|
+
['web', 'Fetch URL'],
|
|
41
|
+
['search', 'Web search'],
|
|
42
|
+
['git', 'Git operations'],
|
|
43
|
+
['pkg install', 'Install package'],
|
|
44
|
+
['pkg remove', 'Remove package'],
|
|
45
|
+
['agent start', 'Start agent'],
|
|
46
|
+
['agent stop', 'Stop agent'],
|
|
47
|
+
['agent list', 'List agents'],
|
|
48
|
+
['agent logs', 'Show agent logs'],
|
|
49
|
+
['tools list', 'List tools'],
|
|
50
|
+
['tools run', 'Run tool'],
|
|
51
|
+
['memory show', 'Show memory'],
|
|
52
|
+
['memory add', 'Add memory'],
|
|
53
|
+
['memory clear', 'Clear memory'],
|
|
54
|
+
['history show', 'Show history'],
|
|
55
|
+
['history clear', 'Clear history'],
|
|
56
|
+
['history export', 'Export history'],
|
|
57
|
+
['env show', 'Show env vars'],
|
|
58
|
+
['env set', 'Set env var'],
|
|
59
|
+
['status', 'System status'],
|
|
60
|
+
['version', 'Version info'],
|
|
61
|
+
['update', 'Check update'],
|
|
62
|
+
['alias create', 'Create alias'],
|
|
63
|
+
['alias list', 'List aliases'],
|
|
64
|
+
['export', 'Export chat'],
|
|
65
|
+
['import', 'Import chat'],
|
|
66
|
+
['summarize', 'Summarize content'],
|
|
67
|
+
['translate', 'Translate text'],
|
|
68
|
+
['generate', 'Generate content'],
|
|
69
|
+
['deploy', 'Deploy project'],
|
|
70
|
+
['debug', 'Debug file'],
|
|
71
|
+
['explain', 'Explain code'],
|
|
72
|
+
['run', 'Run command'],
|
|
73
|
+
];
|
|
74
|
+
|
|
8
75
|
const MAX_HISTORY = 500;
|
|
9
76
|
let history = [];
|
|
10
77
|
|
|
@@ -45,9 +112,4 @@ function createPrompt() {
|
|
|
45
112
|
return rl;
|
|
46
113
|
}
|
|
47
114
|
|
|
48
|
-
|
|
49
|
-
const prefix = c.primary('◇');
|
|
50
|
-
process.stdout.write(` ${prefix} `);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export { createPrompt, showPrompt, addToHistory, loadHistory, history };
|
|
115
|
+
export { createPrompt, addToHistory, loadHistory, history, ALL_COMMANDS };
|
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import boxen from 'boxen';
|
|
3
2
|
import semver from 'semver';
|
|
4
3
|
import { isTermux } from './termux.js';
|
|
5
4
|
|
|
6
5
|
const MIN_NODE = '18.0.0';
|
|
6
|
+
const W = () => process.stdout.columns || 80;
|
|
7
|
+
const line = '═'.repeat(W() - 4);
|
|
8
|
+
|
|
9
|
+
function simpleBox(colorFn, title, msg) {
|
|
10
|
+
console.error();
|
|
11
|
+
console.error(colorFn(` ╔${line}╗`));
|
|
12
|
+
if (title) console.error(colorFn(` ║ ${title}`));
|
|
13
|
+
msg.split('\n').forEach(l => console.error(colorFn(` ║ ${l}`)));
|
|
14
|
+
console.error(colorFn(` ╚${line}╝`));
|
|
15
|
+
console.error();
|
|
16
|
+
}
|
|
7
17
|
|
|
8
18
|
function checkNodeVersion() {
|
|
9
19
|
const current = process.versions.node;
|
|
10
20
|
if (!semver.satisfies(current, `>=${MIN_NODE}`)) {
|
|
11
|
-
|
|
12
|
-
chalk.hex('#ff4466')
|
|
13
|
-
|
|
14
|
-
{
|
|
15
|
-
)
|
|
21
|
+
simpleBox(
|
|
22
|
+
chalk.hex('#ff4466'),
|
|
23
|
+
`✗ Node.js ${MIN_NODE}+ required`,
|
|
24
|
+
`You have v${current}\n${isTermux() ? 'Termux: pkg install nodejs-lts' : 'Install Node.js 18+ from https://nodejs.org'}`
|
|
25
|
+
);
|
|
16
26
|
process.exit(1);
|
|
17
27
|
}
|
|
18
28
|
}
|
|
@@ -22,11 +32,11 @@ async function checkDependencies(pkgDir) {
|
|
|
22
32
|
const { resolve } = await import('path');
|
|
23
33
|
const pkgPath = resolve(pkgDir, 'package.json');
|
|
24
34
|
if (!existsSync(pkgPath)) return true;
|
|
25
|
-
|
|
35
|
+
|
|
26
36
|
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
27
37
|
const deps = { ...pkg.dependencies, ...pkg.peerDependencies };
|
|
28
38
|
const missing = [];
|
|
29
|
-
|
|
39
|
+
|
|
30
40
|
for (const dep of Object.keys(deps || {})) {
|
|
31
41
|
try {
|
|
32
42
|
await import(dep);
|
|
@@ -34,13 +44,13 @@ async function checkDependencies(pkgDir) {
|
|
|
34
44
|
missing.push(dep);
|
|
35
45
|
}
|
|
36
46
|
}
|
|
37
|
-
|
|
47
|
+
|
|
38
48
|
if (missing.length > 0) {
|
|
39
|
-
|
|
40
|
-
chalk.hex('#ffcc00')
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
)
|
|
49
|
+
simpleBox(
|
|
50
|
+
chalk.hex('#ffcc00'),
|
|
51
|
+
'⚠ Missing dependencies',
|
|
52
|
+
missing.join(', ')
|
|
53
|
+
);
|
|
44
54
|
const { execSync } = await import('child_process');
|
|
45
55
|
execSync('npm install', { cwd: pkgDir, stdio: 'inherit' });
|
|
46
56
|
}
|
|
@@ -54,11 +64,11 @@ async function checkUpdates() {
|
|
|
54
64
|
const res = await fetch('https://registry.npmjs.org/clarity-ai/latest');
|
|
55
65
|
const data = await res.json();
|
|
56
66
|
if (data.version && semver.gt(data.version, current)) {
|
|
57
|
-
|
|
58
|
-
chalk.hex('#00d2ff')
|
|
59
|
-
|
|
60
|
-
{
|
|
61
|
-
)
|
|
67
|
+
simpleBox(
|
|
68
|
+
chalk.hex('#00d2ff'),
|
|
69
|
+
'Update Available',
|
|
70
|
+
`clarity-ai v${current} → v${data.version}\nRun: npm install -g clarity-ai`
|
|
71
|
+
);
|
|
62
72
|
}
|
|
63
73
|
} catch {}
|
|
64
74
|
}
|