clarity-ai 2.0.1 → 3.0.2
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/AGENTS.md +4 -0
- package/bin/clarity.js +23 -1
- package/package.json +10 -18
- 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 +85 -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 +55 -15
- 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 +67 -98
- package/src/providers/index.js +27 -36
- 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 +65 -0
- package/src/ui/colors.js +41 -0
- package/src/ui/input.js +60 -0
- package/src/ui/spinner.js +23 -47
- package/src/agent/intent.js +0 -15
- package/src/agent/loop.js +0 -105
- package/src/agent/planner.js +0 -37
- package/src/agent/tools.js +0 -254
- 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/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/config/keys.js +0 -104
- package/src/config/paths.js +0 -22
- package/src/config.js +0 -44
- package/src/history.js +0 -54
- 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/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/AGENTS.md
CHANGED
|
@@ -45,3 +45,7 @@ SSE parsing pattern: read `data: ` lines, parse JSON, yield `choices[0].delta.co
|
|
|
45
45
|
- **No test framework**: none configured. No lint/typecheck scripts.
|
|
46
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
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.
|
|
48
|
+
- **No build step (v3)**: All Ink+React components use `React.createElement()` directly — NO JSX. Node.js cannot parse JSX without a transpiler.
|
|
49
|
+
- **TTY check**: `src/index.js` checks `process.stdin.isTTY` before rendering Ink — prevents `Raw mode not supported` error in non-TTY environments.
|
|
50
|
+
- **Global state**: `src/store.js` uses `useReducer` with a reducer function — dispatches `THOUGHT_START`, `TOOL_CALL`, `ADD_MESSAGE`, `SET_TODOS`, etc.
|
|
51
|
+
- **Parallel subagents**: `src/agent/subagent.js` — `shouldParallelize()` detects multi-step tasks, `planSubagents()` calls model for JSON array, `runSubagents()` runs `Promise.all` with AGENT_REGISTER/TOOLCALL dispatches.
|
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clarity-ai",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "Autonomous AI Agent CLI for Termux",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,21 +19,13 @@
|
|
|
19
19
|
"gradient-string": "^2.0.2",
|
|
20
20
|
"ora": "^8.1.0",
|
|
21
21
|
"inquirer": "^9.2.12",
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"ai",
|
|
32
|
-
"cli",
|
|
33
|
-
"termux",
|
|
34
|
-
"agent",
|
|
35
|
-
"autonomous",
|
|
36
|
-
"clarity"
|
|
37
|
-
],
|
|
38
|
-
"license": "MIT"
|
|
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
|
+
}
|
|
39
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,85 @@
|
|
|
1
|
+
import { callProvider } from '../providers/index.js';
|
|
2
|
+
import { buildSystemPrompt } from '../core/context.js';
|
|
3
|
+
import { addMessage } from '../core/history.js';
|
|
4
|
+
|
|
5
|
+
const TOOLS_DESC = `You are CLARITY, an autonomous AI agent. You have access to these tools:
|
|
6
|
+
- bash <command>: Execute shell commands
|
|
7
|
+
- read_file <path>: Read file contents
|
|
8
|
+
- write_file <path> <content>: Create/overwrite files
|
|
9
|
+
- edit_file <path> <old> <new>: Replace text in files
|
|
10
|
+
- delete_file <path>: Delete files
|
|
11
|
+
- list_dir <dir>: List directory contents
|
|
12
|
+
- search_files <pattern>: Find files by glob
|
|
13
|
+
- grep <pattern> <path>: Search file contents
|
|
14
|
+
- web_search <query>: Search the web
|
|
15
|
+
- web_fetch <url>: Fetch URL content
|
|
16
|
+
- git <subcommand>: Git operations
|
|
17
|
+
- memory <read/write/search/clear>: Persistent memory
|
|
18
|
+
- pkg_manager <npm/pip> <install> <pkg>: Install packages
|
|
19
|
+
- code_runner <lang> <code>: Execute code snippets
|
|
20
|
+
|
|
21
|
+
Respond with JSON:
|
|
22
|
+
{"tool": "tool_name", "args": {"arg1": "val1"}}
|
|
23
|
+
or to respond to user:
|
|
24
|
+
{"response": "your final answer here"}`;
|
|
25
|
+
|
|
26
|
+
export async function agentLoop(userMessage, config, history) {
|
|
27
|
+
const messages = [
|
|
28
|
+
{ role: 'system', content: buildSystemPrompt(TOOLS_DESC) },
|
|
29
|
+
...history.slice(-20).map(m => ({ role: m.role === 'assistant' ? 'assistant' : 'user', content: m.content })),
|
|
30
|
+
{ role: 'user', content: userMessage },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
let finalResponse = '';
|
|
34
|
+
let toolCalls = 0;
|
|
35
|
+
const maxToolCalls = 20;
|
|
36
|
+
|
|
37
|
+
while (toolCalls < maxToolCalls) {
|
|
38
|
+
let fullResponse = '';
|
|
39
|
+
const stream = callProvider(config, messages);
|
|
40
|
+
for await (const chunk of stream) {
|
|
41
|
+
fullResponse += chunk;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const jsonMatch = fullResponse.match(/\{[^]*\}/);
|
|
46
|
+
if (!jsonMatch) {
|
|
47
|
+
finalResponse = fullResponse;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
51
|
+
|
|
52
|
+
if (parsed.response) {
|
|
53
|
+
finalResponse = parsed.response;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (parsed.tool) {
|
|
58
|
+
toolCalls++;
|
|
59
|
+
const toolName = parsed.tool;
|
|
60
|
+
const args = parsed.args || {};
|
|
61
|
+
|
|
62
|
+
let result;
|
|
63
|
+
try {
|
|
64
|
+
const mod = await import(`../tools/${toolName}.js`);
|
|
65
|
+
const toolFn = Object.values(mod)[0];
|
|
66
|
+
result = await toolFn(args);
|
|
67
|
+
} catch (e) {
|
|
68
|
+
result = `Tool error: ${e.message}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
messages.push({ role: 'assistant', content: fullResponse });
|
|
72
|
+
messages.push({ role: 'user', content: `Tool result: ${result}` });
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
finalResponse = fullResponse;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (toolCalls >= maxToolCalls) {
|
|
81
|
+
finalResponse += '\n\n(Max tool calls reached)';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return finalResponse || 'No response generated';
|
|
85
|
+
}
|
|
@@ -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
|
}
|
package/src/commands/config.js
CHANGED
|
@@ -1,146 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export async function keys(args, context) {
|
|
10
|
-
const sub = args[0];
|
|
11
|
-
if (!sub) { warn('Usage', '/keys set|list|remove|test <provider> [key]'); return; }
|
|
12
|
-
|
|
13
|
-
switch (sub) {
|
|
14
|
-
case 'set': {
|
|
15
|
-
if (args.length < 3) { warn('Usage', '/keys set <provider> <key>'); return; }
|
|
16
|
-
try {
|
|
17
|
-
setKey(args[1], args.slice(2).join(' '));
|
|
18
|
-
success('Key Saved', `${PROVIDER_NAMES[args[1]] || args[1]} API key configured.`);
|
|
19
|
-
} catch (err) { errorBox('Error', err.message); }
|
|
20
|
-
break;
|
|
21
|
-
}
|
|
22
|
-
case 'list': {
|
|
23
|
-
const k = listKeys();
|
|
24
|
-
if (Object.keys(k).length === 0) { info('No Keys', 'No API keys configured. Run /init to set up.'); return; }
|
|
25
|
-
const rows = Object.entries(k).map(([p, info]) => [PROVIDER_NAMES[p] || p, info.keyPreview]);
|
|
26
|
-
table(['Provider', 'Key'], rows);
|
|
27
|
-
break;
|
|
28
|
-
}
|
|
29
|
-
case 'remove': {
|
|
30
|
-
if (args.length < 2) { warn('Usage', '/keys remove <provider>'); return; }
|
|
31
|
-
removeKey(args[1]);
|
|
32
|
-
success('Removed', `${PROVIDER_NAMES[args[1]] || args[1]} key removed.`);
|
|
33
|
-
break;
|
|
34
|
-
}
|
|
35
|
-
case 'test': {
|
|
36
|
-
if (args.length < 2) { warn('Usage', '/keys test <provider>'); return; }
|
|
37
|
-
const result = await testKey(args[1]);
|
|
38
|
-
if (result.success) success('Key Valid', `${PROVIDER_NAMES[args[1]] || args[1]} key is working!`);
|
|
39
|
-
else errorBox('Key Invalid', `${PROVIDER_NAMES[args[1]] || args[1]}: ${result.error}`);
|
|
40
|
-
break;
|
|
41
|
-
}
|
|
42
|
-
default:
|
|
43
|
-
warn('Usage', '/keys set|list|remove|test');
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export async function key(args, context) {
|
|
48
|
-
if (args.length < 2) { warn('Usage', '/key <provider> <key>'); return; }
|
|
49
|
-
try {
|
|
50
|
-
setKey(args[0], args.slice(1).join(' '));
|
|
51
|
-
success('Key Saved', `${PROVIDER_NAMES[args[0]] || args[0]} API key configured.`);
|
|
52
|
-
} catch (err) { errorBox('Error', err.message); }
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export async function config(args, context) {
|
|
56
|
-
const sub = args[0];
|
|
57
|
-
if (!sub) { warn('Usage', '/config show|reset [key] [value]'); return; }
|
|
58
|
-
|
|
59
|
-
switch (sub) {
|
|
60
|
-
case 'show': {
|
|
61
|
-
const all = settings.store;
|
|
62
|
-
const rows = Object.entries(all).map(([k, v]) => [k, String(v)]);
|
|
63
|
-
table(['Setting', 'Value'], rows);
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
case 'reset': {
|
|
67
|
-
settings.clear();
|
|
68
|
-
success('Reset', 'Settings reset to defaults.');
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
-
default:
|
|
72
|
-
warn('Usage', '/config show|reset');
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export async function set(args, context) {
|
|
77
|
-
if (args.length < 2) { warn('Usage', '/set <key> <value>'); return; }
|
|
78
|
-
const val = args.slice(1).join(' ');
|
|
79
|
-
const num = parseFloat(val);
|
|
80
|
-
settings.set(args[0], isNaN(num) ? val : num);
|
|
81
|
-
success('Set', `${args[0]} = ${settings.get(args[0])}`);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export async function reset(args, context) {
|
|
85
|
-
settings.clear();
|
|
86
|
-
success('Reset', 'All settings reset to defaults.');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export async function init(args, context) {
|
|
90
|
-
const { default: inquirer } = await import('inquirer');
|
|
91
|
-
divider('CLARITY Setup');
|
|
92
|
-
info('Welcome', "Let's configure your AI agent CLI\nPress Ctrl+C to cancel at any time.");
|
|
93
|
-
|
|
94
|
-
const { providers: selected } = await inquirer.prompt([{
|
|
95
|
-
type: 'checkbox',
|
|
96
|
-
name: 'providers',
|
|
97
|
-
message: 'Which AI providers do you want to configure?',
|
|
98
|
-
choices: [
|
|
99
|
-
{ name: 'Groq (FREE \u2014 Recommended)', value: 'groq', checked: true },
|
|
100
|
-
{ name: 'Google Gemini (FREE)', value: 'gemini', checked: true },
|
|
101
|
-
{ name: 'OpenRouter (FREE models)', value: 'openrouter' },
|
|
102
|
-
{ name: 'Anthropic Claude', value: 'anthropic' },
|
|
103
|
-
{ name: 'OpenAI GPT', value: 'openai' },
|
|
104
|
-
{ name: 'DeepSeek', value: 'deepseek' },
|
|
105
|
-
]
|
|
106
|
-
}]);
|
|
107
|
-
|
|
108
|
-
for (const p of selected) {
|
|
109
|
-
const { key: apiKey } = await inquirer.prompt([{
|
|
110
|
-
type: 'password',
|
|
111
|
-
name: 'key',
|
|
112
|
-
message: `Enter your ${PROVIDER_NAMES[p] || p} API key:`,
|
|
113
|
-
validate: v => v.length > 0 || 'Key is required',
|
|
114
|
-
}]);
|
|
115
|
-
try {
|
|
116
|
-
setKey(p, apiKey);
|
|
117
|
-
success(p, `Key saved for ${PROVIDER_NAMES[p] || p}`);
|
|
118
|
-
} catch (err) {
|
|
119
|
-
errorBox(p, err.message);
|
|
1
|
+
import { loadConfig, saveConfig } from '../config/settings.js';
|
|
2
|
+
import { clr } from '../ui/colors.js';
|
|
3
|
+
export async function configCommand(args, config) {
|
|
4
|
+
if (args.length === 0) {
|
|
5
|
+
const cfg = loadConfig();
|
|
6
|
+
for (const [key, val] of Object.entries(cfg || config)) {
|
|
7
|
+
if (key === 'apiKeys') continue;
|
|
8
|
+
console.log(' ' + clr.secondary(key) + ': ' + clr.dim(JSON.stringify(val)));
|
|
120
9
|
}
|
|
10
|
+
return;
|
|
121
11
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const { model: defaultModel } = await inquirer.prompt([{
|
|
128
|
-
type: 'list',
|
|
129
|
-
name: 'model',
|
|
130
|
-
message: 'Select your default model:',
|
|
131
|
-
choices: models.map(m => ({ name: `${defaultProvider}/${m}`, value: `${defaultProvider}/${m}` })),
|
|
132
|
-
}]);
|
|
133
|
-
settings.set('defaultModel', defaultModel);
|
|
134
|
-
|
|
135
|
-
const { stream, showTokens, saveHist } = await inquirer.prompt([
|
|
136
|
-
{ type: 'confirm', name: 'stream', message: 'Stream responses?', default: true },
|
|
137
|
-
{ type: 'confirm', name: 'showTokens', message: 'Show token usage?', default: true },
|
|
138
|
-
{ type: 'confirm', name: 'saveHist', message: 'Save chat history?', default: true },
|
|
139
|
-
]);
|
|
140
|
-
settings.set('stream', stream);
|
|
141
|
-
settings.set('showTokens', showTokens);
|
|
142
|
-
settings.set('saveHistory', saveHist);
|
|
12
|
+
if (args.length === 2) {
|
|
13
|
+
config[args[0]] = args[1];
|
|
14
|
+
saveConfig(config);
|
|
15
|
+
console.log(clr.success(args[0] + ' = ' + args[1]));
|
|
16
|
+
return config;
|
|
143
17
|
}
|
|
144
|
-
|
|
145
|
-
success('Setup Complete', 'CLARITY configured successfully!\nRun \'clarity\' to start chatting');
|
|
18
|
+
console.log(' ' + clr.dim('Usage: /config [key] [value]'));
|
|
146
19
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { clr } from '../ui/colors.js';
|
|
2
|
+
export async function diffCommand(args, config) {
|
|
3
|
+
const [f1, f2] = args;
|
|
4
|
+
if (!f1 || !f2) return console.log(clr.error('Usage: /diff <file1> <file2>'));
|
|
5
|
+
try {
|
|
6
|
+
const { diffTool } = await import('../tools/diff-tool.js');
|
|
7
|
+
const result = await diffTool({ file1: f1, file2: f2 });
|
|
8
|
+
console.log('\n' + result + '\n');
|
|
9
|
+
} catch (err) {
|
|
10
|
+
console.log(clr.error(`Diff error: ${err.message}`));
|
|
11
|
+
}
|
|
12
|
+
}
|