junis 0.2.0 → 0.2.3
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/dist/cli/index.js +448 -129
- package/dist/server/mcp.js +135 -51
- package/dist/server/stdio.js +135 -51
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -31,7 +31,7 @@ var require_package = __commonJS({
|
|
|
31
31
|
"package.json"(exports2, module2) {
|
|
32
32
|
module2.exports = {
|
|
33
33
|
name: "junis",
|
|
34
|
-
version: "0.2.
|
|
34
|
+
version: "0.2.3",
|
|
35
35
|
description: "One-line device control for AI agents",
|
|
36
36
|
bin: {
|
|
37
37
|
junis: "dist/cli/index.js"
|
|
@@ -87,8 +87,15 @@ function loadConfig() {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
function saveConfig(config) {
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
try {
|
|
91
|
+
import_fs.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
92
|
+
import_fs.default.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
|
|
93
|
+
} catch (err) {
|
|
94
|
+
console.error(`
|
|
95
|
+
\u274C \uC124\uC815 \uD30C\uC77C \uC800\uC7A5 \uC2E4\uD328: ${err.message}`);
|
|
96
|
+
console.error(` \uC218\uB3D9\uC73C\uB85C ${CONFIG_FILE} \uC5D0 \uC800\uC7A5\uD574\uC8FC\uC138\uC694.`);
|
|
97
|
+
console.error(` \uB0B4\uC6A9: ${JSON.stringify(config, null, 2)}`);
|
|
98
|
+
}
|
|
92
99
|
}
|
|
93
100
|
function clearConfig() {
|
|
94
101
|
if (import_fs.default.existsSync(CONFIG_FILE)) {
|
|
@@ -113,11 +120,19 @@ var JUNIS_WEB = (() => {
|
|
|
113
120
|
return null;
|
|
114
121
|
})();
|
|
115
122
|
async function authenticate(deviceName, platform2, onBrowserOpen, onWaiting) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
123
|
+
let startRes;
|
|
124
|
+
try {
|
|
125
|
+
startRes = await fetch(`${JUNIS_API}/api/auth/device/start`, {
|
|
126
|
+
method: "POST",
|
|
127
|
+
headers: { "Content-Type": "application/json" },
|
|
128
|
+
body: JSON.stringify({ device_name: deviceName, platform: platform2 })
|
|
129
|
+
});
|
|
130
|
+
} catch (err) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`\uC11C\uBC84\uC5D0 \uC5F0\uACB0\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC778\uD130\uB137 \uC5F0\uACB0\uC744 \uD655\uC778\uD558\uAC70\uB098 \uC7A0\uC2DC \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.
|
|
133
|
+
(${err.message})`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
121
136
|
if (!startRes.ok) {
|
|
122
137
|
const body = await startRes.text().catch(() => "");
|
|
123
138
|
throw new Error(`Auth \uC2DC\uC791 \uC2E4\uD328: ${startRes.status} ${body}`);
|
|
@@ -125,15 +140,29 @@ async function authenticate(deviceName, platform2, onBrowserOpen, onWaiting) {
|
|
|
125
140
|
const startData = await startRes.json();
|
|
126
141
|
const verificationUri = JUNIS_WEB ? startData.verification_uri.replace(/^https?:\/\/[^/]+/, JUNIS_WEB) : startData.verification_uri;
|
|
127
142
|
onBrowserOpen?.(verificationUri);
|
|
128
|
-
|
|
143
|
+
try {
|
|
144
|
+
await (0, import_open.default)(verificationUri);
|
|
145
|
+
} catch {
|
|
146
|
+
console.warn(`
|
|
147
|
+
\u26A0\uFE0F \uBE0C\uB77C\uC6B0\uC800\uB97C \uC790\uB3D9\uC73C\uB85C \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC544\uB798 URL\uC744 \uC9C1\uC811 \uC5F4\uC5B4\uC8FC\uC138\uC694:
|
|
148
|
+
|
|
149
|
+
${verificationUri}
|
|
150
|
+
`);
|
|
151
|
+
}
|
|
129
152
|
const deadline = Date.now() + startData.expires_in * 1e3;
|
|
130
153
|
const intervalMs = startData.interval * 1e3;
|
|
131
154
|
let waitingCallbackCalled = false;
|
|
132
155
|
while (Date.now() < deadline) {
|
|
133
156
|
await sleep(intervalMs);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
157
|
+
let pollRes;
|
|
158
|
+
try {
|
|
159
|
+
pollRes = await fetch(
|
|
160
|
+
`${JUNIS_API}/api/auth/device/poll?code=${startData.device_code}`
|
|
161
|
+
);
|
|
162
|
+
} catch {
|
|
163
|
+
onWaiting?.();
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
137
166
|
if (pollRes.status === 202) {
|
|
138
167
|
if (!waitingCallbackCalled) {
|
|
139
168
|
waitingCallbackCalled = true;
|
|
@@ -184,15 +213,18 @@ var RelayClient = class {
|
|
|
184
213
|
if (this.destroyed) return;
|
|
185
214
|
const url = `${JUNIS_WS}/ws/devices/${this.config.device_key}`;
|
|
186
215
|
console.log(`\u{1F517} \uB9B4\uB808\uC774 \uC11C\uBC84 \uC5F0\uACB0 \uC911...`);
|
|
187
|
-
|
|
216
|
+
const ws = new import_ws.default(url, {
|
|
188
217
|
headers: { Authorization: `Bearer ${this.config.token}` }
|
|
189
218
|
});
|
|
190
|
-
this.ws
|
|
219
|
+
this.ws = ws;
|
|
220
|
+
ws.on("open", () => {
|
|
221
|
+
if (this.ws !== ws) return;
|
|
191
222
|
console.log("\u2705 \uB9B4\uB808\uC774 \uC11C\uBC84 \uC5F0\uACB0\uB428");
|
|
192
223
|
this.reconnectDelay = 1e3;
|
|
193
224
|
this.startHeartbeat();
|
|
194
225
|
});
|
|
195
|
-
|
|
226
|
+
ws.on("message", async (raw) => {
|
|
227
|
+
if (this.ws !== ws) return;
|
|
196
228
|
try {
|
|
197
229
|
const msg = JSON.parse(raw.toString());
|
|
198
230
|
if (msg.type === "pong") return;
|
|
@@ -211,9 +243,10 @@ var RelayClient = class {
|
|
|
211
243
|
} catch {
|
|
212
244
|
}
|
|
213
245
|
});
|
|
214
|
-
|
|
215
|
-
this.stopHeartbeat();
|
|
246
|
+
ws.on("close", async (code) => {
|
|
216
247
|
if (this.destroyed) return;
|
|
248
|
+
if (this.ws !== ws) return;
|
|
249
|
+
this.stopHeartbeat();
|
|
217
250
|
if (code === 4001) {
|
|
218
251
|
this.destroyed = true;
|
|
219
252
|
if (this.onAuthExpired) {
|
|
@@ -230,7 +263,7 @@ var RelayClient = class {
|
|
|
230
263
|
setTimeout(() => this.connect(), this.reconnectDelay);
|
|
231
264
|
this.reconnectDelay = Math.min(this.reconnectDelay * 2, 3e4);
|
|
232
265
|
});
|
|
233
|
-
|
|
266
|
+
ws.on("error", (err) => {
|
|
234
267
|
console.error(`\uB9B4\uB808\uC774 \uC624\uB958: ${err.message}`);
|
|
235
268
|
});
|
|
236
269
|
}
|
|
@@ -276,6 +309,7 @@ var import_path2 = __toESM(require("path"));
|
|
|
276
309
|
var import_glob = require("glob");
|
|
277
310
|
var import_zod = require("zod");
|
|
278
311
|
var execAsync = (0, import_util.promisify)(import_child_process.exec);
|
|
312
|
+
var execFileAsync = (0, import_util.promisify)(import_child_process.execFile);
|
|
279
313
|
var FilesystemTools = class {
|
|
280
314
|
register(server) {
|
|
281
315
|
server.tool(
|
|
@@ -321,8 +355,16 @@ ${error.stderr ?? ""}`
|
|
|
321
355
|
encoding: import_zod.z.enum(["utf-8", "base64"]).optional().default("utf-8").describe("\uC778\uCF54\uB529")
|
|
322
356
|
},
|
|
323
357
|
async ({ path: filePath, encoding }) => {
|
|
324
|
-
|
|
325
|
-
|
|
358
|
+
try {
|
|
359
|
+
const content = await import_promises.default.readFile(filePath, encoding);
|
|
360
|
+
return { content: [{ type: "text", text: content }] };
|
|
361
|
+
} catch (err) {
|
|
362
|
+
const e = err;
|
|
363
|
+
if (e.code === "ENOENT") {
|
|
364
|
+
return { content: [{ type: "text", text: `\u274C \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${filePath}` }], isError: true };
|
|
365
|
+
}
|
|
366
|
+
return { content: [{ type: "text", text: `\u274C \uD30C\uC77C \uC77D\uAE30 \uC2E4\uD328: ${e.message}` }], isError: true };
|
|
367
|
+
}
|
|
326
368
|
}
|
|
327
369
|
);
|
|
328
370
|
server.tool(
|
|
@@ -345,9 +387,17 @@ ${error.stderr ?? ""}`
|
|
|
345
387
|
path: import_zod.z.string().describe("\uB514\uB809\uD1A0\uB9AC \uACBD\uB85C")
|
|
346
388
|
},
|
|
347
389
|
async ({ path: dirPath }) => {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
390
|
+
try {
|
|
391
|
+
const entries = await import_promises.default.readdir(dirPath, { withFileTypes: true });
|
|
392
|
+
const lines = entries.map((e) => `${e.isDirectory() ? "\u{1F4C1}" : "\u{1F4C4}"} ${e.name}`);
|
|
393
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
394
|
+
} catch (err) {
|
|
395
|
+
const e = err;
|
|
396
|
+
if (e.code === "ENOENT") {
|
|
397
|
+
return { content: [{ type: "text", text: `\u274C \uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${dirPath}` }], isError: true };
|
|
398
|
+
}
|
|
399
|
+
return { content: [{ type: "text", text: `\u274C \uB514\uB809\uD1A0\uB9AC \uC77D\uAE30 \uC2E4\uD328: ${e.message}` }], isError: true };
|
|
400
|
+
}
|
|
351
401
|
}
|
|
352
402
|
);
|
|
353
403
|
server.tool(
|
|
@@ -360,18 +410,20 @@ ${error.stderr ?? ""}`
|
|
|
360
410
|
},
|
|
361
411
|
async ({ pattern, directory, file_pattern }) => {
|
|
362
412
|
try {
|
|
363
|
-
const { stdout } = await
|
|
364
|
-
|
|
413
|
+
const { stdout } = await execFileAsync(
|
|
414
|
+
"rg",
|
|
415
|
+
["--no-heading", "-n", pattern, directory],
|
|
365
416
|
{ timeout: 1e4 }
|
|
366
417
|
);
|
|
367
418
|
return { content: [{ type: "text", text: stdout || "\uACB0\uACFC \uC5C6\uC74C" }] };
|
|
368
419
|
} catch {
|
|
369
|
-
const
|
|
420
|
+
const safeDirectory = import_path2.default.resolve(directory);
|
|
421
|
+
const files = await (0, import_glob.glob)(file_pattern, { cwd: safeDirectory });
|
|
370
422
|
const results = [];
|
|
371
423
|
for (const file of files.slice(0, 100)) {
|
|
372
424
|
try {
|
|
373
425
|
const content = await import_promises.default.readFile(
|
|
374
|
-
import_path2.default.join(
|
|
426
|
+
import_path2.default.join(safeDirectory, file),
|
|
375
427
|
"utf-8"
|
|
376
428
|
);
|
|
377
429
|
const lines = content.split("\n");
|
|
@@ -498,6 +550,17 @@ var import_zod2 = require("zod");
|
|
|
498
550
|
var BrowserTools = class {
|
|
499
551
|
browser = null;
|
|
500
552
|
page = null;
|
|
553
|
+
// 동시 요청 시 race condition 방지용 직렬화 락
|
|
554
|
+
lock = Promise.resolve();
|
|
555
|
+
withLock(fn) {
|
|
556
|
+
let release;
|
|
557
|
+
const next = new Promise((r) => {
|
|
558
|
+
release = r;
|
|
559
|
+
});
|
|
560
|
+
const current = this.lock;
|
|
561
|
+
this.lock = this.lock.then(() => next);
|
|
562
|
+
return current.then(() => fn()).finally(() => release());
|
|
563
|
+
}
|
|
501
564
|
async init() {
|
|
502
565
|
try {
|
|
503
566
|
this.browser = await import_playwright.chromium.launch({ headless: true });
|
|
@@ -520,22 +583,22 @@ var BrowserTools = class {
|
|
|
520
583
|
"browser_navigate",
|
|
521
584
|
"URL\uB85C \uC774\uB3D9",
|
|
522
585
|
{ url: import_zod2.z.string().describe("\uC774\uB3D9\uD560 URL") },
|
|
523
|
-
|
|
586
|
+
({ url }) => this.withLock(async () => {
|
|
524
587
|
const page = requirePage();
|
|
525
588
|
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
526
589
|
return {
|
|
527
590
|
content: [{ type: "text", text: `\uC774\uB3D9 \uC644\uB8CC: ${page.url()}` }]
|
|
528
591
|
};
|
|
529
|
-
}
|
|
592
|
+
})
|
|
530
593
|
);
|
|
531
594
|
server.tool(
|
|
532
595
|
"browser_click",
|
|
533
596
|
"\uC694\uC18C \uD074\uB9AD",
|
|
534
597
|
{ selector: import_zod2.z.string().describe("CSS \uC120\uD0DD\uC790") },
|
|
535
|
-
|
|
598
|
+
({ selector }) => this.withLock(async () => {
|
|
536
599
|
await requirePage().click(selector);
|
|
537
600
|
return { content: [{ type: "text", text: "\uD074\uB9AD \uC644\uB8CC" }] };
|
|
538
|
-
}
|
|
601
|
+
})
|
|
539
602
|
);
|
|
540
603
|
server.tool(
|
|
541
604
|
"browser_type",
|
|
@@ -545,12 +608,12 @@ var BrowserTools = class {
|
|
|
545
608
|
text: import_zod2.z.string().describe("\uC785\uB825\uD560 \uD14D\uC2A4\uD2B8"),
|
|
546
609
|
clear: import_zod2.z.boolean().optional().default(false).describe("\uAE30\uC874 \uB0B4\uC6A9 \uC0AD\uC81C \uD6C4 \uC785\uB825")
|
|
547
610
|
},
|
|
548
|
-
|
|
611
|
+
({ selector, text, clear }) => this.withLock(async () => {
|
|
549
612
|
const page = requirePage();
|
|
550
613
|
if (clear) await page.fill(selector, text);
|
|
551
614
|
else await page.type(selector, text);
|
|
552
615
|
return { content: [{ type: "text", text: "\uC785\uB825 \uC644\uB8CC" }] };
|
|
553
|
-
}
|
|
616
|
+
})
|
|
554
617
|
);
|
|
555
618
|
server.tool(
|
|
556
619
|
"browser_screenshot",
|
|
@@ -559,14 +622,14 @@ var BrowserTools = class {
|
|
|
559
622
|
path: import_zod2.z.string().optional().describe("\uC800\uC7A5 \uACBD\uB85C (\uC5C6\uC73C\uBA74 base64 \uBC18\uD658)"),
|
|
560
623
|
full_page: import_zod2.z.boolean().optional().default(false)
|
|
561
624
|
},
|
|
562
|
-
|
|
625
|
+
({ path: path4, full_page }) => this.withLock(async () => {
|
|
563
626
|
const page = requirePage();
|
|
564
627
|
const screenshot = await page.screenshot({
|
|
565
|
-
path:
|
|
628
|
+
path: path4 ?? void 0,
|
|
566
629
|
fullPage: full_page
|
|
567
630
|
});
|
|
568
|
-
if (
|
|
569
|
-
return { content: [{ type: "text", text: `\uC800\uC7A5 \uC644\uB8CC: ${
|
|
631
|
+
if (path4) {
|
|
632
|
+
return { content: [{ type: "text", text: `\uC800\uC7A5 \uC644\uB8CC: ${path4}` }] };
|
|
570
633
|
}
|
|
571
634
|
return {
|
|
572
635
|
content: [
|
|
@@ -577,13 +640,13 @@ var BrowserTools = class {
|
|
|
577
640
|
}
|
|
578
641
|
]
|
|
579
642
|
};
|
|
580
|
-
}
|
|
643
|
+
})
|
|
581
644
|
);
|
|
582
645
|
server.tool(
|
|
583
646
|
"browser_snapshot",
|
|
584
647
|
"\uD398\uC774\uC9C0 \uC811\uADFC\uC131 \uD2B8\uB9AC \uC870\uD68C (\uAD6C\uC870 \uD30C\uC545\uC6A9)",
|
|
585
648
|
{},
|
|
586
|
-
async () => {
|
|
649
|
+
() => this.withLock(async () => {
|
|
587
650
|
const page = requirePage();
|
|
588
651
|
const snapshot = await page.locator("body").ariaSnapshot();
|
|
589
652
|
return {
|
|
@@ -591,29 +654,36 @@ var BrowserTools = class {
|
|
|
591
654
|
{ type: "text", text: snapshot }
|
|
592
655
|
]
|
|
593
656
|
};
|
|
594
|
-
}
|
|
657
|
+
})
|
|
595
658
|
);
|
|
596
659
|
server.tool(
|
|
597
660
|
"browser_evaluate",
|
|
598
661
|
"JavaScript \uC2E4\uD589",
|
|
599
662
|
{ code: import_zod2.z.string().describe("\uC2E4\uD589\uD560 JavaScript \uCF54\uB4DC") },
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
663
|
+
({ code }) => this.withLock(async () => {
|
|
664
|
+
try {
|
|
665
|
+
const result = await requirePage().evaluate(code);
|
|
666
|
+
return {
|
|
667
|
+
content: [
|
|
668
|
+
{ type: "text", text: typeof result === "string" ? result : JSON.stringify(result, null, 2) }
|
|
669
|
+
]
|
|
670
|
+
};
|
|
671
|
+
} catch (err) {
|
|
672
|
+
return {
|
|
673
|
+
content: [{ type: "text", text: `\u274C JavaScript \uC2E4\uD589 \uC624\uB958: ${err.message}` }],
|
|
674
|
+
isError: true
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
})
|
|
608
678
|
);
|
|
609
679
|
server.tool(
|
|
610
680
|
"browser_pdf",
|
|
611
681
|
"\uD604\uC7AC \uD398\uC774\uC9C0 PDF \uC800\uC7A5",
|
|
612
682
|
{ path: import_zod2.z.string().describe("\uC800\uC7A5 \uACBD\uB85C (.pdf)") },
|
|
613
|
-
|
|
614
|
-
await requirePage().pdf({ path:
|
|
615
|
-
return { content: [{ type: "text", text: `PDF \uC800\uC7A5 \uC644\uB8CC: ${
|
|
616
|
-
}
|
|
683
|
+
({ path: path4 }) => this.withLock(async () => {
|
|
684
|
+
await requirePage().pdf({ path: path4 });
|
|
685
|
+
return { content: [{ type: "text", text: `PDF \uC800\uC7A5 \uC644\uB8CC: ${path4}` }] };
|
|
686
|
+
})
|
|
617
687
|
);
|
|
618
688
|
}
|
|
619
689
|
};
|
|
@@ -626,7 +696,11 @@ var import_util2 = require("util");
|
|
|
626
696
|
var execAsync2 = (0, import_util2.promisify)(import_child_process2.exec);
|
|
627
697
|
async function readNotebook(filePath) {
|
|
628
698
|
const raw = await import_promises2.default.readFile(filePath, "utf-8");
|
|
629
|
-
|
|
699
|
+
try {
|
|
700
|
+
return JSON.parse(raw);
|
|
701
|
+
} catch {
|
|
702
|
+
throw new Error(`\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 Jupyter \uB178\uD2B8\uBD81 \uD30C\uC77C\uC785\uB2C8\uB2E4: ${filePath}`);
|
|
703
|
+
}
|
|
630
704
|
}
|
|
631
705
|
async function writeNotebook(filePath, nb) {
|
|
632
706
|
await import_promises2.default.writeFile(filePath, JSON.stringify(nb, null, 1), "utf-8");
|
|
@@ -721,15 +795,21 @@ var NotebookTools = class {
|
|
|
721
795
|
execution_count: cellType === "code" ? null : void 0
|
|
722
796
|
};
|
|
723
797
|
let actualIndex;
|
|
798
|
+
let warning = "";
|
|
724
799
|
if (position === void 0 || position === null) {
|
|
725
800
|
nb.cells.push(newCell);
|
|
726
801
|
actualIndex = nb.cells.length - 1;
|
|
802
|
+
} else if (position > nb.cells.length) {
|
|
803
|
+
nb.cells.push(newCell);
|
|
804
|
+
actualIndex = nb.cells.length - 1;
|
|
805
|
+
warning = ` (\uACBD\uACE0: position ${position}\uC774 \uBC94\uC704\uB97C \uCD08\uACFC\uD558\uC5EC \uB05D(index: ${actualIndex})\uC5D0 \uCD94\uAC00\uB428)`;
|
|
727
806
|
} else {
|
|
728
|
-
|
|
729
|
-
|
|
807
|
+
const clamped = Math.max(0, position);
|
|
808
|
+
nb.cells.splice(clamped, 0, newCell);
|
|
809
|
+
actualIndex = clamped;
|
|
730
810
|
}
|
|
731
811
|
await writeNotebook(filePath, nb);
|
|
732
|
-
return { content: [{ type: "text", text: `\uC140 \uCD94\uAC00 \uC644\uB8CC (index: ${actualIndex})` }] };
|
|
812
|
+
return { content: [{ type: "text", text: `\uC140 \uCD94\uAC00 \uC644\uB8CC (index: ${actualIndex})${warning}` }] };
|
|
733
813
|
}
|
|
734
814
|
);
|
|
735
815
|
server.tool(
|
|
@@ -757,13 +837,13 @@ var import_child_process3 = require("child_process");
|
|
|
757
837
|
var import_util3 = require("util");
|
|
758
838
|
var import_zod4 = require("zod");
|
|
759
839
|
var execAsync3 = (0, import_util3.promisify)(import_child_process3.exec);
|
|
840
|
+
var screenRecordPid = null;
|
|
760
841
|
function platform() {
|
|
761
842
|
if (process.platform === "darwin") return "mac";
|
|
762
843
|
if (process.platform === "win32") return "win";
|
|
763
844
|
return "linux";
|
|
764
845
|
}
|
|
765
846
|
var DeviceTools = class {
|
|
766
|
-
screenRecordPid = null;
|
|
767
847
|
register(server) {
|
|
768
848
|
server.tool(
|
|
769
849
|
"screen_capture",
|
|
@@ -773,15 +853,26 @@ var DeviceTools = class {
|
|
|
773
853
|
},
|
|
774
854
|
async ({ output_path }) => {
|
|
775
855
|
const p = platform();
|
|
856
|
+
const isTmp = !output_path;
|
|
776
857
|
const tmpPath = output_path ?? `/tmp/junis_screen_${Date.now()}.png`;
|
|
777
858
|
const cmd = {
|
|
778
859
|
mac: `screencapture -x "${tmpPath}"`,
|
|
779
860
|
win: `nircmd.exe savescreenshot "${tmpPath}"`,
|
|
780
861
|
linux: `scrot "${tmpPath}"`
|
|
781
862
|
}[p];
|
|
782
|
-
|
|
783
|
-
|
|
863
|
+
try {
|
|
864
|
+
await execAsync3(cmd);
|
|
865
|
+
} catch (err) {
|
|
866
|
+
throw new Error(`\uD654\uBA74 \uCEA1\uCC98 \uC2E4\uD328: ${err.message}`);
|
|
867
|
+
}
|
|
868
|
+
const { readFileSync, unlinkSync } = await import("fs");
|
|
784
869
|
const data = readFileSync(tmpPath).toString("base64");
|
|
870
|
+
if (isTmp) {
|
|
871
|
+
try {
|
|
872
|
+
unlinkSync(tmpPath);
|
|
873
|
+
} catch {
|
|
874
|
+
}
|
|
875
|
+
}
|
|
785
876
|
return {
|
|
786
877
|
content: [{ type: "image", data, mimeType: "image/png" }]
|
|
787
878
|
};
|
|
@@ -795,15 +886,31 @@ var DeviceTools = class {
|
|
|
795
886
|
},
|
|
796
887
|
async ({ output_path }) => {
|
|
797
888
|
const p = platform();
|
|
889
|
+
const isTmp = !output_path;
|
|
798
890
|
const tmpPath = output_path ?? `/tmp/junis_cam_${Date.now()}.jpg`;
|
|
799
891
|
const cmd = {
|
|
800
892
|
mac: `imagesnap "${tmpPath}"`,
|
|
801
893
|
win: `ffmpeg -f dshow -i video="Default" -frames:v 1 "${tmpPath}"`,
|
|
802
894
|
linux: `fswebcam -r 1280x720 "${tmpPath}"`
|
|
803
895
|
}[p];
|
|
804
|
-
|
|
805
|
-
|
|
896
|
+
try {
|
|
897
|
+
await execAsync3(cmd);
|
|
898
|
+
} catch (err) {
|
|
899
|
+
const e = err;
|
|
900
|
+
return {
|
|
901
|
+
content: [{ type: "text", text: `\u274C \uCE74\uBA54\uB77C\uB97C \uCC3E\uC744 \uC218 \uC5C6\uAC70\uB098 \uC811\uADFC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.
|
|
902
|
+
\uC6D0\uC778: ${e.message}
|
|
903
|
+
|
|
904
|
+
\uCE74\uBA54\uB77C\uAC00 \uC5F0\uACB0\uB418\uC5B4 \uC788\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694.` }],
|
|
905
|
+
isError: true
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
const { readFileSync, unlinkSync } = await import("fs");
|
|
806
909
|
const data = readFileSync(tmpPath).toString("base64");
|
|
910
|
+
if (isTmp) try {
|
|
911
|
+
unlinkSync(tmpPath);
|
|
912
|
+
} catch {
|
|
913
|
+
}
|
|
807
914
|
return {
|
|
808
915
|
content: [{ type: "image", data, mimeType: "image/jpeg" }]
|
|
809
916
|
};
|
|
@@ -818,11 +925,17 @@ var DeviceTools = class {
|
|
|
818
925
|
},
|
|
819
926
|
async ({ title, message }) => {
|
|
820
927
|
const p = platform();
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
928
|
+
let cmd;
|
|
929
|
+
if (p === "win") {
|
|
930
|
+
const script = `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show('${message.replace(/'/g, "''")}', '${title.replace(/'/g, "''")}')`;
|
|
931
|
+
const encoded = Buffer.from(script, "utf16le").toString("base64");
|
|
932
|
+
cmd = `powershell -NoProfile -EncodedCommand ${encoded}`;
|
|
933
|
+
} else {
|
|
934
|
+
cmd = {
|
|
935
|
+
mac: `osascript -e 'display notification "${message.replace(/"/g, '\\"')}" with title "${title.replace(/"/g, '\\"')}"'`,
|
|
936
|
+
linux: `notify-send "${title.replace(/"/g, '\\"')}" "${message.replace(/"/g, '\\"')}"`
|
|
937
|
+
}[p] ?? "";
|
|
938
|
+
}
|
|
826
939
|
await execAsync3(cmd);
|
|
827
940
|
return { content: [{ type: "text", text: "\uC54C\uB9BC \uC804\uC1A1 \uC644\uB8CC" }] };
|
|
828
941
|
}
|
|
@@ -863,25 +976,26 @@ var DeviceTools = class {
|
|
|
863
976
|
async ({ action, output_path }) => {
|
|
864
977
|
const p = platform();
|
|
865
978
|
if (action === "start") {
|
|
866
|
-
if (
|
|
979
|
+
if (screenRecordPid) {
|
|
867
980
|
return { content: [{ type: "text", text: "\uC774\uBBF8 \uB179\uD654 \uC911\uC785\uB2C8\uB2E4." }] };
|
|
868
981
|
}
|
|
869
982
|
const tmpPath = output_path ?? `/tmp/junis_record_${Date.now()}.mp4`;
|
|
870
|
-
const { spawn } = await import("child_process");
|
|
983
|
+
const { spawn: spawn2 } = await import("child_process");
|
|
871
984
|
const cmd = p === "mac" ? ["screencapture", ["-v", tmpPath]] : ["ffmpeg", ["-f", p === "win" ? "gdigrab" : "x11grab", "-i", p === "win" ? "desktop" : ":0.0", tmpPath]];
|
|
872
|
-
const child =
|
|
985
|
+
const child = spawn2(cmd[0], cmd[1], { detached: true, stdio: "ignore" });
|
|
873
986
|
child.unref();
|
|
874
|
-
|
|
875
|
-
return { content: [{ type: "text", text: `\uB179\uD654 \uC2DC\uC791\uB428. \uC800\uC7A5 \uACBD\uB85C: ${tmpPath} (PID: ${
|
|
987
|
+
screenRecordPid = child.pid ?? null;
|
|
988
|
+
return { content: [{ type: "text", text: `\uB179\uD654 \uC2DC\uC791\uB428. \uC800\uC7A5 \uACBD\uB85C: ${tmpPath} (PID: ${screenRecordPid})` }] };
|
|
876
989
|
} else {
|
|
877
|
-
if (!
|
|
990
|
+
if (!screenRecordPid) {
|
|
878
991
|
return { content: [{ type: "text", text: "\uD604\uC7AC \uB179\uD654 \uC911\uC774 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
879
992
|
}
|
|
880
993
|
try {
|
|
881
|
-
process.kill(
|
|
994
|
+
process.kill(screenRecordPid, "SIGINT");
|
|
995
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
882
996
|
} catch {
|
|
883
997
|
}
|
|
884
|
-
|
|
998
|
+
screenRecordPid = null;
|
|
885
999
|
return { content: [{ type: "text", text: "\uB179\uD654 \uC911\uC9C0\uB428." }] };
|
|
886
1000
|
}
|
|
887
1001
|
}
|
|
@@ -900,12 +1014,15 @@ var DeviceTools = class {
|
|
|
900
1014
|
} catch {
|
|
901
1015
|
}
|
|
902
1016
|
}
|
|
903
|
-
const res = await fetch("
|
|
1017
|
+
const res = await fetch("http://ip-api.com/json/");
|
|
904
1018
|
const data = await res.json();
|
|
1019
|
+
if (data.status !== "success") {
|
|
1020
|
+
throw new Error(`IP \uC704\uCE58 \uC870\uD68C \uC2E4\uD328: ${data.message ?? data.status}`);
|
|
1021
|
+
}
|
|
905
1022
|
return {
|
|
906
1023
|
content: [{
|
|
907
1024
|
type: "text",
|
|
908
|
-
text: `\uC704\uB3C4: ${data.
|
|
1025
|
+
text: `\uC704\uB3C4: ${data.lat}, \uACBD\uB3C4: ${data.lon}, \uB3C4\uC2DC: ${data.city}, \uAD6D\uAC00: ${data.country} (IP \uAE30\uBC18 \uCD94\uC815)`
|
|
909
1026
|
}]
|
|
910
1027
|
};
|
|
911
1028
|
}
|
|
@@ -1178,6 +1295,171 @@ async function handleMCPRequest(id, payload) {
|
|
|
1178
1295
|
return null;
|
|
1179
1296
|
}
|
|
1180
1297
|
|
|
1298
|
+
// src/cli/daemon.ts
|
|
1299
|
+
var import_fs2 = __toESM(require("fs"));
|
|
1300
|
+
var import_path3 = __toESM(require("path"));
|
|
1301
|
+
var import_os2 = __toESM(require("os"));
|
|
1302
|
+
var import_child_process4 = require("child_process");
|
|
1303
|
+
var CONFIG_DIR2 = import_path3.default.join(import_os2.default.homedir(), ".junis");
|
|
1304
|
+
var PID_FILE = import_path3.default.join(CONFIG_DIR2, "junis.pid");
|
|
1305
|
+
var LOG_DIR = import_path3.default.join(CONFIG_DIR2, "logs");
|
|
1306
|
+
var LOG_FILE = import_path3.default.join(LOG_DIR, "junis.log");
|
|
1307
|
+
var PLIST_PATH = import_path3.default.join(
|
|
1308
|
+
import_os2.default.homedir(),
|
|
1309
|
+
"Library/LaunchAgents/ai.junis.plist"
|
|
1310
|
+
);
|
|
1311
|
+
var SYSTEMD_PATH = import_path3.default.join(
|
|
1312
|
+
import_os2.default.homedir(),
|
|
1313
|
+
".config/systemd/user/junis.service"
|
|
1314
|
+
);
|
|
1315
|
+
function isRunning() {
|
|
1316
|
+
try {
|
|
1317
|
+
if (!import_fs2.default.existsSync(PID_FILE)) return { running: false };
|
|
1318
|
+
const pid = parseInt(import_fs2.default.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
1319
|
+
if (isNaN(pid)) return { running: false };
|
|
1320
|
+
process.kill(pid, 0);
|
|
1321
|
+
return { running: true, pid };
|
|
1322
|
+
} catch {
|
|
1323
|
+
try {
|
|
1324
|
+
import_fs2.default.unlinkSync(PID_FILE);
|
|
1325
|
+
} catch {
|
|
1326
|
+
}
|
|
1327
|
+
return { running: false };
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
function writePid(pid) {
|
|
1331
|
+
import_fs2.default.mkdirSync(CONFIG_DIR2, { recursive: true });
|
|
1332
|
+
import_fs2.default.writeFileSync(PID_FILE, String(pid), "utf-8");
|
|
1333
|
+
}
|
|
1334
|
+
function startDaemon(port) {
|
|
1335
|
+
import_fs2.default.mkdirSync(LOG_DIR, { recursive: true });
|
|
1336
|
+
const nodePath = process.execPath;
|
|
1337
|
+
const scriptPath = process.argv[1];
|
|
1338
|
+
const out = import_fs2.default.openSync(LOG_FILE, "a");
|
|
1339
|
+
const err = import_fs2.default.openSync(LOG_FILE, "a");
|
|
1340
|
+
const child = (0, import_child_process4.spawn)(nodePath, [scriptPath, "start", "--daemon", "--port", String(port)], {
|
|
1341
|
+
detached: true,
|
|
1342
|
+
stdio: ["ignore", out, err]
|
|
1343
|
+
});
|
|
1344
|
+
child.unref();
|
|
1345
|
+
const pid = child.pid;
|
|
1346
|
+
writePid(pid);
|
|
1347
|
+
}
|
|
1348
|
+
function stopDaemon() {
|
|
1349
|
+
const { running, pid } = isRunning();
|
|
1350
|
+
if (!running || !pid) return false;
|
|
1351
|
+
try {
|
|
1352
|
+
process.kill(pid, "SIGTERM");
|
|
1353
|
+
try {
|
|
1354
|
+
import_fs2.default.unlinkSync(PID_FILE);
|
|
1355
|
+
} catch {
|
|
1356
|
+
}
|
|
1357
|
+
return true;
|
|
1358
|
+
} catch {
|
|
1359
|
+
return false;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
var ServiceManager = class {
|
|
1363
|
+
get platform() {
|
|
1364
|
+
if (process.platform === "darwin") return "mac";
|
|
1365
|
+
if (process.platform === "win32") return "win";
|
|
1366
|
+
return "linux";
|
|
1367
|
+
}
|
|
1368
|
+
async install() {
|
|
1369
|
+
const nodePath = process.execPath;
|
|
1370
|
+
const scriptPath = process.argv[1];
|
|
1371
|
+
if (this.platform === "mac") {
|
|
1372
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1373
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1374
|
+
<plist version="1.0">
|
|
1375
|
+
<dict>
|
|
1376
|
+
<key>Label</key>
|
|
1377
|
+
<string>ai.junis</string>
|
|
1378
|
+
<key>ProgramArguments</key>
|
|
1379
|
+
<array>
|
|
1380
|
+
<string>${nodePath}</string>
|
|
1381
|
+
<string>${scriptPath}</string>
|
|
1382
|
+
<string>start</string>
|
|
1383
|
+
<string>--daemon</string>
|
|
1384
|
+
</array>
|
|
1385
|
+
<key>EnvironmentVariables</key>
|
|
1386
|
+
<dict>
|
|
1387
|
+
<key>HOME</key>
|
|
1388
|
+
<string>${import_os2.default.homedir()}</string>
|
|
1389
|
+
<key>PATH</key>
|
|
1390
|
+
<string>${process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin"}</string>
|
|
1391
|
+
</dict>
|
|
1392
|
+
<key>RunAtLoad</key>
|
|
1393
|
+
<true/>
|
|
1394
|
+
<key>KeepAlive</key>
|
|
1395
|
+
<true/>
|
|
1396
|
+
<key>StandardOutPath</key>
|
|
1397
|
+
<string>${LOG_FILE}</string>
|
|
1398
|
+
<key>StandardErrorPath</key>
|
|
1399
|
+
<string>${LOG_FILE}</string>
|
|
1400
|
+
</dict>
|
|
1401
|
+
</plist>`;
|
|
1402
|
+
import_fs2.default.mkdirSync(import_path3.default.dirname(PLIST_PATH), { recursive: true });
|
|
1403
|
+
import_fs2.default.mkdirSync(LOG_DIR, { recursive: true });
|
|
1404
|
+
import_fs2.default.writeFileSync(PLIST_PATH, plist, "utf-8");
|
|
1405
|
+
try {
|
|
1406
|
+
(0, import_child_process4.execSync)(`launchctl unload "${PLIST_PATH}" 2>/dev/null || true`);
|
|
1407
|
+
(0, import_child_process4.execSync)(`launchctl load "${PLIST_PATH}"`);
|
|
1408
|
+
} catch (e) {
|
|
1409
|
+
throw new Error(`launchctl load \uC2E4\uD328: ${e.message}`);
|
|
1410
|
+
}
|
|
1411
|
+
} else if (this.platform === "linux") {
|
|
1412
|
+
const unit = `[Unit]
|
|
1413
|
+
Description=Junis Device Agent
|
|
1414
|
+
After=network.target
|
|
1415
|
+
|
|
1416
|
+
[Service]
|
|
1417
|
+
ExecStart=${nodePath} ${scriptPath} start --daemon
|
|
1418
|
+
Restart=always
|
|
1419
|
+
RestartSec=5
|
|
1420
|
+
Environment=HOME=${import_os2.default.homedir()}
|
|
1421
|
+
Environment=PATH=${process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin"}
|
|
1422
|
+
StandardOutput=append:${LOG_FILE}
|
|
1423
|
+
StandardError=append:${LOG_FILE}
|
|
1424
|
+
|
|
1425
|
+
[Install]
|
|
1426
|
+
WantedBy=default.target`;
|
|
1427
|
+
import_fs2.default.mkdirSync(import_path3.default.dirname(SYSTEMD_PATH), { recursive: true });
|
|
1428
|
+
import_fs2.default.mkdirSync(LOG_DIR, { recursive: true });
|
|
1429
|
+
import_fs2.default.writeFileSync(SYSTEMD_PATH, unit, "utf-8");
|
|
1430
|
+
(0, import_child_process4.execSync)("systemctl --user daemon-reload");
|
|
1431
|
+
(0, import_child_process4.execSync)("systemctl --user enable junis");
|
|
1432
|
+
(0, import_child_process4.execSync)("systemctl --user start junis");
|
|
1433
|
+
} else {
|
|
1434
|
+
(0, import_child_process4.execSync)(
|
|
1435
|
+
`schtasks /Create /F /TN "Junis" /TR "${nodePath} ${scriptPath} start --daemon" /SC ONLOGON /RL HIGHEST`
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
async uninstall() {
|
|
1440
|
+
if (this.platform === "mac") {
|
|
1441
|
+
try {
|
|
1442
|
+
(0, import_child_process4.execSync)(`launchctl unload "${PLIST_PATH}" 2>/dev/null || true`);
|
|
1443
|
+
if (import_fs2.default.existsSync(PLIST_PATH)) import_fs2.default.unlinkSync(PLIST_PATH);
|
|
1444
|
+
} catch {
|
|
1445
|
+
}
|
|
1446
|
+
} else if (this.platform === "linux") {
|
|
1447
|
+
try {
|
|
1448
|
+
(0, import_child_process4.execSync)("systemctl --user stop junis 2>/dev/null || true");
|
|
1449
|
+
(0, import_child_process4.execSync)("systemctl --user disable junis 2>/dev/null || true");
|
|
1450
|
+
if (import_fs2.default.existsSync(SYSTEMD_PATH)) import_fs2.default.unlinkSync(SYSTEMD_PATH);
|
|
1451
|
+
(0, import_child_process4.execSync)("systemctl --user daemon-reload 2>/dev/null || true");
|
|
1452
|
+
} catch {
|
|
1453
|
+
}
|
|
1454
|
+
} else {
|
|
1455
|
+
try {
|
|
1456
|
+
(0, import_child_process4.execSync)('schtasks /Delete /F /TN "Junis" 2>nul || true');
|
|
1457
|
+
} catch {
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
};
|
|
1462
|
+
|
|
1181
1463
|
// src/cli/index.ts
|
|
1182
1464
|
var { version } = require_package();
|
|
1183
1465
|
import_commander.program.name("junis").description("AI\uAC00 \uB0B4 \uB514\uBC14\uC774\uC2A4\uB97C \uC644\uC804 \uC81C\uC5B4\uD558\uB294 MCP \uC11C\uBC84").version(version);
|
|
@@ -1185,9 +1467,9 @@ function getSystemInfo() {
|
|
|
1185
1467
|
const platform2 = process.platform;
|
|
1186
1468
|
if (platform2 === "darwin") {
|
|
1187
1469
|
try {
|
|
1188
|
-
const { execSync } = require("child_process");
|
|
1189
|
-
const sw =
|
|
1190
|
-
const hw =
|
|
1470
|
+
const { execSync: execSync2 } = require("child_process");
|
|
1471
|
+
const sw = execSync2("sw_vers -productVersion", { encoding: "utf8" }).trim();
|
|
1472
|
+
const hw = execSync2("sysctl -n machdep.cpu.brand_string", { encoding: "utf8" }).trim();
|
|
1191
1473
|
return `macOS ${sw} (${hw})`;
|
|
1192
1474
|
} catch {
|
|
1193
1475
|
return "macOS";
|
|
@@ -1229,22 +1511,67 @@ function printStep1(port) {
|
|
|
1229
1511
|
console.log(` \u25C9 Local MCP endpoint ........... http://localhost:${port}/mcp`);
|
|
1230
1512
|
console.log("");
|
|
1231
1513
|
}
|
|
1232
|
-
import_commander.program.command("start", { isDefault: true }).description("Junis \uC5D0\uC774\uC804\uD2B8\uC640 \uC5F0\uACB0 \uC2DC\uC791").option("--local", "\uB85C\uCEEC MCP \uC11C\uBC84\uB9CC \uC2E4\uD589 (\uD074\uB77C\uC6B0\uB4DC \uC5F0\uACB0 \uC5C6\uC74C)").option("--port <number>", "\uD3EC\uD2B8 \uBC88\uD638", "3000").option("--reset", "\uAE30\uC874 \uC778\uC99D \uCD08\uAE30\uD654 \uD6C4 \uC7AC\uB85C\uADF8\uC778").action(async (options) => {
|
|
1514
|
+
import_commander.program.command("start", { isDefault: true }).description("Junis \uC5D0\uC774\uC804\uD2B8\uC640 \uC5F0\uACB0 \uC2DC\uC791").option("--local", "\uB85C\uCEEC MCP \uC11C\uBC84\uB9CC \uC2E4\uD589 (\uD074\uB77C\uC6B0\uB4DC \uC5F0\uACB0 \uC5C6\uC74C)").option("--port <number>", "\uD3EC\uD2B8 \uBC88\uD638", "3000").option("--reset", "\uAE30\uC874 \uC778\uC99D \uCD08\uAE30\uD654 \uD6C4 \uC7AC\uB85C\uADF8\uC778").option("--daemon", "\uB370\uBAAC \uBAA8\uB4DC\uB85C \uC2E4\uD589 (\uB0B4\uBD80\uC6A9, launchd/systemd\uC5D0\uC11C \uC0AC\uC6A9)").action(async (options) => {
|
|
1233
1515
|
const port = parseInt(options.port, 10);
|
|
1516
|
+
if (options.daemon) {
|
|
1517
|
+
if (options.local) {
|
|
1518
|
+
await startMCPServer(port);
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
let config2 = options.reset ? null : loadConfig();
|
|
1522
|
+
if (!config2) {
|
|
1523
|
+
console.error("\u274C \uC778\uC99D \uC815\uBCF4 \uC5C6\uC74C. npx junis \uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
|
|
1524
|
+
process.exit(1);
|
|
1525
|
+
}
|
|
1526
|
+
const deviceName2 = config2.device_name;
|
|
1527
|
+
const platformName2 = process.platform === "darwin" ? "macos" : process.platform === "win32" ? "windows" : "linux";
|
|
1528
|
+
const actualPort = await startMCPServer(port);
|
|
1529
|
+
console.log(`[junis daemon] MCP server started on port ${actualPort}`);
|
|
1530
|
+
const relay = new RelayClient(config2, handleMCPRequest, async () => {
|
|
1531
|
+
console.log("[junis daemon] \uC138\uC158 \uB9CC\uB8CC - \uC7AC\uC778\uC99D \uD544\uC694");
|
|
1532
|
+
try {
|
|
1533
|
+
let waitingPrinted = false;
|
|
1534
|
+
const authResult = await authenticate(
|
|
1535
|
+
deviceName2,
|
|
1536
|
+
platformName2,
|
|
1537
|
+
(uri) => {
|
|
1538
|
+
console.log(`[junis daemon] \uBE0C\uB77C\uC6B0\uC800 \uC7AC\uC778\uC99D: ${uri}`);
|
|
1539
|
+
},
|
|
1540
|
+
() => {
|
|
1541
|
+
if (!waitingPrinted) waitingPrinted = true;
|
|
1542
|
+
}
|
|
1543
|
+
);
|
|
1544
|
+
config2.token = authResult.token;
|
|
1545
|
+
saveConfig(config2);
|
|
1546
|
+
relay.restart();
|
|
1547
|
+
} catch (e) {
|
|
1548
|
+
console.error("[junis daemon] \uC7AC\uC778\uC99D \uC2E4\uD328:", e);
|
|
1549
|
+
process.exit(1);
|
|
1550
|
+
}
|
|
1551
|
+
});
|
|
1552
|
+
await relay.connect();
|
|
1553
|
+
console.log("[junis daemon] relay connected");
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1234
1556
|
printBanner();
|
|
1235
1557
|
if (options.local) {
|
|
1236
|
-
const
|
|
1237
|
-
printStep1(
|
|
1558
|
+
const actualPort = await startMCPServer(port);
|
|
1559
|
+
printStep1(actualPort);
|
|
1238
1560
|
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1239
1561
|
console.log(" \u2705 ALL SET \u2014 Local MCP server is running");
|
|
1240
1562
|
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1241
1563
|
return;
|
|
1242
1564
|
}
|
|
1565
|
+
const { running, pid } = isRunning();
|
|
1566
|
+
if (running) {
|
|
1567
|
+
console.log(`\u2705 Junis \uC2E4\uD589 \uC911\uC785\uB2C8\uB2E4. (PID: ${pid})`);
|
|
1568
|
+
console.log(" \uC885\uB8CC\uD558\uB824\uBA74: npx junis stop");
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1243
1571
|
let config = options.reset ? null : loadConfig();
|
|
1244
1572
|
const deviceName = config?.device_name ?? `${process.env["USER"] ?? "user"}'s ${getDeviceName()}`;
|
|
1245
1573
|
const platformName = process.platform === "darwin" ? "macos" : process.platform === "win32" ? "windows" : "linux";
|
|
1246
|
-
|
|
1247
|
-
printStep1(actualPort);
|
|
1574
|
+
printStep1(port);
|
|
1248
1575
|
if (!config) {
|
|
1249
1576
|
let waitingPrinted = false;
|
|
1250
1577
|
const authResult = await authenticate(
|
|
@@ -1305,53 +1632,42 @@ import_commander.program.command("start", { isDefault: true }).description("Juni
|
|
|
1305
1632
|
console.log(" \u25C9 Status ....................... \u{1F7E2} online");
|
|
1306
1633
|
console.log("");
|
|
1307
1634
|
}
|
|
1308
|
-
const relay = new RelayClient(config, handleMCPRequest, async () => {
|
|
1309
|
-
console.log("\n\u{1F511} \uC138\uC158\uC774 \uB9CC\uB8CC\uB410\uC2B5\uB2C8\uB2E4. \uC790\uB3D9\uC73C\uB85C \uC7AC\uC778\uC99D\uD569\uB2C8\uB2E4...");
|
|
1310
|
-
try {
|
|
1311
|
-
let waitingPrinted = false;
|
|
1312
|
-
const authResult = await authenticate(
|
|
1313
|
-
deviceName,
|
|
1314
|
-
platformName,
|
|
1315
|
-
(uri) => {
|
|
1316
|
-
console.log(" Opening browser for re-auth...");
|
|
1317
|
-
console.log(` \u2192 ${uri}`);
|
|
1318
|
-
process.stdout.write(" Waiting for login \xB7");
|
|
1319
|
-
},
|
|
1320
|
-
() => {
|
|
1321
|
-
process.stdout.write("\xB7");
|
|
1322
|
-
}
|
|
1323
|
-
);
|
|
1324
|
-
console.log(`
|
|
1325
|
-
\u2705 \uC7AC\uC778\uC99D \uC644\uB8CC`);
|
|
1326
|
-
config.token = authResult.token;
|
|
1327
|
-
saveConfig(config);
|
|
1328
|
-
relay.restart();
|
|
1329
|
-
} catch (e) {
|
|
1330
|
-
console.error("\n\u274C \uC7AC\uC778\uC99D \uC2E4\uD328:", e);
|
|
1331
|
-
process.exit(1);
|
|
1332
|
-
}
|
|
1333
|
-
});
|
|
1334
|
-
await relay.connect();
|
|
1335
1635
|
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1336
|
-
console.log("
|
|
1636
|
+
console.log(" STEP 5 \xB7 Starting Background Service");
|
|
1337
1637
|
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1638
|
+
const svc = new ServiceManager();
|
|
1639
|
+
try {
|
|
1640
|
+
await svc.install();
|
|
1641
|
+
console.log(" \u25C9 Service registered ........... \u2705");
|
|
1642
|
+
console.log(" \u25C9 Auto-start on boot ........... \u2705");
|
|
1643
|
+
} catch (e) {
|
|
1644
|
+
console.warn(` \u26A0\uFE0F \uC11C\uBE44\uC2A4 \uB4F1\uB85D \uC2E4\uD328: ${e.message}`);
|
|
1645
|
+
console.warn(" \uBC31\uADF8\uB77C\uC6B4\uB4DC \uD504\uB85C\uC138\uC2A4\uB85C\uB9CC \uC2E4\uD589\uD569\uB2C8\uB2E4.");
|
|
1646
|
+
startDaemon(port);
|
|
1647
|
+
}
|
|
1338
1648
|
const webUrl = process.env.JUNIS_WEB_URL ?? "https://junis.ai";
|
|
1339
1649
|
console.log("");
|
|
1340
|
-
console.log("
|
|
1650
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1651
|
+
console.log(" \u2705 ALL SET \u2014 Junis\uAC00 \uBC31\uADF8\uB77C\uC6B4\uB4DC\uC5D0\uC11C \uC2E4\uD589 \uC911\uC785\uB2C8\uB2E4.");
|
|
1652
|
+
console.log(" \uBD80\uD305 \uC2DC \uC790\uB3D9\uC73C\uB85C \uC2DC\uC791\uB429\uB2C8\uB2E4.");
|
|
1653
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1654
|
+
console.log("");
|
|
1341
1655
|
console.log(` \u2192 ${webUrl}`);
|
|
1342
|
-
console.log("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
1343
|
-
console.log("\u2502 \u2502");
|
|
1344
|
-
console.log("\u2502 Try asking your AI: \u2502");
|
|
1345
|
-
console.log("\u2502 \u2502");
|
|
1346
|
-
console.log('\u2502 "\uB370\uC2A4\uD06C\uD1B1 \uD30C\uC77C \uBAA9\uB85D \uBCF4\uC5EC\uC918" \u2502');
|
|
1347
|
-
console.log('\u2502 "\uD06C\uB86C\uC5D0\uC11C \uC624\uB298 \uB274\uC2A4 \uAC80\uC0C9\uD574\uC918" \u2502');
|
|
1348
|
-
console.log('\u2502 "\uCE74\uBA54\uB77C\uB85C \uC0AC\uC9C4 \uD55C \uC7A5 \uCC0D\uC5B4\uC918" \u2502');
|
|
1349
|
-
console.log('\u2502 "\uC774 \uC8FC\uD53C\uD130 \uB178\uD2B8\uBD81 3\uBC88 \uC140 \uC218\uC815\uD574\uC918" \u2502');
|
|
1350
|
-
console.log("\u2502 \u2502");
|
|
1351
|
-
console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
1352
1656
|
console.log("");
|
|
1353
|
-
console.log("
|
|
1657
|
+
console.log(" \uC885\uB8CC\uD558\uB824\uBA74: npx junis stop");
|
|
1354
1658
|
console.log("");
|
|
1659
|
+
process.exit(0);
|
|
1660
|
+
});
|
|
1661
|
+
import_commander.program.command("stop").description("\uBC31\uADF8\uB77C\uC6B4\uB4DC \uC11C\uBE44\uC2A4 \uC911\uC9C0 \uBC0F \uC790\uB3D9\uC2DC\uC791 \uD574\uC81C").action(async () => {
|
|
1662
|
+
const stopped = stopDaemon();
|
|
1663
|
+
const svc = new ServiceManager();
|
|
1664
|
+
await svc.uninstall();
|
|
1665
|
+
if (stopped) {
|
|
1666
|
+
console.log("\u2705 Junis \uC11C\uBE44\uC2A4\uAC00 \uC911\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
1667
|
+
} else {
|
|
1668
|
+
console.log("\u2139\uFE0F \uC2E4\uD589 \uC911\uC778 Junis \uD504\uB85C\uC138\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
1669
|
+
}
|
|
1670
|
+
console.log(" \uC790\uB3D9\uC2DC\uC791\uC774 \uD574\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
1355
1671
|
});
|
|
1356
1672
|
import_commander.program.command("logout").description("\uC778\uC99D \uC815\uBCF4 \uC0AD\uC81C").action(() => {
|
|
1357
1673
|
clearConfig();
|
|
@@ -1359,14 +1675,17 @@ import_commander.program.command("logout").description("\uC778\uC99D \uC815\uBCF
|
|
|
1359
1675
|
});
|
|
1360
1676
|
import_commander.program.command("status").description("\uD604\uC7AC \uC0C1\uD0DC \uD655\uC778").action(() => {
|
|
1361
1677
|
const config = loadConfig();
|
|
1678
|
+
const { running, pid } = isRunning();
|
|
1362
1679
|
if (!config) {
|
|
1363
1680
|
console.log("\u274C \uC778\uC99D \uC5C6\uC74C (npx junis \uC2E4\uD589 \uD544\uC694)");
|
|
1681
|
+
} else if (running) {
|
|
1682
|
+
console.log(`\u2705 \uC2E4\uD589 \uC911 (PID: ${pid})`);
|
|
1683
|
+
console.log(` \uB514\uBC14\uC774\uC2A4: ${config.device_name}`);
|
|
1684
|
+
console.log(` \uB4F1\uB85D\uC77C: ${config.created_at}`);
|
|
1364
1685
|
} else {
|
|
1365
|
-
console.log(
|
|
1366
|
-
|
|
1367
|
-
\
|
|
1368
|
-
\uB4F1\uB85D\uC77C: ${config.created_at}`
|
|
1369
|
-
);
|
|
1686
|
+
console.log("\u26A0\uFE0F \uC778\uC99D\uB428, \uC11C\uBE44\uC2A4 \uC911\uC9C0 \uC0C1\uD0DC");
|
|
1687
|
+
console.log(` \uB514\uBC14\uC774\uC2A4: ${config.device_name}`);
|
|
1688
|
+
console.log(" \uC2DC\uC791\uD558\uB824\uBA74: npx junis");
|
|
1370
1689
|
}
|
|
1371
1690
|
});
|
|
1372
1691
|
import_commander.program.parse();
|