clarity-ai 1.1.0 → 1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clarity-ai",
3
- "version": "1.1.0",
3
+ "version": "1.2.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
- "strip-ansi": "^7.1.0",
38
- "wrap-ansi": "^9.0.0"
27
+ "glob": "^10.3.12"
39
28
  },
40
29
  "keywords": [
41
30
  "ai",
@@ -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.log(boxen(
20
- chalk.hex('#ff4466').bold(`Node.js 18+ required. You have v${nodeVersion}`) + '\n' +
21
- chalk.hex('#ffcc00')('Termux: pkg install nodejs-lts'),
22
- { padding: 1, borderColor: 'red', borderStyle: 'round' }
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(boxen(
30
- chalk.hex('#00ff88').bold('CLARITY installed successfully!') + '\n' +
31
- chalk.hex('#00d2ff')('Run: clarity init'),
32
- { padding: 1, borderColor: 'green', borderStyle: 'round' }
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 = await import('fs').then(fs => fs.lstatSync(wrapperPath));
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
  }
@@ -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) throw new Error(`Groq API error: ${res.status} ${res.statusText}`);
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/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
- function hr(char = '─', color = c.dim) {
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 gap(n = 1) {
10
- console.log('\n'.repeat(n - 1));
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 blocks = {
14
- info(title, msg) {
15
- gap();
16
- console.log(c.info(` ┌── ${title} ${''.repeat(Math.max(0, (process.stdout.columns || 80) - title.length - 10))}┐`));
17
- msg.split('\n').forEach(l => console.log(c.info(' │') + c.white(` ${l}`.padEnd((process.stdout.columns || 80) - 4)) + c.info('')));
18
- console.log(c.info(` └${'─'.repeat((process.stdout.columns || 80) - 4)}┘`));
19
- gap();
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
- success(title, msg) {
23
- gap();
24
- console.log(c.success(` ┌── ${title} ${''.repeat(Math.max(0, (process.stdout.columns || 80) - title.length - 10))}┐`));
25
- msg.split('\n').forEach(l => console.log(c.success(' │') + c.white(` ${l}`.padEnd((process.stdout.columns || 80) - 4)) + c.success('│')));
26
- console.log(c.success(` └${'─'.repeat((process.stdout.columns || 80) - 4)}┘`));
27
- gap();
28
- },
29
-
30
- warn(title, msg) {
31
- gap();
32
- console.log(c.warning(` ┌── ⚠ ${title} ${'─'.repeat(Math.max(0, (process.stdout.columns || 80) - title.length - 10))}┐`));
33
- msg.split('\n').forEach(l => console.log(c.warning(' │') + c.white(` ${l}`.padEnd((process.stdout.columns || 80) - 4)) + c.warning('│')));
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
- error(title, msg) {
39
- gap();
40
- console.log(c.error(` ┌── ${title} ${'─'.repeat(Math.max(0, (process.stdout.columns || 80) - title.length - 10))}┐`));
41
- msg.split('\n').forEach(l => console.log(c.error(' │') + c.white(` ${l}`.padEnd((process.stdout.columns || 80) - 4)) + c.error('│')));
42
- console.log(c.error(` └${'─'.repeat((process.stdout.columns || 80) - 4)}┘`));
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
- console.log(c.dim(` ┌─ ${lang} ${'─'.repeat(Math.max(0, (process.stdout.columns || 80) - lang.length - 8))}`));
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((process.stdout.columns || 80) - 4)}`));
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((process.stdout.columns || 80) - 4)}`));
51
+ console.log(c.dim(` └${'─'.repeat(w - 4)}`));
56
52
  },
57
53
 
58
- ai(msg) {
59
- const w = process.stdout.columns || 80;
60
- console.log(c.accent(` ╔${'═'.repeat(w - 4)}╗`));
61
- msg.split('\n').forEach(l => console.log(c.accent(' ║') + c.ai(` ${l}`.padEnd(w - 4)) + c.accent('║')));
62
- console.log(c.accent(` ╚${'═'.repeat(w - 4)}╝`));
63
- gap();
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 = process.stdout.columns || 80;
68
- console.log(c.primary(` ┌${'─'.repeat(w - 4)}┐`));
69
- msg.split('\n').forEach(l => console.log(c.primary(' │') + c.user(` ${l}`.padEnd(w - 4)) + c.primary('│')));
70
- console.log(c.primary(` └${'─'.repeat(w - 4)}┘`));
71
- gap();
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 = process.stdout.columns || 80;
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
- hr();
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,23 +4,39 @@ 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, showPrompt, addToHistory, loadHistory } from './prompt.js';
7
+ import { createPrompt, showPrompt, addToHistory, loadHistory, showSuggestions, ALL_COMMANDS } from './prompt.js';
8
8
  import blocks from './blocks.js';
9
9
  import spin from './spinner.js';
10
+ import agentManager from '../agents/manager.js';
10
11
  import commandRegistry from '../commands/index.js';
11
12
  import memory from '../memory/store.js';
13
+ import { runTool } from '../tools/index.js';
14
+ import { isTermux } from '../utils/termux.js';
12
15
 
13
16
  let rl = null;
14
17
  let conversation = [];
15
18
 
19
+ const SYSTEM_PROMPT = `You are CLARITY, an autonomous AI agent running in the user's terminal (Termux on Android).
20
+
21
+ You have FULL access to tools: bash execution, file operations, web search, code execution, git, and package management.
22
+ 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.
23
+
24
+ Be proactive. If the user asks to do a task, break it down and execute each step.
25
+ Keep responses concise and terminal-friendly. Use markdown for code blocks.
26
+
27
+ Available tools: bash, files (create/read/delete/list), web fetch, web search, code run (js/python/bash), git, pkg (npm/pip/pkg), system info
28
+
29
+ Current environment: ${process.platform} ${process.arch}, Node ${process.versions.node}
30
+ Directory: ${process.cwd()}
31
+ Termux: ${isTermux()}`;
32
+
16
33
  function renderHeader() {
17
34
  const model = settings.get('defaultModel') || 'groq/llama3-70b-8192';
18
35
  const w = process.stdout.columns || 80;
19
36
  const left = c.accent('◈');
20
37
  const mid = c.muted(` ${model} `);
21
38
  const right = c.muted('/help');
22
- const total = 6 + mid.length + right.length + 2;
23
- const pad = '.'.repeat(Math.max(0, w - total - 4));
39
+ const pad = '.'.repeat(Math.max(0, Math.floor((w - mid.length - right.length - 8) / 2)));
24
40
  console.log(c.border(` ${left}${c.dim(pad)}${mid}${c.dim(pad)}${right}`));
25
41
  blocks.divider();
26
42
  }
@@ -39,11 +55,22 @@ function startChat() {
39
55
 
40
56
  rl = createPrompt();
41
57
  showPrompt();
58
+ let cmdBuffer = '';
59
+
60
+ process.stdin.on('keypress', (char, key) => {
61
+ if (key && key.name === 'slash' && !cmdBuffer) {
62
+ cmdBuffer = '/';
63
+ suggestCommands('');
64
+ }
65
+ if (key && key.name === 'escape') {
66
+ if (cmdBuffer) { cmdBuffer = ''; }
67
+ }
68
+ });
42
69
 
43
70
  rl.on('line', async (line) => {
44
71
  const input = line.trim();
45
72
  if (!input) { showPrompt(); return; }
46
-
73
+ cmdBuffer = '';
47
74
  addToHistory(input);
48
75
 
49
76
  if (input.startsWith('/')) {
@@ -51,7 +78,6 @@ function startChat() {
51
78
  if (result?.exit) { closeChat(); return; }
52
79
  } else {
53
80
  conversation.push({ role: 'user', content: input });
54
- blocks.user(input);
55
81
  await handleAIResponse();
56
82
  }
57
83
  showPrompt();
@@ -68,6 +94,22 @@ function startChat() {
68
94
  });
69
95
  }
70
96
 
97
+ function suggestCommands(partial) {
98
+ const w = process.stdout.columns || 80;
99
+ const matches = ALL_COMMANDS.filter(([cmd]) => cmd.startsWith(partial));
100
+ const top = matches.slice(0, 6);
101
+ if (top.length === 0) return;
102
+ console.log();
103
+ console.log(c.dim(` ${'─'.repeat(w - 4)}`));
104
+ top.forEach(([cmd, desc], i) => {
105
+ const tag = c.badge('/' + cmd, i === 0 ? 'cyan' : 'purple');
106
+ console.log(` ${tag} ${c.muted(desc)}`);
107
+ });
108
+ console.log(c.dim(` ${'─'.repeat(w - 4)}`));
109
+ console.log();
110
+ showPrompt();
111
+ }
112
+
71
113
  async function handleAIResponse() {
72
114
  const model = settings.get('defaultModel') || 'groq/llama3-70b-8192';
73
115
  const [provider] = model.split('/');
@@ -78,8 +120,8 @@ async function handleAIResponse() {
78
120
  return;
79
121
  }
80
122
 
81
- const ctx = memory.getContext();
82
- const messages = [...ctx, ...conversation];
123
+ const systemMsg = { role: 'system', content: SYSTEM_PROMPT + '\n\nUser memory: ' + (memory.show().filter(m => m.role === 'system').map(m => m.content).join('; ') || 'none') };
124
+ const messages = [systemMsg, ...conversation];
83
125
 
84
126
  spin.start('CLARITY is thinking...');
85
127
 
@@ -92,24 +134,33 @@ async function handleAIResponse() {
92
134
  console.log(c.accent(` ╔${'═'.repeat(w - 4)}╗`));
93
135
  process.stdout.write(c.accent(' ║ ') + c.ai('CLARITY ') + c.white(''));
94
136
  let full = '';
95
- for await (const chunk of stream) {
96
- full += chunk;
97
- process.stdout.write(c.white(chunk));
137
+ try {
138
+ for await (const chunk of stream) {
139
+ full += chunk;
140
+ process.stdout.write(c.white(chunk));
141
+ }
142
+ } catch (streamErr) {
143
+ if (full) process.stdout.write(c.warning('\n\n[stream interrupted]'));
144
+ else throw streamErr;
98
145
  }
99
146
  console.log();
100
147
  console.log(c.accent(` ╚${'═'.repeat(w - 4)}╝`));
101
148
  console.log();
102
- conversation.push({ role: 'assistant', content: full });
103
- memory.add(conversation);
149
+ if (full.trim()) {
150
+ conversation.push({ role: 'assistant', content: full });
151
+ memory.add(conversation);
152
+ }
104
153
  } else {
105
154
  spin.stop();
106
155
  let full = '';
107
156
  for await (const chunk of stream) {
108
157
  full += chunk;
109
158
  }
110
- blocks.ai(full);
111
- conversation.push({ role: 'assistant', content: full });
112
- memory.add(conversation);
159
+ if (full.trim()) {
160
+ blocks.ai(full);
161
+ conversation.push({ role: 'assistant', content: full });
162
+ memory.add(conversation);
163
+ }
113
164
  }
114
165
 
115
166
  if (settings.get('showTokens')) {
package/src/ui/prompt.js CHANGED
@@ -5,8 +5,75 @@ import paths from '../config/paths.js';
5
5
  import c from './colors.js';
6
6
 
7
7
  const HISTORY_FILE = paths.history;
8
+ const ALL_COMMANDS = [
9
+ ['help', 'Show all commands'],
10
+ ['init', 'Setup wizard'],
11
+ ['chat', 'Start chat session'],
12
+ ['ask', 'One-shot question'],
13
+ ['clear', 'Clear screen'],
14
+ ['exit', 'Exit CLARITY'],
15
+ ['keys set', 'Set API key'],
16
+ ['keys list', 'List API keys'],
17
+ ['keys remove', 'Remove API key'],
18
+ ['keys test', 'Test API key'],
19
+ ['model set', 'Set default model'],
20
+ ['model list', 'List models'],
21
+ ['config show', 'Show config'],
22
+ ['config reset', 'Reset config'],
23
+ ['theme set', 'Set theme'],
24
+ ['theme list', 'List themes'],
25
+ ['file create', 'Create file'],
26
+ ['file read', 'Read file'],
27
+ ['file delete', 'Delete file'],
28
+ ['file list', 'List directory'],
29
+ ['file edit', 'Edit file'],
30
+ ['bash', 'Run command'],
31
+ ['code run', 'Run code file'],
32
+ ['code write', 'AI write code'],
33
+ ['code explain', 'Explain code'],
34
+ ['code fix', 'Fix code'],
35
+ ['code review', 'Review code'],
36
+ ['code refactor', 'Refactor code'],
37
+ ['code test', 'Generate tests'],
38
+ ['code docs', 'Generate docs'],
39
+ ['web', 'Fetch URL'],
40
+ ['search', 'Web search'],
41
+ ['git', 'Git operations'],
42
+ ['pkg install', 'Install package'],
43
+ ['pkg remove', 'Remove package'],
44
+ ['agent start', 'Start agent'],
45
+ ['agent stop', 'Stop agent'],
46
+ ['agent list', 'List agents'],
47
+ ['agent logs', 'Show agent logs'],
48
+ ['tools list', 'List tools'],
49
+ ['tools run', 'Run tool'],
50
+ ['memory show', 'Show memory'],
51
+ ['memory add', 'Add memory'],
52
+ ['memory clear', 'Clear memory'],
53
+ ['history show', 'Show history'],
54
+ ['history clear', 'Clear history'],
55
+ ['history export', 'Export history'],
56
+ ['env show', 'Show env vars'],
57
+ ['env set', 'Set env var'],
58
+ ['status', 'System status'],
59
+ ['version', 'Version info'],
60
+ ['update', 'Check update'],
61
+ ['alias create', 'Create alias'],
62
+ ['alias list', 'List aliases'],
63
+ ['export', 'Export chat'],
64
+ ['import', 'Import chat'],
65
+ ['summarize', 'Summarize content'],
66
+ ['translate', 'Translate text'],
67
+ ['generate', 'Generate content'],
68
+ ['deploy', 'Deploy project'],
69
+ ['debug', 'Debug file'],
70
+ ['explain', 'Explain code'],
71
+ ['run', 'Run command'],
72
+ ];
73
+
8
74
  const MAX_HISTORY = 500;
9
75
  let history = [];
76
+ let showCmdSuggest = false;
10
77
 
11
78
  function loadHistory() {
12
79
  try {
@@ -32,6 +99,17 @@ function addToHistory(line) {
32
99
  }
33
100
  }
34
101
 
102
+ function showSuggestions(rl, partial) {
103
+ const p = partial.replace('/', '');
104
+ if (!p) {
105
+ showCmdSuggest = true;
106
+ return ALL_COMMANDS.slice(0, 8);
107
+ }
108
+ const matches = ALL_COMMANDS.filter(([cmd]) => cmd.startsWith(p));
109
+ showCmdSuggest = matches.length > 0;
110
+ return matches.slice(0, 8);
111
+ }
112
+
35
113
  function createPrompt() {
36
114
  loadHistory();
37
115
  const rl = readline.createInterface({
@@ -46,8 +124,7 @@ function createPrompt() {
46
124
  }
47
125
 
48
126
  function showPrompt() {
49
- const prefix = c.primary('');
50
- process.stdout.write(` ${prefix} `);
127
+ process.stdout.write(` ${c.primary('')} `);
51
128
  }
52
129
 
53
- export { createPrompt, showPrompt, addToHistory, loadHistory, history };
130
+ export { createPrompt, showPrompt, addToHistory, loadHistory, history, showSuggestions, 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
- console.error(boxen(
12
- chalk.hex('#ff4466').bold(`[ERROR] Node.js ${MIN_NODE}+ required. You have v${current}\n`) +
13
- chalk.hex('#ffcc00')(isTermux() ? 'Termux: pkg install nodejs-lts' : 'Install Node.js 18+ from https://nodejs.org'),
14
- { padding: 1, borderColor: 'red', borderStyle: 'round' }
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
- console.warn(boxen(
40
- chalk.hex('#ffcc00').bold('Missing dependencies: ') + missing.join(', ') + '\n' +
41
- chalk.dim('Auto-installing...'),
42
- { padding: 1, borderColor: 'yellow', borderStyle: 'round' }
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
- console.log(boxen(
58
- chalk.hex('#00d2ff').bold(`clarity-ai v${current} → v${data.version}\n`) +
59
- chalk.hex('#7b2ff7')('Run: npm install -g clarity-ai'),
60
- { padding: 1, borderColor: 'cyan', borderStyle: 'round' }
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
  }