clarity-ai 1.3.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +6 -0
- package/AGENTS.md +47 -0
- package/bin/clarity.js +1 -1
- package/config.json.example +8 -0
- package/package.json +10 -11
- package/src/agent/intent.js +15 -0
- package/src/agent/loop.js +105 -0
- package/src/agent/planner.js +37 -0
- package/src/agent/tools.js +254 -0
- package/src/agents/CodeAgent.js +214 -47
- package/src/agents/FileAgent.js +76 -30
- package/src/agents/MonitorAgent.js +85 -35
- package/src/agents/ShellAgent.js +79 -19
- package/src/agents/WebAgent.js +81 -31
- package/src/agents/manager.js +28 -17
- package/src/banner.js +14 -0
- package/src/blocks.js +100 -0
- package/src/colors.js +31 -0
- package/src/commands/chat.js +104 -0
- package/src/commands/config.js +146 -0
- package/src/commands/files.js +106 -0
- package/src/commands/index.js +21 -777
- package/src/commands/model.js +93 -0
- package/src/commands/system.js +131 -0
- package/src/commands/utility.js +332 -0
- package/src/commands/web.js +82 -0
- package/src/config.js +44 -0
- package/src/history.js +54 -0
- package/src/input.js +36 -0
- package/src/main.js +121 -0
- package/src/setup.js +137 -0
- package/src/index.js +0 -87
- package/src/tools/bash.js +0 -25
- package/src/tools/code.js +0 -52
- package/src/tools/files.js +0 -62
- package/src/tools/git.js +0 -67
- package/src/tools/index.js +0 -40
- package/src/tools/pkg.js +0 -46
- package/src/tools/search.js +0 -29
- package/src/tools/system.js +0 -29
- package/src/tools/web.js +0 -24
- package/src/ui/banner.js +0 -25
- package/src/ui/blocks.js +0 -130
- package/src/ui/chatbox.js +0 -260
- package/src/ui/colors.js +0 -27
- package/src/ui/prompt.js +0 -126
package/.env.example
ADDED
package/AGENTS.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# CLARITY-AI — Agent Guide
|
|
2
|
+
|
|
3
|
+
## Project
|
|
4
|
+
Single-package Node.js CLI tool (`clarity-ai`) — ESM module (`"type": "module"`).
|
|
5
|
+
Entry: `bin/clarity.js` → imports `src/index.js`.
|
|
6
|
+
Install: `npm install -g clarity-ai` → command `clarity`.
|
|
7
|
+
|
|
8
|
+
## Key Architecture
|
|
9
|
+
- **`src/providers/index.js`**: `sendMessage()` MUST be a plain `function`, NOT `async function`. Wrapping an async generator in `async` returns a Promise instead of an async iterable → `for await...of` breaks ("stream is not async iterable"). Each provider exports `async function* sendMessage(...)`. Provider model lists updated as of June 2026 — see `capabilities` object.
|
|
10
|
+
- **`src/commands/index.js`**: All 40+ slash commands in one file, routed by switch. Single-file design — do NOT split into separate command files without changing the dispatcher. `/model` uses interactive inquirer selector. `/provider` switches provider + auto-filters models.
|
|
11
|
+
- **`src/config/keys.js`**: API keys stored via `conf` package, obfuscated with base64-reverse (not real encryption). Provider regex patterns are strict — Gemini keys must match `/^AIzaSy[A-Za-z0-9_-]{20,}$/`.
|
|
12
|
+
- **`src/ui/chatbox.js`**: Prompt bar design — grey fill (`\x1b[48;5;236m`) + purple outline (`chalk.hex('#7b2ff7')`). `renderPromptBar()` draws the bar, `showPrompt()` writes ` ◆ `. Pressing `/` at the prompt shows all command suggestions via `suggestCommands()` with `rl._refreshLine()`.
|
|
13
|
+
- **`src/ui/prompt.js`**: History management only. `showPrompt()` and `showSuggestions()` moved to `chatbox.js`. `createPrompt()` creates readline interface with `terminal: true` (enables keypress events).
|
|
14
|
+
- **Default model**: `groq/llama-3.3-70b-versatile` (updated in `src/config/settings.js`).
|
|
15
|
+
|
|
16
|
+
## Termux Quirks
|
|
17
|
+
- No `node-gyp`, no native modules, no `fsevents`.
|
|
18
|
+
- `npm install -g` symlinks don't work for execution on Termux (permission denied across mount points). Postinstall creates a shell wrapper at `$PREFIX/bin/clarity`.
|
|
19
|
+
- `open` package replaced with `termux-open-url` via `exec`.
|
|
20
|
+
- Console clear: `console.clear()` at startup.
|
|
21
|
+
|
|
22
|
+
## Commands
|
|
23
|
+
```bash
|
|
24
|
+
# Dev workflow (no test/lint scripts configured)
|
|
25
|
+
npm start # node bin/clarity.js
|
|
26
|
+
node bin/clarity.js /status # run individual command
|
|
27
|
+
node bin/clarity.js # interactive chat mode
|
|
28
|
+
|
|
29
|
+
# Publishing
|
|
30
|
+
npm version patch && npm publish # bump + publish (needs npm token with write access)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Provider Streaming Contract
|
|
34
|
+
Each provider file exports `{ PROVIDER, sendMessage }` where `sendMessage` is:
|
|
35
|
+
```js
|
|
36
|
+
async function* sendMessage(apiKey, messages, model, stream) → AsyncGenerator<string>
|
|
37
|
+
```
|
|
38
|
+
SSE parsing pattern: read `data: ` lines, parse JSON, yield `choices[0].delta.content`.
|
|
39
|
+
|
|
40
|
+
## Known Gotchas
|
|
41
|
+
- **Provider router must NOT be async**: `src/providers/index.js` `sendMessage` is a regular function returning the async generator directly.
|
|
42
|
+
- **Model name format**: config stores `"provider/modelname"` (e.g. `groq/llama-3.3-70b-versatile`). Router strips prefix before passing to provider.
|
|
43
|
+
- **No boxen/ora deps**: v1.2.0 removed them. Custom ANSI escape shaded boxes in `src/ui/blocks.js`. Custom spinner in `src/ui/spinner.js`.
|
|
44
|
+
- **Config paths**: XDG-compliant (`~/.config/clarity/`, `~/.local/share/clarity/`, `~/.cache/clarity/`).
|
|
45
|
+
- **No test framework**: none configured. No lint/typecheck scripts.
|
|
46
|
+
- **Keypress events**: `process.stdin.on('keypress', ...)` works because `readline.createInterface({ terminal: true })` internally calls `emitKeypressEvents`. Set raw mode BEFORE creating readline if needed.
|
|
47
|
+
- **Prompt bar**: `renderPromptBar()` draws grey-filled purple box. Call after every output cycle so the bar stays at bottom. `showPrompt()` writes ` ◆ `. Always pair: render bar, then show prompt.
|
package/bin/clarity.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import '../src/
|
|
2
|
+
import '../src/main.js';
|
package/package.json
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clarity-ai",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "AI
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "Autonomous AI Agent CLI for Termux",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "src/index.js",
|
|
7
6
|
"bin": {
|
|
8
7
|
"clarity": "bin/clarity.js"
|
|
9
8
|
},
|
|
10
9
|
"scripts": {
|
|
11
10
|
"start": "node bin/clarity.js",
|
|
12
|
-
"
|
|
11
|
+
"dev": "node --watch bin/clarity.js"
|
|
13
12
|
},
|
|
14
13
|
"engines": {
|
|
15
14
|
"node": ">=18.0.0"
|
|
16
15
|
},
|
|
17
16
|
"dependencies": {
|
|
18
17
|
"chalk": "^5.3.0",
|
|
18
|
+
"figlet": "^1.7.0",
|
|
19
|
+
"gradient-string": "^2.0.2",
|
|
20
|
+
"ora": "^8.1.0",
|
|
19
21
|
"inquirer": "^9.2.12",
|
|
20
22
|
"conf": "^12.0.0",
|
|
21
23
|
"marked": "^12.0.0",
|
|
22
24
|
"marked-terminal": "^7.1.0",
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"cli-table3": "^0.6.3",
|
|
25
|
+
"cli-table3": "^0.6.5",
|
|
26
|
+
"wrap-ansi": "^9.0.1",
|
|
26
27
|
"semver": "^7.6.0",
|
|
27
28
|
"glob": "^10.3.12"
|
|
28
29
|
},
|
|
@@ -31,10 +32,8 @@
|
|
|
31
32
|
"cli",
|
|
32
33
|
"termux",
|
|
33
34
|
"agent",
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"gemini",
|
|
37
|
-
"groq"
|
|
35
|
+
"autonomous",
|
|
36
|
+
"clarity"
|
|
38
37
|
],
|
|
39
38
|
"license": "MIT"
|
|
40
39
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const HIGH_WEIGHT = ['create','build','write','make','generate','fix','debug','patch','run','execute','install','setup','search and','find and','look up and'];
|
|
2
|
+
|
|
3
|
+
const LOW_WEIGHT = ['can you','please','help me'];
|
|
4
|
+
|
|
5
|
+
export function detectIntent(message) {
|
|
6
|
+
const msg = message.toLowerCase();
|
|
7
|
+
let score = 0;
|
|
8
|
+
for (const w of HIGH_WEIGHT) {
|
|
9
|
+
if (msg.includes(w)) { score += 2; break; }
|
|
10
|
+
}
|
|
11
|
+
for (const w of LOW_WEIGHT) {
|
|
12
|
+
if (msg.includes(w)) { score += 1; }
|
|
13
|
+
}
|
|
14
|
+
return score >= 2 ? 'agent' : 'chat';
|
|
15
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { toolBox, success, errorBox } from '../blocks.js';
|
|
2
|
+
import { sendMessage } from '../providers/index.js';
|
|
3
|
+
import { executeTool } from './tools.js';
|
|
4
|
+
|
|
5
|
+
const AGENT_PROMPT = `You are CLARITY, an autonomous AI agent. You have tools available. Respond in JSON:
|
|
6
|
+
{
|
|
7
|
+
"thought": "your reasoning",
|
|
8
|
+
"tool": "tool_name",
|
|
9
|
+
"args": { "key": "value" },
|
|
10
|
+
"done": false
|
|
11
|
+
}
|
|
12
|
+
When task is complete, set done: true and provide a summary.`;
|
|
13
|
+
|
|
14
|
+
function extractJSON(text) {
|
|
15
|
+
const match = text.match(/\{[\s\S]*\}/);
|
|
16
|
+
if (!match) return null;
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(match[0]);
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function agentLoop(conversation, model, apiKey) {
|
|
25
|
+
const messages = [
|
|
26
|
+
{ role: 'system', content: AGENT_PROMPT },
|
|
27
|
+
...conversation
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
let step = 0;
|
|
31
|
+
|
|
32
|
+
while (true) {
|
|
33
|
+
step++;
|
|
34
|
+
|
|
35
|
+
const gen = sendMessage(apiKey, messages, model, false);
|
|
36
|
+
let raw = '';
|
|
37
|
+
for await (const chunk of gen) {
|
|
38
|
+
raw += chunk;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let parsed = null;
|
|
42
|
+
let attempts = 0;
|
|
43
|
+
while (!parsed && attempts < 3) {
|
|
44
|
+
parsed = extractJSON(raw);
|
|
45
|
+
if (!parsed) {
|
|
46
|
+
attempts++;
|
|
47
|
+
if (attempts < 3) {
|
|
48
|
+
const retryGen = sendMessage(apiKey, [
|
|
49
|
+
{ role: 'system', content: AGENT_PROMPT },
|
|
50
|
+
...messages,
|
|
51
|
+
{ role: 'user', content: `Your previous response was not valid JSON. Please respond with valid JSON only:\n${raw}\n\nRespond ONLY with valid JSON.` }
|
|
52
|
+
], model, false);
|
|
53
|
+
raw = '';
|
|
54
|
+
for await (const chunk of retryGen) {
|
|
55
|
+
raw += chunk;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!parsed) {
|
|
62
|
+
errorBox('Agent Error', 'Failed to parse JSON response after 3 attempts');
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const { thought, tool, args, done, summary } = parsed;
|
|
67
|
+
|
|
68
|
+
if (done) {
|
|
69
|
+
success('Task Complete', summary || 'Done');
|
|
70
|
+
return summary || 'Done';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!tool) {
|
|
74
|
+
errorBox('Agent Error', 'No tool specified in response');
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const start = Date.now();
|
|
79
|
+
let result;
|
|
80
|
+
let status;
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
result = await executeTool(tool, args || {});
|
|
84
|
+
status = 'success';
|
|
85
|
+
} catch (err) {
|
|
86
|
+
result = err.message;
|
|
87
|
+
status = 'error';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const elapsed = Date.now() - start;
|
|
91
|
+
|
|
92
|
+
toolBox(tool, {
|
|
93
|
+
args: args || {},
|
|
94
|
+
status,
|
|
95
|
+
duration: elapsed + 'ms',
|
|
96
|
+
result: typeof result === 'string' ? result.slice(0, 500) : JSON.stringify(result).slice(0, 500)
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
messages.push({ role: 'assistant', content: JSON.stringify(parsed) });
|
|
100
|
+
messages.push({
|
|
101
|
+
role: 'user',
|
|
102
|
+
content: `Tool "${tool}" returned: ${typeof result === 'string' ? result.slice(0, 2000) : JSON.stringify(result).slice(0, 2000)}`
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { sendMessage } from '../providers/index.js';
|
|
2
|
+
|
|
3
|
+
const PLAN_PROMPT = `You are CLARITY's planning system. Given a user task, create a step-by-step plan using available tools.
|
|
4
|
+
|
|
5
|
+
Available tools: file_read, file_write, file_append, file_delete, file_list, file_exists, shell_run, shell_run_silent, npm_install, pkg_install, web_search, web_fetch, code_run, git_status, git_commit, sys_info, env_get, env_set, ai_ask, summarize
|
|
6
|
+
|
|
7
|
+
Respond in JSON:
|
|
8
|
+
{
|
|
9
|
+
"steps": [
|
|
10
|
+
{ "step": "description of what to do", "tool": "tool_name", "args": { "key": "value" } }
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
Provide only the JSON, no other text.`;
|
|
15
|
+
|
|
16
|
+
export async function createPlan(task, model, apiKey) {
|
|
17
|
+
const messages = [
|
|
18
|
+
{ role: 'system', content: PLAN_PROMPT },
|
|
19
|
+
{ role: 'user', content: task }
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const gen = sendMessage(apiKey, messages, model, false);
|
|
23
|
+
let raw = '';
|
|
24
|
+
for await (const chunk of gen) {
|
|
25
|
+
raw += chunk;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const match = raw.match(/\{[\s\S]*\}/);
|
|
29
|
+
if (!match) return [{ step: 'Failed to parse plan', tool: null, args: null }];
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const parsed = JSON.parse(match[0]);
|
|
33
|
+
return parsed.steps || [];
|
|
34
|
+
} catch {
|
|
35
|
+
return [{ step: 'Failed to parse plan', tool: null, args: null }];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { readFileSync, writeFileSync, appendFileSync, unlinkSync, readdirSync, existsSync } from 'fs';
|
|
3
|
+
import { cpus, totalmem } from 'os';
|
|
4
|
+
|
|
5
|
+
const tools = [];
|
|
6
|
+
|
|
7
|
+
tools.push({
|
|
8
|
+
name: 'file_read',
|
|
9
|
+
description: 'Read file contents',
|
|
10
|
+
args: [{ name: 'path', type: 'string', required: true, description: 'File path to read' }],
|
|
11
|
+
execute({ path }) {
|
|
12
|
+
return readFileSync(path, 'utf-8');
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
tools.push({
|
|
17
|
+
name: 'file_write',
|
|
18
|
+
description: 'Write or create a file',
|
|
19
|
+
args: [
|
|
20
|
+
{ name: 'path', type: 'string', required: true, description: 'File path' },
|
|
21
|
+
{ name: 'content', type: 'string', required: true, description: 'Content to write' }
|
|
22
|
+
],
|
|
23
|
+
execute({ path, content }) {
|
|
24
|
+
writeFileSync(path, content, 'utf-8');
|
|
25
|
+
return `Written ${path}`;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
tools.push({
|
|
30
|
+
name: 'file_append',
|
|
31
|
+
description: 'Append content to a file',
|
|
32
|
+
args: [
|
|
33
|
+
{ name: 'path', type: 'string', required: true, description: 'File path' },
|
|
34
|
+
{ name: 'content', type: 'string', required: true, description: 'Content to append' }
|
|
35
|
+
],
|
|
36
|
+
execute({ path, content }) {
|
|
37
|
+
appendFileSync(path, content + '\n', 'utf-8');
|
|
38
|
+
return `Appended to ${path}`;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
tools.push({
|
|
43
|
+
name: 'file_delete',
|
|
44
|
+
description: 'Delete a file',
|
|
45
|
+
args: [{ name: 'path', type: 'string', required: true, description: 'File path' }],
|
|
46
|
+
execute({ path }) {
|
|
47
|
+
unlinkSync(path);
|
|
48
|
+
return `Deleted ${path}`;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
tools.push({
|
|
53
|
+
name: 'file_list',
|
|
54
|
+
description: 'List directory contents',
|
|
55
|
+
args: [{ name: 'dir', type: 'string', required: false, description: 'Directory path (default: current)' }],
|
|
56
|
+
execute({ dir }) {
|
|
57
|
+
return readdirSync(dir || '.').join('\n');
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
tools.push({
|
|
62
|
+
name: 'file_exists',
|
|
63
|
+
description: 'Check if a file exists',
|
|
64
|
+
args: [{ name: 'path', type: 'string', required: true, description: 'File path' }],
|
|
65
|
+
execute({ path }) {
|
|
66
|
+
return String(existsSync(path));
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
tools.push({
|
|
71
|
+
name: 'shell_run',
|
|
72
|
+
description: 'Run a bash command and capture stdout+stderr',
|
|
73
|
+
args: [{ name: 'command', type: 'string', required: true, description: 'Bash command to run' }],
|
|
74
|
+
execute({ command }) {
|
|
75
|
+
return execSync(command, { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 }) || '(no output)';
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
tools.push({
|
|
80
|
+
name: 'shell_run_silent',
|
|
81
|
+
description: 'Run a bash command with no output captured',
|
|
82
|
+
args: [{ name: 'command', type: 'string', required: true, description: 'Bash command to run' }],
|
|
83
|
+
execute({ command }) {
|
|
84
|
+
execSync(command, { encoding: 'utf-8', stdio: 'ignore' });
|
|
85
|
+
return 'done';
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
tools.push({
|
|
90
|
+
name: 'npm_install',
|
|
91
|
+
description: 'Install npm packages',
|
|
92
|
+
args: [{ name: 'packages', type: 'array', required: true, description: 'Package names to install' }],
|
|
93
|
+
execute({ packages }) {
|
|
94
|
+
const pkgs = Array.isArray(packages) ? packages.join(' ') : packages;
|
|
95
|
+
return execSync(`npm install ${pkgs}`, { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 }) || 'done';
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
tools.push({
|
|
100
|
+
name: 'pkg_install',
|
|
101
|
+
description: 'Install Termux packages via pkg',
|
|
102
|
+
args: [{ name: 'packages', type: 'array', required: true, description: 'Package names to install' }],
|
|
103
|
+
execute({ packages }) {
|
|
104
|
+
const pkgs = Array.isArray(packages) ? packages.join(' ') : packages;
|
|
105
|
+
return execSync(`pkg install -y ${pkgs}`, { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 }) || 'done';
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
tools.push({
|
|
110
|
+
name: 'web_search',
|
|
111
|
+
description: 'Search DuckDuckGo for instant answers',
|
|
112
|
+
args: [{ name: 'query', type: 'string', required: true, description: 'Search query' }],
|
|
113
|
+
async execute({ query }) {
|
|
114
|
+
const url = `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_html=1`;
|
|
115
|
+
const res = await fetch(url);
|
|
116
|
+
const data = await res.json();
|
|
117
|
+
return data.AbstractText || (data.RelatedTopics || []).slice(0, 5).map(t => t.Text || t).join('\n') || 'No results found';
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
tools.push({
|
|
122
|
+
name: 'web_fetch',
|
|
123
|
+
description: 'Fetch a URL and return text content',
|
|
124
|
+
args: [{ name: 'url', type: 'string', required: true, description: 'URL to fetch' }],
|
|
125
|
+
async execute({ url }) {
|
|
126
|
+
const res = await fetch(url);
|
|
127
|
+
const html = await res.text();
|
|
128
|
+
return html.replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim().slice(0, 10000);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
tools.push({
|
|
133
|
+
name: 'code_run',
|
|
134
|
+
description: 'Write code to temp file and execute it',
|
|
135
|
+
args: [
|
|
136
|
+
{ name: 'language', type: 'string', required: true, description: 'Programming language (js, py, sh, rb, go, rs)' },
|
|
137
|
+
{ name: 'code', type: 'string', required: true, description: 'Source code to run' }
|
|
138
|
+
],
|
|
139
|
+
execute({ language, code }) {
|
|
140
|
+
const extMap = { js: '.js', py: '.py', sh: '.sh', rb: '.rb', go: '.go', rs: '.rs' };
|
|
141
|
+
const ext = extMap[language] || '.tmp';
|
|
142
|
+
const tmp = `/tmp/clarity-code-${Date.now()}${ext}`;
|
|
143
|
+
writeFileSync(tmp, code, 'utf-8');
|
|
144
|
+
const runner = {
|
|
145
|
+
js: `node ${tmp}`, py: `python3 ${tmp}`, sh: `bash ${tmp}`,
|
|
146
|
+
rb: `ruby ${tmp}`, go: `go run ${tmp}`,
|
|
147
|
+
rs: `rustc ${tmp} -o /tmp/clarity-bin && /tmp/clarity-bin`
|
|
148
|
+
};
|
|
149
|
+
return execSync(runner[language] || `bash ${tmp}`, { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 });
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
tools.push({
|
|
154
|
+
name: 'git_status',
|
|
155
|
+
description: 'Show current git repository status',
|
|
156
|
+
args: [],
|
|
157
|
+
execute() {
|
|
158
|
+
return execSync('git status', { encoding: 'utf-8' });
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
tools.push({
|
|
163
|
+
name: 'git_commit',
|
|
164
|
+
description: 'Stage all changes and commit',
|
|
165
|
+
args: [{ name: 'message', type: 'string', required: true, description: 'Commit message' }],
|
|
166
|
+
execute({ message }) {
|
|
167
|
+
execSync('git add -A', { encoding: 'utf-8' });
|
|
168
|
+
const safeMsg = message.replace(/'/g, "'\\''");
|
|
169
|
+
return execSync(`git commit -m '${safeMsg}'`, { encoding: 'utf-8' });
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
tools.push({
|
|
174
|
+
name: 'sys_info',
|
|
175
|
+
description: 'Get CPU, memory, Node version, platform info',
|
|
176
|
+
args: [],
|
|
177
|
+
execute() {
|
|
178
|
+
const info = {
|
|
179
|
+
platform: process.platform,
|
|
180
|
+
node: process.version,
|
|
181
|
+
cpu: cpus().length + ' cores',
|
|
182
|
+
memory: Math.round(totalmem() / 1024 / 1024 / 1024) + ' GB',
|
|
183
|
+
arch: process.arch,
|
|
184
|
+
cwd: process.cwd()
|
|
185
|
+
};
|
|
186
|
+
return Object.entries(info).map(([k, v]) => `${k}: ${v}`).join('\n');
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
tools.push({
|
|
191
|
+
name: 'env_get',
|
|
192
|
+
description: 'Get an environment variable',
|
|
193
|
+
args: [{ name: 'key', type: 'string', required: true, description: 'Variable name' }],
|
|
194
|
+
execute({ key }) {
|
|
195
|
+
return process.env[key] || '(not set)';
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
tools.push({
|
|
200
|
+
name: 'env_set',
|
|
201
|
+
description: 'Set an environment variable in .env file',
|
|
202
|
+
args: [
|
|
203
|
+
{ name: 'key', type: 'string', required: true, description: 'Variable name' },
|
|
204
|
+
{ name: 'value', type: 'string', required: true, description: 'Variable value' }
|
|
205
|
+
],
|
|
206
|
+
execute({ key, value }) {
|
|
207
|
+
appendFileSync('.env', `${key}=${value}\n`, 'utf-8');
|
|
208
|
+
return `Set ${key}=${value}`;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
tools.push({
|
|
213
|
+
name: 'ai_ask',
|
|
214
|
+
description: 'Ask another AI model a question',
|
|
215
|
+
args: [
|
|
216
|
+
{ name: 'prompt', type: 'string', required: true, description: 'Prompt to send' },
|
|
217
|
+
{ name: 'model', type: 'string', required: false, description: 'Model name (e.g. groq/llama-3.3-70b-versatile)' }
|
|
218
|
+
],
|
|
219
|
+
async execute({ prompt, model }) {
|
|
220
|
+
const { sendMessage } = await import('../providers/index.js');
|
|
221
|
+
const m = model || 'groq/llama-3.3-70b-versatile';
|
|
222
|
+
const messages = [{ role: 'user', content: prompt }];
|
|
223
|
+
const gen = sendMessage(process.env.SECRET_API_KEY || '', messages, m, false);
|
|
224
|
+
let result = '';
|
|
225
|
+
for await (const chunk of gen) {
|
|
226
|
+
result += chunk;
|
|
227
|
+
}
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
tools.push({
|
|
233
|
+
name: 'summarize',
|
|
234
|
+
description: 'Summarize a long text using AI',
|
|
235
|
+
args: [{ name: 'text', type: 'string', required: true, description: 'Text to summarize' }],
|
|
236
|
+
async execute({ text }) {
|
|
237
|
+
const { sendMessage } = await import('../providers/index.js');
|
|
238
|
+
const messages = [{ role: 'user', content: `Summarize this text concisely:\n\n${text}` }];
|
|
239
|
+
const gen = sendMessage(process.env.SECRET_API_KEY || '', messages, 'groq/llama-3.3-70b-versatile', false);
|
|
240
|
+
let result = '';
|
|
241
|
+
for await (const chunk of gen) {
|
|
242
|
+
result += chunk;
|
|
243
|
+
}
|
|
244
|
+
return result;
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
export { tools };
|
|
249
|
+
|
|
250
|
+
export async function executeTool(name, argsObj) {
|
|
251
|
+
const tool = tools.find(t => t.name === name);
|
|
252
|
+
if (!tool) throw new Error(`Unknown tool: ${name}`);
|
|
253
|
+
return await tool.execute(argsObj);
|
|
254
|
+
}
|