clarity-ai 3.2.0 → 4.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 (98) hide show
  1. package/bin/clarity.js +9 -16
  2. package/core/agent.js +52 -0
  3. package/core/config.js +39 -0
  4. package/core/intentDetect.js +13 -0
  5. package/core/keyCheck.js +21 -0
  6. package/core/providers/index.js +79 -0
  7. package/core/tools.js +178 -0
  8. package/package.json +12 -15
  9. package/{src → src.old}/agents/loop.js +108 -101
  10. package/src.old/commands/model.js +100 -0
  11. package/{src → src.old}/config/settings.js +1 -1
  12. package/src.old/main.js +88 -0
  13. package/src.old/providers/claude.js +35 -0
  14. package/src.old/providers/deepseek.js +47 -0
  15. package/src.old/providers/gemini.js +35 -0
  16. package/src.old/providers/groq.js +99 -0
  17. package/src.old/providers/index.js +55 -0
  18. package/src.old/providers/openai.js +47 -0
  19. package/src.old/providers/openrouter.js +49 -0
  20. package/{src → src.old}/ui/banner.js +1 -1
  21. package/ui/App.js +129 -0
  22. package/ui/Banner.js +13 -0
  23. package/ui/CommandPicker.js +74 -0
  24. package/ui/InputBar.js +33 -0
  25. package/ui/MessageList.js +50 -0
  26. package/ui/ModelPicker.js +96 -0
  27. package/ui/StatusBar.js +21 -0
  28. package/src/commands/model.js +0 -26
  29. package/src/main.js +0 -168
  30. package/src/providers/claude.js +0 -61
  31. package/src/providers/deepseek.js +0 -53
  32. package/src/providers/gemini.js +0 -48
  33. package/src/providers/groq.js +0 -56
  34. package/src/providers/index.js +0 -38
  35. package/src/providers/openai.js +0 -52
  36. package/src/providers/openrouter.js +0 -52
  37. /package/{AGENTS.md → AGENTS.md.old} +0 -0
  38. /package/{src → src.old}/agents/code-agent.js +0 -0
  39. /package/{src → src.old}/agents/file-agent.js +0 -0
  40. /package/{src → src.old}/agents/git-agent.js +0 -0
  41. /package/{src → src.old}/agents/monitor-agent.js +0 -0
  42. /package/{src → src.old}/agents/planner.js +0 -0
  43. /package/{src → src.old}/agents/shell-agent.js +0 -0
  44. /package/{src → src.old}/agents/web-agent.js +0 -0
  45. /package/{src → src.old}/commands/agent.js +0 -0
  46. /package/{src → src.old}/commands/chat.js +0 -0
  47. /package/{src → src.old}/commands/clear.js +0 -0
  48. /package/{src → src.old}/commands/config.js +0 -0
  49. /package/{src → src.old}/commands/diff.js +0 -0
  50. /package/{src → src.old}/commands/exit.js +0 -0
  51. /package/{src → src.old}/commands/export.js +0 -0
  52. /package/{src → src.old}/commands/fetch.js +0 -0
  53. /package/{src → src.old}/commands/git.js +0 -0
  54. /package/{src → src.old}/commands/help.js +0 -0
  55. /package/{src → src.old}/commands/history.js +0 -0
  56. /package/{src → src.old}/commands/index.js +0 -0
  57. /package/{src → src.old}/commands/keys.js +0 -0
  58. /package/{src → src.old}/commands/memory.js +0 -0
  59. /package/{src → src.old}/commands/provider.js +0 -0
  60. /package/{src → src.old}/commands/run.js +0 -0
  61. /package/{src → src.old}/commands/search.js +0 -0
  62. /package/{src → src.old}/commands/task.js +0 -0
  63. /package/{src → src.old}/commands/tools.js +0 -0
  64. /package/{src → src.old}/commands/undo.js +0 -0
  65. /package/{src → src.old}/core/context.js +0 -0
  66. /package/{src → src.old}/core/history.js +0 -0
  67. /package/{src → src.old}/core/memory.js +0 -0
  68. /package/{src → src.old}/core/setup.js +0 -0
  69. /package/{src → src.old}/tools/agent-spawn.js +0 -0
  70. /package/{src → src.old}/tools/bash.js +0 -0
  71. /package/{src → src.old}/tools/clipboard-tool.js +0 -0
  72. /package/{src → src.old}/tools/code-runner.js +0 -0
  73. /package/{src → src.old}/tools/compress-tool.js +0 -0
  74. /package/{src → src.old}/tools/context-tool.js +0 -0
  75. /package/{src → src.old}/tools/delete-file.js +0 -0
  76. /package/{src → src.old}/tools/diff-tool.js +0 -0
  77. /package/{src → src.old}/tools/edit-file.js +0 -0
  78. /package/{src → src.old}/tools/env-tool.js +0 -0
  79. /package/{src → src.old}/tools/git-tool.js +0 -0
  80. /package/{src → src.old}/tools/grep.js +0 -0
  81. /package/{src → src.old}/tools/list-dir.js +0 -0
  82. /package/{src → src.old}/tools/memory-tool.js +0 -0
  83. /package/{src → src.old}/tools/notify-tool.js +0 -0
  84. /package/{src → src.old}/tools/pkg-manager.js +0 -0
  85. /package/{src → src.old}/tools/read-file.js +0 -0
  86. /package/{src → src.old}/tools/run-tests.js +0 -0
  87. /package/{src → src.old}/tools/screenshot-tool.js +0 -0
  88. /package/{src → src.old}/tools/search-files.js +0 -0
  89. /package/{src → src.old}/tools/task-planner.js +0 -0
  90. /package/{src → src.old}/tools/version-check.js +0 -0
  91. /package/{src → src.old}/tools/web-fetch.js +0 -0
  92. /package/{src → src.old}/tools/web-search.js +0 -0
  93. /package/{src → src.old}/tools/write-file.js +0 -0
  94. /package/{src → src.old}/ui/blocks.js +0 -0
  95. /package/{src → src.old}/ui/colors.js +0 -0
  96. /package/{src → src.old}/ui/input.js +0 -0
  97. /package/{src → src.old}/ui/prompt.js +0 -0
  98. /package/{src → src.old}/ui/spinner.js +0 -0
