uwonbot 1.1.3 → 1.1.5

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
@@ -14,7 +14,7 @@ showBanner();
14
14
  program
15
15
  .name('uwonbot')
16
16
  .description('Uwonbot AI Assistant — Your AI controls your computer')
17
- .version('1.1.3');
17
+ .version('1.1.5');
18
18
 
19
19
  program
20
20
  .command('login')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uwonbot",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "Uwonbot AI Assistant CLI — Your AI controls your computer",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -27,6 +27,7 @@
27
27
  "commander": "^12.1.0",
28
28
  "conf": "^13.0.1",
29
29
  "inquirer": "^12.0.0",
30
+ "mic": "^2.1.2",
30
31
  "node-fetch": "^3.3.2",
31
32
  "open": "^10.1.0",
32
33
  "ora": "^8.1.0",
@@ -34,7 +35,6 @@
34
35
  },
35
36
  "optionalDependencies": {
36
37
  "@nut-tree-fork/nut-js": "^4.2.0",
37
- "mic": "^2.5.1",
38
38
  "screenshot-desktop": "^1.15.0"
39
39
  }
40
40
  }
package/src/agent.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { WebSocketServer } from 'ws';
2
- import { exec } from 'child_process';
2
+ import WebSocket from 'ws';
3
+ import { exec, execSync } from 'child_process';
3
4
  import { promisify } from 'util';
4
5
  import chalk from 'chalk';
5
6
  import { getConfig } from './config.js';
@@ -8,6 +9,53 @@ import { fetchAssistants, setIdToken } from './firebase-client.js';
8
9
 
9
10
  const execAsync = promisify(exec);
10
11
  const WEB_APP_URL = 'https://chartapp-653e1.web.app';
12
+
13
+ function isPortInUse(port) {
14
+ return new Promise((resolve) => {
15
+ const ws = new WebSocket(`ws://localhost:${port}`);
16
+ const timer = setTimeout(() => { ws.terminate(); resolve(false); }, 1000);
17
+ ws.on('open', () => { clearTimeout(timer); ws.close(); resolve(true); });
18
+ ws.on('error', () => { clearTimeout(timer); resolve(false); });
19
+ });
20
+ }
21
+
22
+ function killProcessOnPort(port) {
23
+ try {
24
+ if (process.platform === 'win32') {
25
+ const out = execSync(`netstat -ano | findstr :${port}`, { encoding: 'utf8' });
26
+ const pid = out.trim().split(/\s+/).pop();
27
+ if (pid && pid !== '0') execSync(`taskkill /F /PID ${pid}`, { stdio: 'ignore' });
28
+ } else {
29
+ execSync(`lsof -ti :${port} | xargs kill -9 2>/dev/null`, { stdio: 'ignore' });
30
+ }
31
+ return true;
32
+ } catch { return false; }
33
+ }
34
+
35
+ function checkSoxInstalled() {
36
+ try { execSync('which sox', { stdio: 'ignore' }); return true; } catch {
37
+ try { execSync('which rec', { stdio: 'ignore' }); return true; } catch { return false; }
38
+ }
39
+ }
40
+
41
+ async function ensureSox() {
42
+ if (checkSoxInstalled()) return true;
43
+ console.log(chalk.yellow(' ⚠ SoX가 설치되어 있지 않습니다 (박수 감지에 필요)'));
44
+ if (process.platform === 'darwin') {
45
+ console.log(chalk.cyan(' → Homebrew로 자동 설치 시도 중...'));
46
+ try {
47
+ execSync('brew install sox', { stdio: 'inherit' });
48
+ console.log(chalk.green(' ✓ SoX 설치 완료'));
49
+ return true;
50
+ } catch {
51
+ console.log(chalk.yellow(' ⚠ SoX 자동 설치 실패. 수동 설치: brew install sox'));
52
+ return false;
53
+ }
54
+ }
55
+ console.log(chalk.gray(' macOS: brew install sox'));
56
+ console.log(chalk.gray(' Linux: sudo apt install sox'));
57
+ return false;
58
+ }
11
59
  const platform = process.platform;
