uwonbot 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/uwonbot.js CHANGED
@@ -5,13 +5,14 @@ import { loginCommand, logoutCommand, whoamiCommand } from '../src/auth.js';
5
5
  import { listAssistants, selectAssistant } from '../src/assistants.js';
6
6
  import { startChat } from '../src/chat.js';
7
7
  import { getConfig } from '../src/config.js';
8
+ import { startAgent } from '../src/agent.js';
8
9
 
9
10
  showBanner();
10
11
 
11
12
  program
12
13
  .name('uwonbot')
13
14
  .description('Uwonbot AI Assistant — Your AI controls your computer')
14
- .version('1.0.0');
15
+ .version('1.0.4');
15
16
 
16
17
  program
17
18
  .command('login')
@@ -52,6 +53,43 @@ program
52
53
  }
53
54
  });
54
55
 
56
+ program
57
+ .command('agent')
58
+ .description('Start the local agent for OS-level mouse/keyboard control')
59
+ .option('-p, --port <port>', 'WebSocket server port', '9876')
60
+ .action(async (opts) => {
61
+ await startAgent(parseInt(opts.port));
62
+ });
63
+
64
+ program
65
+ .command('upgrade')
66
+ .description('Upgrade uwonbot to the latest version')
67
+ .action(async () => {
68
+ const { execSync } = await import('child_process');
69
+ const chalk = (await import('chalk')).default;
70
+ console.log(chalk.cyan(' Checking for updates...'));
71
+ try {
72
+ const latest = execSync('npm view uwonbot version', { encoding: 'utf8' }).trim();
73
+ const { readFileSync } = await import('fs');
74
+ const { fileURLToPath } = await import('url');
75
+ const { dirname, join } = await import('path');
76
+ const __dirname = dirname(fileURLToPath(import.meta.url));
77
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
78
+ const current = pkg.version;
79
+ if (current === latest) {
80
+ console.log(chalk.green(` ✓ Already on latest version (v${current})`));
81
+ return;
82
+ }
83
+ console.log(chalk.yellow(` Current: v${current} → Latest: v${latest}`));
84
+ console.log(chalk.cyan(' Upgrading...'));
85
+ execSync(`npm install -g uwonbot@${latest}`, { stdio: 'inherit' });
86
+ console.log(chalk.green(`\n ✓ Upgraded to v${latest}!`));
87
+ } catch (e) {
88
+ console.error(chalk.red(` ✗ Upgrade failed: ${e.message}`));
89
+ console.log(chalk.gray(' Try manually: npm install -g uwonbot@latest'));
90
+ }
91
+ });
92
+
55
93
  program
56
94
  .command('run <command>')
57
95
  .description('Ask your assistant to run a task')
@@ -78,6 +116,8 @@ if (process.argv.length <= 2) {
78
116
  console.log(' uwonbot chat Start chatting with your AI assistant');
79
117
  console.log(' uwonbot assistants List your AI assistants');
80
118
  console.log(' uwonbot run "..." Ask AI to run a task');
119
+ console.log(' uwonbot agent Start local agent (OS control)');
120
+ console.log(' uwonbot upgrade Upgrade to latest version');
81
121
  console.log(' uwonbot logout Log out');
82
122
  console.log('');
83
123
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uwonbot",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Uwonbot AI Assistant CLI — Your AI controls your computer",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -29,6 +29,11 @@
29
29
  "inquirer": "^12.0.0",
30
30
  "node-fetch": "^3.3.2",
31
31
  "open": "^10.1.0",
32
- "ora": "^8.1.0"
32
+ "ora": "^8.1.0",
33
+ "ws": "^8.18.0"
34
+ },
35
+ "optionalDependencies": {
36
+ "@nut-tree-fork/nut-js": "^4.2.0",
37
+ "screenshot-desktop": "^1.15.0"
33
38
  }
34
39
  }