package/bin/clarity.js CHANGED
@@ -1,24 +1,17 @@
1
1
  #!/usr/bin/env node
2
- // bin/clarity.js
3
- import { showBanner } from '../src/ui/banner.js';
4
- import { isFirstRun, loadConfig } from '../src/config/settings.js';
5
- import { runSetupWizard } from '../src/core/setup.js';
6
- import { startChat } from '../src/main.js';
2
+ import React from 'react';
3
+ import { render } from 'ink';
4
+ import { App } from '../ui/App.js';
5
+ import { loadConfig } from '../core/config.js';
6
+ import { ensureApiKey } from '../core/keyCheck.js';
7
7
 
8
8
  async function main() {
9
- const firstRun = isFirstRun();
10
- let config = firstRun ? {} : loadConfig();
11
-
12
- await showBanner(config.version, config.provider, config.model);
13
-
14
- if (firstRun) {
15
- config = await runSetupWizard();
16
- }
17
-
18
- await startChat(config);
9
+ const config = loadConfig();
10
+ await ensureApiKey(config.provider || 'groq');
11
+ render(React.createElement(App, { config }), { fullscreen: true });
19
12
  }
20
13
 
21
14
  main().catch(err => {
22
- console.error('\x1b[31mFatal error:\x1b[0m', err.message);
15
+ console.error('\n\x1b[31mFatal error:\x1b[0m', err.message);
23
16
  process.exit(1);
24
17
  });
