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 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 };