clarity-ai 1.0.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.
Files changed (44) hide show
  1. package/README.md +108 -0
  2. package/bin/clarity.js +2 -0
  3. package/package.json +51 -0
  4. package/scripts/postinstall.js +53 -0
  5. package/src/agents/BaseAgent.js +54 -0
  6. package/src/agents/CodeAgent.js +57 -0
  7. package/src/agents/FileAgent.js +39 -0
  8. package/src/agents/MonitorAgent.js +54 -0
  9. package/src/agents/ShellAgent.js +31 -0
  10. package/src/agents/WebAgent.js +39 -0
  11. package/src/agents/manager.js +116 -0
  12. package/src/commands/index.js +725 -0
  13. package/src/config/keys.js +104 -0
  14. package/src/config/paths.js +22 -0
  15. package/src/config/settings.js +28 -0
  16. package/src/index.js +86 -0
  17. package/src/memory/context.js +38 -0
  18. package/src/memory/store.js +54 -0
  19. package/src/providers/claude.js +61 -0
  20. package/src/providers/deepseek.js +53 -0
  21. package/src/providers/gemini.js +48 -0
  22. package/src/providers/groq.js +52 -0
  23. package/src/providers/index.js +39 -0
  24. package/src/providers/openai.js +52 -0
  25. package/src/providers/openrouter.js +52 -0
  26. package/src/tools/bash.js +25 -0
  27. package/src/tools/code.js +52 -0
  28. package/src/tools/files.js +62 -0
  29. package/src/tools/git.js +67 -0
  30. package/src/tools/index.js +40 -0
  31. package/src/tools/pkg.js +46 -0
  32. package/src/tools/search.js +29 -0
  33. package/src/tools/system.js +29 -0
  34. package/src/tools/web.js +24 -0
  35. package/src/ui/banner.js +15 -0
  36. package/src/ui/blocks.js +55 -0
  37. package/src/ui/chatbox.js +126 -0
  38. package/src/ui/colors.js +22 -0
  39. package/src/ui/prompt.js +49 -0
  40. package/src/ui/spinner.js +43 -0
  41. package/src/utils/logger.js +40 -0
  42. package/src/utils/markdown.js +25 -0
  43. package/src/utils/termux.js +38 -0
  44. package/src/utils/version-check.js +66 -0
