clarity-ai 3.0.1 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/clarity.js +23 -1
- package/package.json +21 -15
- package/scripts/postinstall.js +13 -8
- package/src/agents/code-agent.js +18 -0
- package/src/agents/file-agent.js +24 -0
- package/src/agents/git-agent.js +27 -0
- package/src/agents/loop.js +205 -0
- package/src/agents/monitor-agent.js +22 -0
- package/src/agents/planner.js +21 -0
- package/src/agents/shell-agent.js +11 -0
- package/src/agents/web-agent.js +16 -0
- package/src/commands/agent.js +14 -0
- package/src/commands/chat.js +6 -102
- package/src/commands/clear.js +5 -0
- package/src/commands/config.js +15 -142
- package/src/commands/diff.js +12 -0
- package/src/commands/exit.js +5 -0
- package/src/commands/export.js +12 -0
- package/src/commands/fetch.js +16 -0
- package/src/commands/git.js +12 -0
- package/src/commands/help.js +32 -0
- package/src/commands/history.js +13 -0
- package/src/commands/index.js +51 -134
- package/src/commands/keys.js +30 -0
- package/src/commands/memory.js +23 -0
- package/src/commands/model.js +10 -92
- package/src/commands/provider.js +26 -0
- package/src/commands/run.js +16 -0
- package/src/commands/search.js +13 -0
- package/src/commands/task.js +24 -0
- package/src/commands/tools.js +43 -0
- package/src/commands/undo.js +10 -0
- package/src/config/settings.js +31 -24
- package/src/core/context.js +39 -0
- package/src/core/history.js +27 -0
- package/src/core/memory.js +40 -0
- package/src/core/setup.js +87 -0
- package/src/main.js +75 -99
- package/src/providers/index.js +29 -13
- package/src/tools/agent-spawn.js +13 -0
- package/src/tools/bash.js +9 -0
- package/src/tools/clipboard-tool.js +14 -0
- package/src/tools/code-runner.js +17 -0
- package/src/tools/compress-tool.js +21 -0
- package/src/tools/context-tool.js +16 -0
- package/src/tools/delete-file.js +11 -0
- package/src/tools/diff-tool.js +19 -0
- package/src/tools/edit-file.js +13 -0
- package/src/tools/env-tool.js +19 -0
- package/src/tools/git-tool.js +9 -0
- package/src/tools/grep.js +13 -0
- package/src/tools/list-dir.js +20 -0
- package/src/tools/memory-tool.js +18 -0
- package/src/tools/notify-tool.js +9 -0
- package/src/tools/pkg-manager.js +19 -0
- package/src/tools/read-file.js +9 -0
- package/src/tools/run-tests.js +10 -0
- package/src/tools/screenshot-tool.js +9 -0
- package/src/tools/search-files.js +10 -0
- package/src/tools/task-planner.js +11 -0
- package/src/tools/version-check.js +12 -0
- package/src/tools/web-fetch.js +15 -0
- package/src/tools/web-search.js +18 -0
- package/src/tools/write-file.js +12 -0
- package/src/ui/banner.js +45 -0
- package/src/ui/blocks.js +132 -0
- package/src/ui/colors.js +41 -0
- package/src/ui/input.js +29 -0
- package/src/ui/spinner.js +23 -47
- package/src/agent/intent.js +0 -9
- package/src/agent/loop.js +0 -105
- package/src/agent/planner.js +0 -37
- package/src/agent/runner.js +0 -143
- package/src/agent/subagent.js +0 -55
- package/src/agent/tools.js +0 -132
- package/src/agents/BaseAgent.js +0 -54
- package/src/agents/CodeAgent.js +0 -224
- package/src/agents/FileAgent.js +0 -85
- package/src/agents/MonitorAgent.js +0 -104
- package/src/agents/ShellAgent.js +0 -91
- package/src/agents/WebAgent.js +0 -89
- package/src/agents/manager.js +0 -127
- package/src/app.js +0 -88
- package/src/banner.js +0 -14
- package/src/blocks.js +0 -100
- package/src/colors.js +0 -31
- package/src/commands/files.js +0 -106
- package/src/commands/system.js +0 -131
- package/src/commands/utility.js +0 -332
- package/src/commands/web.js +0 -82
- package/src/components/AIMessage.js +0 -21
- package/src/components/AgentTree.js +0 -42
- package/src/components/Banner.js +0 -15
- package/src/components/InputBar.js +0 -40
- package/src/components/MessageList.js +0 -23
- package/src/components/Spinner.js +0 -13
- package/src/components/StatusBar.js +0 -24
- package/src/components/ThoughtBox.js +0 -23
- package/src/components/TodoTree.js +0 -31
- package/src/components/ToolCall.js +0 -69
- package/src/components/UserMessage.js +0 -9
- package/src/config/keys.js +0 -104
- package/src/config/paths.js +0 -22
- package/src/config.js +0 -29
- package/src/history.js +0 -54
- package/src/index.js +0 -43
- package/src/input.js +0 -36
- package/src/memory/context.js +0 -38
- package/src/memory/store.js +0 -54
- package/src/setup.js +0 -137
- package/src/store.js +0 -68
- package/src/utils/logger.js +0 -40
- package/src/utils/markdown.js +0 -25
- package/src/utils/termux.js +0 -38
- package/src/utils/version-check.js +0 -76
package/bin/clarity.js
CHANGED
|
@@ -1,2 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
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';
|
|
7
|
+
|
|
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);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
main().catch(err => {
|
|
22
|
+
console.error('\x1b[31mFatal error:\x1b[0m', err.message);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
});
|
package/package.json
CHANGED
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clarity-ai",
|
|
3
|
-
"version": "3.0
|
|
4
|
-
"description": "Autonomous AI Agent
|
|
3
|
+
"version": "3.1.0",
|
|
4
|
+
"description": "Autonomous AI Agent CLI for Termux",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"bin": {
|
|
6
|
+
"bin": {
|
|
7
|
+
"clarity": "bin/clarity.js"
|
|
8
|
+
},
|
|
7
9
|
"scripts": {
|
|
8
10
|
"start": "node bin/clarity.js",
|
|
9
11
|
"dev": "node --watch bin/clarity.js"
|
|
10
12
|
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18.0.0"
|
|
15
|
+
},
|
|
11
16
|
"dependencies": {
|
|
12
|
-
"ink": "^4.4.1",
|
|
13
|
-
"react": "^18.2.0",
|
|
14
17
|
"chalk": "^5.3.0",
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
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"
|
|
30
|
+
}
|
|
25
31
|
}
|
package/scripts/postinstall.js
CHANGED
|
@@ -40,15 +40,20 @@ const binDir = process.env.npm_config_prefix ? `${process.env.npm_config_prefix}
|
|
|
40
40
|
if (isTermux && binDir) {
|
|
41
41
|
const { existsSync, writeFileSync, chmodSync, unlinkSync, lstatSync } = await import('fs');
|
|
42
42
|
const { resolve } = await import('path');
|
|
43
|
-
const
|
|
43
|
+
const symlinkPath = resolve(binDir, 'clarity');
|
|
44
44
|
|
|
45
45
|
try {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
console.log(chalk.hex('#00ff88')(' ✓ Termux wrapper created at:'), chalk.hex('#00d2ff')(wrapperPath));
|
|
46
|
+
if (existsSync(symlinkPath)) {
|
|
47
|
+
const stat = lstatSync(symlinkPath);
|
|
48
|
+
if (stat.isSymbolicLink()) {
|
|
49
|
+
unlinkSync(symlinkPath);
|
|
50
|
+
}
|
|
52
51
|
}
|
|
53
|
-
|
|
52
|
+
const clarityPkgDir = process.env.PREFIX ? `${process.env.PREFIX}/lib/node_modules/clarity-ai` : resolve(process.cwd());
|
|
53
|
+
writeFileSync(symlinkPath, `#!/data/data/com.termux/files/usr/bin/bash\nexec node "${clarityPkgDir}/bin/clarity.js" "$@"\n`);
|
|
54
|
+
chmodSync(symlinkPath, '755');
|
|
55
|
+
console.log(chalk.hex('#00ff88')(' ✓ Termux wrapper created at:'), chalk.hex('#00d2ff')(symlinkPath));
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.error(chalk.hex('#ff4466')(' ✗ Failed to create Termux wrapper:'), e.message);
|
|
58
|
+
}
|
|
54
59
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { writeFileSync, unlinkSync } from 'fs';
|
|
3
|
+
|
|
4
|
+
export async function codeAgent({ language, code, test }) {
|
|
5
|
+
const ext = language === 'python' ? 'py' : language === 'javascript' ? 'js' : 'sh';
|
|
6
|
+
const tmpFile = `/tmp/clarity-agent.${ext}`;
|
|
7
|
+
try {
|
|
8
|
+
writeFileSync(tmpFile, code + (test ? `\n${test}` : ''), 'utf8');
|
|
9
|
+
const cmd = language === 'python' ? `python3 ${tmpFile}` :
|
|
10
|
+
language === 'javascript' ? `node ${tmpFile}` : `bash ${tmpFile}`;
|
|
11
|
+
const out = execSync(cmd, { encoding: 'utf8', timeout: 15000 });
|
|
12
|
+
return out || '(no output)';
|
|
13
|
+
} catch (err) {
|
|
14
|
+
return err.stdout || err.stderr || err.message;
|
|
15
|
+
} finally {
|
|
16
|
+
try { unlinkSync(tmpFile); } catch {}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, readdirSync } from 'fs';
|
|
2
|
+
import { dirname, resolve } from 'path';
|
|
3
|
+
|
|
4
|
+
export async function fileAgent({ action, path, content, pattern }) {
|
|
5
|
+
const abs = resolve(path || '.');
|
|
6
|
+
switch (action) {
|
|
7
|
+
case 'read':
|
|
8
|
+
if (!existsSync(abs)) return `Error: Not found: ${abs}`;
|
|
9
|
+
return readFileSync(abs, 'utf8');
|
|
10
|
+
case 'write':
|
|
11
|
+
if (!existsSync(dirname(abs))) mkdirSync(dirname(abs), { recursive: true });
|
|
12
|
+
writeFileSync(abs, content || '', 'utf8');
|
|
13
|
+
return `Written: ${abs}`;
|
|
14
|
+
case 'delete':
|
|
15
|
+
if (!existsSync(abs)) return `Error: Not found: ${abs}`;
|
|
16
|
+
unlinkSync(abs);
|
|
17
|
+
return `Deleted: ${abs}`;
|
|
18
|
+
case 'list':
|
|
19
|
+
if (!existsSync(abs)) return `Error: Not found: ${abs}`;
|
|
20
|
+
return readdirSync(abs).join('\n');
|
|
21
|
+
default:
|
|
22
|
+
return 'Usage: file <read/write/delete/list> <path> [content]';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
|
|
3
|
+
export async function gitAgent({ action, message, branch }) {
|
|
4
|
+
try {
|
|
5
|
+
switch (action) {
|
|
6
|
+
case 'status':
|
|
7
|
+
return execSync('git status', { encoding: 'utf8' });
|
|
8
|
+
case 'log':
|
|
9
|
+
return execSync('git log --oneline -10', { encoding: 'utf8' });
|
|
10
|
+
case 'commit':
|
|
11
|
+
execSync('git add .', { encoding: 'utf8' });
|
|
12
|
+
return execSync(`git commit -m "${message || 'update'}"`, { encoding: 'utf8' });
|
|
13
|
+
case 'push':
|
|
14
|
+
return execSync('git push', { encoding: 'utf8', timeout: 30000 });
|
|
15
|
+
case 'pull':
|
|
16
|
+
return execSync('git pull', { encoding: 'utf8', timeout: 30000 });
|
|
17
|
+
case 'branch':
|
|
18
|
+
return execSync(`git branch ${branch || ''}`, { encoding: 'utf8' });
|
|
19
|
+
case 'diff':
|
|
20
|
+
return execSync('git diff', { encoding: 'utf8' });
|
|
21
|
+
default:
|
|
22
|
+
return 'Usage: git <status|log|commit|push|pull|branch|diff> [args]';
|
|
23
|
+
}
|
|
24
|
+
} catch (err) {
|
|
25
|
+
return err.stderr || err.message;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { callProvider } from '../providers/index.js';
|
|
2
|
+
import { buildSystemPrompt } from '../core/context.js';
|
|
3
|
+
import { addMessage } from '../core/history.js';
|
|
4
|
+
import { renderEdit, renderWrite, renderTool } from '../ui/blocks.js';
|
|
5
|
+
import { diffLines } from 'diff';
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
8
|
+
|
|
9
|
+
export const SYSTEM_PROMPT = 'You are CLARITY, an autonomous AI agent CLI running in Termux on Android.\n\n' +
|
|
10
|
+
'## CRITICAL RULES — NEVER VIOLATE\n\n' +
|
|
11
|
+
'1. NEVER fabricate file contents, directory structures, or command outputs.\n' +
|
|
12
|
+
' - If you have not read a file, say "I have not read that file yet."\n' +
|
|
13
|
+
' - If a command has not run, say "I will run that now."\n' +
|
|
14
|
+
' - NEVER invent results.\n\n' +
|
|
15
|
+
'2. NEVER hallucinate tool calls — only call tools you actually have.\n' +
|
|
16
|
+
' Available tools: bash, read_file, write_file, edit_file, delete_file,\n' +
|
|
17
|
+
' list_directory, search_files, grep, web_search, web_fetch, git,\n' +
|
|
18
|
+
' run_tests, memory, task_planner, code_runner, pkg_manager, env,\n' +
|
|
19
|
+
' diff, compress, screenshot, clipboard, notify, version_check,\n' +
|
|
20
|
+
' context, agent_spawn.\n\n' +
|
|
21
|
+
'3. NEVER confirm an action before doing it — just do it.\n' +
|
|
22
|
+
' Bad: "I will create the workspace folder now." [then waits]\n' +
|
|
23
|
+
' Good: [calls bash tool: mkdir workspace] then "Created workspace/."\n\n' +
|
|
24
|
+
'4. ALWAYS ground responses in actual tool output.\n' +
|
|
25
|
+
' If a tool returns an error, report the actual error. Do not pretend it succeeded.\n\n' +
|
|
26
|
+
'5. SHORT responses. No filler. No "Certainly!" No "Great question!"\n' +
|
|
27
|
+
' Just: action -> result -> next step if needed.\n\n' +
|
|
28
|
+
'6. When editing files, ALWAYS read the file first, then make targeted edits.\n' +
|
|
29
|
+
' Never overwrite a file you have not read.\n\n' +
|
|
30
|
+
'7. For multi-step tasks, plan first with task_planner, then execute step by step.\n' +
|
|
31
|
+
' Show each step as you complete it.\n\n' +
|
|
32
|
+
'## RESPONSE FORMAT\n\n' +
|
|
33
|
+
'- Plain text for explanations. No markdown headers in chat.\n' +
|
|
34
|
+
'- File edits shown as diffs automatically by the UI — do not describe edits in text.\n' +
|
|
35
|
+
'- Code in responses: just write it plainly, no fences needed unless showing a standalone snippet.\n' +
|
|
36
|
+
'- Errors: state the actual error and what you will try next.\n\n' +
|
|
37
|
+
'## IDENTITY\n\n' +
|
|
38
|
+
'You are CLARITY v3.0. You are running on ' + process.platform + '.\n' +
|
|
39
|
+
'Current working directory: ' + process.cwd() + '\n' +
|
|
40
|
+
'Agent mode: ON — you can and should use tools without asking permission.\n' +
|
|
41
|
+
'\nAvailable tools with JSON schema:\n' +
|
|
42
|
+
'- bash { command: string }\n' +
|
|
43
|
+
'- read_file { path: string }\n' +
|
|
44
|
+
'- write_file { path: string, content: string }\n' +
|
|
45
|
+
'- edit_file { path: string, old_str: string, new_str: string }\n' +
|
|
46
|
+
'- delete_file { path: string }\n' +
|
|
47
|
+
'- list_directory { dir: string }\n' +
|
|
48
|
+
'- search_files { pattern: string }\n' +
|
|
49
|
+
'- grep { pattern: string, path: string }\n' +
|
|
50
|
+
'- web_search { query: string }\n' +
|
|
51
|
+
'- web_fetch { url: string }\n' +
|
|
52
|
+
'- git { subcommand: string }\n' +
|
|
53
|
+
'- memory { action: string, key?: string, value?: string }\n' +
|
|
54
|
+
'- code_runner { language: string, code: string }\n' +
|
|
55
|
+
'- pkg_manager { manager: string, action: string, packages: string }\n\n' +
|
|
56
|
+
'Respond with JSON:\n' +
|
|
57
|
+
'{"tool": "tool_name", "args": {"arg1": "val1"}}\n' +
|
|
58
|
+
'or to respond to user:\n' +
|
|
59
|
+
'{"response": "your final answer here"}';
|
|
60
|
+
|
|
61
|
+
function filterContextHunks(hunks, ctx) {
|
|
62
|
+
const changedIdx = hunks
|
|
63
|
+
.map((h, i) => (h.type !== 'context' ? i : -1))
|
|
64
|
+
.filter((i) => i >= 0);
|
|
65
|
+
return hunks.filter((_, i) => {
|
|
66
|
+
if (hunks[i].type !== 'context') return true;
|
|
67
|
+
for (const c of changedIdx) {
|
|
68
|
+
if (Math.abs(i - c) <= ctx) return true;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function executeToolCall(toolName, toolInput) {
|
|
75
|
+
switch (toolName) {
|
|
76
|
+
case 'write_file': {
|
|
77
|
+
const { path, content } = toolInput;
|
|
78
|
+
const lines = content.split('\n').length;
|
|
79
|
+
writeFileSync(path, content, 'utf-8');
|
|
80
|
+
console.log(renderWrite(path, lines));
|
|
81
|
+
return 'Written ' + lines + ' lines to ' + path;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
case 'edit_file': {
|
|
85
|
+
const { path, old_str, new_str } = toolInput;
|
|
86
|
+
if (!existsSync(path)) return 'Error: File not found: ' + path;
|
|
87
|
+
const original = readFileSync(path, 'utf-8');
|
|
88
|
+
const updated = original.replace(old_str, new_str);
|
|
89
|
+
|
|
90
|
+
const changes = diffLines(original, updated);
|
|
91
|
+
const hunks = [];
|
|
92
|
+
let lineNum = 1;
|
|
93
|
+
for (const part of changes) {
|
|
94
|
+
for (const line of part.value.split('\n').slice(0, -1)) {
|
|
95
|
+
if (part.added) {
|
|
96
|
+
hunks.push({ type: 'add', line, lineNum });
|
|
97
|
+
} else if (part.removed) {
|
|
98
|
+
hunks.push({ type: 'remove', line, lineNum });
|
|
99
|
+
} else {
|
|
100
|
+
hunks.push({ type: 'context', line, lineNum });
|
|
101
|
+
}
|
|
102
|
+
lineNum++;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const filtered = filterContextHunks(hunks, 2);
|
|
107
|
+
writeFileSync(path, updated, 'utf-8');
|
|
108
|
+
console.log(renderEdit(path, filtered));
|
|
109
|
+
return 'Edited ' + path;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
case 'bash': {
|
|
113
|
+
const { command } = toolInput;
|
|
114
|
+
try {
|
|
115
|
+
const result = execSync(command, { encoding: 'utf-8', timeout: 30000 });
|
|
116
|
+
const out = result || '(no output)';
|
|
117
|
+
console.log(renderTool('bash', command, out));
|
|
118
|
+
return out;
|
|
119
|
+
} catch (err) {
|
|
120
|
+
const errMsg = err.stderr || err.message;
|
|
121
|
+
console.log(renderTool('bash', command, errMsg));
|
|
122
|
+
return 'Error: ' + errMsg;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
case 'read_file': {
|
|
127
|
+
const { path } = toolInput;
|
|
128
|
+
if (!existsSync(path)) return 'Error: File not found: ' + path;
|
|
129
|
+
const content = readFileSync(path, 'utf-8');
|
|
130
|
+
console.log(renderTool('read_file', path, content.slice(0, 200)));
|
|
131
|
+
return content;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
default: {
|
|
135
|
+
try {
|
|
136
|
+
const mod = await import('../tools/' + toolName + '.js');
|
|
137
|
+
const toolFn = Object.values(mod)[0];
|
|
138
|
+
const result = await toolFn(toolInput);
|
|
139
|
+
console.log(renderTool(toolName, JSON.stringify(toolInput).slice(0, 60), result));
|
|
140
|
+
return result;
|
|
141
|
+
} catch (e) {
|
|
142
|
+
return 'Tool error: ' + e.message;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export async function agentLoop(userMessage, config, history) {
|
|
149
|
+
const messages = [
|
|
150
|
+
{ role: 'system', content: buildSystemPrompt(SYSTEM_PROMPT) },
|
|
151
|
+
...history.slice(-20).map((m) => ({
|
|
152
|
+
role: m.role === 'assistant' ? 'assistant' : 'user',
|
|
153
|
+
content: m.content,
|
|
154
|
+
})),
|
|
155
|
+
{ role: 'user', content: userMessage },
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
let finalResponse = '';
|
|
159
|
+
let toolCalls = 0;
|
|
160
|
+
const maxToolCalls = 20;
|
|
161
|
+
|
|
162
|
+
while (toolCalls < maxToolCalls) {
|
|
163
|
+
let fullResponse = '';
|
|
164
|
+
const stream = callProvider(config, messages);
|
|
165
|
+
for await (const chunk of stream) {
|
|
166
|
+
fullResponse += chunk;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const jsonMatch = fullResponse.match(/\{[^]*\}/);
|
|
170
|
+
if (!jsonMatch) {
|
|
171
|
+
finalResponse = fullResponse;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let parsed;
|
|
176
|
+
try {
|
|
177
|
+
parsed = JSON.parse(jsonMatch[0]);
|
|
178
|
+
} catch {
|
|
179
|
+
finalResponse = fullResponse;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (parsed.response) {
|
|
184
|
+
finalResponse = parsed.response;
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (parsed.tool) {
|
|
189
|
+
toolCalls++;
|
|
190
|
+
const toolName = parsed.tool;
|
|
191
|
+
const args = parsed.args || {};
|
|
192
|
+
|
|
193
|
+
const result = await executeToolCall(toolName, args);
|
|
194
|
+
|
|
195
|
+
messages.push({ role: 'assistant', content: fullResponse });
|
|
196
|
+
messages.push({ role: 'user', content: 'Tool result: ' + result });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (toolCalls >= maxToolCalls) {
|
|
201
|
+
finalResponse += '\n\n(Max tool calls reached)';
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return finalResponse || 'No response generated';
|
|
205
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { watch } from 'fs';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
|
|
4
|
+
let watchers = [];
|
|
5
|
+
|
|
6
|
+
export async function monitorAgent({ action, path, event }) {
|
|
7
|
+
if (action === 'watch') {
|
|
8
|
+
const abs = resolve(path || '.');
|
|
9
|
+
const watcher = watch(abs, { recursive: true }, (eventType, filename) => {
|
|
10
|
+
console.log(`[monitor] ${eventType}: ${filename}`);
|
|
11
|
+
});
|
|
12
|
+
watchers.push(watcher);
|
|
13
|
+
return `Watching: ${abs}`;
|
|
14
|
+
} else if (action === 'stop') {
|
|
15
|
+
for (const w of watchers) w.close();
|
|
16
|
+
watchers = [];
|
|
17
|
+
return 'Monitoring stopped';
|
|
18
|
+
} else if (action === 'status') {
|
|
19
|
+
return `Active watchers: ${watchers.length}`;
|
|
20
|
+
}
|
|
21
|
+
return 'Usage: monitor <watch|stop|status> [path]';
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { callProvider } from '../providers/index.js';
|
|
2
|
+
|
|
3
|
+
export async function planTask(userMessage, config) {
|
|
4
|
+
const messages = [
|
|
5
|
+
{ role: 'system', content: 'You are a task planner. Break down the user\'s goal into a numbered step-by-step plan. Be specific and actionable. Respond with a JSON array of steps.' },
|
|
6
|
+
{ role: 'user', content: userMessage },
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
let response = '';
|
|
10
|
+
const stream = callProvider(config, messages);
|
|
11
|
+
for await (const chunk of stream) {
|
|
12
|
+
response += chunk;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const jsonMatch = response.match(/\[[^]*\]/);
|
|
17
|
+
if (jsonMatch) return JSON.parse(jsonMatch[0]);
|
|
18
|
+
} catch {}
|
|
19
|
+
|
|
20
|
+
return response.split('\n').filter(l => /^\d+\./.test(l)).map((l, i) => ({ step: i + 1, task: l.replace(/^\d+\.\s*/, '') }));
|
|
21
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
|
|
3
|
+
export async function shellAgent({ command, cwd, timeout }) {
|
|
4
|
+
if (!command) return 'No command specified';
|
|
5
|
+
try {
|
|
6
|
+
const out = execSync(command, { encoding: 'utf8', timeout: timeout || 30000, cwd: cwd || process.cwd() });
|
|
7
|
+
return out || '(no output)';
|
|
8
|
+
} catch (err) {
|
|
9
|
+
return err.stderr || err.message;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export async function webAgent({ action, url, query }) {
|
|
2
|
+
try {
|
|
3
|
+
if (action === 'search') {
|
|
4
|
+
const res = await fetch(`https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_html=1`);
|
|
5
|
+
const data = await res.json();
|
|
6
|
+
return data.AbstractText || JSON.stringify(data.RelatedTopics?.slice(0, 5) || 'No results');
|
|
7
|
+
} else if (action === 'fetch') {
|
|
8
|
+
const res = await fetch(url);
|
|
9
|
+
const html = await res.text();
|
|
10
|
+
return html.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim().substring(0, 5000);
|
|
11
|
+
}
|
|
12
|
+
return 'Usage: web <search|fetch> <query|url>';
|
|
13
|
+
} catch (err) {
|
|
14
|
+
return `Error: ${err.message}`;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { clr } from '../ui/colors.js';
|
|
2
|
+
export async function agentCommand(args, config) {
|
|
3
|
+
const mode = args[0];
|
|
4
|
+
if (mode === 'on' || mode === 'true') {
|
|
5
|
+
config.agentMode = true;
|
|
6
|
+
console.log(clr.success('Agent mode: ON'));
|
|
7
|
+
} else if (mode === 'off' || mode === 'false') {
|
|
8
|
+
config.agentMode = false;
|
|
9
|
+
console.log(clr.warning('Agent mode: OFF'));
|
|
10
|
+
} else {
|
|
11
|
+
console.log(clr.info(`Agent mode: ${config.agentMode ? 'ON' : 'OFF'}`));
|
|
12
|
+
}
|
|
13
|
+
return config;
|
|
14
|
+
}
|
package/src/commands/chat.js
CHANGED
|
@@ -1,104 +1,8 @@
|
|
|
1
|
-
import blocks from '../blocks.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const { info, success, warn, error: errorBox, table, divider } = blocks;
|
|
8
|
-
|
|
9
|
-
export async function clear(args, context) {
|
|
10
|
-
console.clear();
|
|
11
|
-
success('Cleared', 'Screen and context cleared.');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export async function history(args, context) {
|
|
15
|
-
const sub = args[0];
|
|
16
|
-
if (!sub) { warn('Usage', '/history show|clear|export [n|file]'); return; }
|
|
17
|
-
|
|
18
|
-
switch (sub) {
|
|
19
|
-
case 'show': {
|
|
20
|
-
const n = args[1] ? parseInt(args[1]) : 20;
|
|
21
|
-
const ctx = memStore.show();
|
|
22
|
-
const recent = ctx.slice(-n);
|
|
23
|
-
if (recent.length === 0) { info('History', 'No history.'); return; }
|
|
24
|
-
recent.forEach(m => console.log(`${c.muted(m.role)}: ${m.content.slice(0, 150)}`));
|
|
25
|
-
break;
|
|
26
|
-
}
|
|
27
|
-
case 'clear': {
|
|
28
|
-
memStore.clear();
|
|
29
|
-
success('Cleared', 'History cleared.');
|
|
30
|
-
break;
|
|
31
|
-
}
|
|
32
|
-
case 'export': {
|
|
33
|
-
const file = args[1] || `clarity-chat-${Date.now()}.md`;
|
|
34
|
-
const fullPath = resolve(file);
|
|
35
|
-
const ctx = memStore.show();
|
|
36
|
-
const md = ctx.map(m => `**${m.role}**: ${m.content}`).join('\n\n');
|
|
37
|
-
writeFileSync(fullPath, `# CLARITY Chat Export\n\n${md}`, 'utf8');
|
|
38
|
-
success('Exported', c.filename(fullPath));
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
default:
|
|
42
|
-
warn('Usage', '/history show|clear|export');
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export async function retry(args, context) {
|
|
47
|
-
const ctx = memStore.show();
|
|
48
|
-
const lastUser = ctx.filter(m => m.role === 'user').pop();
|
|
49
|
-
if (!lastUser) { warn('Retry', 'No previous user message to retry.'); return; }
|
|
50
|
-
info('Retry', `Retrying: "${lastUser.content.slice(0, 80)}..."`);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export async function copy(args, context) {
|
|
54
|
-
const ctx = memStore.show();
|
|
55
|
-
const last = ctx.filter(m => m.role === 'assistant').pop();
|
|
56
|
-
if (!last) { warn('Copy', 'No AI response to copy.'); return; }
|
|
57
|
-
try {
|
|
58
|
-
if (process.env.PREFIX?.includes('com.termux') || process.env.TERMUX_VERSION) {
|
|
59
|
-
const { execSync } = await import('child_process');
|
|
60
|
-
execSync(`termux-clipboard-set "${last.content.replace(/"/g, '\\"')}"`);
|
|
61
|
-
} else {
|
|
62
|
-
const { default: clipboard } = await import('clipboardy');
|
|
63
|
-
clipboard.writeSync(last.content);
|
|
64
|
-
}
|
|
65
|
-
success('Copied', 'Last AI response copied to clipboard.');
|
|
66
|
-
} catch {
|
|
67
|
-
info('Copy', last.content.slice(0, 500));
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export async function ask(args, context) {
|
|
72
|
-
const question = args.join(' ');
|
|
73
|
-
if (!question) { warn('Usage', '/ask <question>'); return; }
|
|
74
|
-
blocks.user(question);
|
|
75
|
-
info('Ask', 'Configure API keys with /init first to get AI responses.');
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export async function memory(args, context) {
|
|
79
|
-
const sub = args[0];
|
|
80
|
-
if (!sub) { warn('Usage', '/memory show|add|clear'); return; }
|
|
81
|
-
|
|
82
|
-
switch (sub) {
|
|
83
|
-
case 'show': {
|
|
84
|
-
const ctx = memStore.show();
|
|
85
|
-
if (ctx.length === 0) { info('Memory', 'No context stored.'); return; }
|
|
86
|
-
ctx.forEach(m => console.log(`${c.muted(m.role)}: ${m.content.slice(0, 200)}`));
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
89
|
-
case 'add': {
|
|
90
|
-
const note = args.slice(1).join(' ');
|
|
91
|
-
if (!note) { warn('Usage', '/memory add <note>'); return; }
|
|
92
|
-
memStore.addNote(note);
|
|
93
|
-
success('Memory Added', note);
|
|
94
|
-
break;
|
|
95
|
-
}
|
|
96
|
-
case 'clear': {
|
|
97
|
-
memStore.clear();
|
|
98
|
-
success('Cleared', 'Memory cleared.');
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
default:
|
|
102
|
-
warn('Usage', '/memory show|add|clear');
|
|
1
|
+
import { blocks } from '../ui/blocks.js';
|
|
2
|
+
export async function chatCommand(args, config) {
|
|
3
|
+
if (args[0] === 'new') {
|
|
4
|
+
console.log(blocks.info('New chat session started.', '◆ CHAT'));
|
|
5
|
+
return { ...config, chatHistory: [] };
|
|
103
6
|
}
|
|
7
|
+
console.log(blocks.info('Chat mode active. Provider: ' + config.provider + ', Model: ' + config.model, '◆ CHAT'));
|
|
104
8
|
}
|