package/src/agent.js ADDED
@@ -0,0 +1,305 @@
1
+ import { WebSocketServer } from 'ws';
2
+ import { exec } from 'child_process';
3
+ import { promisify } from 'util';
4
+ import chalk from 'chalk';
5
+ import { getConfig } from './config.js';
6
+
7
+ const execAsync = promisify(exec);
8
+ const platform = process.platform;
9
+
10
+ let robot = null;
11
+ let screenshotDesktop = null;
12
+
13
+ async function loadNativeModules() {
14
+ try {
15
+ robot = (await import('@nut-tree-fork/nut-js')).default || (await import('@nut-tree-fork/nut-js'));
16
+ if (robot.mouse) {
17
+ robot.mouse.config.autoDelayMs = 0;
18
+ robot.mouse.config.mouseSpeed = 2000;
19
+ }
20
+ console.log(chalk.green(' ✓ nut-js loaded (mouse/keyboard control)'));
21
+ } catch {
22
+ try {
23
+ const rjs = await import('robotjs');
24
+ robot = rjs.default || rjs;
25
+ console.log(chalk.green(' ✓ robotjs loaded (mouse/keyboard control)'));
26
+ } catch {
27
+ console.log(chalk.yellow(' ⚠ No mouse/keyboard module found.'));
28
+ console.log(chalk.yellow(' Install: npm install -g @nut-tree-fork/nut-js'));
29
+ robot = null;
30
+ }
31
+ }
32
+
33
+ try {
34
+ const mod = await import('screenshot-desktop');
35
+ screenshotDesktop = mod.default || mod;
36
+ console.log(chalk.green(' ✓ screenshot-desktop loaded'));
37
+ } catch {
38
+ console.log(chalk.yellow(' ⚠ screenshot-desktop not found.'));
39
+ console.log(chalk.yellow(' Install: npm install -g screenshot-desktop'));
40
+ screenshotDesktop = null;
41
+ }
42
+ }
43
+
44
+ async function getScreenSize() {
45
+ if (robot?.screen?.width) {
46
+ const w = await robot.screen.width();
47
+ const h = await robot.screen.height();
48
+ return { width: w, height: h };
49
+ }
50
+ if (robot?.getScreenSize) {
51
+ return robot.getScreenSize();
52
+ }
53
+ return { width: 1920, height: 1080 };
54
+ }
55
+
56
+ async function moveMouse(x, y) {
57
+ const screen = await getScreenSize();
58
+ const px = Math.round(x * screen.width);
59
+ const py = Math.round(y * screen.height);
60
+
61
+ if (robot?.mouse?.setPosition) {
62
+ const { Point } = await import('@nut-tree-fork/nut-js');
63
+ await robot.mouse.setPosition(new Point(px, py));
64
+ } else if (robot?.moveMouse) {
65
+ robot.moveMouse(px, py);
66
+ }
67
+ }
68
+
69
+ async function mouseClick(button = 'left', double = false) {
70
+ if (robot?.mouse?.click) {
71
+ const { Button } = await import('@nut-tree-fork/nut-js');
72
+ const btn = button === 'right' ? Button.RIGHT : Button.LEFT;
73
+ await robot.mouse.click(btn);
74
+ if (double) await robot.mouse.click(btn);
75
+ } else if (robot?.mouseClick) {
76
+ robot.mouseClick(button, double);
77
+ }
78
+ }
79
+
80
+ async function mouseDown(button = 'left') {
81
+ if (robot?.mouse?.pressButton) {
82
+ const { Button } = await import('@nut-tree-fork/nut-js');
83
+ await robot.mouse.pressButton(button === 'right' ? Button.RIGHT : Button.LEFT);
84
+ } else if (robot?.mouseToggle) {
85
+ robot.mouseToggle('down', button);
86
+ }
87
+ }
88
+
89
+ async function mouseUp(button = 'left') {
90
+ if (robot?.mouse?.releaseButton) {
91
+ const { Button } = await import('@nut-tree-fork/nut-js');
92
+ await robot.mouse.releaseButton(button === 'right' ? Button.RIGHT : Button.LEFT);
93
+ } else if (robot?.mouseToggle) {
94
+ robot.mouseToggle('up', button);
95
+ }
96
+ }
97
+
98
+ async function mouseScroll(dx, dy) {
99
+ if (robot?.mouse?.scrollDown && robot?.mouse?.scrollUp) {
100
+ const amount = Math.abs(dy);
101
+ if (dy > 0) await robot.mouse.scrollDown(amount);
102
+ else await robot.mouse.scrollUp(amount);
103
+ } else if (robot?.scrollMouse) {
104
+ robot.scrollMouse(dx, dy);
105
+ }
106
+ }
107
+
108
+ async function mouseDrag(fromX, fromY, toX, toY) {
109
+ await moveMouse(fromX, fromY);
110
+ await mouseDown('left');
111
+ await new Promise(r => setTimeout(r, 50));
112
+ await moveMouse(toX, toY);
113
+ await new Promise(r => setTimeout(r, 50));
114
+ await mouseUp('left');
115
+ }
116
+
117
+ async function typeText(text) {
118
+ if (robot?.keyboard?.type) {
119
+ await robot.keyboard.type(text);
120
+ } else if (robot?.typeString) {
121
+ robot.typeString(text);
122
+ }
123
+ }
124
+
125
+ async function pressKey(keys) {
126
+ const keyArr = keys.split('+').map(k => k.trim().toLowerCase());
127
+
128
+ if (robot?.keyboard?.pressKey && robot?.keyboard?.releaseKey) {
129
+ const { Key } = await import('@nut-tree-fork/nut-js');
130
+ const mapped = keyArr.map(k => {
131
+ const map = {
132
+ cmd: Key.LeftSuper, command: Key.LeftSuper, meta: Key.LeftSuper,
133
+ ctrl: Key.LeftControl, control: Key.LeftControl,
134
+ alt: Key.LeftAlt, option: Key.LeftAlt,
135
+ shift: Key.LeftShift,
136
+ enter: Key.Return, return: Key.Return,
137
+ tab: Key.Tab, escape: Key.Escape, esc: Key.Escape,
138
+ space: Key.Space, backspace: Key.Backspace, delete: Key.Delete,
139
+ up: Key.Up, down: Key.Down, left: Key.Left, right: Key.Right,
140
+ home: Key.Home, end: Key.End, pageup: Key.PageUp, pagedown: Key.PageDown,
141
+ f1: Key.F1, f2: Key.F2, f3: Key.F3, f4: Key.F4, f5: Key.F5,
142
+ };
143
+ return map[k] || Key[k.toUpperCase()] || Key[k];
144
+ }).filter(Boolean);
145
+
146
+ for (const k of mapped) await robot.keyboard.pressKey(k);
147
+ for (const k of mapped.reverse()) await robot.keyboard.releaseKey(k);
148
+ } else if (robot?.keyTap) {
149
+ const modifiers = [];
150
+ let mainKey = keyArr[keyArr.length - 1];
151
+ for (let i = 0; i < keyArr.length - 1; i++) {
152
+ const k = keyArr[i];
153
+ if (k === 'cmd' || k === 'command') modifiers.push('command');
154
+ else if (k === 'ctrl' || k === 'control') modifiers.push('control');
155
+ else if (k === 'alt' || k === 'option') modifiers.push('alt');
156
+ else if (k === 'shift') modifiers.push('shift');
157
+ }
158
+ robot.keyTap(mainKey, modifiers);
159
+ }
160
+ }
161
+
162
+ async function takeScreenshot() {
163
+ if (!screenshotDesktop) return null;
164
+ try {
165
+ const buf = await screenshotDesktop({ format: 'png' });
166
+ return buf.toString('base64');
167
+ } catch (e) {
168
+ console.error('Screenshot failed:', e.message);
169
+ return null;
170
+ }
171
+ }
172
+
173
+ async function openApp(appName) {
174
+ const name = appName.toLowerCase();
175
+ try {
176
+ if (platform === 'darwin') {
177
+ const appMap = {
178
+ chrome: 'Google Chrome', safari: 'Safari', firefox: 'Firefox',
179
+ finder: 'Finder', terminal: 'Terminal', code: 'Visual Studio Code',
180
+ slack: 'Slack', spotify: 'Spotify', discord: 'Discord',
181
+ notes: 'Notes', calculator: 'Calculator', music: 'Music',
182
+ };
183
+ const resolved = appMap[name] || appName;
184
+ await execAsync(`open -a "${resolved}"`);
185
+ } else if (platform === 'win32') {
186
+ await execAsync(`start "" "${appName}"`);
187
+ } else {
188
+ await execAsync(`xdg-open "${appName}" || ${appName}`);
189
+ }
190
+ return true;
191
+ } catch (e) {
192
+ console.error('App open failed:', e.message);
193
+ return false;
194
+ }
195
+ }
196
+
197
+ async function handleCommand(msg) {
198
+ try {
199
+ const cmd = JSON.parse(msg);
200
+ switch (cmd.type) {
201
+ case 'mouse_move':
202
+ await moveMouse(cmd.x, cmd.y);
203
+ return { ok: true };
204
+ case 'mouse_click':
205
+ await mouseClick(cmd.button || 'left', cmd.double || false);
206
+ return { ok: true };
207
+ case 'mouse_down':
208
+ await mouseDown(cmd.button || 'left');
209
+ return { ok: true };
210
+ case 'mouse_up':
211
+ await mouseUp(cmd.button || 'left');
212
+ return { ok: true };
213
+ case 'mouse_scroll':
214
+ await mouseScroll(cmd.dx || 0, cmd.dy || 0);
215
+ return { ok: true };
216
+ case 'mouse_drag':
217
+ await mouseDrag(cmd.fromX, cmd.fromY, cmd.toX, cmd.toY);
218
+ return { ok: true };
219
+ case 'key_type':
220
+ await typeText(cmd.text || '');
221
+ return { ok: true };
222
+ case 'key_press':
223
+ await pressKey(cmd.keys || '');
224
+ return { ok: true };
225
+ case 'screenshot':
226
+ const img = await takeScreenshot();
227
+ return { ok: !!img, image: img };
228
+ case 'app_open':
229
+ const opened = await openApp(cmd.name || '');
230
+ return { ok: opened };
231
+ case 'screen_size':
232
+ const size = await getScreenSize();
233
+ return { ok: true, ...size };
234
+ case 'ping':
235
+ return { ok: true, pong: true };
236
+ default:
237
+ return { ok: false, error: `Unknown command: ${cmd.type}` };
238
+ }
239
+ } catch (e) {
240
+ return { ok: false, error: e.message };
241
+ }
242
+ }
243
+
244
+ export async function startAgent(port = 9876) {
245
+ console.log('');
246
+ console.log(chalk.bold.cyan(' 🖥️ Uwonbot Agent'));
247
+ console.log(chalk.gray(' ────────────────────────'));
248
+ console.log('');
249
+
250
+ await loadNativeModules();
251
+ console.log('');
252
+
253
+ const config = getConfig();
254
+ const uid = config.get('uid');
255
+ const email = config.get('email');
256
+
257
+ if (!uid) {
258
+ console.log(chalk.red(' ✗ Not logged in. Run: uwonbot login'));
259
+ process.exit(1);
260
+ }
261
+
262
+ console.log(chalk.gray(` User: ${email}`));
263
+ console.log(chalk.gray(` Port: ${port}`));
264
+ console.log('');
265
+
266
+ const wss = new WebSocketServer({ port });
267
+
268
+ wss.on('connection', (ws, req) => {
269
+ const origin = req.headers.origin || 'unknown';
270
+ console.log(chalk.green(` ✓ Client connected from ${origin}`));
271
+
272
+ ws.on('message', async (data) => {
273
+ const result = await handleCommand(data.toString());
274
+ ws.send(JSON.stringify(result));
275
+ });
276
+
277
+ ws.on('close', () => {
278
+ console.log(chalk.yellow(' ○ Client disconnected'));
279
+ });
280
+
281
+ ws.send(JSON.stringify({ type: 'welcome', agent: 'uwonbot', version: '1.0.3', uid }));
282
+ });
283
+
284
+ wss.on('error', (err) => {
285
+ if (err.code === 'EADDRINUSE') {
286
+ console.log(chalk.red(` ✗ Port ${port} is already in use`));
287
+ console.log(chalk.gray(` Try: uwonbot agent --port ${port + 1}`));
288
+ process.exit(1);
289
+ }
290
+ console.error(chalk.red(' ✗ Server error:'), err.message);
291
+ });
292
+
293
+ console.log(chalk.bold.green(` ✓ Agent running on ws://localhost:${port}`));
294
+ console.log(chalk.gray(' Waiting for connections...'));
295
+ console.log('');
296
+ console.log(chalk.gray(' Press Ctrl+C to stop'));
297
+ console.log('');
298
+
299
+ process.on('SIGINT', () => {
300
+ console.log('');
301
+ console.log(chalk.yellow(' Agent stopped.'));
302
+ wss.close();
303
+ process.exit(0);
304
+ });
305
+ }
package/src/banner.js CHANGED
@@ -1,11 +1,25 @@
1
1
  import chalk from 'chalk';
