clarity-ai 3.0.2 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,71 @@
1
+ # Change Log
2
+
3
+ ---
4
+
5
+ ## 3.1.0 (2026-06-05)
6
+
7
+ ### UI Rewrite — OpenCode Style
8
+ - **User messages**: Light purple background (`#2D1B4E`), `#C39BD3` text, `❯ YOU` header bar
9
+ - **AI responses**: Left border only (`│`), no box, no background, `◆ CLARITY` header
10
+ - **File edits**: OpenCode diff format — `✎ filename` bar + `+`/`-` lines with line numbers
11
+ - **File writes**: `+ filename` header with line count written
12
+ - **Tool calls**: Inline `⚙ toolname · input` with preview output (max 8 lines)
13
+ - **Info/Error/Success/Warning**: Clean single-line renders with icons
14
+
15
+ ### System Prompt — Anti-Hallucination
16
+ - Strict rules: NEVER fabricate content, NEVER confirm before acting
17
+ - Always ground responses in actual tool output
18
+ - Read files before editing, never overwrite unread files
19
+ - Short, direct responses — no filler
20
+
21
+ ### Agent Loop — Inline Diff Display
22
+ - `executeToolCall()` handles write_file, edit_file, bash, read_file natively
23
+ - edit_file computes unified diff using `diff` package (`diffLines`)
24
+ - `filterContextHunks()` shows ±2 context lines around changes
25
+ - Dynamic fallback for other tools via `import()` from `tools/` directory
26
+
27
+ ---
28
+
29
+ ## 3.0.2 (2026-06-05)
30
+
31
+ - Complete rebuild per CLARITY_V3_PROMPT.md spec
32
+ - Removed all Ink+React code (15 files) — replaced with readline-based TUI
33
+ - 25 tool modules in `src/tools/`, 20 command modules in `src/commands/`
34
+ - 6 provider modules with streaming SSE parsing
35
+ - ReAct agent loop with JSON-only tool call parsing
36
+ - Figlet+gradient animated banner, first-run setup wizard
37
+
38
+ ---
39
+
40
+ ## 3.0.1 (2026-06-05)
41
+
42
+ - Fixed JSX → createElement conversion in all Ink components
43
+ - Fixed import errors and TTY check
44
+ - Fixed Termux bin wrapper (symlink → bash script)
45
+
46
+ ---
47
+
48
+ ## 3.0.0 (2026-06-05)
49
+
50
+ - Complete Ink+React TUI rewrite
51
+ - 11 React components, global state with reducer
52
+ - Parallel subagent spawning
53
+ - 15 tools, 17 commands
54
+
55
+ ---
56
+
57
+ ## 2.0.0 (2026-06-04)
58
+
59
+ - Figlet+gradient banner, 12 block types with timestamps
60
+ - 22-token color palette
61
+ - ReAct agent loop with JSON parsing and 3 retry attempts
62
+ - 20 tools, first-run wizard, split commands
63
+
64
+ ---
65
+
66
+ ## 1.3.0 (2026-06-04)
67
+
68
+ - Provider model lists updated to 2026
69
+ - Interactive `/model` selector, `/provider` command
70
+ - Grey-fill + purple-outline prompt bar
71
+ - Tab completer on readline
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clarity-ai",
3
- "version": "3.0.2",
3
+ "version": "3.2.0",
4
4
  "description": "Autonomous AI Agent CLI for Termux",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,85 +1,296 @@