@@ -0,0 +1,725 @@
1
+ import blocks from '../ui/blocks.js';
2
+ import c from '../ui/colors.js';
3
+ import settings from '../config/settings.js';
4
+ import { setKey, getKey, listKeys, removeKey, testKey, hasAnyKeys, PROVIDER_NAMES } from '../config/keys.js';
5
+ import { listModels, capabilities, getPriorityProviders } from '../providers/index.js';
6
+ import agentManager from '../agents/manager.js';
7
+ import { listTools, runTool } from '../tools/index.js';
8
+ import memory from '../memory/store.js';
9
+ import { isTermux, openURL } from '../utils/termux.js';
10
+ import { renderMarkdown } from '../utils/markdown.js';
11
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, unlinkSync } from 'fs';
12
+ import { resolve, dirname, basename, extname, join } from 'path';
13
+ import readline from 'readline';
14
+
15
+ const commandRegistry = {
16
+ async execute(input, context = {}) {
17
+ const parts = input.trim().split(/\s+/);
18
+ const cmd = parts[0].toLowerCase();
19
+ const args = parts.slice(1);
20
+ const fullInput = input.trim().slice(cmd.length).trim();
21
+
22
+ try {
23
+ switch (cmd) {
24
+ case '/help': return this.help(args);
25
+ case '/init': return this.init();
26
+ case '/chat': return this.chat();
27
+ case '/ask': return this.ask(args);
28
+ case '/clear': return this.clear();
29
+ case '/exit': return this.exit();
30
+
31
+ case '/keys': return this.keys(args);
32
+ case '/model': return this.model(args);
33
+ case '/config': return this.config(args);
34
+ case '/theme': return this.theme(args);
35
+
36
+ case '/file': return this.file(args);
37
+ case '/bash': return this.bash(args);
38
+ case '/code': return this.code(args);
39
+ case '/web': return this.web(args);
40
+ case '/search': return this.search(args);
41
+ case '/git': return this.git(args);
42
+ case '/pkg': return this.pkg(args);
43
+
44
+ case '/agent': return this.agent(args);
45
+ case '/tools': return this.tools(args);
46
+
47
+ case '/memory': return this.memory(args);
48
+ case '/history': return this.history(args);
49
+
50
+ case '/env': return this.env(args);
51
+ case '/status': return this.status();
52
+ case '/version': return this.version();
53
+
54
+ case '/translate': args.length ? blocks.ai(`Translation of "${args.join(' ')}": [AI translation unavailable - configure API keys]`) : blocks.warn('Usage', '/translate <text>');
55
+ case '/summarize': return args.length ? this.summarize(args[0]) : blocks.warn('Usage', '/summarize <file|url>');
56
+ case '/generate': return args.length ? this.generate(args) : blocks.warn('Usage', '/generate <type>');
57
+ case '/export': return args.length ? this.exportChat(args[0]) : blocks.warn('Usage', '/export <json|md|txt>');
58
+ case '/import': return args.length ? this.importChat(args[0]) : blocks.warn('Usage', '/import <file>');
59
+ case '/alias': return this.alias(args);
60
+ case '/plugin': return this.plugin(args);
61
+ case '/update': return this.update();
62
+ case '/debug': return args.length ? this.debug(args[0]) : blocks.warn('Usage', '/debug <file>');
63
+ case '/review': return args.length ? this.review(args[0]) : blocks.warn('Usage', '/review <file>');
64
+ case '/refactor': return args.length ? this.refactor(args[0]) : blocks.warn('Usage', '/refactor <file>');
65
+ case '/test': return args.length ? this.test(args[0]) : blocks.warn('Usage', '/test <file>');
66
+ case '/docs': return args.length ? this.docs(args[0]) : blocks.warn('Usage', '/docs <file>');
67
+ case '/deploy': return args.length ? this.deploy(args[0]) : blocks.warn('Usage', '/deploy <target>');
68
+ case '/explain': return args.length ? this.explain(args[0]) : blocks.warn('Usage', '/explain <code or file>');
69
+ case '/run': blocks.info('Run', 'Use /bash <command> to run shell commands');
70
+ default:
71
+ blocks.warn('Unknown Command', `Unknown command: ${cmd}. Type /help for available commands.`);
72
+ }
73
+ } catch (err) {
74
+ blocks.error('Command Error', err.message);
75
+ }
76
+ return null;
77
+ },
78
+
79
+ async init() {
80
+ const { default: inquirer } = await import('inquirer');
81
+ blocks.divider('CLARITY Setup');
82
+ blocks.info('Welcome', 'Let\'s configure your AI agent CLI\nPress Ctrl+C to cancel at any time.');
83
+
84
+ const { providers: selected } = await inquirer.prompt([{
85
+ type: 'checkbox',
86
+ name: 'providers',
87
+ message: 'Which AI providers do you want to configure?',
88
+ choices: [
89
+ { name: 'Groq (FREE — Recommended)', value: 'groq', checked: true },
90
+ { name: 'Google Gemini (FREE)', value: 'gemini', checked: true },
91
+ { name: 'OpenRouter (FREE models)', value: 'openrouter' },
92
+ { name: 'Anthropic Claude', value: 'anthropic' },
93
+ { name: 'OpenAI GPT', value: 'openai' },
94
+ { name: 'DeepSeek', value: 'deepseek' },
95
+ ]
96
+ }]);
97
+
98
+ for (const p of selected) {
99
+ const { key } = await inquirer.prompt([{
100
+ type: 'password',
101
+ name: 'key',
102
+ message: `Enter your ${PROVIDER_NAMES[p] || p} API key:`,
103
+ validate: v => v.length > 0 || 'Key is required',
104
+ }]);
105
+ try {
106
+ setKey(p, key);
107
+ blocks.success(p, `Key saved for ${PROVIDER_NAMES[p] || p}`);
108
+ } catch (err) {
109
+ blocks.error(p, err.message);
110
+ }
111
+ }
112
+
113
+ const configured = Object.keys(listKeys());
114
+ if (configured.length > 0) {
115
+ const defaultProvider = getPriorityProviders(configured)[0] || configured[0];
116
+ const models = listModels(defaultProvider);
117
+ const { model } = await inquirer.prompt([{
118
+ type: 'list',
119
+ name: 'model',
120
+ message: 'Select your default model:',
121
+ choices: models.map(m => ({ name: `${defaultProvider}/${m}`, value: `${defaultProvider}/${m}` })),
122
+ }]);
123
+ settings.set('defaultModel', model);
124
+
125
+ const { stream, showTokens, saveHist } = await inquirer.prompt([
126
+ { type: 'confirm', name: 'stream', message: 'Stream responses?', default: true },
127
+ { type: 'confirm', name: 'showTokens', message: 'Show token usage?', default: true },
128
+ { type: 'confirm', name: 'saveHist', message: 'Save chat history?', default: true },
129
+ ]);
130
+ settings.set('stream', stream);
131
+ settings.set('showTokens', showTokens);
132
+ settings.set('saveHistory', saveHist);
133
+ }
134
+
135
+ blocks.success('Setup Complete', 'CLARITY configured successfully!\nRun \'clarity\' to start chatting');
136
+ },
137
+
138
+ async help(args) {
139
+ if (args.length > 0) {
140
+ const topic = args[0].replace('/', '');
141
+ blocks.info(`Help: /${topic}`, `See /help for all available commands.`);
142
+ return;
143
+ }
144
+
145
+ const categories = {
146
+ 'CHAT & AI': ['/chat', '/ask <q>', '/clear', '/history show|clear|export', '/memory show|add|clear'],
147
+ 'CONFIGURATION': ['/init', '/keys set|list|remove|test', '/model set|list', '/config show|reset', '/theme set|list'],
148
+ 'FILES': ['/file create|read|delete|list|edit', '/bash <command>', '/code run|write|explain|fix|review|refactor|test|docs'],
149
+ 'WEB & SEARCH': ['/web <url>', '/search <query>', '/translate <text>', '/summarize <file|url>'],
150
+ 'AGENTS & TOOLS': ['/agent start|stop|list|logs', '/tools list|run'],
151
+ 'DEVELOPMENT': ['/git <action>', '/pkg install|remove|search', '/debug <file>', '/deploy <target>', '/generate <type>'],
152
+ 'SYSTEM': ['/env show|set|clear', '/status', '/version', '/update', '/alias create|list|remove', '/plugin install|list|remove'],
153
+ 'UTILITIES': ['/export <json|md|txt>', '/import <file>', '/run', '/help [command]', '/exit'],
154
+ };
155
+
156
+ console.log(c.primary('┌─ CLARITY COMMANDS ───────────────────────────────────────────────┐'));
157
+ for (const [cat, cmds] of Object.entries(categories)) {
158
+ console.log(c.muted(` ${cat}`));
159
+ cmds.forEach(cmd => {
160
+ const [name, ...rest] = cmd.split(' ');
161
+ console.log(` ${c.cmd(name)} ${c.muted(rest.join(' '))}`);
162
+ });
163
+ console.log();
164
+ }
165
+ console.log(c.primary('└──────────────────────────────────────────────────────────────────┘'));
166
+ },
167
+
168
+ ask(args) {
169
+ const question = args.join(' ');
170
+ if (!question) { blocks.warn('Usage', '/ask <question>'); return; }
171
+ blocks.user(question);
172
+ blocks.info('Ask', 'Configure API keys with /init first to get AI responses.');
173
+ },
174
+
175
+ chat() {
176
+ blocks.info('Chat', 'Already in chat mode. Type your message or use / commands.');
177
+ },
178
+
179
+ clear() {
180
+ console.clear();
181
+ blocks.success('Cleared', 'Screen and context cleared.');
182
+ },
183
+
184
+ exit() {
185
+ blocks.info('Goodbye', 'Thank you for using CLARITY!');
186
+ return { exit: true };
187
+ },
188
+
189
+ async keys(args) {
190
+ const sub = args[0];
191
+ if (!sub) {
192
+ blocks.warn('Usage', '/keys set|list|remove|test <provider> [key]');
193
+ return;
194
+ }
195
+
196
+ switch (sub) {
197
+ case 'set': {
198
+ if (args.length < 3) { blocks.warn('Usage', '/keys set <provider> <key>'); return; }
199
+ try {
200
+ setKey(args[1], args.slice(2).join(' '));
201
+ blocks.success('Key Saved', `${PROVIDER_NAMES[args[1]] || args[1]} API key configured.`);
202
+ } catch (err) { blocks.error('Error', err.message); }
203
+ break;
204
+ }
205
+ case 'list': {
206
+ const keys = listKeys();
207
+ if (Object.keys(keys).length === 0) { blocks.info('No Keys', 'No API keys configured. Run /init to set up.'); return; }
208
+ const rows = Object.entries(keys).map(([p, info]) => [PROVIDER_NAMES[p] || p, info.keyPreview]);
209
+ blocks.table(['Provider', 'Key'], rows);
210
+ break;
211
+ }
212
+ case 'remove': {
213
+ if (args.length < 2) { blocks.warn('Usage', '/keys remove <provider>'); return; }
214
+ removeKey(args[1]);
215
+ blocks.success('Removed', `${PROVIDER_NAMES[args[1]] || args[1]} key removed.`);
216
+ break;
217
+ }
218
+ case 'test': {
219
+ if (args.length < 2) { blocks.warn('Usage', '/keys test <provider>'); return; }
220
+ const result = await testKey(args[1]);
221
+ if (result.success) blocks.success('Key Valid', `${PROVIDER_NAMES[args[1]] || args[1]} key is working!`);
222
+ else blocks.error('Key Invalid', `${PROVIDER_NAMES[args[1]] || args[1]}: ${result.error}`);
223
+ break;
224
+ }
225
+ default:
226
+ blocks.warn('Usage', '/keys set|list|remove|test <provider>');
227
+ }
228
+ },
229
+
230
+ async model(args) {
231
+ const sub = args[0];
232
+ if (!sub) { blocks.warn('Usage', '/model list|set <provider/model>'); return; }
233
+
234
+ switch (sub) {
235
+ case 'list': {
236
+ const configured = Object.keys(listKeys());
237
+ if (configured.length === 0) { blocks.info('No Keys', 'Configure keys first with /init'); return; }
238
+ for (const p of configured) {
239
+ const models = listModels(p);
240
+ blocks.info(p, models.map(m => `${p}/${m}`).join('\n'));
241
+ }
242
+ break;
243
+ }
244
+ case 'set': {
245
+ if (args.length < 2) { blocks.warn('Usage', '/model set <provider/model>'); return; }
246
+ settings.set('defaultModel', args[1]);
247
+ blocks.success('Model Set', `Default model: ${args[1]}`);
248
+ break;
249
+ }
250
+ default:
251
+ blocks.warn('Usage', '/model list|set <provider/model>');
252
+ }
253
+ },
254
+
255
+ config(args) {
256
+ const sub = args[0];
257
+ if (!sub) { blocks.warn('Usage', '/config show|reset'); return; }
258
+
259
+ switch (sub) {
260
+ case 'show': {
261
+ const all = settings.store;
262
+ const rows = Object.entries(all).map(([k, v]) => [k, String(v)]);
263
+ blocks.table(['Setting', 'Value'], rows);
264
+ break;
265
+ }
266
+ case 'reset': {
267
+ settings.clear();
268
+ blocks.success('Reset', 'Settings reset to defaults.');
269
+ break;
270
+ }
271
+ default:
272
+ blocks.warn('Usage', '/config show|reset');
273
+ }
274
+ },
275
+
276
+ theme(args) {
277
+ const sub = args[0];
278
+ if (!sub) { blocks.warn('Usage', '/theme set|list'); return; }
279
+
280
+ switch (sub) {
281
+ case 'set': {
282
+ if (args.length < 2) { blocks.warn('Usage', '/theme set <name>'); return; }
283
+ settings.set('theme', args[1]);
284
+ blocks.success('Theme Set', `Theme: ${args[1]}`);
285
+ break;
286
+ }
287
+ case 'list': {
288
+ blocks.info('Themes', 'dark, light, neon, mono');
289
+ break;
290
+ }
291
+ default:
292
+ blocks.warn('Usage', '/theme set|list');
293
+ }
294
+ },
295
+
296
+ async file(args) {
297
+ const sub = args[0];
298
+ if (!sub) { blocks.warn('Usage', '/file create|read|delete|list|edit <path>'); return; }
299
+ const filePath = args.slice(1).join(' ');
300
+
301
+ switch (sub) {
302
+ case 'create': {
303
+ if (!filePath) { blocks.warn('Usage', '/file create <path>'); return; }
304
+ const fullPath = resolve(filePath);
305
+ await mkdirSync(dirname(fullPath), { recursive: true });
306
+ writeFileSync(fullPath, '', 'utf8');
307
+ blocks.success('Created', c.filename(fullPath));
308
+ break;
309
+ }
310
+ case 'read': {
311
+ if (!filePath) { blocks.warn('Usage', '/file read <path>'); return; }
312
+ const fullPath = resolve(filePath);
313
+ if (!existsSync(fullPath)) { blocks.error('Not Found', `File not found: ${fullPath}`); return; }
314
+ const content = readFileSync(fullPath, 'utf8');
315
+ blocks.file(fullPath, content.slice(0, 2000) + (content.length > 2000 ? '\n... (truncated)' : ''));
316
+ break;
317
+ }
318
+ case 'delete': {
319
+ if (!filePath) { blocks.warn('Usage', '/file delete <path>'); return; }
320
+ const fullPath = resolve(filePath);
321
+ if (!existsSync(fullPath)) { blocks.error('Not Found', `File not found: ${fullPath}`); return; }
322
+ unlinkSync(fullPath);
323
+ blocks.warn('Deleted', c.filename(fullPath));
324
+ break;
325
+ }
326
+ case 'list': {
327
+ const dir = resolve(filePath || '.');
328
+ if (!existsSync(dir)) { blocks.error('Not Found', `Directory not found: ${dir}`); return; }
329
+ const items = readdirSync(dir);
330
+ const rows = items.map(name => {
331
+ const full = join(dir, name);
332
+ const isDir = statSync(full).isDirectory();
333
+ return [isDir ? c.primary(name + '/') : name, isDir ? 'dir' : 'file'];
334
+ });
335
+ blocks.table(['Name', 'Type'], rows);
336
+ break;
337
+ }
338
+ case 'edit': {
339
+ if (!filePath) { blocks.warn('Usage', '/file edit <path>'); return; }
340
+ const fullPath = resolve(filePath);
341
+ if (!existsSync(fullPath)) { blocks.error('Not Found', `File not found: ${fullPath}`); return; }
342
+ const editor = process.env.EDITOR || 'nano';
343
+ const { execSync } = await import('child_process');
344
+ execSync(`${editor} "${fullPath}"`, { stdio: 'inherit' });
345
+ blocks.success('Edited', c.filename(fullPath));
346
+ break;
347
+ }
348
+ default:
349
+ blocks.warn('Usage', '/file create|read|delete|list|edit <path>');
350
+ }
351
+ },
352
+
353
+ async bash(args) {
354
+ if (args.length === 0) { blocks.warn('Usage', '/bash <command>'); return; }
355
+ const cmd = args.join(' ');
356
+
357
+ const { exec } = await import('child_process');
358
+ const { promisify } = await import('util');
359
+ const execAsync = promisify(exec);
360
+
361
+ try {
362
+ blocks.info('Running', c.cmd(cmd));
363
+ const { stdout, stderr } = await execAsync(cmd, { timeout: 30000 });
364
+ if (stdout) console.log(c.code(stdout));
365
+ if (stderr) console.log(c.warning(stderr));
366
+ } catch (err) {
367
+ if (err.stdout) console.log(c.code(err.stdout));
368
+ if (err.stderr) console.log(c.error(err.stderr));
369
+ if (!err.stdout && !err.stderr) blocks.error('Error', err.message);
370
+ }
371
+ },
372
+
373
+ async code(args) {
374
+ const sub = args[0];
375
+ if (!sub) { blocks.warn('Usage', '/code run|write|explain|fix|review|refactor|test|docs <file>'); return; }
376
+
377
+ if (sub === 'run') {
378
+ const filePath = args.slice(1).join(' ');
379
+ if (!filePath) { blocks.warn('Usage', '/code run <file>'); return; }
380
+ const fullPath = resolve(filePath);
381
+ if (!existsSync(fullPath)) { blocks.error('Not Found', `File not found: ${fullPath}`); return; }
382
+ const ext = extname(fullPath);
383
+ const { exec } = await import('child_process');
384
+ const { promisify } = await import('util');
385
+ const execAsync = promisify(exec);
386
+
387
+ try {
388
+ let cmd;
389
+ if (ext === '.js' || ext === '.mjs') cmd = `node "${fullPath}"`;
390
+ else if (ext === '.py') cmd = `python3 "${fullPath}"`;
391
+ else if (ext === '.sh') cmd = `bash "${fullPath}"`;
392
+ else cmd = `node "${fullPath}"`;
393
+
394
+ blocks.info('Running', c.cmd(cmd));
395
+ const { stdout, stderr } = await execAsync(cmd, { timeout: 30000 });
396
+ if (stdout) console.log(c.code(stdout));
397
+ if (stderr) blocks.warn('Stderr', stderr);
398
+ } catch (err) {
399
+ blocks.error('Error', err.message);
400
+ }
401
+ } else {
402
+ blocks.info('AI Feature', `/${sub} requires API key configuration. Run /init first.`);
403
+ }
404
+ },
405
+
406
+ async web(args) {
407
+ if (args.length === 0) { blocks.warn('Usage', '/web <url>'); return; }
408
+ const url = args[0];
409
+ try {
410
+ const res = await fetch(url);
411
+ const text = await res.text();
412
+ const clean = text.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
413
+ .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
414
+ .replace(/<[^>]+>/g, ' ')
415
+ .replace(/\s+/g, ' ').trim();
416
+ blocks.info(url, `Status: ${res.status}\n${clean.slice(0, 1500)}`);
417
+ } catch (err) {
418
+ blocks.error('Fetch Error', err.message);
419
+ }
420
+ },
421
+
422
+ async search(args) {
423
+ if (args.length === 0) { blocks.warn('Usage', '/search <query>'); return; }
424
+ const query = args.join(' ');
425
+ try {
426
+ const url = `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_html=1`;
427
+ const res = await fetch(url);
428
+ const data = await res.json();
429
+ const results = [];
430
+ if (data.AbstractText) results.push({ title: 'Summary', snippet: data.AbstractText });
431
+ if (data.RelatedTopics) {
432
+ data.RelatedTopics.slice(0, 5).forEach(t => {
433
+ if (t.Text) results.push({ title: t.Text.split(' - ')[0], snippet: t.Text });
434
+ });
435
+ }
436
+ if (results.length === 0) { blocks.info('No Results', `No results for: ${query}`); return; }
437
+ results.forEach((r, i) => console.log(`${c.primary(String(i + 1))}. ${c.white(r.title)}\n ${c.muted(r.snippet.slice(0, 200))}\n`));
438
+ } catch (err) {
439
+ blocks.error('Search Error', err.message);
440
+ }
441
+ },
442
+
443
+ async git(args) {
444
+ if (args.length === 0) { blocks.warn('Usage', '/git <action> [args]'); return; }
445
+ const { exec } = await import('child_process');
446
+ const { promisify } = await import('util');
447
+ const execAsync = promisify(exec);
448
+
449
+ try {
450
+ const cmd = 'git ' + args.join(' ');
451
+ const { stdout, stderr } = await execAsync(cmd, { timeout: 30000 });
452
+ if (stdout) console.log(c.code(stdout.slice(0, 2000)));
453
+ if (stderr) console.log(c.warning(stderr.slice(0, 1000)));
454
+ } catch (err) {
455
+ blocks.error('Git Error', err.message);
456
+ }
457
+ },
458
+
459
+ async pkg(args) {
460
+ if (args.length < 2) { blocks.warn('Usage', '/pkg install|remove|search <name>'); return; }
461
+ const { exec } = await import('child_process');
462
+ const { promisify } = await import('util');
463
+ const execAsync = promisify(exec);
464
+
465
+ const action = args[0];
466
+ const name = args.slice(1).join(' ');
467
+ const mgr = isTermux() ? 'pkg' : 'npm';
468
+
469
+ try {
470
+ blocks.info('Package', `${mgr} ${action} ${name}`);
471
+ let cmd;
472
+ if (mgr === 'pkg') cmd = `pkg ${action === 'remove' ? 'uninstall' : action} -y ${name}`;
473
+ else cmd = `npm ${action} ${name}`;
474
+ const { stdout, stderr } = await execAsync(cmd, { timeout: 120000 });
475
+ if (stdout) console.log(c.code(stdout.slice(0, 1000)));
476
+ if (stderr) blocks.warn('', stderr.slice(0, 500));
477
+ } catch (err) {
478
+ blocks.error('Error', err.message);
479
+ }
480
+ },
481
+
482
+ async agent(args) {
483
+ const sub = args[0];
484
+ if (!sub) { blocks.warn('Usage', '/agent start|stop|list|logs <type|id> [config]'); return; }
485
+
486
+ switch (sub) {
487
+ case 'list': {
488
+ agentManager.showStatus();
489
+ break;
490
+ }
491
+ case 'start': {
492
+ if (args.length < 2) { blocks.warn('Usage', '/agent start <type> [config]'); return; }
493
+ const type = args[1];
494
+ const config = args[2] ? JSON.parse(args.slice(2).join(' ')) : {};
495
+ const id = await agentManager.start(type, config);
496
+ blocks.success('Agent Started', `ID: ${id}`);
497
+ break;
498
+ }
499
+ case 'stop': {
500
+ if (args.length < 2) { blocks.warn('Usage', '/agent stop <id>'); return; }
501
+ await agentManager.stop(args[1]);
502
+ blocks.success('Agent Stopped', `Agent ${args[1]} stopped.`);
503
+ break;
504
+ }
505
+ case 'logs': {
506
+ if (args.length < 2) { blocks.warn('Usage', '/agent logs <id> [lines]'); return; }
507
+ const lines = args[2] ? parseInt(args[2]) : 20;
508
+ const logs = agentManager.getLogs(args[1], lines);
509
+ if (logs.length === 0) { blocks.info('Logs', 'No logs.'); return; }
510
+ logs.forEach(l => console.log(c.muted(`[${l.time.slice(11, 19)}] [${l.level}]`) + ' ' + l.msg));
511
+ break;
512
+ }
513
+ default:
514
+ blocks.warn('Usage', '/agent start|stop|list|logs');
515
+ }
516
+ },
517
+
518
+ tools(args) {
519
+ const sub = args[0];
520
+ if (!sub) { blocks.warn('Usage', '/tools list|run <name> [args]'); return; }
521
+
522
+ switch (sub) {
523
+ case 'list': {
524
+ const tools = listTools();
525
+ const rows = tools.map(t => [t.name, t.category, t.description]);
526
+ blocks.table(['Name', 'Category', 'Description'], rows);
527
+ break;
528
+ }
529
+ case 'run': {
530
+ if (args.length < 2) { blocks.warn('Usage', '/tools run <name> [args]'); return; }
531
+ blocks.warn('Tools', 'Tool execution requires API integration.');
532
+ break;
533
+ }
534
+ default:
535
+ blocks.warn('Usage', '/tools list|run');
536
+ }
537
+ },
538
+
539
+ memory(args) {
540
+ const sub = args[0];
541
+ if (!sub) { blocks.warn('Usage', '/memory show|add|clear'); return; }
542
+
543
+ switch (sub) {
544
+ case 'show': {
545
+ const ctx = memory.show();
546
+ if (ctx.length === 0) { blocks.info('Memory', 'No context stored.'); return; }
547
+ ctx.forEach(m => console.log(`${c.muted(m.role)}: ${m.content.slice(0, 200)}`));
548
+ break;
549
+ }
550
+ case 'add': {
551
+ const note = args.slice(1).join(' ');
552
+ if (!note) { blocks.warn('Usage', '/memory add <note>'); return; }
553
+ memory.addNote(note);
554
+ blocks.success('Memory Added', note);
555
+ break;
556
+ }
557
+ case 'clear': {
558
+ memory.clear();
559
+ blocks.success('Cleared', 'Memory cleared.');
560
+ break;
561
+ }
562
+ default:
563
+ blocks.warn('Usage', '/memory show|add|clear');
564
+ }
565
+ },
566
+
567
+ history(args) {
568
+ const sub = args[0];
569
+ if (!sub) { blocks.warn('Usage', '/history show|clear|export [n|file]'); return; }
570
+
571
+ switch (sub) {
572
+ case 'show': {
573
+ const n = args[1] ? parseInt(args[1]) : 20;
574
+ const ctx = memory.show();
575
+ const recent = ctx.slice(-n);
576
+ if (recent.length === 0) { blocks.info('History', 'No history.'); return; }
577
+ recent.forEach(m => console.log(`${c.muted(m.role)}: ${m.content.slice(0, 150)}`));
578
+ break;
579
+ }
580
+ case 'clear': {
581
+ memory.clear();
582
+ blocks.success('Cleared', 'History cleared.');
583
+ break;
584
+ }
585
+ case 'export': {
586
+ const file = args[1] || `clarity-chat-${Date.now()}.md`;
587
+ const fullPath = resolve(file);
588
+ const ctx = memory.show();
589
+ const md = ctx.map(m => `**${m.role}**: ${m.content}`).join('\n\n');
590
+ writeFileSync(fullPath, `# CLARITY Chat Export\n\n${md}`, 'utf8');
591
+ blocks.success('Exported', c.filename(fullPath));
592
+ break;
593
+ }
594
+ default:
595
+ blocks.warn('Usage', '/history show|clear|export');
596
+ }
597
+ },
598
+
599
+ env(args) {
600
+ const sub = args[0];
601
+ if (!sub) { blocks.warn('Usage', '/env show|set|clear'); return; }
602
+
603
+ switch (sub) {
604
+ case 'show': {
605
+ const rows = Object.entries(process.env).slice(0, 30).map(([k, v]) => [k, String(v).slice(0, 60)]);
606
+ blocks.table(['Variable', 'Value'], rows);
607
+ break;
608
+ }
609
+ case 'set': {
610
+ if (args.length < 3) { blocks.warn('Usage', '/env set <KEY> <value>'); return; }
611
+ process.env[args[1]] = args.slice(2).join(' ');
612
+ blocks.success('Set', `${args[1]}=${args.slice(2).join(' ')}`);
613
+ break;
614
+ }
615
+ case 'clear': {
616
+ blocks.warn('Env', 'Cannot clear all env variables.');
617
+ break;
618
+ }
619
+ default:
620
+ blocks.warn('Usage', '/env show|set|clear');
621
+ }
622
+ },
623
+
624
+ status() {
625
+ const mem = process.memoryUsage();
626
+ const rows = [
627
+ ['CLARITY', 'v1.0.0'],
628
+ ['Node.js', process.version],
629
+ ['Platform', `${process.platform} ${process.arch}`],
630
+ ['Termux', isTermux() ? 'Yes' : 'No'],
631
+ ['CWD', process.cwd()],
632
+ ['Memory', `${Math.round(mem.heapUsed / 1024 / 1024)}MB / ${Math.round(mem.heapTotal / 1024 / 1024)}MB`],
633
+ ['Uptime', `${Math.round(process.uptime())}s`],
634
+ ];
635
+ const keys = listKeys();
636
+ Object.entries(PROVIDER_NAMES).forEach(([p, name]) => {
637
+ rows.push([name, keys[p] ? '✓ Configured' : '○ Not set']);
638
+ });
639
+ blocks.table(['Key', 'Value'], rows);
640
+ },
641
+
642
+ version() {
643
+ blocks.info('CLARITY v1.0.0', 'AI Agent CLI for Termux\nNode.js ' + process.version + '\n' + (isTermux() ? 'Termux mode: active' : 'Standard terminal'));
644
+ },
645
+
646
+ summarize(target) {
647
+ if (!target) { blocks.warn('Usage', '/summarize <file|url>'); return; }
648
+ blocks.warn('Summarize', 'AI feature. Configure API keys with /init.');
649
+ },
650
+
651
+ generate(args) {
652
+ if (args.length === 0) { blocks.warn('Usage', '/generate <type> (readme|gitignore|dockerfile)'); return; }
653
+ blocks.warn('Generate', 'AI feature. Configure API keys with /init.');
654
+ },
655
+
656
+ exportChat(format) {
657
+ if (!format) { blocks.warn('Usage', '/export <json|md|txt>'); return; }
658
+ const ctx = memory.show();
659
+ const file = `clarity-export-${Date.now()}.${format}`;
660
+ let content = '';
661
+ if (format === 'json') content = JSON.stringify(ctx, null, 2);
662
+ else if (format === 'md') content = ctx.map(m => `**${m.role}**: ${m.content}`).join('\n\n');
663
+ else content = ctx.map(m => `${m.role}: ${m.content}`).join('\n\n');
664
+ writeFileSync(resolve(file), content, 'utf8');
665
+ blocks.success('Exported', c.filename(resolve(file)));
666
+ },
667
+
668
+ importChat(file) {
669
+ if (!file) { blocks.warn('Usage', '/import <file>'); return; }
670
+ blocks.warn('Import', 'Import feature - read file and restore chat session.');
671
+ },
672
+
673
+ alias(args) {
674
+ if (!args[0]) { blocks.warn('Usage', '/alias create|list|remove <name> [cmd]'); return; }
675
+ blocks.warn('Alias', 'Alias feature not yet implemented.');
676
+ },
677
+
678
+ plugin(args) {
679
+ if (!args[0]) { blocks.warn('Usage', '/plugin install|list|remove <name>'); return; }
680
+ blocks.warn('Plugin', 'Plugin system not yet implemented.');
681
+ },
682
+
683
+ update() {
684
+ blocks.info('Update', 'Run: npm install -g clarity-ai@latest');
685
+ },
686
+
687
+ debug(file) {
688
+ if (!file) { blocks.warn('Usage', '/debug <file>'); return; }
689
+ const fullPath = resolve(file);
690
+ if (!existsSync(fullPath)) { blocks.error('Not Found', `File not found: ${fullPath}`); return; }
691
+ blocks.warn('Debug', 'AI-powered debugging. Configure API keys with /init.');
692
+ },
693
+
694
+ review(file) {
695
+ if (!file) { blocks.warn('Usage', '/review <file>'); return; }
696
+ blocks.warn('Review', 'AI-powered code review. Configure API keys with /init.');
697
+ },
698
+
699
+ refactor(file) {
700
+ if (!file) { blocks.warn('Usage', '/refactor <file>'); return; }
701
+ blocks.warn('Refactor', 'AI-powered refactoring. Configure API keys with /init.');
702
+ },
703
+
704
+ test(file) {
705
+ if (!file) { blocks.warn('Usage', '/test <file>'); return; }
706
+ blocks.warn('Test', 'AI-powered test generation. Configure API keys with /init.');
707
+ },
708
+
709
+ docs(file) {
710
+ if (!file) { blocks.warn('Usage', '/docs <file>'); return; }
711
+ blocks.warn('Docs', 'AI-powered documentation. Configure API keys with /init.');
712
+ },
713
+
714
+ deploy(target) {
715
+ if (!target) { blocks.warn('Usage', '/deploy <target>'); return; }
716
+ blocks.info('Deploy via', `npm publish`);
717
+ },
718
+
719
+ explain(codeOrFile) {
720
+ if (!codeOrFile) { blocks.warn('Usage', '/explain <code or file>'); return; }
721
+ blocks.warn('Explain', 'AI-powered explanation. Configure API keys with /init.');
722
+ },
723
+ };
724
+
725
+ export default commandRegistry;