package/core/agent.js ADDED
@@ -0,0 +1,52 @@
1
+ import { callAI } from './providers/index.js';
2
+ import { TOOLS, executeTool } from './tools.js';
3
+ import { extractCommandFromText } from './intentDetect.js';
4
+
5
+ export async function runAgent({ messages, model, provider, agentMode, onToolCall }) {
6
+ let history = [...messages];
7
+ const MAX_ITERATIONS = 10;
8
+
9
+ for (let i = 0; i < MAX_ITERATIONS; i++) {
10
+ const response = await callAI({
11
+ model, provider,
12
+ messages: history,
13
+ tools: agentMode ? TOOLS : undefined,
14
+ });
15
+
16
+ const { content, tool_calls, finish_reason } = response;
17
+
18
+ if (!tool_calls || tool_calls.length === 0 || finish_reason === 'stop') {
19
+ if (agentMode) {
20
+ const cmd = extractCommandFromText(content || '');
21
+ if (cmd) {
22
+ onToolCall?.('bash(' + cmd.slice(0, 60) + ')');
23
+ const result = await executeTool('bash', { command: cmd });
24
+ history.push({ role: 'assistant', content });
25
+ history.push({ role: 'tool', name: 'bash', content: String(result) });
26
+ continue;
27
+ }
28
+ }
29
+ return content;
30
+ }
31
+
32
+ history.push({ role: 'assistant', content, tool_calls });
33
+
34
+ for (const call of tool_calls) {
35
+ const name = call.function.name;
36
+ const args = JSON.parse(call.function.arguments || '{}');
37
+
38
+ onToolCall?.(name + '(' + JSON.stringify(args).slice(0, 80) + ')');
39
+
40
+ const result = await executeTool(name, args);
41
+
42
+ history.push({
43
+ role: 'tool',
44
+ tool_call_id: call.id,
45
+ name,
46
+ content: String(result),
47
+ });
48
+ }
49
+ }
50
+
51
+ return 'Max iterations reached.';
52
+ }
package/core/config.js ADDED
@@ -0,0 +1,39 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
4
+
5
+ const CONFIG_DIR = join(homedir(), '.clarity');
6
+ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
7
+
8
+ const DEFAULTS = {
9
+ provider: 'groq',
10
+ model: 'groq/llama-3.3-70b-versatile',
11
+ keys: {},
12
+ agentMode: true,
13
+ maxTokens: 4096,
14
+ temperature: 0.7,
15
+ };
16
+
17
+ export function loadConfig() {
18
+ if (!existsSync(CONFIG_FILE)) {
19
+ return { ...DEFAULTS };
20
+ }
21
+ try {
22
+ const raw = readFileSync(CONFIG_FILE, 'utf-8');
23
+ return { ...DEFAULTS, ...JSON.parse(raw) };
24
+ } catch {
25
+ return { ...DEFAULTS };
26
+ }
27
+ }
28
+
29
+ export function saveConfig(config) {
30
+ if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
31
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
32
+ }
33
+
34
+ export function saveKey(provider, key) {
35
+ const config = loadConfig();
36
+ config.keys = config.keys || {};
37
+ config.keys[provider] = key;
38
+ saveConfig(config);
39
+ }
@@ -0,0 +1,13 @@
1
+ export function extractCommandFromText(text) {
2
+ const patterns = [
3
+ /```(?:bash|sh)\n([\s\S]+?)```/,
4
+ /Run(?:: | the following)?\s*`([^`]+)`/i,
5
+ /execute[:\s]+`([^`]+)`/i,
6
+ /run (?:this |the following )?command[:\s]*`([^`]+)`/i,
7
+ ];
8
+ for (const p of patterns) {
9
+ const m = text.match(p);
10
+ if (m) return m[1].trim();
11
+ }
12
+ return null;
13
+ }
@@ -0,0 +1,21 @@
1
+ import { createInterface } from 'readline';
2
+ import { loadConfig, saveKey } from './config.js';
3
+
4
+ export function ensureApiKey(provider) {
5
+ const config = loadConfig();
6
+ const key = config.keys?.[provider];
7
+ if (key) return key;
8
+
9
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
10
+ return new Promise((resolve) => {
11
+ rl.question(
12
+ '\n\x1b[33m No ' + provider + ' API key found.\x1b[0m\n Enter your ' + provider + ' key: ',
13
+ (answer) => {
14
+ rl.close();
15
+ const trimmed = answer.trim();
16
+ saveKey(provider, trimmed);
17
+ resolve(trimmed);
18
+ }
19
+ );
20
+ });
21
+ }
@@ -0,0 +1,79 @@
1
+ import Groq from 'groq-sdk';
2
+
3
+ export async function callAI({ model, provider, messages, tools }) {
4
+ const providerName = provider || 'groq';
5
+ const modelName = model.replace(/^[^/]+\//, '');
6
+
7
+ switch (providerName.toLowerCase()) {
8
+ case 'groq': {
9
+ const { loadConfig } = await import('../config.js');
10
+ const config = loadConfig();
11
+ const apiKey = config.keys?.groq;
12
+ if (!apiKey) throw new Error('No Groq API key set. Use /keys groq <key>.');
13
+
14
+ const client = new Groq({ apiKey });
15
+ const res = await client.chat.completions.create({
16
+ model: modelName,
17
+ messages,
18
+ tools: tools && tools.length > 0 ? tools : undefined,
19
+ tool_choice: tools && tools.length > 0 ? 'auto' : undefined,
20
+ });
21
+ const choice = res.choices[0];
22
+ return {
23
+ content: choice.message.content || '',
24
+ tool_calls: choice.message.tool_calls || null,
25
+ finish_reason: choice.finish_reason,
26
+ };
27
+ }
28
+
29
+ case 'openrouter': {
30
+ const { loadConfig } = await import('../config.js');
31
+ const config = loadConfig();
32
+ const apiKey = config.keys?.openrouter;
33
+ if (!apiKey) throw new Error('No OpenRouter API key set.');
34
+
35
+ const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
36
+ method: 'POST',
37
+ headers: {
38
+ 'Content-Type': 'application/json',
39
+ 'Authorization': 'Bearer ' + apiKey,
40
+ 'HTTP-Referer': 'https://clarity-ai.local',
41
+ 'X-Title': 'CLARITY AI',
42
+ },
43
+ body: JSON.stringify({
44
+ model: modelName,
45
+ messages,
46
+ tools: tools && tools.length > 0 ? tools : undefined,
47
+ }),
48
+ });
49
+ const data = await res.json();
50
+ const choice = data.choices?.[0];
51
+ return {
52
+ content: choice?.message?.content || '',
53
+ tool_calls: choice?.message?.tool_calls || null,
54
+ finish_reason: choice?.finish_reason,
55
+ };
56
+ }
57
+
58
+ case 'gemini': {
59
+ const { GoogleGenerativeAI } = await import('@google/generative-ai');
60
+ const { loadConfig } = await import('../config.js');
61
+ const config = loadConfig();
62
+ const apiKey = config.keys?.gemini;
63
+ if (!apiKey) throw new Error('No Gemini API key set.');
64
+
65
+ const genAI = new GoogleGenerativeAI(apiKey);
66
+ const model_ = genAI.getGenerativeModel({ model: modelName });
67
+ const chat = model_.startChat({ messages });
68
+ const result = await chat.sendMessage(messages[messages.length - 1]?.content || '');
69
+ return {
70
+ content: result.response.text() || '',
71
+ tool_calls: null,
72
+ finish_reason: 'stop',
73
+ };
74
+ }
75
+
76
+ default:
77
+ throw new Error('Unknown provider: ' + providerName);
78
+ }
79
+ }
package/core/tools.js ADDED
@@ -0,0 +1,178 @@
1
+ import { exec } from 'child_process';
2
+ import { readFile, writeFile, readdir, mkdir } from 'fs/promises';
3
+ import { existsSync } from 'fs';
4
+ import { dirname } from 'path';
5
+ import { promisify } from 'util';
6
+
7
+ const execAsync = promisify(exec);
8
+
9
+ async function outputFile(path, content) {
10
+ const dir = dirname(path);
11
+ if (!existsSync(dir)) await mkdir(dir, { recursive: true });
12
+ await writeFile(path, content, 'utf-8');
13
+ }
14
+
15
+ export const TOOLS = [
16
+ {
17
+ type: 'function',
18
+ function: {
19
+ name: 'bash',
20
+ description: 'Execute a bash command in the Termux shell and return stdout/stderr',
21
+ parameters: {
22
+ type: 'object',
23
+ properties: {
24
+ command: { type: 'string', description: 'The bash command to run' },
25
+ },
26
+ required: ['command'],
27
+ },
28
+ },
29
+ },
30
+ {
31
+ type: 'function',
32
+ function: {
33
+ name: 'read_file',
34
+ description: 'Read a file from the filesystem',
35
+ parameters: {
36
+ type: 'object',
37
+ properties: {
38
+ path: { type: 'string' },
39
+ offset: { type: 'number', description: 'Line offset to start reading' },
40
+ limit: { type: 'number', description: 'Max lines to read' },
41
+ },
42
+ required: ['path'],
43
+ },
44
+ },
45
+ },
46
+ {
47
+ type: 'function',
48
+ function: {
49
+ name: 'write_file',
50
+ description: 'Write content to a file',
51
+ parameters: {
52
+ type: 'object',
53
+ properties: {
54
+ path: { type: 'string' },
55
+ content: { type: 'string' },
56
+ },
57
+ required: ['path', 'content'],
58
+ },
59
+ },
60
+ },
61
+ {
62
+ type: 'function',
63
+ function: {
64
+ name: 'list_dir',
65
+ description: 'List files in a directory',
66
+ parameters: {
67
+ type: 'object',
68
+ properties: {
69
+ path: { type: 'string', default: '.' },
70
+ },
71
+ },
72
+ },
73
+ },
74
+ {
75
+ type: 'function',
76
+ function: {
77
+ name: 'grep',
78
+ description: 'Search for a pattern in files',
79
+ parameters: {
80
+ type: 'object',
81
+ properties: {
82
+ pattern: { type: 'string' },
83
+ path: { type: 'string' },
84
+ flags: { type: 'string', default: '-r' },
85
+ },
86
+ required: ['pattern', 'path'],
87
+ },
88
+ },
89
+ },
90
+ {
91
+ type: 'function',
92
+ function: {
93
+ name: 'web_search',
94
+ description: 'Search the web for information',
95
+ parameters: {
96
+ type: 'object',
97
+ properties: {
98
+ query: { type: 'string' },
99
+ },
100
+ required: ['query'],
101
+ },
102
+ },
103
+ },
104
+ {
105
+ type: 'function',
106
+ function: {
107
+ name: 'web_fetch',
108
+ description: 'Fetch a URL and return its text content',
109
+ parameters: {
110
+ type: 'object',
111
+ properties: {
112
+ url: { type: 'string', format: 'uri' },
113
+ },
114
+ required: ['url'],
115
+ },
116
+ },
117
+ },
118
+ ];
119
+
120
+ export async function executeTool(name, args) {
121
+ switch (name) {
122
+ case 'bash': {
123
+ try {
124
+ const { stdout, stderr } = await execAsync(args.command, { timeout: 30000 });
125
+ return stdout || stderr || '(no output)';
126
+ } catch (e) {
127
+ return 'Error: ' + (e.stderr || e.message);
128
+ }
129
+ }
130
+ case 'read_file': {
131
+ const content = await readFile(args.path, 'utf8');
132
+ const lines = content.split('\n');
133
+ const start = args.offset || 0;
134
+ const end = args.limit ? start + args.limit : lines.length;
135
+ return lines.slice(start, end).join('\n');
136
+ }
137
+ case 'write_file': {
138
+ await outputFile(args.path, args.content);
139
+ return 'Written to ' + args.path;
140
+ }
141
+ case 'list_dir': {
142
+ const files = await readdir(args.path || '.');
143
+ return files.join('\n');
144
+ }
145
+ case 'grep': {
146
+ try {
147
+ const { stdout } = await execAsync(
148
+ 'grep ' + (args.flags || '-r') + ' "' + args.pattern + '" "' + args.path + '"',
149
+ { timeout: 10000 }
150
+ );
151
+ return stdout || '(no matches)';
152
+ } catch (e) {
153
+ return e.stdout || '(no matches)';
154
+ }
155
+ }
156
+ case 'web_search': {
157
+ try {
158
+ const url = 'https://api.duckduckgo.com/?q=' + encodeURIComponent(args.query) + '&format=json&no_html=1';
159
+ const res = await fetch(url);
160
+ const data = await res.json();
161
+ return data.AbstractText || data.RelatedTopics?.slice(0,3)?.map(t => t.Text).join('\n') || 'No results';
162
+ } catch (e) {
163
+ return 'Search error: ' + e.message;
164
+ }
165
+ }
166
+ case 'web_fetch': {
167
+ try {
168
+ const res = await fetch(args.url);
169
+ const text = await res.text();
170
+ return text.slice(0, 5000);
171
+ } catch (e) {
172
+ return 'Fetch error: ' + e.message;
173
+ }
174
+ }
175
+ default:
176
+ return 'Unknown tool: ' + name;
177
+ }
178
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "clarity-ai",
3
- "version": "3.2.0",
4
- "description": "Autonomous AI Agent CLI for Termux",
3
+ "version": "4.0.0",
4
+ "description": "Ink+React TUI Autonomous AI Agent CLI for Termux",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "clarity": "bin/clarity.js"
@@ -14,18 +14,15 @@
14
14
  "node": ">=18.0.0"
15
15
  },
16
16
  "dependencies": {
17
- "chalk": "^5.3.0",
18
- "figlet": "^1.7.0",
19
- "gradient-string": "^2.0.2",
20
- "ora": "^8.1.0",
21
- "inquirer": "^9.2.12",
22
- "cli-table3": "^0.6.3",
23
- "glob": "^10.3.10",
24
- "chokidar": "^3.5.3",
25
- "marked": "^9.1.6",
26
- "marked-terminal": "^6.1.0",
27
- "diff": "^5.1.0",
28
- "archiver": "^6.0.1",
29
- "strip-ansi": "^7.1.0"
17
+ "@google/generative-ai": "^0.24.1",
18
+ "@inkjs/ui": "^2.0.0",
19
+ "@langchain/langgraph": "^1.3.5",
20
+ "groq-sdk": "^1.2.1",
21
+ "ink": "^7.0.5",
22
+ "ink-big-text": "^2.0.0",
23
+ "ink-gradient": "^4.0.1",
24
+ "ink-select-input": "^6.2.0",
25
+ "ink-spinner": "^5.0.0",
26
+ "ink-text-input": "^6.0.0"
30
27
  }
31
28
  }