12
60
 
13
61
  let robot = null;
@@ -358,6 +406,14 @@ export async function startAgent(port = 9876, options = {}) {
358
406
  console.log(chalk.gray(' ────────────────────────'));
359
407
  console.log('');
360
408
 
409
+ const alreadyRunning = await isPortInUse(port);
410
+ if (alreadyRunning) {
411
+ console.log(chalk.yellow(` ⚠ Agent가 이미 포트 ${port}에서 실행 중입니다.`));
412
+ console.log(chalk.gray(' 기존 프로세스를 종료하고 재시작합니다...'));
413
+ killProcessOnPort(port);
414
+ await new Promise(r => setTimeout(r, 1500));
415
+ }
416
+
361
417
  await loadNativeModules();
362
418
  console.log('');
363
419
 
@@ -388,6 +444,7 @@ export async function startAgent(port = 9876, options = {}) {
388
444
  console.log('');
389
445
 
390
446
  if (!options.noMic) {
447
+ await ensureSox();
391
448
  const clapListener = new ClapListener(async () => {
392
449
  console.log(chalk.bold.cyan(' 👏 박수 감지! 비서 활성화 중...'));
393
450
 
@@ -442,11 +499,27 @@ export async function startAgent(port = 9876, options = {}) {
442
499
  ws.send(JSON.stringify({ type: 'welcome', agent: 'uwonbot', version: '1.1.2', uid }));
443
500
  });
444
501
 
445
- wss.on('error', (err) => {
502
+ wss.on('error', async (err) => {
446
503
  if (err.code === 'EADDRINUSE') {
447
- console.log(chalk.red(` Port ${port} is already in use`));
448
- console.log(chalk.gray(` Try: uwonbot agent --port ${port + 1}`));
449
- process.exit(1);
504
+ console.log(chalk.yellow(` 포트 ${port} 충돌 기존 프로세스 종료 후 재시도...`));
505
+ killProcessOnPort(port);
506
+ await new Promise(r => setTimeout(r, 2000));
507
+ try {
508
+ const retry = new WebSocketServer({ port });
509
+ retry.on('connection', (ws, req) => {
510
+ ws.on('message', async (data) => {
511
+ const result = await handleCommand(data.toString());
512
+ ws.send(JSON.stringify(result));
513
+ });
514
+ ws.send(JSON.stringify({ type: 'welcome', agent: 'uwonbot', version: '1.1.4', uid }));
515
+ });
516
+ console.log(chalk.green(` ✓ 재시도 성공 — ws://localhost:${port}`));
517
+ } catch {
518
+ console.log(chalk.red(` ✗ 포트 ${port}를 사용할 수 없습니다.`));
519
+ console.log(chalk.gray(` uwonbot agent --port ${port + 1}`));
520
+ process.exit(1);
521
+ }
522
+ return;
450
523
  }
451
524
  console.error(chalk.red(' ✗ Server error:'), err.message);
452
525
  });
package/src/autostart.js CHANGED
@@ -12,9 +12,25 @@ function getUwonbotPath() {
12
12
  const p = execSync('which uwonbot', { encoding: 'utf8' }).trim();
13
13
  if (p) return p;
14
14
  } catch {}
15
+ const globalPaths = [
16
+ '/usr/local/bin/uwonbot',
17
+ '/opt/homebrew/bin/uwonbot',
18
+ join(homedir(), '.npm-global', 'bin', 'uwonbot'),
19
+ join(homedir(), '.nvm', 'versions', 'node', '*', 'bin', 'uwonbot'),
20
+ ];
21
+ for (const gp of globalPaths) {
22
+ if (existsSync(gp)) return gp;
23
+ }
15
24
  return 'uwonbot';
16
25
  }
17
26
 
27
+ function getNodePath() {
28
+ try {
29
+ return execSync('which node', { encoding: 'utf8' }).trim();
30
+ } catch {}
31
+ return '/usr/local/bin/node';
32
+ }
33
+
18
34
  function getMacPlistPath() {
19
35
  const dir = join(homedir(), 'Library', 'LaunchAgents');
20
36
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
@@ -36,6 +52,10 @@ export function enableAutostart() {
36
52
  const logDir = join(homedir(), '.uwonbot');
37
53
  if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
38
54
 
55
+ const nodePath = getNodePath();
56
+ const nodeDir = nodePath.replace(/\/node$/, '');
57
+ const fullPath = `/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin:${nodeDir}:${process.env.PATH || ''}`;
58
+
39
59
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
40
60
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
41
61
  <plist version="1.0">
@@ -50,7 +70,12 @@ export function enableAutostart() {
50
70
  <key>RunAtLoad</key>
51
71
  <true/>
52
72
  <key>KeepAlive</key>
53
- <true/>
73
+ <dict>
74
+ <key>SuccessfulExit</key>
75
+ <false/>
76
+ </dict>
77
+ <key>ThrottleInterval</key>
78
+ <integer>10</integer>
54
79
  <key>StandardOutPath</key>
55
80
  <string>${logPath}</string>
56
81
  <key>StandardErrorPath</key>
@@ -58,7 +83,9 @@ export function enableAutostart() {
58
83
  <key>EnvironmentVariables</key>
59
84
  <dict>
60
85
  <key>PATH</key>
61
- <string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin:${process.env.PATH || ''}</string>
86
+ <string>${fullPath}</string>
87
+ <key>HOME</key>
88
+ <string>${homedir()}</string>
62
89
  </dict>
63
90
  </dict>
64
91
  </plist>`;
@@ -69,11 +96,16 @@ export function enableAutostart() {
69
96
  } catch {}
70
97
  try {
71
98
  execSync(`launchctl load -w "${plistPath}"`);
72
- } catch {}
73
-
74
- console.log(chalk.green(' ✓ uwonbot agent 자동 시작이 등록되었습니다'));
75
- console.log(chalk.gray(` ${plistPath}`));
99
+ console.log(chalk.green(' ✓ uwonbot agent 자동 시작이 등록되었습니다'));
100
+ console.log(chalk.green(' ✓ agent가 백그라운드에서 실행되었습니다'));
101
+ } catch (e) {
102
+ console.log(chalk.yellow(` ⚠ launchctl 등록 중 문제: ${e.message}`));
103
+ }
104
+ console.log(chalk.gray(` 설정: ${plistPath}`));
76
105
  console.log(chalk.gray(` 로그: ${logPath}`));
106
+ console.log('');
107
+ console.log(chalk.white(' 컴퓨터를 켤 때마다 자동으로 agent가 실행됩니다.'));
108
+ console.log(chalk.white(' 박수 감지, OS 제어 등이 항상 준비됩니다.'));
77
109
  return true;
78
110
  }
79
111
 
@@ -25,9 +25,13 @@ export default class ClapListener {
25
25
  let micModule;
26
26
  try {
27
27
  micModule = (await import('mic')).default;
28
- } catch {
29
- console.log(chalk.yellow(' ⚠ mic module not found. Clap detection disabled.'));
30
- console.log(chalk.gray(' Install: npm install -g mic'));
28
+ if (!micModule) throw new Error('mic default export is null');
29
+ } catch (e) {
30
+ console.log(chalk.yellow(' mic 모듈을 로드할 수 없습니다. 박수 감지를 사용할 수 없습니다.'));
31
+ console.log(chalk.gray(' 해결 방법:'));
32
+ console.log(chalk.white(' 1. npm install -g uwonbot@latest (mic 포함 재설치)'));
33
+ console.log(chalk.white(' 2. brew install sox (macOS)'));
34
+ console.log(chalk.gray(` 원인: ${e.message}`));
31
35
  return false;
32
36
  }
33
37