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
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # CLARITY — AI Agent CLI
2
+
3
+ ```
4
+ ██████╗██╗ █████╗ ██████╗ ██╗████████╗██╗ ██╗
5
+ ██╔════╝██║ ██╔══██╗██╔══██╗██║╚══██╔══╝╚██╗ ██╔╝
6
+ ██║ ██║ ███████║██████╔╝██║ ██║ ╚████╔╝
7
+ ██║ ██║ ██╔══██║██╔══██╗██║ ██║ ╚██╔╝
8
+ ╚██████╗███████╗██║ ██║██║ ██║██║ ██║ ██║
9
+ ╚═════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝
10
+ ```
11
+
12
+ AI agent CLI for Termux and terminal — chat, code, automate.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npm install -g clarity-ai
18
+ ```
19
+
20
+ ### Termux Install
21
+
22
+ ```bash
23
+ pkg update && pkg install nodejs-lts
24
+ npm install -g clarity-ai
25
+ clarity init
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ```bash
31
+ # Interactive setup
32
+ clarity init
33
+
34
+ # Start chatting
35
+ clarity
36
+
37
+ # One-shot question
38
+ clarity /ask "What is the weather?"
39
+
40
+ # Run commands
41
+ clarity /bash ls -la
42
+ ```
43
+
44
+ ## Commands
45
+
46
+ ### Chat & AI
47
+ | Command | Description |
48
+ |---|---|
49
+ | `/chat` | Start interactive chat session |
50
+ | `/ask <q>` | One-shot question |
51
+ | `/clear` | Clear screen and conversation |
52
+ | `/history show [n]` | Show last N messages |
53
+ | `/memory show\|add\|clear` | AI memory management |
54
+
55
+ ### Configuration
56
+ | Command | Description |
57
+ |---|---|
58
+ | `/init` | Interactive setup wizard |
59
+ | `/keys set\|list\|remove\|test` | API key management |
60
+ | `/model set\|list` | Switch AI model |
61
+ | `/config show\|reset` | Settings management |
62
+ | `/theme set\|list` | Color themes |
63
+
64
+ ### Files & Code
65
+ | Command | Description |
66
+ |---|---|
67
+ | `/file create\|read\|delete\|list\|edit` | File operations |
68
+ | `/bash <command>` | Execute shell commands |
69
+ | `/code run\|write\|explain\|fix` | Code operations |
70
+ | `/git <action>` | Git operations |
71
+
72
+ ### Web & Search
73
+ | Command | Description |
74
+ |---|---|
75
+ | `/web <url>` | Fetch and display URL |
76
+ | `/search <query>` | Web search |
77
+ | `/translate <text>` | Translate text |
78
+ | `/summarize <file\|url>` | Summarize content |
79
+
80
+ ### Agents & Tools
81
+ | Command | Description |
82
+ |---|---|
83
+ | `/agent start\|stop\|list\|logs` | Background agents |
84
+ | `/tools list\|run` | Tool management |
85
+
86
+ ### System
87
+ | Command | Description |
88
+ |---|---|
89
+ | `/status` | System status |
90
+ | `/version` | Version info |
91
+ | `/env show\|set` | Environment variables |
92
+ | `/help [command]` | Command help |
93
+ | `/exit` | Exit CLARITY |
94
+
95
+ ## Provider Comparison
96
+
97
+ | Provider | Free Tier | Streaming | Priority |
98
+ |---|---|---|---|
99
+ | Groq | ✓ | ✓ | 1 (fastest) |
100
+ | Google Gemini | ✓ | ✓ | 2 |
101
+ | DeepSeek | Cheap | ✓ | 3 |
102
+ | OpenRouter | ✓ | ✓ | 4 |
103
+ | OpenAI | Paid | ✓ | 5 |
104
+ | Anthropic | Paid | ✓ | 6 |
105
+
106
+ ## License
107
+
108
+ MIT
package/bin/clarity.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../src/index.js';
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "clarity-ai",
3
+ "version": "1.0.0",
4
+ "description": "AI agent CLI for Termux and terminal — chat, code, automate",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "clarity": "bin/clarity.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node bin/clarity.js",
12
+ "postinstall": "node scripts/postinstall.js"
13
+ },
14
+ "engines": {
15
+ "node": ">=18.0.0"
16
+ },
17
+ "dependencies": {
18
+ "chalk": "^5.3.0",
19
+ "inquirer": "^9.2.12",
20
+ "node-fetch": "^3.3.2",
21
+ "conf": "^12.0.0",
22
+ "marked": "^12.0.0",
23
+ "marked-terminal": "^7.1.0",
24
+ "ora": "^8.0.1",
25
+ "boxen": "^7.1.1",
26
+ "figlet": "^1.7.0",
27
+ "gradient-string": "^2.0.2",
28
+ "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
+ "semver": "^7.6.0",
37
+ "strip-ansi": "^7.1.0",
38
+ "wrap-ansi": "^9.0.0"
39
+ },
40
+ "keywords": [
41
+ "ai",
42
+ "cli",
43
+ "termux",
44
+ "agent",
45
+ "clarity",
46
+ "chatgpt",
47
+ "gemini",
48
+ "groq"
49
+ ],
50
+ "license": "MIT"
51
+ }
@@ -0,0 +1,53 @@
1
+ import figlet from 'figlet';
2
+ import gradient from 'gradient-string';
3
+ import boxen from 'boxen';
4
+ import chalk from 'chalk';
5
+
6
+ const GRADIENT = gradient(['#00d2ff', '#7b2ff7']);
7
+
8
+ try {
9
+ const text = figlet.textSync('CLARITY', { font: 'ANSI Shadow' });
10
+ console.log(GRADIENT(text));
11
+ } catch {
12
+ console.log(GRADIENT('CLARITY'));
13
+ }
14
+
15
+ const nodeVersion = process.versions.node;
16
+ const [major] = nodeVersion.split('.').map(Number);
17
+
18
+ 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
+ ));
24
+ process.exit(1);
25
+ }
26
+
27
+ const isTermux = process.env.PREFIX?.includes('com.termux');
28
+
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."));
36
+
37
+ const binDir = process.env.npm_config_prefix ? `${process.env.npm_config_prefix}/bin` : null;
38
+ if (isTermux && binDir) {
39
+ const { existsSync, writeFileSync, chmodSync, unlinkSync } = await import('fs');
40
+ const { resolve } = await import('path');
41
+ const wrapperPath = resolve(binDir, 'clarity');
42
+ const targetPath = resolve(process.argv[1] || '.', '..', 'bin', 'clarity.js');
43
+
44
+ try {
45
+ const stat = await import('fs').then(fs => fs.lstatSync(wrapperPath));
46
+ if (stat.isSymbolicLink()) {
47
+ unlinkSync(wrapperPath);
48
+ 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
+ chmodSync(wrapperPath, '755');
50
+ console.log(chalk.hex('#00ff88')('✓ Termux wrapper created at:'), chalk.hex('#00d2ff')(wrapperPath));
51
+ }
52
+ } catch {}
53
+ }
@@ -0,0 +1,54 @@
1
+ import logger from '../utils/logger.js';
2
+
3
+ class BaseAgent {
4
+ constructor(config = {}) {
5
+ this.id = config.id || `agent_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
6
+ this.name = config.name || 'Agent';
7
+ this.type = config.type || 'base';
8
+ this.status = 'stopped';
9
+ this.logs = [];
10
+ this.config = config;
11
+ this._interval = null;
12
+ }
13
+
14
+ async start() {
15
+ this.status = 'running';
16
+ this.log('info', 'Agent started');
17
+ }
18
+
19
+ async stop() {
20
+ this.status = 'stopped';
21
+ if (this._interval) {
22
+ clearInterval(this._interval);
23
+ this._interval = null;
24
+ }
25
+ this.log('info', 'Agent stopped');
26
+ }
27
+
28
+ async pause() {
29
+ this.status = 'paused';
30
+ this.log('info', 'Agent paused');
31
+ }
32
+
33
+ async resume() {
34
+ this.status = 'running';
35
+ this.log('info', 'Agent resumed');
36
+ }
37
+
38
+ log(level, msg) {
39
+ const entry = { level, msg, time: new Date().toISOString() };
40
+ this.logs.push(entry);
41
+ if (this.logs.length > 1000) this.logs.shift();
42
+ logger[level](`[${this.id}] ${msg}`);
43
+ }
44
+
45
+ emit(event, data) {
46
+ this.log('info', `Event: ${event} ${data ? JSON.stringify(data) : ''}`);
47
+ }
48
+
49
+ getStatus() {
50
+ return { id: this.id, name: this.name, type: this.type, status: this.status, logCount: this.logs.length };
51
+ }
52
+ }
53
+
54
+ export default BaseAgent;
@@ -0,0 +1,57 @@
1
+ import BaseAgent from './BaseAgent.js';
2
+ import { watch, readFileSync, existsSync } from 'fs';
3
+ import { resolve } from 'path';
4
+
5
+ class CodeAgent extends BaseAgent {
6
+ constructor(config) {
7
+ super({ ...config, type: 'code', name: config.name || 'Code Watcher' });
8
+ this.file = config.file;
9
+ this.language = config.language || 'auto';
10
+ this.autoFix = config.autoFix || false;
11
+ this._watcher = null;
12
+ }
13
+
14
+ async start() {
15
+ if (!this.file) {
16
+ this.log('error', 'No file specified');
17
+ return;
18
+ }
19
+ await super.start();
20
+ const filePath = resolve(this.file);
21
+ this.log('info', `Watching ${filePath} for errors`);
22
+
23
+ try {
24
+ const content = readFileSync(filePath, 'utf8');
25
+ this.analyze(content);
26
+ } catch {}
27
+
28
+ this._watcher = watch(filePath, (eventType) => {
29
+ if (eventType === 'change') {
30
+ try {
31
+ const content = readFileSync(filePath, 'utf8');
32
+ this.analyze(content);
33
+ } catch {}
34
+ }
35
+ });
36
+ }
37
+
38
+ analyze(content) {
39
+ const lines = content.split('\n');
40
+ const errors = [];
41
+ lines.forEach((line, i) => {
42
+ if (line.length > 200) errors.push({ line: i + 1, msg: 'Line too long' });
43
+ if (line.includes('\t')) errors.push({ line: i + 1, msg: 'Tab character detected' });
44
+ });
45
+ if (errors.length > 0) {
46
+ this.log('warn', `Found ${errors.length} issues in ${this.file}`);
47
+ errors.forEach(e => this.log('warn', ` L${e.line}: ${e.msg}`));
48
+ }
49
+ }
50
+
51
+ async stop() {
52
+ if (this._watcher) { this._watcher.close(); this._watcher = null; }
53
+ await super.stop();
54
+ }
55
+ }
56
+
57
+ export default CodeAgent;
@@ -0,0 +1,39 @@
1
+ import BaseAgent from './BaseAgent.js';
2
+ import { watch } from 'fs';
3
+ import { resolve } from 'path';
4
+
5
+ class FileAgent extends BaseAgent {
6
+ constructor(config) {
7
+ super({ ...config, type: 'file', name: config.name || 'File Watcher' });
8
+ this.watchDir = config.watchDir || '.';
9
+ this.pattern = config.pattern || '*';
10
+ this.onChangePrompt = config.onChangePrompt || 'File changed: {path}';
11
+ this._watcher = null;
12
+ }
13
+
14
+ async start() {
15
+ await super.start();
16
+ try {
17
+ const dir = resolve(this.watchDir);
18
+ this._watcher = watch(dir, { recursive: true }, (eventType, filename) => {
19
+ if (filename && filename.match(this.pattern.replace('*', '.*'))) {
20
+ this.log('info', `File ${eventType}: ${filename}`);
21
+ this.emit('fileChange', { eventType, filename });
22
+ }
23
+ });
24
+ this.log('info', `Watching ${dir} for changes`);
25
+ } catch (err) {
26
+ this.log('error', `Watch error: ${err.message}`);
27
+ }
28
+ }
29
+
30
+ async stop() {
31
+ if (this._watcher) {
32
+ this._watcher.close();
33
+ this._watcher = null;
34
+ }
35
+ await super.stop();
36
+ }
37
+ }
38
+
39
+ export default FileAgent;
@@ -0,0 +1,54 @@
1
+ import BaseAgent from './BaseAgent.js';
2
+ import os from 'os';
3
+
4
+ class MonitorAgent extends BaseAgent {
5
+ constructor(config) {
6
+ super({ ...config, type: 'monitor', name: config.name || 'System Monitor' });
7
+ this.intervalMs = config.intervalMs || 5000;
8
+ this.alertThresholds = config.alertThresholds || { cpu: 90, memory: 90 };
9
+ }
10
+
11
+ async start() {
12
+ await super.start();
13
+ this.log('info', `Monitoring system every ${this.intervalMs}ms`);
14
+ this._interval = setInterval(() => this.check(), this.intervalMs);
15
+ }
16
+
17
+ check() {
18
+ const mem = process.memoryUsage();
19
+ const memPct = Math.round((mem.heapUsed / mem.heapTotal) * 100);
20
+ const load = os.loadavg()[0];
21
+ const totalMem = os.totalmem();
22
+ const freeMem = os.freemem();
23
+ const memUsage = Math.round(((totalMem - freeMem) / totalMem) * 100);
24
+
25
+ const stats = {
26
+ cpu: load.toFixed(2),
27
+ memory: memPct,
28
+ systemMem: memUsage,
29
+ heap: Math.round(mem.heapUsed / 1024 / 1024) + 'MB',
30
+ uptime: Math.round(process.uptime()) + 's',
31
+ };
32
+
33
+ if (memPct > this.alertThresholds.memory) {
34
+ this.log('warn', `High memory usage: ${memPct}%`);
35
+ }
36
+ if (load > this.alertThresholds.cpu / 100) {
37
+ this.log('warn', `High CPU load: ${load}`);
38
+ }
39
+
40
+ this.emit('stats', stats);
41
+ }
42
+
43
+ getStats() {
44
+ const mem = process.memoryUsage();
45
+ return {
46
+ cpu: os.loadavg()[0].toFixed(2),
47
+ memory: Math.round((mem.heapUsed / mem.heapTotal) * 100),
48
+ uptime: Math.round(process.uptime()),
49
+ os: `${os.platform()} ${os.arch()}`,
50
+ };
51
+ }
52
+ }
53
+
54
+ export default MonitorAgent;
@@ -0,0 +1,31 @@
1
+ import BaseAgent from './BaseAgent.js';
2
+ import { exec } from 'child_process';
3
+ import { promisify } from 'util';
4
+
5
+ const execAsync = promisify(exec);
6
+
7
+ class ShellAgent extends BaseAgent {
8
+ constructor(config) {
9
+ super({ ...config, type: 'shell', name: config.name || 'Shell Runner' });
10
+ this.command = config.command || 'echo hello';
11
+ this.intervalMs = config.intervalMs || 60000;
12
+ this.storeOutput = config.storeOutput !== false;
13
+ }
14
+
15
+ async start() {
16
+ await super.start();
17
+ this.log('info', `Running command every ${this.intervalMs}ms: ${this.command}`);
18
+ this._interval = setInterval(async () => {
19
+ try {
20
+ const { stdout, stderr } = await execAsync(this.command);
21
+ if (this.storeOutput) this.log('info', `Output: ${stdout.trim().slice(0, 200)}`);
22
+ if (stderr) this.log('warn', `Stderr: ${stderr.trim().slice(0, 200)}`);
23
+ this.emit('commandComplete', { command: this.command, stdout, stderr });
24
+ } catch (err) {
25
+ this.log('error', `Command failed: ${err.message}`);
26
+ }
27
+ }, this.intervalMs);
28
+ }
29
+ }
30
+
31
+ export default ShellAgent;
@@ -0,0 +1,39 @@
1
+ import BaseAgent from './BaseAgent.js';
2
+
3
+ class WebAgent extends BaseAgent {
4
+ constructor(config) {
5
+ super({ ...config, type: 'web', name: config.name || 'Web Fetcher' });
6
+ this.url = config.url;
7
+ this.intervalMs = config.intervalMs || 300000;
8
+ this.lastContent = '';
9
+ }
10
+
11
+ async start() {
12
+ if (!this.url) {
13
+ this.log('error', 'No URL configured');
14
+ return;
15
+ }
16
+ await super.start();
17
+ this.log('info', `Fetching ${this.url} every ${this.intervalMs}ms`);
18
+ await this.fetch();
19
+ this._interval = setInterval(() => this.fetch(), this.intervalMs);
20
+ }
21
+
22
+ async fetch() {
23
+ try {
24
+ const res = await fetch(this.url);
25
+ const text = await res.text();
26
+ const clean = text.replace(/<[^>]+>/g, '').trim().slice(0, 500);
27
+ if (this.lastContent && clean !== this.lastContent) {
28
+ this.log('info', 'Content changed!');
29
+ this.emit('contentChange', { url: this.url, preview: clean.slice(0, 100) });
30
+ }
31
+ this.lastContent = clean;
32
+ this.log('info', `Fetched ${this.url} (${text.length} bytes)`);
33
+ } catch (err) {
34
+ this.log('error', `Fetch failed: ${err.message}`);
35
+ }
36
+ }
37
+ }
38
+
39
+ export default WebAgent;
@@ -0,0 +1,116 @@
1
+ import BaseAgent from './BaseAgent.js';
2
+ import FileAgent from './FileAgent.js';
3
+ import ShellAgent from './ShellAgent.js';
4
+ import WebAgent from './WebAgent.js';
5
+ import CodeAgent from './CodeAgent.js';
6
+ import MonitorAgent from './MonitorAgent.js';
7
+ import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs';
8
+ import { resolve, dirname } from 'path';
9
+ import paths from '../config/paths.js';
10
+ import blocks from '../ui/blocks.js';
11
+
12
+ const AGENT_MAP = {
13
+ file: FileAgent,
14
+ shell: ShellAgent,
15
+ web: WebAgent,
16
+ code: CodeAgent,
17
+ monitor: MonitorAgent,
18
+ };
19
+
20
+ class AgentManager {
21
+ constructor() {
22
+ this.agents = new Map();
23
+ }
24
+
25
+ async start(agentType, config = {}) {
26
+ const AgentClass = AGENT_MAP[agentType];
27
+ if (!AgentClass) throw new Error(`Unknown agent type: ${agentType}`);
28
+
29
+ const agent = new AgentClass(config);
30
+ this.agents.set(agent.id, agent);
31
+ await agent.start();
32
+ this.save();
33
+ return agent.id;
34
+ }
35
+
36
+ async stop(agentId) {
37
+ const agent = this.agents.get(agentId);
38
+ if (!agent) throw new Error(`Agent not found: ${agentId}`);
39
+ await agent.stop();
40
+ this.agents.delete(agentId);
41
+ this.save();
42
+ }
43
+
44
+ list() {
45
+ return Array.from(this.agents.values()).map(a => a.getStatus());
46
+ }
47
+
48
+ getLogs(agentId, lines = 50) {
49
+ const agent = this.agents.get(agentId);
50
+ if (!agent) throw new Error(`Agent not found: ${agentId}`);
51
+ return agent.logs.slice(-lines);
52
+ }
53
+
54
+ async restart(agentId) {
55
+ const agent = this.agents.get(agentId);
56
+ if (!agent) throw new Error(`Agent not found: ${agentId}`);
57
+ await agent.stop();
58
+ await agent.start();
59
+ this.save();
60
+ }
61
+
62
+ save() {
63
+ try {
64
+ const dir = dirname(paths.agents);
65
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
66
+ const data = Array.from(this.agents.values()).map(a => ({
67
+ type: a.type, config: a.config, id: a.id, name: a.name, status: a.status
68
+ }));
69
+ writeFileSync(paths.agents, JSON.stringify(data, null, 2));
70
+ } catch {}
71
+ }
72
+
73
+ async restore() {
74
+ try {
75
+ if (!existsSync(paths.agents)) return;
76
+ const data = JSON.parse(readFileSync(paths.agents, 'utf8'));
77
+ for (const item of data) {
78
+ const AgentClass = AGENT_MAP[item.type];
79
+ if (AgentClass && item.status === 'running') {
80
+ const agent = new AgentClass(item.config);
81
+ this.agents.set(agent.id, agent);
82
+ await agent.start();
83
+ }
84
+ }
85
+ } catch {}
86
+ }
87
+
88
+ showStatus() {
89
+ const list = this.list();
90
+ if (list.length === 0) {
91
+ blocks.info('Agents', 'No agents running.');
92
+ return;
93
+ }
94
+ const headers = ['ID', 'Name', 'Type', 'Status', 'Logs'];
95
+ const rows = list.map(a => [
96
+ a.id.slice(0, 12),
97
+ a.name,
98
+ a.type,
99
+ statusBadge(a.status),
100
+ String(a.logCount),
101
+ ]);
102
+ blocks.table(headers, rows);
103
+ }
104
+ }
105
+
106
+ function statusBadge(status) {
107
+ switch (status) {
108
+ case 'running': return '● running';
109
+ case 'error': return '✗ error';
110
+ case 'idle': return '○ idle';
111
+ default: return '– stopped';
112
+ }
113
+ }
114
+
115
+ const agentManager = new AgentManager();
116
+ export default agentManager;