clarity-ai 1.2.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clarity-ai",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "AI agent CLI for Termux and terminal — chat, code, automate",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -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', '/model set|list', '/config show|reset', '/theme set|list'],
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,27 +233,75 @@ const commandRegistry = {
232
233
 
233
234
  async model(args) {
234
235
  const sub = args[0];
235
- if (!sub) { blocks.warn('Usage', '/model list|set <provider/model>'); return; }
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
- switch (sub) {
238
- case 'list': {
239
- const configured = Object.keys(listKeys());
240
- if (configured.length === 0) { blocks.info('No Keys', 'Configure keys first with /init'); return; }
241
- for (const p of configured) {
242
- const models = listModels(p);
243
- blocks.info(p, models.map(m => `${p}/${m}`).join('\n'));
244
- }
245
- break;
246
- }
247
- case 'set': {
248
- if (args.length < 2) { blocks.warn('Usage', '/model set <provider/model>'); return; }
249
- settings.set('defaultModel', args[1]);
250
- blocks.success('Model Set', `Default model: ${args[1]}`);
251
- break;
252
- }
253
- default:
254
- blocks.warn('Usage', '/model list|set <provider/model>');
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
+ return { resetRL: true };
271
+ },
272
+
273
+ async provider(args) {
274
+ const configured = Object.keys(listKeys());
275
+ if (configured.length === 0) { blocks.info('No Keys', 'Configure keys first with /init'); return; }
276
+
277
+ const { default: inquirer } = await import('inquirer');
278
+ const { provider } = await inquirer.prompt([{
279
+ type: 'list',
280
+ name: 'provider',
281
+ message: 'Select AI provider:',
282
+ choices: configured.map(p => ({
283
+ name: PROVIDER_NAMES[p] || p,
284
+ value: p,
285
+ })),
286
+ }]);
287
+
288
+ const models = listModels(provider);
289
+ if (models.length > 0) {
290
+ const { model } = await inquirer.prompt([{
291
+ type: 'list',
292
+ name: 'model',
293
+ message: `Select ${PROVIDER_NAMES[provider]} model:`,
294
+ choices: models.map(m => ({
295
+ name: `${provider}/${m}`,
296
+ value: `${provider}/${m}`,
297
+ })),
298
+ }]);
299
+ settings.set('defaultModel', model);
300
+ blocks.success('Provider & Model Set', `${PROVIDER_NAMES[provider]} → ${model}`);
301
+ } else {
302
+ blocks.warn('No Models', `No models listed for ${PROVIDER_NAMES[provider]}`);
255
303
  }
304
+ return { resetRL: true };
256
305
  },
257
306
 
258
307
  config(args) {
@@ -3,7 +3,7 @@ import paths from './paths.js';
3
3
 
4
4
  const schema = {
5
5
  theme: { type: 'string', default: 'dark' },
6
- defaultModel: { type: 'string', default: 'groq/llama3-70b-8192' },
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/llama3-70b-8192',
20
+ defaultModel: 'groq/llama-3.3-70b-versatile',
21
21
  stream: true,
22
22
  showTokens: true,
23
23
  saveHistory: true,
@@ -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: ['llama3-70b-8192', 'llama3-8b-8192', 'mixtral-8x7b-32768', 'gemma2-9b-it'], baseURL: 'https://api.groq.com/openai/v1' },
14
- gemini: { free: true, streaming: true, models: ['gemini-1.5-flash', 'gemini-1.5-pro', 'gemini-2.0-flash-exp'], baseURL: 'https://generativelanguage.googleapis.com/v1beta' },
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.1-8b-instruct:free', 'google/gemma-2-9b-it:free', 'mistralai/mistral-7b-instruct:free'], baseURL: 'https://openrouter.ai/api/v1' },
17
- anthropic: { free: false, streaming: true, models: ['claude-3-5-haiku-20241022', 'claude-3-5-sonnet-20241022'], baseURL: 'https://api.anthropic.com/v1' },
18
- openai: { free: false, streaming: true, models: ['gpt-4o-mini', 'gpt-4o', 'gpt-3.5-turbo'], baseURL: 'https://api.openai.com/v1' },
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/chatbox.js CHANGED
@@ -4,17 +4,26 @@ 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, showSuggestions, ALL_COMMANDS } from './prompt.js';
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
- import agentManager from '../agents/manager.js';
11
10
  import commandRegistry from '../commands/index.js';
12
11
  import memory from '../memory/store.js';
13
- import { runTool } from '../tools/index.js';
14
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
+
19
+ const RESET = '\x1b[0m';
20
+ const GREY_BG = '\x1b[48;5;236m';
21
+ const PURPLE_BG = '\x1b[48;5;53m';
22
+ const FILL = '░';
15
23
 
16
24
  let rl = null;
17
25
  let conversation = [];
26
+ let cmdBuffer = '';
18
27
 
19
28
  const SYSTEM_PROMPT = `You are CLARITY, an autonomous AI agent running in the user's terminal (Termux on Android).
20
29
 
@@ -30,122 +39,188 @@ Current environment: ${process.platform} ${process.arch}, Node ${process.version
30
39
  Directory: ${process.cwd()}
31
40
  Termux: ${isTermux()}`;
32
41
 
33
- function renderHeader() {
34
- const model = settings.get('defaultModel') || 'groq/llama3-70b-8192';
35
- const w = process.stdout.columns || 80;
36
- const left = c.accent('◈');
37
- const mid = c.muted(` ${model} `);
38
- const right = c.muted('/help');
39
- const pad = '.'.repeat(Math.max(0, Math.floor((w - mid.length - right.length - 8) / 2)));
40
- console.log(c.border(` ${left}${c.dim(pad)}${mid}${c.dim(pad)}${right}`));
41
- blocks.divider();
42
+ function getW() {
43
+ return process.stdout.columns || 80;
42
44
  }
43
45
 
44
- function startChat() {
45
- if (!hasAnyKeys()) {
46
- blocks.warn('No API Keys', 'Run /init to configure API keys first.');
47
- return;
48
- }
49
-
50
- loadHistory();
51
- console.clear();
52
- renderBanner();
53
- renderHeader();
54
- console.log();
46
+ function fill(w) {
47
+ return FILL.repeat(w);
48
+ }
55
49
 
56
- rl = createPrompt();
57
- showPrompt();
58
- let cmdBuffer = '';
50
+ function renderPromptBar() {
51
+ const model = settings.get('defaultModel') || 'groq/llama-3.3-70b-versatile';
52
+ const w = getW();
53
+ const line = '━'.repeat(w - 4);
54
+ const left = ` ${model} `;
55
+ const right = `v${VERSION} /help `;
56
+ const gap = Math.max(0, w - 4 - left.length - right.length);
59
57
 
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
- });
58
+ console.log(c.accent(` ┏${line}┓`));
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('')}`));
62
+ console.log(c.accent(` ┗${line}┛`));
63
+ }
69
64
 
70
- rl.on('line', async (line) => {
71
- const input = line.trim();
72
- if (!input) { showPrompt(); return; }
73
- cmdBuffer = '';
74
- addToHistory(input);
65
+ function showPrompt() {
66
+ process.stdout.write(` ${c.accent('◆')} `);
67
+ }
75
68
 
76
- if (input.startsWith('/')) {
77
- const result = await commandRegistry.execute(input, { rl, conversation, showPrompt });
78
- if (result?.exit) { closeChat(); return; }
79
- } else {
80
- conversation.push({ role: 'user', content: input });
81
- await handleAIResponse();
82
- }
83
- showPrompt();
84
- });
69
+ function attachReadlineHandlers() {
70
+ rl.removeAllListeners('line');
71
+ rl.removeAllListeners('close');
72
+ rl.removeAllListeners('SIGINT');
85
73
 
74
+ rl.on('line', onLine);
86
75
  rl.on('close', () => {
87
76
  console.log(c.muted('\nGoodbye!'));
88
77
  process.exit(0);
89
78
  });
90
-
91
79
  rl.on('SIGINT', () => {
92
80
  console.log(c.muted('\nUse /exit to quit'));
81
+ renderPromptBar();
93
82
  showPrompt();
94
83
  });
95
84
  }
96
85
 
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;
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);
102
112
  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)}`);
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('│')}`));
107
118
  });
108
- console.log(c.dim(` ${'─'.repeat(w - 4)}`));
119
+ console.log(c.primary(` │${GREY_BG}${fill(w - 4)}${RESET}${c.primary('│')}`));
120
+ console.log(c.primary(` └${line}┘`));
121
+ console.log();
122
+ }
123
+
124
+ function startChat() {
125
+ if (!hasAnyKeys()) {
126
+ blocks.warn('No API Keys', 'Run /init to configure API keys first.');
127
+ return;
128
+ }
129
+
130
+ loadHistory();
131
+ console.clear();
132
+ renderBanner();
109
133
  console.log();
134
+ renderPromptBar();
110
135
  showPrompt();
136
+ rl = createPrompt();
137
+ attachReadlineHandlers();
138
+
139
+ process.stdin.on('keypress', onKeypress);
140
+ }
141
+
142
+ function onKeypress(char, key) {
143
+ if (!key) return;
144
+
145
+ if (key.name === 'slash' && !cmdBuffer) {
146
+ cmdBuffer = '/';
147
+ showCmdList();
148
+ return;
149
+ }
150
+ if (key.name === 'escape') {
151
+ cmdBuffer = '';
152
+ }
153
+ }
154
+
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);
162
+ top.forEach(([cmd, desc], i) => {
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('│')}`));
165
+ });
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)}┘`));
111
171
  }
112
172
 
113
173
  async function handleAIResponse() {
114
- const model = settings.get('defaultModel') || 'groq/llama3-70b-8192';
174
+ const model = settings.get('defaultModel') || 'groq/llama-3.3-70b-versatile';
115
175
  const [provider] = model.split('/');
116
176
  const apiKey = getKey(provider);
117
177
 
118
178
  if (!apiKey) {
119
- blocks.error('Key Missing', `No API key for ${PROVIDER_NAMES[provider] || provider}`);
179
+ blocks.error('Key Missing', `No key for ${PROVIDER_NAMES[provider] || provider}`);
120
180
  return;
121
181
  }
122
182
 
123
- const systemMsg = { role: 'system', content: SYSTEM_PROMPT + '\n\nUser memory: ' + (memory.show().filter(m => m.role === 'system').map(m => m.content).join('; ') || 'none') };
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
+ };
124
187
  const messages = [systemMsg, ...conversation];
125
188
 
126
- spin.start('CLARITY is thinking...');
189
+ spin.start('thinking...');
127
190
 
128
191
  try {
129
192
  const stream = sendMessage(apiKey, messages, model, settings.get('stream'));
193
+ const streaming = settings.get('stream');
130
194
 
131
- if (settings.get('stream')) {
195
+ if (streaming) {
132
196
  spin.stop();
133
- const w = process.stdout.columns || 80;
134
- console.log(c.accent(` ╔${''.repeat(w - 4)}╗`));
135
- process.stdout.write(c.accent(' ║ ') + c.ai('CLARITY ') + c.white(''));
197
+ const w = getW();
198
+ const line = ''.repeat(w - 4);
199
+
200
+ console.log(c.accent(` ┏${line}┓`));
201
+ console.log(c.accent(` ┃${PURPLE_BG}${fill(w - 4)}${RESET}${c.accent('┃')}`));
202
+ process.stdout.write(c.accent(` ┃${PURPLE_BG} ${c.ai('CLARITY')} `));
136
203
  let full = '';
204
+
137
205
  try {
138
206
  for await (const chunk of stream) {
139
207
  full += chunk;
140
- process.stdout.write(c.white(chunk));
208
+ process.stdout.write(chunk);
141
209
  }
142
210
  } catch (streamErr) {
143
- if (full) process.stdout.write(c.warning('\n\n[stream interrupted]'));
211
+ if (full) process.stdout.write(c.warning('\n\n[interrupted]'));
144
212
  else throw streamErr;
145
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('┃')}`);
146
219
  console.log();