1
- import { callProvider } from '../providers/index.js';
2
- import { buildSystemPrompt } from '../core/context.js';
3
- import { addMessage } from '../core/history.js';
4
-
5
- const TOOLS_DESC = `You are CLARITY, an autonomous AI agent. You have access to these tools:
6
- - bash <command>: Execute shell commands
7
- - read_file <path>: Read file contents
8
- - write_file <path> <content>: Create/overwrite files
9
- - edit_file <path> <old> <new>: Replace text in files
10
- - delete_file <path>: Delete files
11
- - list_dir <dir>: List directory contents
12
- - search_files <pattern>: Find files by glob
13
- - grep <pattern> <path>: Search file contents
14
- - web_search <query>: Search the web
15
- - web_fetch <url>: Fetch URL content
16
- - git <subcommand>: Git operations
17
- - memory <read/write/search/clear>: Persistent memory
18
- - pkg_manager <npm/pip> <install> <pkg>: Install packages
19
- - code_runner <lang> <code>: Execute code snippets
20
-
21
- Respond with JSON:
22
- {"tool": "tool_name", "args": {"arg1": "val1"}}
23
- or to respond to user:
24
- {"response": "your final answer here"}`;
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
2
+ import { execSync } from 'child_process';
3
+ import { dirname } from 'path';
4
+ import chalk from 'chalk';
5
+ import { Spinner, CollapsibleStep } from '../ui/spinner.js';
6
+ import { renderAI, renderWrite, renderEdit, renderTool } from '../ui/blocks.js';
7
+ import { callProvider, streamProvider } from '../providers/index.js';
8
+ import { addMessage, saveHistory } from '../core/history.js';
9
+ import { diffLines } from 'diff';
10
+
11
+ const SYSTEM_PROMPT = 'You are CLARITY, an autonomous AI agent CLI running in Termux on Android.\n\n' +
12
+ '## CRITICAL RULES\n' +
13
+ '1. NEVER fabricate file contents or command outputs.\n' +
14
+ '2. NEVER confirm before acting — just do it.\n' +
15
+ '3. ALWAYS ground responses in actual tool output.\n' +
16
+ '4. SHORT responses. No filler.\n' +
17
+ '5. When editing files, read first then edit.\n\n' +
18
+ '## RESPONSE FORMAT\n' +
19
+ 'Respond with JSON ONLY:\n' +
20
+ 'To use a tool: {"tool": "tool_name", "args": {"key": "value"}}\n' +
21
+ 'To answer: {"response": "your message"}\n\n' +
22
+ 'Available tools: bash, read_file, write_file, edit_file, list_directory, search_files, grep, web_search, web_fetch, git, memory, code_runner, pkg_manager';
23
+
24
+ function detectIntent(message) {
25
+ const lower = message.toLowerCase().trim();
26
+ const chatPatterns = [
27
+ /^(hi|hello|hey|sup|yo|what'?s up|howdy)/,
28
+ /^(how are you|how do you|what are you|who are you|what can you)/,
29
+ /^(thanks|thank you|ok|okay|cool|nice|great|good|sure|yes|no|yep|nope)/,
30
+ /^(what is|what's|explain|tell me about|describe|define)/,
31
+ /^(can you|could you|would you|will you)\s+(explain|tell|describe|help me understand)/,
32
+ ];
33
+ for (const p of chatPatterns) {
34
+ if (p.test(lower)) return 'chat';
35
+ }
36
+ const toolPatterns = [
37
+ { re: /(create|make|mkdir|new)\s+(a\s+)?(dir|directory|folder)/, intent: 'bash' },
38
+ { re: /(create|make|write|generate)\s+(a\s+)?(file|script|code|program)/, intent: 'write_file' },
39
+ { re: /(run|execute|launch|start)\s+/, intent: 'bash' },
40
+ { re: /(edit|modify|change|update|fix)\s+(the\s+)?file/, intent: 'edit_file' },
41
+ { re: /(read|show|display|cat|open)\s+(the\s+)?file/, intent: 'read_file' },
42
+ { re: /(list|ls|show)\s+(files|dir|directory|folders)/, intent: 'list_directory' },
43
+ { re: /(search|find|grep|look for)/, intent: 'search' },
44
+ { re: /(install|npm|pip|pkg)\s+/, intent: 'bash' },
45
+ { re: /(git\s+)/, intent: 'git' },
46
+ { re: /(cd\s+|navigate to|go to)/, intent: 'bash' },
47
+ ];
48
+ for (const { re } of toolPatterns) {
49
+ if (re.test(lower)) return 'agent';
50
+ }
51
+ return 'chat';
52
+ }
53
+
54
+ async function executeTool(name, args) {
55
+ switch (name) {
56
+ case 'bash': {
57
+ try {
58
+ const out = execSync(args.command, {
59
+ encoding: 'utf-8', timeout: 30000,
60
+ cwd: process.env.HOME || '/data/data/com.termux/files/home',
61
+ });
62
+ return out.trim() || '(no output)';
63
+ } catch (e) {
64
+ return 'ERROR: ' + (e.stderr || e.message || String(e));
65
+ }
66
+ }
67
+ case 'read_file': {
68
+ if (!existsSync(args.path)) return 'ERROR: File not found: ' + args.path;
69
+ return readFileSync(args.path, 'utf-8');
70
+ }
71
+ case 'write_file': {
72
+ const dir = dirname(args.path);
73
+ if (dir !== '.') mkdirSync(dir, { recursive: true });
74
+ writeFileSync(args.path, args.content, 'utf-8');
75
+ const lines = args.content.split('\n').length;
76
+ return 'Written ' + lines + ' lines to ' + args.path;
77
+ }
78
+ case 'edit_file': {
79
+ if (!existsSync(args.path)) return 'ERROR: File not found: ' + args.path;
80
+ const original = readFileSync(args.path, 'utf-8');
81
+ if (!original.includes(args.old_str)) return 'ERROR: String not found in ' + args.path;
82
+ const updated = original.replace(args.old_str, args.new_str);
83
+ writeFileSync(args.path, updated, 'utf-8');
84
+ return 'Edited ' + args.path;
85
+ }
86
+ case 'list_directory': {
87
+ try {
88
+ const out = execSync('ls -la "' + (args.path || '.') + '"', { encoding: 'utf-8' });
89
+ return out.trim();
90
+ } catch (e) {
91
+ return 'ERROR: ' + e.message;
92
+ }
93
+ }
94
+ case 'search_files': {
95
+ try {
96
+ const out = execSync('find "' + (args.path || '.') + '" -name "' + args.pattern + '" 2>/dev/null | head -20', { encoding: 'utf-8' });
97
+ return out.trim() || 'No files found';
98
+ } catch (e) {
99
+ return 'ERROR: ' + e.message;
100
+ }
101
+ }
102
+ case 'grep': {
103
+ try {
104
+ const out = execSync('grep -rn "' + args.pattern + '" "' + args.path + '" 2>/dev/null | head -20', { encoding: 'utf-8' });
105
+ return out.trim() || 'No matches';
106
+ } catch (e) {
107
+ return 'No matches or error: ' + e.message;
108
+ }
109
+ }
110
+ default: {
111
+ try {
112
+ const mod = await import('../tools/' + name + '.js');
113
+ const toolFn = Object.values(mod)[0];
114
+ const result = await toolFn(args);
115
+ return result;
116
+ } catch (e) {
117
+ return 'Tool error: ' + e.message;
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ function renderToolStep(name, args, result, elapsed) {
124
+ const argPreview = (() => {
125
+ if (args.command) return String(args.command).slice(0, 50);
126
+ if (args.path) return args.path;
127
+ if (args.pattern) return args.pattern;
128
+ return JSON.stringify(args).slice(0, 50);
129
+ })();
130
+
131
+ const header =
132
+ chalk.hex('#00FFFF')('\u2699 ') +
133
+ chalk.hex('#00FFFF').bold(name) +
134
+ chalk.dim(' \u00b7 ' + argPreview) +
135
+ chalk.dim(' (' + elapsed + 'ms)');
136
+ console.log(header);
137
+
138
+ if (name === 'write_file' && args.content) {
139
+ const lineCount = args.content.split('\n').length;
140
+ console.log(renderWrite(args.path, lineCount));
141
+ return;
142
+ }
143
+
144
+ if (name === 'edit_file' && existsSync(args.path)) {
145
+ try {
146
+ const original = readFileSync(args.path, 'utf-8');
147
+ const updated = original.replace(args.old_str || '', args.new_str || '');
148
+ const changes = diffLines(original, updated);
149
+ const hunks = [];
150
+ let lineNum = 1;
151
+ for (const part of changes) {
152
+ const partLines = part.value.split('\n');
153
+ if (partLines[partLines.length - 1] === '') partLines.pop();
154
+ for (const line of partLines) {
155
+ if (part.added) hunks.push({ type: 'add', line, lineNum });
156
+ else if (part.removed) hunks.push({ type: 'remove', line, lineNum });
157
+ else hunks.push({ type: 'context', line, lineNum });
158
+ lineNum++;
159
+ }
160
+ }
161
+ const changed = new Set(hunks.map((h, i) => h.type !== 'context' ? i : -1).filter(i => i >= 0));
162
+ const filtered = hunks.filter((_, i) => {
163
+ if (hunks[i].type !== 'context') return true;
164
+ for (const c of changed) if (Math.abs(i - c) <= 2) return true;
165
+ return false;
166
+ });
167
+ console.log(renderEdit(args.path, filtered));
168
+ } catch {}
169
+ return;
170
+ }
171
+
172
+ if (result && result !== '(no output)' && !result.startsWith('ERROR') && name !== 'read_file') {
173
+ const resultLines = String(result).split('\n').slice(0, 6);
174
+ for (const l of resultLines) {
175
+ console.log(chalk.dim(' \u2502 ') + chalk.hex('#AAAAAA')(l));
176
+ }
177
+ if (String(result).split('\n').length > 6) {
178
+ console.log(chalk.dim(' \u2502 ...'));
179
+ }
180
+ }
181
+
182
+ if (result && String(result).startsWith('ERROR')) {
183
+ console.log(chalk.hex('#FF4757')(' \u2716 ') + chalk.hex('#FF4757')(result));
184
+ }
185
+ }
25
186
 
26
187
  export async function agentLoop(userMessage, config, history) {
27
- const messages = [
28
- { role: 'system', content: buildSystemPrompt(TOOLS_DESC) },
29
- ...history.slice(-20).map(m => ({ role: m.role === 'assistant' ? 'assistant' : 'user', content: m.content })),
188
+ const intent = detectIntent(userMessage);
189
+ const spinner = new Spinner();
190
+ const msgHistory = Array.isArray(history) ? history : (history.messages || []);
191
+
192
+ if (intent === 'chat') {
193
+ spinner.start('Thinking', 'think');
194
+ const t0 = Date.now();
195
+
196
+ const messages = [
197
+ { role: 'system', content: 'You are CLARITY-AI, a helpful assistant. Keep responses short.' },
198
+ ...msgHistory.slice(-10).map(m => ({ role: m.role === 'assistant' ? 'assistant' : 'user', content: m.content })),
199
+ { role: 'user', content: userMessage },
200
+ ];
201
+
202
+ const response = await callProvider(config, messages);
203
+ const elapsed = Date.now() - t0;
204
+ spinner.stop('Thought ' + (elapsed < 1000 ? elapsed + 'ms' : (elapsed/1000).toFixed(1) + 's'), 'done');
205
+
206
+ addMessage(msgHistory, 'user', userMessage);
207
+ addMessage(msgHistory, 'assistant', response);
208
+ saveHistory(msgHistory);
209
+
210
+ console.log();
211
+ console.log(renderAI(response));
212
+ console.log();
213
+ return;
214
+ }
215
+
216
+ let messages = [
217
+ { role: 'system', content: SYSTEM_PROMPT },
218
+ ...msgHistory.slice(-20).map(m => ({ role: m.role === 'assistant' ? 'assistant' : 'user', content: m.content })),
30
219
  { role: 'user', content: userMessage },
31
220
  ];
32
221
 
33
- let finalResponse = '';
34
- let toolCalls = 0;
35
- const maxToolCalls = 20;
222
+ let loopCount = 0;
223
+ const MAX_LOOPS = 15;
224
+
225
+ while (loopCount < MAX_LOOPS) {
226
+ loopCount++;
227
+
228
+ spinner.start('Thinking', 'think');
229
+ const t0 = Date.now();
36
230
 
37
- while (toolCalls < maxToolCalls) {
38
231
  let fullResponse = '';
39
- const stream = callProvider(config, messages);
232
+ const stream = streamProvider(config, messages);
40
233
  for await (const chunk of stream) {
41
234
  fullResponse += chunk;
42
235
  }
43
236
 
44
- try {
45
- const jsonMatch = fullResponse.match(/\{[^]*\}/);
46
- if (!jsonMatch) {
47
- finalResponse = fullResponse;
48
- break;
49
- }
50
- const parsed = JSON.parse(jsonMatch[0]);
51
-
52
- if (parsed.response) {
53
- finalResponse = parsed.response;
54
- break;
55
- }
237
+ const thinkMs = Date.now() - t0;
238
+ spinner.stop('Thought ' + (thinkMs < 1000 ? thinkMs + 'ms' : (thinkMs/1000).toFixed(1) + 's'), 'done');
56
239
 
57
- if (parsed.tool) {
58
- toolCalls++;
59
- const toolName = parsed.tool;
60
- const args = parsed.args || {};
61
-
62
- let result;
63
- try {
64
- const mod = await import(`../tools/${toolName}.js`);
65
- const toolFn = Object.values(mod)[0];
66
- result = await toolFn(args);
67
- } catch (e) {
68
- result = `Tool error: ${e.message}`;
69
- }
240
+ const jsonMatch = fullResponse.match(/\{[^]*\}/);
241
+ if (!jsonMatch) {
242
+ console.log();
243
+ console.log(renderAI(fullResponse));
244
+ console.log();
245
+ addMessage(msgHistory, 'user', userMessage);
246
+ addMessage(msgHistory, 'assistant', fullResponse);
247
+ saveHistory(msgHistory);
248
+ return;
249
+ }
70
250
 
71
- messages.push({ role: 'assistant', content: fullResponse });
72
- messages.push({ role: 'user', content: `Tool result: ${result}` });
73
- }
251
+ let parsed;
252
+ try {
253
+ parsed = JSON.parse(jsonMatch[0]);
74
254
  } catch {
75
- finalResponse = fullResponse;
76
- break;
255
+ console.log();
256
+ console.log(renderAI(fullResponse));
257
+ console.log();
258
+ addMessage(msgHistory, 'user', userMessage);
259
+ addMessage(msgHistory, 'assistant', fullResponse);
260
+ saveHistory(msgHistory);
261
+ return;
77
262
  }
78
- }
79
263
 
80
- if (toolCalls >= maxToolCalls) {
81
- finalResponse += '\n\n(Max tool calls reached)';
264
+ if (parsed.response) {
265
+ const finalText = parsed.response;
266
+ console.log();
267
+ console.log(renderAI(finalText));
268
+ console.log();
269
+ addMessage(msgHistory, 'user', userMessage);
270
+ addMessage(msgHistory, 'assistant', finalText);
271
+ saveHistory(msgHistory);
272
+ return;
273
+ }
274
+
275
+ if (parsed.tool) {
276
+ const name = parsed.tool;
277
+ const args = parsed.args || {};
278
+ const argPreview = args.command || args.path || args.pattern || '';
279
+
280
+ spinner.start('Running ' + name, 'tool');
281
+ spinner.update('Running ' + name, argPreview.slice(0, 40));
282
+
283
+ const ts = Date.now();
284
+ const result = await executeTool(name, args);
285
+ const te = Date.now() - ts;
286
+
287
+ spinner.stop(name + ' \u00b7 ' + argPreview.slice(0, 30), 'done');
288
+ renderToolStep(name, args, result, te);
289
+
290
+ messages.push({ role: 'assistant', content: fullResponse });
291
+ messages.push({ role: 'user', content: 'Tool result: ' + result });
292
+ }
82
293
  }
83
294
 
84
- return finalResponse || 'No response generated';
295
+ console.log(chalk.hex('#FFB800')('\u26a0 Max tool calls reached. Stopping.'));
85
296
  }
@@ -1,61 +1,125 @@
1
- import { helpCommand } from './help.js';
2
- import { chatCommand } from './chat.js';
3
- import { agentCommand } from './agent.js';
4
- import { toolsCommand } from './tools.js';
5
- import { memoryCommand } from './memory.js';
6
- import { historyCommand } from './history.js';
7
- import { modelCommand } from './model.js';
8
- import { providerCommand } from './provider.js';
9
- import { configCommand } from './config.js';
10
- import { clearCommand } from './clear.js';
11
- import { runCommand } from './run.js';
12
- import { taskCommand } from './task.js';
13
- import { searchCommand } from './search.js';
14
- import { fetchCommand } from './fetch.js';
15
- import { gitCommand } from './git.js';
16
- import { diffCommand } from './diff.js';
17
- import { undoCommand } from './undo.js';
18
- import { exportCommand } from './export.js';
19
- import { keysCommand } from './keys.js';
20
- import { exitCommand } from './exit.js';
1
+ export const ALL_COMMANDS = [
2
+ { name: '/help', description: 'Show all commands' },
3
+ { name: '/agent', description: 'Toggle agent mode on/off' },
4
+ { name: '/model', description: 'Switch AI model' },
5
+ { name: '/provider', description: 'Switch AI provider' },
6
+ { name: '/tools', description: 'List all tools + permissions' },
7
+ { name: '/memory', description: 'View or clear memory' },
8
+ { name: '/history', description: 'View conversation history' },
9
+ { name: '/chat', description: 'Start new chat session' },
10
+ { name: '/clear', description: 'Clear screen + redraw banner' },
11
+ { name: '/task', description: 'Run a full autonomous task' },
12
+ { name: '/search', description: 'Web search' },
13
+ { name: '/fetch', description: 'Fetch a URL' },
14
+ { name: '/git', description: 'Git operations' },
15
+ { name: '/diff', description: 'Compare two files' },
16
+ { name: '/run', description: 'Execute a script file' },
17
+ { name: '/keys', description: 'Manage API keys' },
18
+ { name: '/config', description: 'View or edit settings' },
19
+ { name: '/export', description: 'Export chat to markdown' },
20
+ { name: '/compact', description: 'Compress conversation history' },
21
+ { name: '/undo', description: 'Revert last AI file change' },
22
+ { name: '/exit', description: 'Exit CLARITY' },
23
+ ];
21
24
 
22
- const COMMANDS = {
23
- '/help': helpCommand,
24
- '/chat': chatCommand,
25
- '/agent': agentCommand,
26
- '/tools': toolsCommand,
27
- '/memory': memoryCommand,
28
- '/history': historyCommand,
29
- '/model': modelCommand,
30
- '/provider': providerCommand,
31
- '/config': configCommand,
32
- '/clear': clearCommand,
33
- '/run': runCommand,
34
- '/task': taskCommand,
35
- '/search': searchCommand,
36
- '/fetch': fetchCommand,
37
- '/git': gitCommand,
38
- '/diff': diffCommand,
39
- '/undo': undoCommand,
40
- '/export': exportCommand,
41
- '/keys': keysCommand,
42
- '/exit': exitCommand,
43
- };
25
+ export async function dispatchCommand(input, config, history) {
26
+ const parts = String(input).trim().split(/\s+/);
27
+ const cmd = parts[0].toLowerCase();
28
+ const args = parts.slice(1);
44
29
 
45
- export async function dispatchCommand(input, config, rl) {
46
- const parts = input.trim().split(/\s+/);
47
- const cmd = parts[0].toLowerCase();
48
- const args = parts.slice(1);
49
- const handler = COMMANDS[cmd];
50
- if (!handler) {
51
- console.log(`Unknown command: ${cmd}. Type /help for commands.`);
52
- return config;
53
- }
54
30
  try {
55
- const result = await handler(args, config, rl);
56
- return result || config;
57
- } catch (err) {
58
- console.log(`\x1b[31mCommand error:\x1b[0m ${err.message}`);
59
- return config;
31
+ switch (cmd) {
32
+ case '/help': {
33
+ const m = await import('./help.js');
34
+ return m.helpCommand(args, config);
35
+ }
36
+ case '/agent': {
37
+ const m = await import('./agent.js');
38
+ return m.agentCommand(args, config);
39
+ }
40
+ case '/model': {
41
+ const m = await import('./model.js');
42
+ return m.modelCommand(args, config);
43
+ }
44
+ case '/provider': {
45
+ const m = await import('./provider.js');
46
+ return m.providerCommand(args, config);
47
+ }
48
+ case '/tools': {
49
+ const m = await import('./tools.js');
50
+ return m.toolsCommand(args, config);
51
+ }
52
+ case '/memory': {
53
+ const m = await import('./memory.js');
54
+ return m.memoryCommand(args, config);
55
+ }
56
+ case '/history': {
57
+ const m = await import('./history.js');
58
+ return m.historyCommand(args, config);
59
+ }
60
+ case '/chat': {
61
+ const m = await import('./chat.js');
62
+ return m.chatCommand(args, config);
63
+ }
64
+ case '/clear': {
65
+ const { showBanner } = await import('../ui/banner.js');
66
+ process.stdout.write('\x1Bc');
67
+ await showBanner(config.version, config.provider, config.model);
68
+ return;
69
+ }
70
+ case '/task': {
71
+ const m = await import('./task.js');
72
+ return m.taskCommand(args, config);
73
+ }
74
+ case '/search': {
75
+ const m = await import('./search.js');
76
+ return m.searchCommand(args, config);
77
+ }
78
+ case '/fetch': {
79
+ const m = await import('./fetch.js');
80
+ return m.fetchCommand(args, config);
81
+ }
82
+ case '/git': {
83
+ const m = await import('./git.js');
84
+ return m.gitCommand(args, config);
85
+ }
86
+ case '/diff': {
87
+ const m = await import('./diff.js');
88
+ return m.diffCommand(args, config);
89
+ }
90
+ case '/run': {
91
+ const m = await import('./run.js');
92
+ return m.runCommand(args, config);
93
+ }
94
+ case '/keys': {
95
+ const m = await import('./keys.js');
96
+ return m.keysCommand(args, config);
97
+ }
98
+ case '/config': {
99
+ const m = await import('./config.js');
100
+ return m.configCommand(args, config);
101
+ }
102
+ case '/export': {
103
+ const m = await import('./export.js');
104
+ return m.exportCommand(args, config);
105
+ }
106
+ case '/undo': {
107
+ const m = await import('./undo.js');
108
+ return m.undoCommand(args, config);
109
+ }
110
+ case '/exit': {
111
+ const chalk = (await import('chalk')).default;
112
+ console.log(chalk.hex('#00FF9F')('\n\u2714 Goodbye.'));
113
+ process.exit(0);
114
+ }
115
+ default: {
116
+ const chalk = (await import('chalk')).default;
117
+ console.log(chalk.hex('#FF4757')('\u2716 Unknown command: ') + cmd);
118
+ console.log(chalk.dim(' Type / to see all commands'));
119
+ }
120
+ }
121
+ } catch (e) {
122
+ const chalk = (await import('chalk')).default;
123
+ console.log(chalk.hex('#FF4757')('\u2716 Command error: ') + e.message);
60
124
  }
61
125
  }
@@ -1,11 +1,26 @@
1
1
  import { saveConfig } from '../config/settings.js';
2
- import { clr } from '../ui/colors.js';
2
+ import { PROVIDERS, getProvider } from '../providers/index.js';
3
+ import chalk from 'chalk';
4
+
3
5
  export async function modelCommand(args, config) {
4
6
  if (args.length === 0) {
5
- return console.log(clr.info('Current model: ' + config.provider + '/' + config.model));
7
+ console.log(chalk.hex('#54A0FF')('Current model: ') + chalk.white(config.provider + '/' + config.model));
8
+ return;
6
9
  }
7
- config.model = args[0];
10
+
11
+ const modelName = args[0];
12
+
13
+ const prov = getProvider(modelName);
14
+ if (prov) {
15
+ config.provider = prov.value;
16
+ config.model = prov.freeModel;
17
+ saveConfig(config);
18
+ console.log(chalk.hex('#00FF9F')('Switched to provider: ') + chalk.white(prov.name) + chalk.dim(' (' + prov.freeModel + ')'));
19
+ return config;
20
+ }
21
+
22
+ config.model = modelName;
8
23
  saveConfig(config);
9
- console.log(clr.success('Model set to: ' + config.model));
24
+ console.log(chalk.hex('#00FF9F')('Model set to: ') + chalk.white(modelName));
10
25
  return config;
11
26
  }