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 +1 -1
- package/package.json +2 -2
- package/src/agent.js +78 -5
- package/src/autostart.js +38 -6
- package/src/clapListener.js +7 -3
package/bin/uwonbot.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uwonbot",
|
|
3
|
-
"version": "1.1.
|
|
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
|
|
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.
|
|
448
|
-
|
|
449
|
-
|
|
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
|
-
<
|
|
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
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
package/src/clapListener.js
CHANGED
|
@@ -25,9 +25,13 @@ export default class ClapListener {
|
|
|
25
25
|
let micModule;
|
|
26
26
|
try {
|
|
27
27
|
micModule = (await import('mic')).default;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
console.log(chalk.
|
|
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
|
|