147
- console.log(c.accent(` ╚${'═'.repeat(w - 4)}╝`));
220
+ console.log(c.accent(` ┃${PURPLE_BG}${fill(w - 4)}${RESET}${c.accent('┃')}`));
221
+ console.log(c.accent(` ┗${line}┛`));
148
222
  console.log();
223
+
149
224
  if (full.trim()) {
150
225
  conversation.push({ role: 'assistant', content: full });
151
226
  memory.add(conversation);
@@ -164,9 +239,9 @@ async function handleAIResponse() {
164
239
  }
165
240
 
166
241
  if (settings.get('showTokens')) {
167
- const inTokens = Math.ceil(conversation.reduce((s, m) => s + m.content.length, 0) / 4);
168
- const outTokens = Math.ceil(conversation.filter(m => m.role === 'assistant').reduce((s, m) => s + m.content.length, 0) / 4);
169
- console.log(c.dim(` tokens: ${inTokens} in / ${outTokens} out • free`));
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`));
170
245
  }
171
246
  } catch (err) {
172
247
  spin.fail('Error');
@@ -175,7 +250,9 @@ async function handleAIResponse() {
175
250
  }
176
251
 
177
252
  function closeChat() {
253
+ process.stdin.removeListener('keypress', onKeypress);
178
254
  if (rl) rl.close();
255
+ if (process.stdin.isTTY) process.stdin.setRawMode(false);
179
256
  console.log(c.muted('\nGoodbye!'));
180
257
  process.exit(0);
181
258
  }
package/src/ui/prompt.js CHANGED
@@ -2,7 +2,6 @@ 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;
8
7
  const ALL_COMMANDS = [
@@ -16,6 +15,8 @@ const ALL_COMMANDS = [
16
15
  ['keys list', 'List API keys'],
17
16
  ['keys remove', 'Remove API key'],
18
17
  ['keys test', 'Test API key'],
18
+ ['provider', 'Switch AI provider'],
19
+ ['model', 'Select AI model'],
19
20
  ['model set', 'Set default model'],
20
21
  ['model list', 'List models'],
21
22
  ['config show', 'Show config'],
@@ -73,7 +74,6 @@ const ALL_COMMANDS = [
73
74
 
74
75
  const MAX_HISTORY = 500;
75
76
  let history = [];
76
- let showCmdSuggest = false;
77
77
 
78
78
  function loadHistory() {
79
79
  try {
@@ -99,17 +99,6 @@ function addToHistory(line) {
99
99
  }
100
100
  }
101
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
-
113
102
  function createPrompt() {
114
103
  loadHistory();
115
104
  const rl = readline.createInterface({
@@ -119,12 +108,19 @@ function createPrompt() {
119
108
  historySize: MAX_HISTORY,
120
109
  terminal: true,
121
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
+ }
122
122
  });
123
123
  return rl;
124
124
  }
125
125
 
126
- function showPrompt() {
127
- process.stdout.write(` ${c.primary('◆')} `);
128
- }
129
-
130
- export { createPrompt, showPrompt, addToHistory, loadHistory, history, showSuggestions, ALL_COMMANDS };
126
+ export { createPrompt, addToHistory, loadHistory, history, ALL_COMMANDS };