2
+ import { readFileSync } from 'fs';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join } from 'path';
5
+
6
+ function getVersion() {
7
+ try {
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
10
+ return pkg.version || '0.0.0';
11
+ } catch { return '0.0.0'; }
12
+ }
2
13
 
3
14
  export function showBanner() {
15
+ const ver = getVersion();
4
16
  const blue = chalk.hex('#2563eb');
5
17
  const cyan = chalk.hex('#06b6d4');
6
18
  const gray = chalk.hex('#6b7280');
7
19
  const white = chalk.white.bold;
8
20
 
21
+ const verStr = `v${ver}`.padEnd(25);
22
+
9
23
  console.log('');
10
24
  console.log(blue(' ╔══════════════════════════════════════════════════════════════╗'));
11
25
  console.log(blue(' ║ ║'));
@@ -17,7 +31,7 @@ export function showBanner() {
17
31
  console.log(blue(' ║') + cyan(' ╚═════╝ ╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═══╝╚═════╝ ╚═════╝ ╚═╝ ') + blue(' ║'));
18
32
  console.log(blue(' ║ ║'));
19
33
  console.log(blue(' ║') + gray(' AI Assistant — Your AI controls your computer ') + blue(' ║'));
20
- console.log(blue(' ║') + gray(' v1.0.0 https://uwonbot.com ') + blue(' ║'));
34
+ console.log(blue(' ║') + gray(` ${verStr}https://uwonbot.com `) + blue(' ║'));
21
35
  console.log(blue(' ║ ║'));
22
36
  console.log(blue(' ╚══════════════════════════════════════════════════════════════╝'));
23
37
  console.log('');