uwonbot 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -0
- package/bin/uwonbot.js +91 -0
- package/package.json +35 -0
- package/src/assistants.js +97 -0
- package/src/auth.js +92 -0
- package/src/banner.js +29 -0
- package/src/brain.js +231 -0
- package/src/chat.js +127 -0
- package/src/config.js +21 -0
- package/src/firebase-client.js +53 -0
- package/src/index.js +6 -0
- package/src/tools.js +420 -0
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Uwonbot CLI
|
|
2
|
+
|
|
3
|
+
> Your AI controls your computer — AI 어시스턴트가 컴퓨터를 제어합니다
|
|
4
|
+
|
|
5
|
+
Uwonbot은 터미널에서 AI 어시스턴트와 대화하고, 업무를 자동화할 수 있는 CLI 도구입니다.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g uwonbot
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# 로그인
|
|
17
|
+
uwonbot login
|
|
18
|
+
|
|
19
|
+
# AI 어시스턴트와 대화
|
|
20
|
+
uwonbot chat
|
|
21
|
+
|
|
22
|
+
# 특정 어시스턴트와 대화
|
|
23
|
+
uwonbot chat "assistant-name"
|
|
24
|
+
|
|
25
|
+
# AI에게 작업 요청
|
|
26
|
+
uwonbot run "브라우저에서 검색해줘"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Commands
|
|
30
|
+
|
|
31
|
+
| Command | Description |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `uwonbot login` | Uwonbot 계정으로 로그인 |
|
|
34
|
+
| `uwonbot logout` | 로그아웃 |
|
|
35
|
+
| `uwonbot whoami` | 현재 로그인된 사용자 확인 |
|
|
36
|
+
| `uwonbot assistants` | AI 어시스턴트 목록 보기 |
|
|
37
|
+
| `uwonbot chat` | AI 어시스턴트와 대화 시작 |
|
|
38
|
+
| `uwonbot run "..."` | AI에게 작업 실행 요청 |
|
|
39
|
+
|
|
40
|
+
## Features
|
|
41
|
+
|
|
42
|
+
- AI 어시스턴트와 실시간 대화
|
|
43
|
+
- 컴퓨터 제어 자동화 (브라우저, 파일 등)
|
|
44
|
+
- 200+ 서비스 연동 (Excel, Google Sheets, Slack 등)
|
|
45
|
+
- 코드 없이 업무 자동화
|
|
46
|
+
|
|
47
|
+
## License
|
|
48
|
+
|
|
49
|
+
MIT
|
package/bin/uwonbot.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { program } from 'commander';
|
|
3
|
+
import { showBanner } from '../src/banner.js';
|
|
4
|
+
import { loginCommand, logoutCommand, whoamiCommand } from '../src/auth.js';
|
|
5
|
+
import { listAssistants, selectAssistant } from '../src/assistants.js';
|
|
6
|
+
import { startChat } from '../src/chat.js';
|
|
7
|
+
import { getConfig } from '../src/config.js';
|
|
8
|
+
|
|
9
|
+
showBanner();
|
|
10
|
+
|
|
11
|
+
program
|
|
12
|
+
.name('uwonbot')
|
|
13
|
+
.description('Uwonbot AI Assistant — Your AI controls your computer')
|
|
14
|
+
.version('1.0.0');
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.command('login')
|
|
18
|
+
.description('Log in to your Uwonbot account')
|
|
19
|
+
.action(loginCommand);
|
|
20
|
+
|
|
21
|
+
program
|
|
22
|
+
.command('logout')
|
|
23
|
+
.description('Log out of your Uwonbot account')
|
|
24
|
+
.action(logoutCommand);
|
|
25
|
+
|
|
26
|
+
program
|
|
27
|
+
.command('whoami')
|
|
28
|
+
.description('Show current logged-in user')
|
|
29
|
+
.action(whoamiCommand);
|
|
30
|
+
|
|
31
|
+
program
|
|
32
|
+
.command('assistants')
|
|
33
|
+
.description('List your AI assistants')
|
|
34
|
+
.action(listAssistants);
|
|
35
|
+
|
|
36
|
+
program
|
|
37
|
+
.command('chat [assistantName]')
|
|
38
|
+
.description('Start chatting with an AI assistant')
|
|
39
|
+
.action(async (assistantName) => {
|
|
40
|
+
const config = getConfig();
|
|
41
|
+
if (!config.get('uid')) {
|
|
42
|
+
console.log('\n ⚠️ Please log in first: uwonbot login\n');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
if (assistantName) {
|
|
46
|
+
await startChat(assistantName);
|
|
47
|
+
} else {
|
|
48
|
+
const assistant = await selectAssistant();
|
|
49
|
+
if (assistant) {
|
|
50
|
+
await startChat(null, assistant);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
program
|
|
56
|
+
.command('run <command>')
|
|
57
|
+
.description('Ask your assistant to run a task')
|
|
58
|
+
.action(async (command) => {
|
|
59
|
+
const config = getConfig();
|
|
60
|
+
if (!config.get('uid')) {
|
|
61
|
+
console.log('\n ⚠️ Please log in first: uwonbot login\n');
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
const assistant = await selectAssistant();
|
|
65
|
+
if (assistant) {
|
|
66
|
+
await startChat(null, assistant, command);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (process.argv.length <= 2) {
|
|
71
|
+
const config = getConfig();
|
|
72
|
+
const uid = config.get('uid');
|
|
73
|
+
if (uid) {
|
|
74
|
+
const email = config.get('email') || 'unknown';
|
|
75
|
+
console.log(` Logged in as: ${email}`);
|
|
76
|
+
console.log('');
|
|
77
|
+
console.log(' Commands:');
|
|
78
|
+
console.log(' uwonbot chat Start chatting with your AI assistant');
|
|
79
|
+
console.log(' uwonbot assistants List your AI assistants');
|
|
80
|
+
console.log(' uwonbot run "..." Ask AI to run a task');
|
|
81
|
+
console.log(' uwonbot logout Log out');
|
|
82
|
+
console.log('');
|
|
83
|
+
} else {
|
|
84
|
+
console.log(' Get started:');
|
|
85
|
+
console.log(' uwonbot login Log in to your account');
|
|
86
|
+
console.log(' uwonbot chat Start chatting after login');
|
|
87
|
+
console.log('');
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
program.parse();
|
|
91
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "uwonbot",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Uwonbot AI Assistant CLI — Your AI controls your computer",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"uwonbot": "./bin/uwonbot.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/",
|
|
12
|
+
"src/"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"start": "node bin/uwonbot.js",
|
|
16
|
+
"dev": "node bin/uwonbot.js"
|
|
17
|
+
},
|
|
18
|
+
"keywords": ["ai", "assistant", "automation", "cli", "computer-use"],
|
|
19
|
+
"author": "Uwonbot",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/uwonbot/uwonbot-cli"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"chalk": "^5.3.0",
|
|
27
|
+
"commander": "^12.1.0",
|
|
28
|
+
"conf": "^13.0.1",
|
|
29
|
+
"firebase": "^11.0.0",
|
|
30
|
+
"inquirer": "^12.0.0",
|
|
31
|
+
"node-fetch": "^3.3.2",
|
|
32
|
+
"open": "^10.1.0",
|
|
33
|
+
"ora": "^8.1.0"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { getConfig } from './config.js';
|
|
5
|
+
import { fetchAssistants } from './firebase-client.js';
|
|
6
|
+
|
|
7
|
+
export async function listAssistants() {
|
|
8
|
+
const config = getConfig();
|
|
9
|
+
const uid = config.get('uid');
|
|
10
|
+
if (!uid) {
|
|
11
|
+
console.log(chalk.yellow('\n ⚠️ Please log in first: uwonbot login\n'));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const spinner = ora('Fetching assistants...').start();
|
|
16
|
+
try {
|
|
17
|
+
const assistants = await fetchAssistants(uid);
|
|
18
|
+
spinner.stop();
|
|
19
|
+
|
|
20
|
+
if (assistants.length === 0) {
|
|
21
|
+
console.log('');
|
|
22
|
+
console.log(chalk.yellow(' No assistants found.'));
|
|
23
|
+
console.log(chalk.gray(' Create one at https://chartapp-653e1.web.app/assistant/create'));
|
|
24
|
+
console.log('');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log('');
|
|
29
|
+
console.log(chalk.white.bold(' Your AI Assistants'));
|
|
30
|
+
console.log(chalk.gray(' ──────────────────'));
|
|
31
|
+
assistants.forEach((a, i) => {
|
|
32
|
+
const brainColor = a.brain === 'claude' ? '#f97316' : a.brain === 'openai' ? '#10b981' : a.brain === 'gemini' ? '#8b5cf6' : '#2563eb';
|
|
33
|
+
const brainLabel = a.brain === 'default' ? 'Uwonbot' : a.brain.charAt(0).toUpperCase() + a.brain.slice(1);
|
|
34
|
+
console.log(` ${chalk.gray(`${i + 1}.`)} ${a.avatar || '🤖'} ${chalk.white.bold(a.name)} ${chalk.hex(brainColor)(`[${brainLabel}]`)}`);
|
|
35
|
+
if (a.description) {
|
|
36
|
+
console.log(` ${chalk.gray(a.description)}`);
|
|
37
|
+
}
|
|
38
|
+
const scopes = (a.scope || []).join(', ');
|
|
39
|
+
if (scopes) {
|
|
40
|
+
console.log(` ${chalk.gray('Scope:')} ${scopes}`);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
console.log('');
|
|
44
|
+
console.log(chalk.gray(' Use: uwonbot chat — to start chatting with an assistant'));
|
|
45
|
+
console.log('');
|
|
46
|
+
} catch (err) {
|
|
47
|
+
spinner.fail(chalk.red('Failed to fetch assistants'));
|
|
48
|
+
console.log(chalk.red(` ${err.message}`));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function selectAssistant() {
|
|
53
|
+
const config = getConfig();
|
|
54
|
+
const uid = config.get('uid');
|
|
55
|
+
if (!uid) {
|
|
56
|
+
console.log(chalk.yellow('\n ⚠️ Please log in first: uwonbot login\n'));
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const spinner = ora('Loading assistants...').start();
|
|
61
|
+
try {
|
|
62
|
+
const assistants = await fetchAssistants(uid);
|
|
63
|
+
spinner.stop();
|
|
64
|
+
|
|
65
|
+
const choices = [
|
|
66
|
+
{ name: `🌐 Uwonbot Guide (Platform usage help)`, value: { id: '__uwonbot__', name: 'Uwonbot Guide', brain: 'default' } },
|
|
67
|
+
...assistants.map(a => ({
|
|
68
|
+
name: `${a.avatar || '🤖'} ${a.name} [${a.brain === 'default' ? 'Uwonbot' : a.brain}]`,
|
|
69
|
+
value: a,
|
|
70
|
+
})),
|
|
71
|
+
new inquirer.Separator(),
|
|
72
|
+
{ name: '+ Create new assistant (opens browser)', value: '__create__' },
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const { selected } = await inquirer.prompt([{
|
|
76
|
+
type: 'list',
|
|
77
|
+
name: 'selected',
|
|
78
|
+
message: 'Choose an assistant:',
|
|
79
|
+
choices,
|
|
80
|
+
}]);
|
|
81
|
+
|
|
82
|
+
if (selected === '__create__') {
|
|
83
|
+
const open = (await import('open')).default;
|
|
84
|
+
await open('https://chartapp-653e1.web.app/assistant/create');
|
|
85
|
+
console.log(chalk.gray(' Opening browser to create a new assistant...'));
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
config.set('activeAssistantId', selected.id);
|
|
90
|
+
config.set('activeAssistantName', selected.name);
|
|
91
|
+
return selected;
|
|
92
|
+
} catch (err) {
|
|
93
|
+
spinner.fail(chalk.red('Failed to load assistants'));
|
|
94
|
+
console.log(chalk.red(` ${err.message}`));
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
package/src/auth.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { getConfig } from './config.js';
|
|
5
|
+
import { loginWithEmail } from './firebase-client.js';
|
|
6
|
+
|
|
7
|
+
export async function loginCommand() {
|
|
8
|
+
const config = getConfig();
|
|
9
|
+
const existing = config.get('uid');
|
|
10
|
+
if (existing) {
|
|
11
|
+
console.log(chalk.yellow(` Already logged in as ${config.get('email')}`));
|
|
12
|
+
const { confirm } = await inquirer.prompt([{
|
|
13
|
+
type: 'confirm',
|
|
14
|
+
name: 'confirm',
|
|
15
|
+
message: 'Log out and log in with a different account?',
|
|
16
|
+
default: false,
|
|
17
|
+
}]);
|
|
18
|
+
if (!confirm) return;
|
|
19
|
+
config.clear();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const answers = await inquirer.prompt([
|
|
23
|
+
{
|
|
24
|
+
type: 'input',
|
|
25
|
+
name: 'email',
|
|
26
|
+
message: 'Email:',
|
|
27
|
+
validate: v => v.includes('@') || 'Please enter a valid email',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
type: 'password',
|
|
31
|
+
name: 'password',
|
|
32
|
+
message: 'Password:',
|
|
33
|
+
mask: '*',
|
|
34
|
+
validate: v => v.length >= 6 || 'Password must be at least 6 characters',
|
|
35
|
+
},
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
const spinner = ora('Logging in...').start();
|
|
39
|
+
try {
|
|
40
|
+
const user = await loginWithEmail(answers.email, answers.password);
|
|
41
|
+
config.set('uid', user.uid);
|
|
42
|
+
config.set('email', user.email);
|
|
43
|
+
config.set('displayName', user.displayName);
|
|
44
|
+
config.set('idToken', user.idToken);
|
|
45
|
+
config.set('refreshToken', user.refreshToken);
|
|
46
|
+
spinner.succeed(chalk.green(`Logged in as ${user.email}`));
|
|
47
|
+
console.log('');
|
|
48
|
+
console.log(chalk.gray(' Next: uwonbot chat — Start chatting with your AI assistant'));
|
|
49
|
+
console.log(chalk.gray(' uwonbot assistants — List your assistants'));
|
|
50
|
+
console.log('');
|
|
51
|
+
} catch (err) {
|
|
52
|
+
spinner.fail(chalk.red('Login failed'));
|
|
53
|
+
if (err.code === 'auth/user-not-found') {
|
|
54
|
+
console.log(chalk.yellow(' No account found. Sign up at https://chartapp-653e1.web.app/auth'));
|
|
55
|
+
} else if (err.code === 'auth/wrong-password' || err.code === 'auth/invalid-credential') {
|
|
56
|
+
console.log(chalk.yellow(' Invalid password. Try again.'));
|
|
57
|
+
} else {
|
|
58
|
+
console.log(chalk.red(` ${err.message}`));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function logoutCommand() {
|
|
64
|
+
const config = getConfig();
|
|
65
|
+
const email = config.get('email');
|
|
66
|
+
config.clear();
|
|
67
|
+
if (email) {
|
|
68
|
+
console.log(chalk.green(` Logged out from ${email}`));
|
|
69
|
+
} else {
|
|
70
|
+
console.log(chalk.gray(' Not logged in'));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function whoamiCommand() {
|
|
75
|
+
const config = getConfig();
|
|
76
|
+
const uid = config.get('uid');
|
|
77
|
+
if (!uid) {
|
|
78
|
+
console.log(chalk.gray(' Not logged in. Run: uwonbot login'));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
console.log('');
|
|
82
|
+
console.log(chalk.white.bold(' Account Info'));
|
|
83
|
+
console.log(chalk.gray(' ─────────────'));
|
|
84
|
+
console.log(` Email: ${chalk.cyan(config.get('email'))}`);
|
|
85
|
+
console.log(` Name: ${config.get('displayName') || chalk.gray('(not set)')}`);
|
|
86
|
+
console.log(` UID: ${chalk.gray(uid)}`);
|
|
87
|
+
const activeAssistant = config.get('activeAssistantName');
|
|
88
|
+
if (activeAssistant) {
|
|
89
|
+
console.log(` Active Assistant: ${chalk.hex('#2563eb')(activeAssistant)}`);
|
|
90
|
+
}
|
|
91
|
+
console.log('');
|
|
92
|
+
}
|
package/src/banner.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
export function showBanner() {
|
|
4
|
+
const blue = chalk.hex('#2563eb');
|
|
5
|
+
const cyan = chalk.hex('#06b6d4');
|
|
6
|
+
const gray = chalk.hex('#6b7280');
|
|
7
|
+
const white = chalk.white.bold;
|
|
8
|
+
|
|
9
|
+
console.log('');
|
|
10
|
+
console.log(blue(' ╔══════════════════════════════════════════════════════════════╗'));
|
|
11
|
+
console.log(blue(' ║ ║'));
|
|
12
|
+
console.log(blue(' ║') + cyan(' ██╗ ██╗██╗ ██╗ ██████╗ ███╗ ██╗██████╗ ██████╗ ████████╗') + blue(' ║'));
|
|
13
|
+
console.log(blue(' ║') + cyan(' ██║ ██║██║ ██║██╔═══██╗████╗ ██║██╔══██╗██╔═══██╗╚══██╔══╝') + blue(' ║'));
|
|
14
|
+
console.log(blue(' ║') + white(' ██║ ██║██║ █╗ ██║██║ ██║██╔██╗ ██║██████╔╝██║ ██║ ██║ ') + blue(' ║'));
|
|
15
|
+
console.log(blue(' ║') + white(' ██║ ██║██║███╗██║██║ ██║██║╚██╗██║██╔══██╗██║ ██║ ██║ ') + blue(' ║'));
|
|
16
|
+
console.log(blue(' ║') + cyan(' ╚██████╔╝╚███╔███╔╝╚██████╔╝██║ ╚████║██████╔╝╚██████╔╝ ██║ ') + blue(' ║'));
|
|
17
|
+
console.log(blue(' ║') + cyan(' ╚═════╝ ╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═══╝╚═════╝ ╚═════╝ ╚═╝ ') + blue(' ║'));
|
|
18
|
+
console.log(blue(' ║ ║'));
|
|
19
|
+
console.log(blue(' ║') + gray(' AI Assistant — Your AI controls your computer ') + blue(' ║'));
|
|
20
|
+
console.log(blue(' ║') + gray(' v1.0.0 https://uwonbot.com ') + blue(' ║'));
|
|
21
|
+
console.log(blue(' ║ ║'));
|
|
22
|
+
console.log(blue(' ╚══════════════════════════════════════════════════════════════╝'));
|
|
23
|
+
console.log('');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function showMiniBar() {
|
|
27
|
+
const blue = chalk.hex('#2563eb');
|
|
28
|
+
console.log(blue(' ─── UWONBOT ───'));
|
|
29
|
+
}
|
package/src/brain.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { executeTool, getToolsForProvider } from './tools.js';
|
|
4
|
+
|
|
5
|
+
const GEMINI_BASE = 'https://generativelanguage.googleapis.com/v1beta/models';
|
|
6
|
+
|
|
7
|
+
function buildSystemPrompt(assistant) {
|
|
8
|
+
const base = assistant.systemPrompt || `You are ${assistant.name || 'Uwonbot'}, an AI assistant.`;
|
|
9
|
+
return `${base}
|
|
10
|
+
|
|
11
|
+
You have full access to the user's computer through tools. You can:
|
|
12
|
+
- Read, write, create, delete, and move files
|
|
13
|
+
- Execute shell commands (install packages, run scripts, etc.)
|
|
14
|
+
- List directories and search files
|
|
15
|
+
- Open URLs and applications
|
|
16
|
+
- Access the clipboard
|
|
17
|
+
- Get system information
|
|
18
|
+
|
|
19
|
+
IMPORTANT RULES:
|
|
20
|
+
1. Always confirm before performing destructive operations (deleting files, overwriting, etc.)
|
|
21
|
+
2. Explain what you're about to do before using tools
|
|
22
|
+
3. If a command fails, explain why and suggest alternatives
|
|
23
|
+
4. Be concise but informative in your responses
|
|
24
|
+
5. Use tools proactively to help the user accomplish their tasks`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function callGemini(apiKey, model, messages, systemPrompt) {
|
|
28
|
+
const modelName = model || 'gemini-2.0-flash';
|
|
29
|
+
const tools = getToolsForProvider('gemini');
|
|
30
|
+
|
|
31
|
+
const contents = messages.map(m => ({
|
|
32
|
+
role: m.role === 'assistant' ? 'model' : 'user',
|
|
33
|
+
parts: [{ text: m.content }],
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
const body = {
|
|
37
|
+
contents,
|
|
38
|
+
systemInstruction: { parts: [{ text: systemPrompt }] },
|
|
39
|
+
tools: [{ functionDeclarations: tools }],
|
|
40
|
+
generationConfig: { maxOutputTokens: 8192, temperature: 0.7 },
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const url = `${GEMINI_BASE}/${modelName}:generateContent?key=${encodeURIComponent(apiKey)}`;
|
|
44
|
+
const res = await fetch(url, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: { 'Content-Type': 'application/json' },
|
|
47
|
+
body: JSON.stringify(body),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (!res.ok) {
|
|
51
|
+
const errData = await res.json().catch(() => ({}));
|
|
52
|
+
throw new Error(`Gemini API error ${res.status}: ${errData?.error?.message || 'Unknown'}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const data = await res.json();
|
|
56
|
+
const candidate = data?.candidates?.[0];
|
|
57
|
+
if (!candidate) throw new Error('No response from Gemini');
|
|
58
|
+
|
|
59
|
+
const parts = candidate.content?.parts || [];
|
|
60
|
+
const textParts = parts.filter(p => p.text).map(p => p.text).join('');
|
|
61
|
+
const functionCalls = parts.filter(p => p.functionCall).map(p => p.functionCall);
|
|
62
|
+
|
|
63
|
+
return { text: textParts, functionCalls, provider: 'gemini' };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function callOpenAI(apiKey, model, messages, systemPrompt) {
|
|
67
|
+
const modelName = model || 'gpt-4o';
|
|
68
|
+
const tools = getToolsForProvider('openai');
|
|
69
|
+
|
|
70
|
+
const apiMessages = [
|
|
71
|
+
{ role: 'system', content: systemPrompt },
|
|
72
|
+
...messages,
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const body = {
|
|
76
|
+
model: modelName,
|
|
77
|
+
messages: apiMessages,
|
|
78
|
+
tools,
|
|
79
|
+
max_tokens: 4096,
|
|
80
|
+
temperature: 0.7,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const res = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
headers: {
|
|
86
|
+
'Content-Type': 'application/json',
|
|
87
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
88
|
+
},
|
|
89
|
+
body: JSON.stringify(body),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (!res.ok) {
|
|
93
|
+
const errData = await res.json().catch(() => ({}));
|
|
94
|
+
throw new Error(`OpenAI API error ${res.status}: ${errData?.error?.message || 'Unknown'}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const data = await res.json();
|
|
98
|
+
const choice = data.choices?.[0];
|
|
99
|
+
if (!choice) throw new Error('No response from OpenAI');
|
|
100
|
+
|
|
101
|
+
const msg = choice.message;
|
|
102
|
+
const toolCalls = (msg.tool_calls || []).map(tc => ({
|
|
103
|
+
id: tc.id,
|
|
104
|
+
name: tc.function.name,
|
|
105
|
+
args: JSON.parse(tc.function.arguments || '{}'),
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
return { text: msg.content || '', toolCalls, provider: 'openai', rawMessage: msg };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function callClaude(apiKey, model, messages, systemPrompt) {
|
|
112
|
+
const modelName = model || 'claude-sonnet-4-20250514';
|
|
113
|
+
const tools = getToolsForProvider('claude');
|
|
114
|
+
|
|
115
|
+
const apiMessages = messages.map(m => ({
|
|
116
|
+
role: m.role,
|
|
117
|
+
content: m.content,
|
|
118
|
+
}));
|
|
119
|
+
|
|
120
|
+
const body = {
|
|
121
|
+
model: modelName,
|
|
122
|
+
max_tokens: 4096,
|
|
123
|
+
system: systemPrompt,
|
|
124
|
+
messages: apiMessages,
|
|
125
|
+
tools,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const res = await fetch('https://api.anthropic.com/v1/messages', {
|
|
129
|
+
method: 'POST',
|
|
130
|
+
headers: {
|
|
131
|
+
'Content-Type': 'application/json',
|
|
132
|
+
'x-api-key': apiKey,
|
|
133
|
+
'anthropic-version': '2023-06-01',
|
|
134
|
+
},
|
|
135
|
+
body: JSON.stringify(body),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
if (!res.ok) {
|
|
139
|
+
const errData = await res.json().catch(() => ({}));
|
|
140
|
+
throw new Error(`Claude API error ${res.status}: ${errData?.error?.message || 'Unknown'}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const data = await res.json();
|
|
144
|
+
const textBlocks = (data.content || []).filter(b => b.type === 'text').map(b => b.text).join('');
|
|
145
|
+
const toolUseBlocks = (data.content || []).filter(b => b.type === 'tool_use').map(b => ({
|
|
146
|
+
id: b.id,
|
|
147
|
+
name: b.name,
|
|
148
|
+
args: b.input,
|
|
149
|
+
}));
|
|
150
|
+
|
|
151
|
+
return { text: textBlocks, toolCalls: toolUseBlocks, provider: 'claude', stopReason: data.stop_reason };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function handleToolCalls(result, apiKey, model, messages, systemPrompt, assistant, depth = 0) {
|
|
155
|
+
if (depth > 10) return result.text || '(Max tool depth reached)';
|
|
156
|
+
|
|
157
|
+
if (result.provider === 'gemini' && result.functionCalls?.length > 0) {
|
|
158
|
+
let allText = result.text || '';
|
|
159
|
+
for (const fc of result.functionCalls) {
|
|
160
|
+
console.log(chalk.hex('#8b5cf6')(` 🔧 Tool: ${fc.name}`));
|
|
161
|
+
const toolResult = await executeTool(fc.name, fc.args || {});
|
|
162
|
+
console.log(chalk.gray(` ✓ Done`));
|
|
163
|
+
|
|
164
|
+
messages.push({ role: 'assistant', content: allText || `Using tool: ${fc.name}` });
|
|
165
|
+
messages.push({ role: 'user', content: `Tool result for ${fc.name}: ${JSON.stringify(toolResult)}` });
|
|
166
|
+
|
|
167
|
+
const nextResult = await callGemini(apiKey, model, messages, systemPrompt);
|
|
168
|
+
return handleToolCalls(nextResult, apiKey, model, messages, systemPrompt, assistant, depth + 1);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (result.provider === 'openai' && result.toolCalls?.length > 0) {
|
|
173
|
+
messages.push(result.rawMessage);
|
|
174
|
+
for (const tc of result.toolCalls) {
|
|
175
|
+
console.log(chalk.hex('#10b981')(` 🔧 Tool: ${tc.name}`));
|
|
176
|
+
const toolResult = await executeTool(tc.name, tc.args);
|
|
177
|
+
console.log(chalk.gray(` ✓ Done`));
|
|
178
|
+
messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify(toolResult) });
|
|
179
|
+
}
|
|
180
|
+
const nextResult = await callOpenAI(apiKey, model, messages, systemPrompt);
|
|
181
|
+
return handleToolCalls(nextResult, apiKey, model, messages, systemPrompt, assistant, depth + 1);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (result.provider === 'claude' && result.toolCalls?.length > 0) {
|
|
185
|
+
const contentBlocks = [];
|
|
186
|
+
if (result.text) contentBlocks.push({ type: 'text', text: result.text });
|
|
187
|
+
for (const tc of result.toolCalls) {
|
|
188
|
+
contentBlocks.push({ type: 'tool_use', id: tc.id, name: tc.name, input: tc.args });
|
|
189
|
+
}
|
|
190
|
+
messages.push({ role: 'assistant', content: contentBlocks });
|
|
191
|
+
|
|
192
|
+
const toolResults = [];
|
|
193
|
+
for (const tc of result.toolCalls) {
|
|
194
|
+
console.log(chalk.hex('#f97316')(` 🔧 Tool: ${tc.name}`));
|
|
195
|
+
const toolResult = await executeTool(tc.name, tc.args);
|
|
196
|
+
console.log(chalk.gray(` ✓ Done`));
|
|
197
|
+
toolResults.push({ type: 'tool_result', tool_use_id: tc.id, content: JSON.stringify(toolResult) });
|
|
198
|
+
}
|
|
199
|
+
messages.push({ role: 'user', content: toolResults });
|
|
200
|
+
|
|
201
|
+
const nextResult = await callClaude(apiKey, model, messages, systemPrompt);
|
|
202
|
+
return handleToolCalls(nextResult, apiKey, model, messages, systemPrompt, assistant, depth + 1);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return result.text || '(No response)';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function sendToBrain(assistant, messages, userMessage) {
|
|
209
|
+
const brain = assistant.brain || 'default';
|
|
210
|
+
const apiKey = assistant.apiKey || '';
|
|
211
|
+
const model = assistant.model || '';
|
|
212
|
+
const systemPrompt = buildSystemPrompt(assistant);
|
|
213
|
+
|
|
214
|
+
messages.push({ role: 'user', content: userMessage });
|
|
215
|
+
|
|
216
|
+
let result;
|
|
217
|
+
if (brain === 'openai') {
|
|
218
|
+
result = await callOpenAI(apiKey, model, messages, systemPrompt);
|
|
219
|
+
} else if (brain === 'claude') {
|
|
220
|
+
result = await callClaude(apiKey, model, messages, systemPrompt);
|
|
221
|
+
} else {
|
|
222
|
+
const key = apiKey || process.env.GEMINI_API_KEY || '';
|
|
223
|
+
if (!key) throw new Error('No API key. Set one in your assistant settings or GEMINI_API_KEY env var.');
|
|
224
|
+
result = await callGemini(key, model, messages, systemPrompt);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const finalText = await handleToolCalls(result, apiKey, model, messages, systemPrompt, assistant);
|
|
228
|
+
|
|
229
|
+
messages.push({ role: 'assistant', content: finalText });
|
|
230
|
+
return finalText;
|
|
231
|
+
}
|
package/src/chat.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import readline from 'readline';
|
|
5
|
+
import { getConfig } from './config.js';
|
|
6
|
+
import { sendToBrain } from './brain.js';
|
|
7
|
+
import { showMiniBar } from './banner.js';
|
|
8
|
+
|
|
9
|
+
export async function startChat(assistantName, assistant, initialCommand) {
|
|
10
|
+
const config = getConfig();
|
|
11
|
+
const uid = config.get('uid');
|
|
12
|
+
if (!uid) {
|
|
13
|
+
console.log(chalk.yellow('\n ⚠️ Please log in first: uwonbot login\n'));
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!assistant) {
|
|
18
|
+
console.log(chalk.yellow('\n No assistant selected.\n'));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const brainLabel = assistant.brain === 'default' ? 'Uwonbot (Gemini)'
|
|
23
|
+
: assistant.brain === 'openai' ? 'OpenAI GPT'
|
|
24
|
+
: assistant.brain === 'claude' ? 'Anthropic Claude'
|
|
25
|
+
: assistant.brain === 'gemini' ? 'Google Gemini'
|
|
26
|
+
: assistant.brain;
|
|
27
|
+
|
|
28
|
+
const brainColor = assistant.brain === 'claude' ? '#f97316'
|
|
29
|
+
: assistant.brain === 'openai' ? '#10b981'
|
|
30
|
+
: assistant.brain === 'gemini' ? '#8b5cf6'
|
|
31
|
+
: '#2563eb';
|
|
32
|
+
|
|
33
|
+
console.log('');
|
|
34
|
+
console.log(chalk.hex('#2563eb')(' ╔══════════════════════════════════════════╗'));
|
|
35
|
+
console.log(chalk.hex('#2563eb')(' ║') + chalk.white.bold(` ${assistant.avatar || '🤖'} ${assistant.name}`) + ' '.repeat(Math.max(1, 38 - assistant.name.length)) + chalk.hex('#2563eb')('║'));
|
|
36
|
+
console.log(chalk.hex('#2563eb')(' ║') + chalk.gray(` Brain: `) + chalk.hex(brainColor)(brainLabel) + ' '.repeat(Math.max(1, 30 - brainLabel.length)) + chalk.hex('#2563eb')('║'));
|
|
37
|
+
console.log(chalk.hex('#2563eb')(' ║') + chalk.gray(' Type "exit" to quit, "clear" to reset ') + chalk.hex('#2563eb')('║'));
|
|
38
|
+
console.log(chalk.hex('#2563eb')(' ╚══════════════════════════════════════════╝'));
|
|
39
|
+
console.log('');
|
|
40
|
+
|
|
41
|
+
const messages = [];
|
|
42
|
+
|
|
43
|
+
if (initialCommand) {
|
|
44
|
+
await processMessage(initialCommand, messages, assistant, brainColor);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const rl = readline.createInterface({
|
|
48
|
+
input: process.stdin,
|
|
49
|
+
output: process.stdout,
|
|
50
|
+
prompt: chalk.hex('#2563eb')(' You > '),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
rl.prompt();
|
|
54
|
+
|
|
55
|
+
rl.on('line', async (line) => {
|
|
56
|
+
const input = line.trim();
|
|
57
|
+
if (!input) {
|
|
58
|
+
rl.prompt();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (input.toLowerCase() === 'exit' || input.toLowerCase() === 'quit') {
|
|
63
|
+
console.log(chalk.gray('\n Goodbye! 👋\n'));
|
|
64
|
+
rl.close();
|
|
65
|
+
process.exit(0);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (input.toLowerCase() === 'clear') {
|
|
69
|
+
messages.length = 0;
|
|
70
|
+
console.log(chalk.gray(' Chat cleared.\n'));
|
|
71
|
+
rl.prompt();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (input.toLowerCase() === 'tools') {
|
|
76
|
+
console.log('');
|
|
77
|
+
console.log(chalk.white.bold(' Available Tools:'));
|
|
78
|
+
console.log(chalk.gray(' ────────────────'));
|
|
79
|
+
console.log(' 📂 read_file, write_file, list_directory, create_directory');
|
|
80
|
+
console.log(' 🗑️ delete_file, move_file, search_files');
|
|
81
|
+
console.log(' ⚙️ run_shell, install_package');
|
|
82
|
+
console.log(' 🌐 open_url, open_application');
|
|
83
|
+
console.log(' 📋 get_clipboard, set_clipboard');
|
|
84
|
+
console.log(' 💻 system_info');
|
|
85
|
+
console.log('');
|
|
86
|
+
rl.prompt();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
rl.pause();
|
|
91
|
+
await processMessage(input, messages, assistant, brainColor);
|
|
92
|
+
rl.resume();
|
|
93
|
+
rl.prompt();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
rl.on('close', () => {
|
|
97
|
+
process.exit(0);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function processMessage(input, messages, assistant, brainColor) {
|
|
102
|
+
const spinner = ora({
|
|
103
|
+
text: chalk.gray('Thinking...'),
|
|
104
|
+
spinner: 'dots',
|
|
105
|
+
color: 'blue',
|
|
106
|
+
}).start();
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const reply = await sendToBrain(assistant, messages, input);
|
|
110
|
+
spinner.stop();
|
|
111
|
+
|
|
112
|
+
console.log('');
|
|
113
|
+
console.log(chalk.hex(brainColor)(` ${assistant.avatar || '🤖'} ${assistant.name} >`));
|
|
114
|
+
const lines = reply.split('\n');
|
|
115
|
+
for (const line of lines) {
|
|
116
|
+
console.log(chalk.white(` ${line}`));
|
|
117
|
+
}
|
|
118
|
+
console.log('');
|
|
119
|
+
} catch (err) {
|
|
120
|
+
spinner.stop();
|
|
121
|
+
console.log(chalk.red(`\n ❌ Error: ${err.message}\n`));
|
|
122
|
+
if (err.message.includes('API key') || err.message.includes('api key')) {
|
|
123
|
+
console.log(chalk.yellow(' Make sure your assistant has a valid API key configured.'));
|
|
124
|
+
console.log(chalk.gray(' Update it at: https://chartapp-653e1.web.app/assistant/create\n'));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
|
|
3
|
+
let _config = null;
|
|
4
|
+
|
|
5
|
+
export function getConfig() {
|
|
6
|
+
if (!_config) {
|
|
7
|
+
_config = new Conf({
|
|
8
|
+
projectName: 'uwonbot',
|
|
9
|
+
schema: {
|
|
10
|
+
uid: { type: 'string', default: '' },
|
|
11
|
+
email: { type: 'string', default: '' },
|
|
12
|
+
displayName: { type: 'string', default: '' },
|
|
13
|
+
idToken: { type: 'string', default: '' },
|
|
14
|
+
refreshToken: { type: 'string', default: '' },
|
|
15
|
+
activeAssistantId: { type: 'string', default: '' },
|
|
16
|
+
activeAssistantName: { type: 'string', default: '' },
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return _config;
|
|
21
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { initializeApp } from 'firebase/app';
|
|
2
|
+
import { getAuth, signInWithEmailAndPassword, signInWithCustomToken } from 'firebase/auth';
|
|
3
|
+
import { getFirestore, collection, getDocs, doc, getDoc, query, orderBy } from 'firebase/firestore';
|
|
4
|
+
|
|
5
|
+
const firebaseConfig = {
|
|
6
|
+
apiKey: "AIzaSyCVq7IdMZCfWqFbd5gBle5JmMX5cVJx68o",
|
|
7
|
+
authDomain: "chartapp-653e1.firebaseapp.com",
|
|
8
|
+
projectId: "chartapp-653e1",
|
|
9
|
+
storageBucket: "chartapp-653e1.firebasestorage.app",
|
|
10
|
+
messagingSenderId: "273874051709",
|
|
11
|
+
appId: "1:273874051709:web:f31e4b40dacb3a0bc76c88",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
let app = null;
|
|
15
|
+
let auth = null;
|
|
16
|
+
let db = null;
|
|
17
|
+
|
|
18
|
+
export function getFirebaseApp() {
|
|
19
|
+
if (!app) {
|
|
20
|
+
app = initializeApp(firebaseConfig);
|
|
21
|
+
auth = getAuth(app);
|
|
22
|
+
db = getFirestore(app);
|
|
23
|
+
}
|
|
24
|
+
return { app, auth, db };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function loginWithEmail(email, password) {
|
|
28
|
+
const { auth } = getFirebaseApp();
|
|
29
|
+
const cred = await signInWithEmailAndPassword(auth, email, password);
|
|
30
|
+
return {
|
|
31
|
+
uid: cred.user.uid,
|
|
32
|
+
email: cred.user.email,
|
|
33
|
+
displayName: cred.user.displayName || '',
|
|
34
|
+
idToken: await cred.user.getIdToken(),
|
|
35
|
+
refreshToken: cred.user.refreshToken,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function fetchAssistants(uid) {
|
|
40
|
+
const { db } = getFirebaseApp();
|
|
41
|
+
const ref = collection(db, 'users', uid, 'assistants');
|
|
42
|
+
const q = query(ref, orderBy('createdAt', 'desc'));
|
|
43
|
+
const snap = await getDocs(q);
|
|
44
|
+
return snap.docs.map(d => ({ id: d.id, ...d.data() }));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function fetchAssistant(uid, assistantId) {
|
|
48
|
+
const { db } = getFirebaseApp();
|
|
49
|
+
const ref = doc(db, 'users', uid, 'assistants', assistantId);
|
|
50
|
+
const snap = await getDoc(ref);
|
|
51
|
+
if (!snap.exists()) return null;
|
|
52
|
+
return { id: snap.id, ...snap.data() };
|
|
53
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { showBanner } from './banner.js';
|
|
2
|
+
export { loginCommand, logoutCommand, whoamiCommand } from './auth.js';
|
|
3
|
+
export { listAssistants, selectAssistant } from './assistants.js';
|
|
4
|
+
export { startChat } from './chat.js';
|
|
5
|
+
export { executeTool, TOOL_DEFINITIONS } from './tools.js';
|
|
6
|
+
export { sendToBrain } from './brain.js';
|
package/src/tools.js
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import { execSync, exec } from 'child_process';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
|
|
7
|
+
const TOOL_DEFINITIONS = [
|
|
8
|
+
{
|
|
9
|
+
name: 'run_shell',
|
|
10
|
+
description: 'Execute a shell command on the user\'s computer. Can install packages, run scripts, manage processes, etc. Returns stdout and stderr.',
|
|
11
|
+
parameters: {
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
command: { type: 'string', description: 'The shell command to execute' },
|
|
15
|
+
cwd: { type: 'string', description: 'Working directory (optional, defaults to current)' },
|
|
16
|
+
},
|
|
17
|
+
required: ['command'],
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'read_file',
|
|
22
|
+
description: 'Read the contents of a file on the user\'s computer.',
|
|
23
|
+
parameters: {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
path: { type: 'string', description: 'Absolute or relative file path' },
|
|
27
|
+
},
|
|
28
|
+
required: ['path'],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'write_file',
|
|
33
|
+
description: 'Write or overwrite a file on the user\'s computer.',
|
|
34
|
+
parameters: {
|
|
35
|
+
type: 'object',
|
|
36
|
+
properties: {
|
|
37
|
+
path: { type: 'string', description: 'Absolute or relative file path' },
|
|
38
|
+
content: { type: 'string', description: 'File content to write' },
|
|
39
|
+
},
|
|
40
|
+
required: ['path', 'content'],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'list_directory',
|
|
45
|
+
description: 'List files and directories in a given path.',
|
|
46
|
+
parameters: {
|
|
47
|
+
type: 'object',
|
|
48
|
+
properties: {
|
|
49
|
+
path: { type: 'string', description: 'Directory path' },
|
|
50
|
+
recursive: { type: 'boolean', description: 'List recursively (max 3 levels)' },
|
|
51
|
+
},
|
|
52
|
+
required: ['path'],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'create_directory',
|
|
57
|
+
description: 'Create a directory (and parent directories if needed).',
|
|
58
|
+
parameters: {
|
|
59
|
+
type: 'object',
|
|
60
|
+
properties: {
|
|
61
|
+
path: { type: 'string', description: 'Directory path to create' },
|
|
62
|
+
},
|
|
63
|
+
required: ['path'],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'delete_file',
|
|
68
|
+
description: 'Delete a file or directory.',
|
|
69
|
+
parameters: {
|
|
70
|
+
type: 'object',
|
|
71
|
+
properties: {
|
|
72
|
+
path: { type: 'string', description: 'Path to delete' },
|
|
73
|
+
recursive: { type: 'boolean', description: 'Delete directory recursively' },
|
|
74
|
+
},
|
|
75
|
+
required: ['path'],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'move_file',
|
|
80
|
+
description: 'Move or rename a file or directory.',
|
|
81
|
+
parameters: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
properties: {
|
|
84
|
+
source: { type: 'string', description: 'Source path' },
|
|
85
|
+
destination: { type: 'string', description: 'Destination path' },
|
|
86
|
+
},
|
|
87
|
+
required: ['source', 'destination'],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'search_files',
|
|
92
|
+
description: 'Search for files matching a pattern using glob or by content.',
|
|
93
|
+
parameters: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
directory: { type: 'string', description: 'Directory to search in' },
|
|
97
|
+
pattern: { type: 'string', description: 'File name pattern (glob)' },
|
|
98
|
+
content: { type: 'string', description: 'Search file contents for this text' },
|
|
99
|
+
},
|
|
100
|
+
required: ['directory'],
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'system_info',
|
|
105
|
+
description: 'Get information about the user\'s computer (OS, CPU, memory, etc.).',
|
|
106
|
+
parameters: {
|
|
107
|
+
type: 'object',
|
|
108
|
+
properties: {},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'open_url',
|
|
113
|
+
description: 'Open a URL in the default browser.',
|
|
114
|
+
parameters: {
|
|
115
|
+
type: 'object',
|
|
116
|
+
properties: {
|
|
117
|
+
url: { type: 'string', description: 'URL to open' },
|
|
118
|
+
},
|
|
119
|
+
required: ['url'],
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'open_application',
|
|
124
|
+
description: 'Open an application on the computer.',
|
|
125
|
+
parameters: {
|
|
126
|
+
type: 'object',
|
|
127
|
+
properties: {
|
|
128
|
+
name: { type: 'string', description: 'Application name (e.g. "Visual Studio Code", "Finder", "Terminal")' },
|
|
129
|
+
},
|
|
130
|
+
required: ['name'],
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: 'install_package',
|
|
135
|
+
description: 'Install a package using npm, pip, brew, or apt.',
|
|
136
|
+
parameters: {
|
|
137
|
+
type: 'object',
|
|
138
|
+
properties: {
|
|
139
|
+
manager: { type: 'string', enum: ['npm', 'pip', 'brew', 'apt'], description: 'Package manager to use' },
|
|
140
|
+
package_name: { type: 'string', description: 'Package name to install' },
|
|
141
|
+
global: { type: 'boolean', description: 'Install globally (for npm)' },
|
|
142
|
+
},
|
|
143
|
+
required: ['manager', 'package_name'],
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: 'get_clipboard',
|
|
148
|
+
description: 'Get the current clipboard content.',
|
|
149
|
+
parameters: { type: 'object', properties: {} },
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: 'set_clipboard',
|
|
153
|
+
description: 'Set clipboard content.',
|
|
154
|
+
parameters: {
|
|
155
|
+
type: 'object',
|
|
156
|
+
properties: {
|
|
157
|
+
text: { type: 'string', description: 'Text to copy to clipboard' },
|
|
158
|
+
},
|
|
159
|
+
required: ['text'],
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
function resolvePath(p) {
|
|
165
|
+
if (p.startsWith('~')) {
|
|
166
|
+
return path.join(os.homedir(), p.slice(1));
|
|
167
|
+
}
|
|
168
|
+
return path.resolve(p);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function executeTool(name, args) {
|
|
172
|
+
switch (name) {
|
|
173
|
+
case 'run_shell': {
|
|
174
|
+
const cwd = args.cwd ? resolvePath(args.cwd) : process.cwd();
|
|
175
|
+
console.log(chalk.gray(` ⚙ Running: ${args.command}`));
|
|
176
|
+
try {
|
|
177
|
+
const result = execSync(args.command, {
|
|
178
|
+
cwd,
|
|
179
|
+
encoding: 'utf-8',
|
|
180
|
+
timeout: 60000,
|
|
181
|
+
maxBuffer: 5 * 1024 * 1024,
|
|
182
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
183
|
+
});
|
|
184
|
+
return { success: true, stdout: result.substring(0, 10000) };
|
|
185
|
+
} catch (err) {
|
|
186
|
+
return {
|
|
187
|
+
success: false,
|
|
188
|
+
exitCode: err.status,
|
|
189
|
+
stdout: (err.stdout || '').substring(0, 5000),
|
|
190
|
+
stderr: (err.stderr || '').substring(0, 5000),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
case 'read_file': {
|
|
196
|
+
const filePath = resolvePath(args.path);
|
|
197
|
+
console.log(chalk.gray(` 📖 Reading: ${filePath}`));
|
|
198
|
+
try {
|
|
199
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
200
|
+
return { success: true, content: content.substring(0, 50000), size: content.length };
|
|
201
|
+
} catch (err) {
|
|
202
|
+
return { success: false, error: err.message };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
case 'write_file': {
|
|
207
|
+
const filePath = resolvePath(args.path);
|
|
208
|
+
console.log(chalk.gray(` ✍️ Writing: ${filePath}`));
|
|
209
|
+
try {
|
|
210
|
+
const dir = path.dirname(filePath);
|
|
211
|
+
if (!fs.existsSync(dir)) {
|
|
212
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
213
|
+
}
|
|
214
|
+
fs.writeFileSync(filePath, args.content, 'utf-8');
|
|
215
|
+
return { success: true, path: filePath, bytes: Buffer.byteLength(args.content) };
|
|
216
|
+
} catch (err) {
|
|
217
|
+
return { success: false, error: err.message };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
case 'list_directory': {
|
|
222
|
+
const dirPath = resolvePath(args.path);
|
|
223
|
+
console.log(chalk.gray(` 📂 Listing: ${dirPath}`));
|
|
224
|
+
try {
|
|
225
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
226
|
+
const items = entries.map(e => ({
|
|
227
|
+
name: e.name,
|
|
228
|
+
type: e.isDirectory() ? 'directory' : 'file',
|
|
229
|
+
size: e.isFile() ? fs.statSync(path.join(dirPath, e.name)).size : undefined,
|
|
230
|
+
}));
|
|
231
|
+
return { success: true, path: dirPath, items: items.slice(0, 200) };
|
|
232
|
+
} catch (err) {
|
|
233
|
+
return { success: false, error: err.message };
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
case 'create_directory': {
|
|
238
|
+
const dirPath = resolvePath(args.path);
|
|
239
|
+
console.log(chalk.gray(` 📁 Creating dir: ${dirPath}`));
|
|
240
|
+
try {
|
|
241
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
242
|
+
return { success: true, path: dirPath };
|
|
243
|
+
} catch (err) {
|
|
244
|
+
return { success: false, error: err.message };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
case 'delete_file': {
|
|
249
|
+
const filePath = resolvePath(args.path);
|
|
250
|
+
console.log(chalk.gray(` 🗑️ Deleting: ${filePath}`));
|
|
251
|
+
try {
|
|
252
|
+
if (args.recursive) {
|
|
253
|
+
fs.rmSync(filePath, { recursive: true, force: true });
|
|
254
|
+
} else {
|
|
255
|
+
fs.unlinkSync(filePath);
|
|
256
|
+
}
|
|
257
|
+
return { success: true, path: filePath };
|
|
258
|
+
} catch (err) {
|
|
259
|
+
return { success: false, error: err.message };
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
case 'move_file': {
|
|
264
|
+
const src = resolvePath(args.source);
|
|
265
|
+
const dest = resolvePath(args.destination);
|
|
266
|
+
console.log(chalk.gray(` 📦 Moving: ${src} → ${dest}`));
|
|
267
|
+
try {
|
|
268
|
+
fs.renameSync(src, dest);
|
|
269
|
+
return { success: true, source: src, destination: dest };
|
|
270
|
+
} catch (err) {
|
|
271
|
+
return { success: false, error: err.message };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
case 'search_files': {
|
|
276
|
+
const dir = resolvePath(args.directory);
|
|
277
|
+
console.log(chalk.gray(` 🔍 Searching in: ${dir}`));
|
|
278
|
+
try {
|
|
279
|
+
let cmd = '';
|
|
280
|
+
if (args.content) {
|
|
281
|
+
cmd = `grep -rl "${args.content.replace(/"/g, '\\"')}" "${dir}" --include="${args.pattern || '*'}" 2>/dev/null | head -30`;
|
|
282
|
+
} else if (args.pattern) {
|
|
283
|
+
cmd = `find "${dir}" -name "${args.pattern}" -maxdepth 5 2>/dev/null | head -50`;
|
|
284
|
+
} else {
|
|
285
|
+
cmd = `find "${dir}" -maxdepth 3 2>/dev/null | head -50`;
|
|
286
|
+
}
|
|
287
|
+
const result = execSync(cmd, { encoding: 'utf-8', timeout: 15000 });
|
|
288
|
+
return { success: true, files: result.trim().split('\n').filter(Boolean) };
|
|
289
|
+
} catch (err) {
|
|
290
|
+
return { success: true, files: [] };
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
case 'system_info': {
|
|
295
|
+
console.log(chalk.gray(` 💻 Getting system info`));
|
|
296
|
+
return {
|
|
297
|
+
success: true,
|
|
298
|
+
platform: os.platform(),
|
|
299
|
+
arch: os.arch(),
|
|
300
|
+
hostname: os.hostname(),
|
|
301
|
+
cpus: os.cpus().length,
|
|
302
|
+
cpuModel: os.cpus()[0]?.model,
|
|
303
|
+
totalMemory: `${Math.round(os.totalmem() / 1073741824)}GB`,
|
|
304
|
+
freeMemory: `${Math.round(os.freemem() / 1073741824)}GB`,
|
|
305
|
+
homeDir: os.homedir(),
|
|
306
|
+
shell: process.env.SHELL || 'unknown',
|
|
307
|
+
nodeVersion: process.version,
|
|
308
|
+
cwd: process.cwd(),
|
|
309
|
+
user: os.userInfo().username,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
case 'open_url': {
|
|
314
|
+
console.log(chalk.gray(` 🌐 Opening: ${args.url}`));
|
|
315
|
+
try {
|
|
316
|
+
const open = (await import('open')).default;
|
|
317
|
+
await open(args.url);
|
|
318
|
+
return { success: true, url: args.url };
|
|
319
|
+
} catch (err) {
|
|
320
|
+
return { success: false, error: err.message };
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
case 'open_application': {
|
|
325
|
+
console.log(chalk.gray(` 🚀 Opening: ${args.name}`));
|
|
326
|
+
try {
|
|
327
|
+
if (os.platform() === 'darwin') {
|
|
328
|
+
execSync(`open -a "${args.name}"`, { timeout: 5000 });
|
|
329
|
+
} else if (os.platform() === 'win32') {
|
|
330
|
+
execSync(`start "" "${args.name}"`, { timeout: 5000 });
|
|
331
|
+
} else {
|
|
332
|
+
execSync(`xdg-open "${args.name}" &`, { timeout: 5000 });
|
|
333
|
+
}
|
|
334
|
+
return { success: true, app: args.name };
|
|
335
|
+
} catch (err) {
|
|
336
|
+
return { success: false, error: err.message };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
case 'install_package': {
|
|
341
|
+
const { manager, package_name } = args;
|
|
342
|
+
let cmd;
|
|
343
|
+
switch (manager) {
|
|
344
|
+
case 'npm': cmd = args.global ? `npm install -g ${package_name}` : `npm install ${package_name}`; break;
|
|
345
|
+
case 'pip': cmd = `pip install ${package_name}`; break;
|
|
346
|
+
case 'brew': cmd = `brew install ${package_name}`; break;
|
|
347
|
+
case 'apt': cmd = `sudo apt-get install -y ${package_name}`; break;
|
|
348
|
+
default: return { success: false, error: `Unknown package manager: ${manager}` };
|
|
349
|
+
}
|
|
350
|
+
console.log(chalk.gray(` 📦 Installing: ${cmd}`));
|
|
351
|
+
try {
|
|
352
|
+
const result = execSync(cmd, { encoding: 'utf-8', timeout: 120000 });
|
|
353
|
+
return { success: true, command: cmd, output: result.substring(0, 5000) };
|
|
354
|
+
} catch (err) {
|
|
355
|
+
return { success: false, error: (err.stderr || err.message).substring(0, 3000) };
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
case 'get_clipboard': {
|
|
360
|
+
try {
|
|
361
|
+
let cmd;
|
|
362
|
+
if (os.platform() === 'darwin') cmd = 'pbpaste';
|
|
363
|
+
else if (os.platform() === 'win32') cmd = 'powershell -command "Get-Clipboard"';
|
|
364
|
+
else cmd = 'xclip -selection clipboard -o';
|
|
365
|
+
const result = execSync(cmd, { encoding: 'utf-8', timeout: 3000 });
|
|
366
|
+
return { success: true, content: result.substring(0, 10000) };
|
|
367
|
+
} catch (err) {
|
|
368
|
+
return { success: false, error: err.message };
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
case 'set_clipboard': {
|
|
373
|
+
try {
|
|
374
|
+
let cmd;
|
|
375
|
+
if (os.platform() === 'darwin') cmd = 'pbcopy';
|
|
376
|
+
else if (os.platform() === 'win32') cmd = 'clip';
|
|
377
|
+
else cmd = 'xclip -selection clipboard';
|
|
378
|
+
const proc = exec(cmd);
|
|
379
|
+
proc.stdin.write(args.text);
|
|
380
|
+
proc.stdin.end();
|
|
381
|
+
return { success: true };
|
|
382
|
+
} catch (err) {
|
|
383
|
+
return { success: false, error: err.message };
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
default:
|
|
388
|
+
return { success: false, error: `Unknown tool: ${name}` };
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function getToolsForProvider(provider) {
|
|
393
|
+
if (provider === 'gemini') {
|
|
394
|
+
return TOOL_DEFINITIONS.map(t => ({
|
|
395
|
+
name: t.name,
|
|
396
|
+
description: t.description,
|
|
397
|
+
parameters: t.parameters,
|
|
398
|
+
}));
|
|
399
|
+
}
|
|
400
|
+
if (provider === 'openai') {
|
|
401
|
+
return TOOL_DEFINITIONS.map(t => ({
|
|
402
|
+
type: 'function',
|
|
403
|
+
function: {
|
|
404
|
+
name: t.name,
|
|
405
|
+
description: t.description,
|
|
406
|
+
parameters: t.parameters,
|
|
407
|
+
},
|
|
408
|
+
}));
|
|
409
|
+
}
|
|
410
|
+
if (provider === 'claude') {
|
|
411
|
+
return TOOL_DEFINITIONS.map(t => ({
|
|
412
|
+
name: t.name,
|
|
413
|
+
description: t.description,
|
|
414
|
+
input_schema: t.parameters,
|
|
415
|
+
}));
|
|
416
|
+
}
|
|
417
|
+
return TOOL_DEFINITIONS;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export { TOOL_DEFINITIONS, executeTool, getToolsForProvider };
|