junis 0.2.0 → 0.2.2
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 +78 -35
- package/dist/server/mcp.js +71 -32
- package/dist/server/stdio.js +71 -32
- 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.2",
|
|
35
35
|
description: "One-line device control for AI agents",
|
|
36
36
|
bin: {
|
|
37
37
|
junis: "dist/cli/index.js"
|
|
@@ -87,8 +87,12 @@ 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
|
+
throw new Error(`\uC778\uC99D \uC815\uBCF4 \uC800\uC7A5 \uC2E4\uD328: ${err.message}`);
|
|
95
|
+
}
|
|
92
96
|
}
|
|
93
97
|
function clearConfig() {
|
|
94
98
|
if (import_fs.default.existsSync(CONFIG_FILE)) {
|
|
@@ -276,6 +280,7 @@ var import_path2 = __toESM(require("path"));
|
|
|
276
280
|
var import_glob = require("glob");
|
|
277
281
|
var import_zod = require("zod");
|
|
278
282
|
var execAsync = (0, import_util.promisify)(import_child_process.exec);
|
|
283
|
+
var execFileAsync = (0, import_util.promisify)(import_child_process.execFile);
|
|
279
284
|
var FilesystemTools = class {
|
|
280
285
|
register(server) {
|
|
281
286
|
server.tool(
|
|
@@ -360,8 +365,9 @@ ${error.stderr ?? ""}`
|
|
|
360
365
|
},
|
|
361
366
|
async ({ pattern, directory, file_pattern }) => {
|
|
362
367
|
try {
|
|
363
|
-
const { stdout } = await
|
|
364
|
-
|
|
368
|
+
const { stdout } = await execFileAsync(
|
|
369
|
+
"rg",
|
|
370
|
+
["--no-heading", "-n", pattern, directory],
|
|
365
371
|
{ timeout: 1e4 }
|
|
366
372
|
);
|
|
367
373
|
return { content: [{ type: "text", text: stdout || "\uACB0\uACFC \uC5C6\uC74C" }] };
|
|
@@ -498,6 +504,17 @@ var import_zod2 = require("zod");
|
|
|
498
504
|
var BrowserTools = class {
|
|
499
505
|
browser = null;
|
|
500
506
|
page = null;
|
|
507
|
+
// 동시 요청 시 race condition 방지용 직렬화 락
|
|
508
|
+
lock = Promise.resolve();
|
|
509
|
+
withLock(fn) {
|
|
510
|
+
let release;
|
|
511
|
+
const next = new Promise((r) => {
|
|
512
|
+
release = r;
|
|
513
|
+
});
|
|
514
|
+
const current = this.lock;
|
|
515
|
+
this.lock = this.lock.then(() => next);
|
|
516
|
+
return current.then(() => fn()).finally(() => release());
|
|
517
|
+
}
|
|
501
518
|
async init() {
|
|
502
519
|
try {
|
|
503
520
|
this.browser = await import_playwright.chromium.launch({ headless: true });
|
|
@@ -520,22 +537,22 @@ var BrowserTools = class {
|
|
|
520
537
|
"browser_navigate",
|
|
521
538
|
"URL\uB85C \uC774\uB3D9",
|
|
522
539
|
{ url: import_zod2.z.string().describe("\uC774\uB3D9\uD560 URL") },
|
|
523
|
-
|
|
540
|
+
({ url }) => this.withLock(async () => {
|
|
524
541
|
const page = requirePage();
|
|
525
542
|
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
526
543
|
return {
|
|
527
544
|
content: [{ type: "text", text: `\uC774\uB3D9 \uC644\uB8CC: ${page.url()}` }]
|
|
528
545
|
};
|
|
529
|
-
}
|
|
546
|
+
})
|
|
530
547
|
);
|
|
531
548
|
server.tool(
|
|
532
549
|
"browser_click",
|
|
533
550
|
"\uC694\uC18C \uD074\uB9AD",
|
|
534
551
|
{ selector: import_zod2.z.string().describe("CSS \uC120\uD0DD\uC790") },
|
|
535
|
-
|
|
552
|
+
({ selector }) => this.withLock(async () => {
|
|
536
553
|
await requirePage().click(selector);
|
|
537
554
|
return { content: [{ type: "text", text: "\uD074\uB9AD \uC644\uB8CC" }] };
|
|
538
|
-
}
|
|
555
|
+
})
|
|
539
556
|
);
|
|
540
557
|
server.tool(
|
|
541
558
|
"browser_type",
|
|
@@ -545,12 +562,12 @@ var BrowserTools = class {
|
|
|
545
562
|
text: import_zod2.z.string().describe("\uC785\uB825\uD560 \uD14D\uC2A4\uD2B8"),
|
|
546
563
|
clear: import_zod2.z.boolean().optional().default(false).describe("\uAE30\uC874 \uB0B4\uC6A9 \uC0AD\uC81C \uD6C4 \uC785\uB825")
|
|
547
564
|
},
|
|
548
|
-
|
|
565
|
+
({ selector, text, clear }) => this.withLock(async () => {
|
|
549
566
|
const page = requirePage();
|
|
550
567
|
if (clear) await page.fill(selector, text);
|
|
551
568
|
else await page.type(selector, text);
|
|
552
569
|
return { content: [{ type: "text", text: "\uC785\uB825 \uC644\uB8CC" }] };
|
|
553
|
-
}
|
|
570
|
+
})
|
|
554
571
|
);
|
|
555
572
|
server.tool(
|
|
556
573
|
"browser_screenshot",
|
|
@@ -559,7 +576,7 @@ var BrowserTools = class {
|
|
|
559
576
|
path: import_zod2.z.string().optional().describe("\uC800\uC7A5 \uACBD\uB85C (\uC5C6\uC73C\uBA74 base64 \uBC18\uD658)"),
|
|
560
577
|
full_page: import_zod2.z.boolean().optional().default(false)
|
|
561
578
|
},
|
|
562
|
-
|
|
579
|
+
({ path: path3, full_page }) => this.withLock(async () => {
|
|
563
580
|
const page = requirePage();
|
|
564
581
|
const screenshot = await page.screenshot({
|
|
565
582
|
path: path3 ?? void 0,
|
|
@@ -577,13 +594,13 @@ var BrowserTools = class {
|
|
|
577
594
|
}
|
|
578
595
|
]
|
|
579
596
|
};
|
|
580
|
-
}
|
|
597
|
+
})
|
|
581
598
|
);
|
|
582
599
|
server.tool(
|
|
583
600
|
"browser_snapshot",
|
|
584
601
|
"\uD398\uC774\uC9C0 \uC811\uADFC\uC131 \uD2B8\uB9AC \uC870\uD68C (\uAD6C\uC870 \uD30C\uC545\uC6A9)",
|
|
585
602
|
{},
|
|
586
|
-
async () => {
|
|
603
|
+
() => this.withLock(async () => {
|
|
587
604
|
const page = requirePage();
|
|
588
605
|
const snapshot = await page.locator("body").ariaSnapshot();
|
|
589
606
|
return {
|
|
@@ -591,29 +608,29 @@ var BrowserTools = class {
|
|
|
591
608
|
{ type: "text", text: snapshot }
|
|
592
609
|
]
|
|
593
610
|
};
|
|
594
|
-
}
|
|
611
|
+
})
|
|
595
612
|
);
|
|
596
613
|
server.tool(
|
|
597
614
|
"browser_evaluate",
|
|
598
615
|
"JavaScript \uC2E4\uD589",
|
|
599
616
|
{ code: import_zod2.z.string().describe("\uC2E4\uD589\uD560 JavaScript \uCF54\uB4DC") },
|
|
600
|
-
|
|
617
|
+
({ code }) => this.withLock(async () => {
|
|
601
618
|
const result = await requirePage().evaluate(code);
|
|
602
619
|
return {
|
|
603
620
|
content: [
|
|
604
621
|
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
605
622
|
]
|
|
606
623
|
};
|
|
607
|
-
}
|
|
624
|
+
})
|
|
608
625
|
);
|
|
609
626
|
server.tool(
|
|
610
627
|
"browser_pdf",
|
|
611
628
|
"\uD604\uC7AC \uD398\uC774\uC9C0 PDF \uC800\uC7A5",
|
|
612
629
|
{ path: import_zod2.z.string().describe("\uC800\uC7A5 \uACBD\uB85C (.pdf)") },
|
|
613
|
-
|
|
630
|
+
({ path: path3 }) => this.withLock(async () => {
|
|
614
631
|
await requirePage().pdf({ path: path3 });
|
|
615
632
|
return { content: [{ type: "text", text: `PDF \uC800\uC7A5 \uC644\uB8CC: ${path3}` }] };
|
|
616
|
-
}
|
|
633
|
+
})
|
|
617
634
|
);
|
|
618
635
|
}
|
|
619
636
|
};
|
|
@@ -626,7 +643,11 @@ var import_util2 = require("util");
|
|
|
626
643
|
var execAsync2 = (0, import_util2.promisify)(import_child_process2.exec);
|
|
627
644
|
async function readNotebook(filePath) {
|
|
628
645
|
const raw = await import_promises2.default.readFile(filePath, "utf-8");
|
|
629
|
-
|
|
646
|
+
try {
|
|
647
|
+
return JSON.parse(raw);
|
|
648
|
+
} catch {
|
|
649
|
+
throw new Error(`\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 .ipynb \uD30C\uC77C: ${filePath}`);
|
|
650
|
+
}
|
|
630
651
|
}
|
|
631
652
|
async function writeNotebook(filePath, nb) {
|
|
632
653
|
await import_promises2.default.writeFile(filePath, JSON.stringify(nb, null, 1), "utf-8");
|
|
@@ -725,8 +746,9 @@ var NotebookTools = class {
|
|
|
725
746
|
nb.cells.push(newCell);
|
|
726
747
|
actualIndex = nb.cells.length - 1;
|
|
727
748
|
} else {
|
|
728
|
-
|
|
729
|
-
|
|
749
|
+
const clamped = Math.max(0, Math.min(position, nb.cells.length));
|
|
750
|
+
nb.cells.splice(clamped, 0, newCell);
|
|
751
|
+
actualIndex = clamped;
|
|
730
752
|
}
|
|
731
753
|
await writeNotebook(filePath, nb);
|
|
732
754
|
return { content: [{ type: "text", text: `\uC140 \uCD94\uAC00 \uC644\uB8CC (index: ${actualIndex})` }] };
|
|
@@ -757,13 +779,13 @@ var import_child_process3 = require("child_process");
|
|
|
757
779
|
var import_util3 = require("util");
|
|
758
780
|
var import_zod4 = require("zod");
|
|
759
781
|
var execAsync3 = (0, import_util3.promisify)(import_child_process3.exec);
|
|
782
|
+
var screenRecordPid = null;
|
|
760
783
|
function platform() {
|
|
761
784
|
if (process.platform === "darwin") return "mac";
|
|
762
785
|
if (process.platform === "win32") return "win";
|
|
763
786
|
return "linux";
|
|
764
787
|
}
|
|
765
788
|
var DeviceTools = class {
|
|
766
|
-
screenRecordPid = null;
|
|
767
789
|
register(server) {
|
|
768
790
|
server.tool(
|
|
769
791
|
"screen_capture",
|
|
@@ -773,15 +795,24 @@ var DeviceTools = class {
|
|
|
773
795
|
},
|
|
774
796
|
async ({ output_path }) => {
|
|
775
797
|
const p = platform();
|
|
798
|
+
const isTmp = !output_path;
|
|
776
799
|
const tmpPath = output_path ?? `/tmp/junis_screen_${Date.now()}.png`;
|
|
777
800
|
const cmd = {
|
|
778
801
|
mac: `screencapture -x "${tmpPath}"`,
|
|
779
802
|
win: `nircmd.exe savescreenshot "${tmpPath}"`,
|
|
780
803
|
linux: `scrot "${tmpPath}"`
|
|
781
804
|
}[p];
|
|
782
|
-
|
|
783
|
-
|
|
805
|
+
try {
|
|
806
|
+
await execAsync3(cmd);
|
|
807
|
+
} catch (err) {
|
|
808
|
+
throw new Error(`\uD654\uBA74 \uCEA1\uCC98 \uC2E4\uD328: ${err.message}`);
|
|
809
|
+
}
|
|
810
|
+
const { readFileSync, unlinkSync } = await import("fs");
|
|
784
811
|
const data = readFileSync(tmpPath).toString("base64");
|
|
812
|
+
if (isTmp) try {
|
|
813
|
+
unlinkSync(tmpPath);
|
|
814
|
+
} catch {
|
|
815
|
+
}
|
|
785
816
|
return {
|
|
786
817
|
content: [{ type: "image", data, mimeType: "image/png" }]
|
|
787
818
|
};
|
|
@@ -795,15 +826,24 @@ var DeviceTools = class {
|
|
|
795
826
|
},
|
|
796
827
|
async ({ output_path }) => {
|
|
797
828
|
const p = platform();
|
|
829
|
+
const isTmp = !output_path;
|
|
798
830
|
const tmpPath = output_path ?? `/tmp/junis_cam_${Date.now()}.jpg`;
|
|
799
831
|
const cmd = {
|
|
800
832
|
mac: `imagesnap "${tmpPath}"`,
|
|
801
833
|
win: `ffmpeg -f dshow -i video="Default" -frames:v 1 "${tmpPath}"`,
|
|
802
834
|
linux: `fswebcam -r 1280x720 "${tmpPath}"`
|
|
803
835
|
}[p];
|
|
804
|
-
|
|
805
|
-
|
|
836
|
+
try {
|
|
837
|
+
await execAsync3(cmd);
|
|
838
|
+
} catch (err) {
|
|
839
|
+
throw new Error(`\uCE74\uBA54\uB77C \uCD2C\uC601 \uC2E4\uD328: ${err.message}`);
|
|
840
|
+
}
|
|
841
|
+
const { readFileSync, unlinkSync } = await import("fs");
|
|
806
842
|
const data = readFileSync(tmpPath).toString("base64");
|
|
843
|
+
if (isTmp) try {
|
|
844
|
+
unlinkSync(tmpPath);
|
|
845
|
+
} catch {
|
|
846
|
+
}
|
|
807
847
|
return {
|
|
808
848
|
content: [{ type: "image", data, mimeType: "image/jpeg" }]
|
|
809
849
|
};
|
|
@@ -863,7 +903,7 @@ var DeviceTools = class {
|
|
|
863
903
|
async ({ action, output_path }) => {
|
|
864
904
|
const p = platform();
|
|
865
905
|
if (action === "start") {
|
|
866
|
-
if (
|
|
906
|
+
if (screenRecordPid) {
|
|
867
907
|
return { content: [{ type: "text", text: "\uC774\uBBF8 \uB179\uD654 \uC911\uC785\uB2C8\uB2E4." }] };
|
|
868
908
|
}
|
|
869
909
|
const tmpPath = output_path ?? `/tmp/junis_record_${Date.now()}.mp4`;
|
|
@@ -871,17 +911,17 @@ var DeviceTools = class {
|
|
|
871
911
|
const cmd = p === "mac" ? ["screencapture", ["-v", tmpPath]] : ["ffmpeg", ["-f", p === "win" ? "gdigrab" : "x11grab", "-i", p === "win" ? "desktop" : ":0.0", tmpPath]];
|
|
872
912
|
const child = spawn(cmd[0], cmd[1], { detached: true, stdio: "ignore" });
|
|
873
913
|
child.unref();
|
|
874
|
-
|
|
875
|
-
return { content: [{ type: "text", text: `\uB179\uD654 \uC2DC\uC791\uB428. \uC800\uC7A5 \uACBD\uB85C: ${tmpPath} (PID: ${
|
|
914
|
+
screenRecordPid = child.pid ?? null;
|
|
915
|
+
return { content: [{ type: "text", text: `\uB179\uD654 \uC2DC\uC791\uB428. \uC800\uC7A5 \uACBD\uB85C: ${tmpPath} (PID: ${screenRecordPid})` }] };
|
|
876
916
|
} else {
|
|
877
|
-
if (!
|
|
917
|
+
if (!screenRecordPid) {
|
|
878
918
|
return { content: [{ type: "text", text: "\uD604\uC7AC \uB179\uD654 \uC911\uC774 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
879
919
|
}
|
|
880
920
|
try {
|
|
881
|
-
process.kill(
|
|
921
|
+
process.kill(screenRecordPid, "SIGINT");
|
|
882
922
|
} catch {
|
|
883
923
|
}
|
|
884
|
-
|
|
924
|
+
screenRecordPid = null;
|
|
885
925
|
return { content: [{ type: "text", text: "\uB179\uD654 \uC911\uC9C0\uB428." }] };
|
|
886
926
|
}
|
|
887
927
|
}
|
|
@@ -900,12 +940,15 @@ var DeviceTools = class {
|
|
|
900
940
|
} catch {
|
|
901
941
|
}
|
|
902
942
|
}
|
|
903
|
-
const res = await fetch("
|
|
943
|
+
const res = await fetch("http://ip-api.com/json/");
|
|
904
944
|
const data = await res.json();
|
|
945
|
+
if (data.status !== "success") {
|
|
946
|
+
throw new Error(`IP \uC704\uCE58 \uC870\uD68C \uC2E4\uD328: ${data.message ?? data.status}`);
|
|
947
|
+
}
|
|
905
948
|
return {
|
|
906
949
|
content: [{
|
|
907
950
|
type: "text",
|
|
908
|
-
text: `\uC704\uB3C4: ${data.
|
|
951
|
+
text: `\uC704\uB3C4: ${data.lat}, \uACBD\uB3C4: ${data.lon}, \uB3C4\uC2DC: ${data.city}, \uAD6D\uAC00: ${data.country} (IP \uAE30\uBC18 \uCD94\uC815)`
|
|
909
952
|
}]
|
|
910
953
|
};
|
|
911
954
|
}
|
package/dist/server/mcp.js
CHANGED
|
@@ -46,6 +46,7 @@ var import_path = __toESM(require("path"));
|
|
|
46
46
|
var import_glob = require("glob");
|
|
47
47
|
var import_zod = require("zod");
|
|
48
48
|
var execAsync = (0, import_util.promisify)(import_child_process.exec);
|
|
49
|
+
var execFileAsync = (0, import_util.promisify)(import_child_process.execFile);
|
|
49
50
|
var FilesystemTools = class {
|
|
50
51
|
register(server) {
|
|
51
52
|
server.tool(
|
|
@@ -130,8 +131,9 @@ ${error.stderr ?? ""}`
|
|
|
130
131
|
},
|
|
131
132
|
async ({ pattern, directory, file_pattern }) => {
|
|
132
133
|
try {
|
|
133
|
-
const { stdout } = await
|
|
134
|
-
|
|
134
|
+
const { stdout } = await execFileAsync(
|
|
135
|
+
"rg",
|
|
136
|
+
["--no-heading", "-n", pattern, directory],
|
|
135
137
|
{ timeout: 1e4 }
|
|
136
138
|
);
|
|
137
139
|
return { content: [{ type: "text", text: stdout || "\uACB0\uACFC \uC5C6\uC74C" }] };
|
|
@@ -268,6 +270,17 @@ var import_zod2 = require("zod");
|
|
|
268
270
|
var BrowserTools = class {
|
|
269
271
|
browser = null;
|
|
270
272
|
page = null;
|
|
273
|
+
// 동시 요청 시 race condition 방지용 직렬화 락
|
|
274
|
+
lock = Promise.resolve();
|
|
275
|
+
withLock(fn) {
|
|
276
|
+
let release;
|
|
277
|
+
const next = new Promise((r) => {
|
|
278
|
+
release = r;
|
|
279
|
+
});
|
|
280
|
+
const current = this.lock;
|
|
281
|
+
this.lock = this.lock.then(() => next);
|
|
282
|
+
return current.then(() => fn()).finally(() => release());
|
|
283
|
+
}
|
|
271
284
|
async init() {
|
|
272
285
|
try {
|
|
273
286
|
this.browser = await import_playwright.chromium.launch({ headless: true });
|
|
@@ -290,22 +303,22 @@ var BrowserTools = class {
|
|
|
290
303
|
"browser_navigate",
|
|
291
304
|
"URL\uB85C \uC774\uB3D9",
|
|
292
305
|
{ url: import_zod2.z.string().describe("\uC774\uB3D9\uD560 URL") },
|
|
293
|
-
|
|
306
|
+
({ url }) => this.withLock(async () => {
|
|
294
307
|
const page = requirePage();
|
|
295
308
|
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
296
309
|
return {
|
|
297
310
|
content: [{ type: "text", text: `\uC774\uB3D9 \uC644\uB8CC: ${page.url()}` }]
|
|
298
311
|
};
|
|
299
|
-
}
|
|
312
|
+
})
|
|
300
313
|
);
|
|
301
314
|
server.tool(
|
|
302
315
|
"browser_click",
|
|
303
316
|
"\uC694\uC18C \uD074\uB9AD",
|
|
304
317
|
{ selector: import_zod2.z.string().describe("CSS \uC120\uD0DD\uC790") },
|
|
305
|
-
|
|
318
|
+
({ selector }) => this.withLock(async () => {
|
|
306
319
|
await requirePage().click(selector);
|
|
307
320
|
return { content: [{ type: "text", text: "\uD074\uB9AD \uC644\uB8CC" }] };
|
|
308
|
-
}
|
|
321
|
+
})
|
|
309
322
|
);
|
|
310
323
|
server.tool(
|
|
311
324
|
"browser_type",
|
|
@@ -315,12 +328,12 @@ var BrowserTools = class {
|
|
|
315
328
|
text: import_zod2.z.string().describe("\uC785\uB825\uD560 \uD14D\uC2A4\uD2B8"),
|
|
316
329
|
clear: import_zod2.z.boolean().optional().default(false).describe("\uAE30\uC874 \uB0B4\uC6A9 \uC0AD\uC81C \uD6C4 \uC785\uB825")
|
|
317
330
|
},
|
|
318
|
-
|
|
331
|
+
({ selector, text, clear }) => this.withLock(async () => {
|
|
319
332
|
const page = requirePage();
|
|
320
333
|
if (clear) await page.fill(selector, text);
|
|
321
334
|
else await page.type(selector, text);
|
|
322
335
|
return { content: [{ type: "text", text: "\uC785\uB825 \uC644\uB8CC" }] };
|
|
323
|
-
}
|
|
336
|
+
})
|
|
324
337
|
);
|
|
325
338
|
server.tool(
|
|
326
339
|
"browser_screenshot",
|
|
@@ -329,7 +342,7 @@ var BrowserTools = class {
|
|
|
329
342
|
path: import_zod2.z.string().optional().describe("\uC800\uC7A5 \uACBD\uB85C (\uC5C6\uC73C\uBA74 base64 \uBC18\uD658)"),
|
|
330
343
|
full_page: import_zod2.z.boolean().optional().default(false)
|
|
331
344
|
},
|
|
332
|
-
|
|
345
|
+
({ path: path2, full_page }) => this.withLock(async () => {
|
|
333
346
|
const page = requirePage();
|
|
334
347
|
const screenshot = await page.screenshot({
|
|
335
348
|
path: path2 ?? void 0,
|
|
@@ -347,13 +360,13 @@ var BrowserTools = class {
|
|
|
347
360
|
}
|
|
348
361
|
]
|
|
349
362
|
};
|
|
350
|
-
}
|
|
363
|
+
})
|
|
351
364
|
);
|
|
352
365
|
server.tool(
|
|
353
366
|
"browser_snapshot",
|
|
354
367
|
"\uD398\uC774\uC9C0 \uC811\uADFC\uC131 \uD2B8\uB9AC \uC870\uD68C (\uAD6C\uC870 \uD30C\uC545\uC6A9)",
|
|
355
368
|
{},
|
|
356
|
-
async () => {
|
|
369
|
+
() => this.withLock(async () => {
|
|
357
370
|
const page = requirePage();
|
|
358
371
|
const snapshot = await page.locator("body").ariaSnapshot();
|
|
359
372
|
return {
|
|
@@ -361,29 +374,29 @@ var BrowserTools = class {
|
|
|
361
374
|
{ type: "text", text: snapshot }
|
|
362
375
|
]
|
|
363
376
|
};
|
|
364
|
-
}
|
|
377
|
+
})
|
|
365
378
|
);
|
|
366
379
|
server.tool(
|
|
367
380
|
"browser_evaluate",
|
|
368
381
|
"JavaScript \uC2E4\uD589",
|
|
369
382
|
{ code: import_zod2.z.string().describe("\uC2E4\uD589\uD560 JavaScript \uCF54\uB4DC") },
|
|
370
|
-
|
|
383
|
+
({ code }) => this.withLock(async () => {
|
|
371
384
|
const result = await requirePage().evaluate(code);
|
|
372
385
|
return {
|
|
373
386
|
content: [
|
|
374
387
|
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
375
388
|
]
|
|
376
389
|
};
|
|
377
|
-
}
|
|
390
|
+
})
|
|
378
391
|
);
|
|
379
392
|
server.tool(
|
|
380
393
|
"browser_pdf",
|
|
381
394
|
"\uD604\uC7AC \uD398\uC774\uC9C0 PDF \uC800\uC7A5",
|
|
382
395
|
{ path: import_zod2.z.string().describe("\uC800\uC7A5 \uACBD\uB85C (.pdf)") },
|
|
383
|
-
|
|
396
|
+
({ path: path2 }) => this.withLock(async () => {
|
|
384
397
|
await requirePage().pdf({ path: path2 });
|
|
385
398
|
return { content: [{ type: "text", text: `PDF \uC800\uC7A5 \uC644\uB8CC: ${path2}` }] };
|
|
386
|
-
}
|
|
399
|
+
})
|
|
387
400
|
);
|
|
388
401
|
}
|
|
389
402
|
};
|
|
@@ -396,7 +409,11 @@ var import_util2 = require("util");
|
|
|
396
409
|
var execAsync2 = (0, import_util2.promisify)(import_child_process2.exec);
|
|
397
410
|
async function readNotebook(filePath) {
|
|
398
411
|
const raw = await import_promises2.default.readFile(filePath, "utf-8");
|
|
399
|
-
|
|
412
|
+
try {
|
|
413
|
+
return JSON.parse(raw);
|
|
414
|
+
} catch {
|
|
415
|
+
throw new Error(`\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 .ipynb \uD30C\uC77C: ${filePath}`);
|
|
416
|
+
}
|
|
400
417
|
}
|
|
401
418
|
async function writeNotebook(filePath, nb) {
|
|
402
419
|
await import_promises2.default.writeFile(filePath, JSON.stringify(nb, null, 1), "utf-8");
|
|
@@ -495,8 +512,9 @@ var NotebookTools = class {
|
|
|
495
512
|
nb.cells.push(newCell);
|
|
496
513
|
actualIndex = nb.cells.length - 1;
|
|
497
514
|
} else {
|
|
498
|
-
|
|
499
|
-
|
|
515
|
+
const clamped = Math.max(0, Math.min(position, nb.cells.length));
|
|
516
|
+
nb.cells.splice(clamped, 0, newCell);
|
|
517
|
+
actualIndex = clamped;
|
|
500
518
|
}
|
|
501
519
|
await writeNotebook(filePath, nb);
|
|
502
520
|
return { content: [{ type: "text", text: `\uC140 \uCD94\uAC00 \uC644\uB8CC (index: ${actualIndex})` }] };
|
|
@@ -527,13 +545,13 @@ var import_child_process3 = require("child_process");
|
|
|
527
545
|
var import_util3 = require("util");
|
|
528
546
|
var import_zod4 = require("zod");
|
|
529
547
|
var execAsync3 = (0, import_util3.promisify)(import_child_process3.exec);
|
|
548
|
+
var screenRecordPid = null;
|
|
530
549
|
function platform() {
|
|
531
550
|
if (process.platform === "darwin") return "mac";
|
|
532
551
|
if (process.platform === "win32") return "win";
|
|
533
552
|
return "linux";
|
|
534
553
|
}
|
|
535
554
|
var DeviceTools = class {
|
|
536
|
-
screenRecordPid = null;
|
|
537
555
|
register(server) {
|
|
538
556
|
server.tool(
|
|
539
557
|
"screen_capture",
|
|
@@ -543,15 +561,24 @@ var DeviceTools = class {
|
|
|
543
561
|
},
|
|
544
562
|
async ({ output_path }) => {
|
|
545
563
|
const p = platform();
|
|
564
|
+
const isTmp = !output_path;
|
|
546
565
|
const tmpPath = output_path ?? `/tmp/junis_screen_${Date.now()}.png`;
|
|
547
566
|
const cmd = {
|
|
548
567
|
mac: `screencapture -x "${tmpPath}"`,
|
|
549
568
|
win: `nircmd.exe savescreenshot "${tmpPath}"`,
|
|
550
569
|
linux: `scrot "${tmpPath}"`
|
|
551
570
|
}[p];
|
|
552
|
-
|
|
553
|
-
|
|
571
|
+
try {
|
|
572
|
+
await execAsync3(cmd);
|
|
573
|
+
} catch (err) {
|
|
574
|
+
throw new Error(`\uD654\uBA74 \uCEA1\uCC98 \uC2E4\uD328: ${err.message}`);
|
|
575
|
+
}
|
|
576
|
+
const { readFileSync, unlinkSync } = await import("fs");
|
|
554
577
|
const data = readFileSync(tmpPath).toString("base64");
|
|
578
|
+
if (isTmp) try {
|
|
579
|
+
unlinkSync(tmpPath);
|
|
580
|
+
} catch {
|
|
581
|
+
}
|
|
555
582
|
return {
|
|
556
583
|
content: [{ type: "image", data, mimeType: "image/png" }]
|
|
557
584
|
};
|
|
@@ -565,15 +592,24 @@ var DeviceTools = class {
|
|
|
565
592
|
},
|
|
566
593
|
async ({ output_path }) => {
|
|
567
594
|
const p = platform();
|
|
595
|
+
const isTmp = !output_path;
|
|
568
596
|
const tmpPath = output_path ?? `/tmp/junis_cam_${Date.now()}.jpg`;
|
|
569
597
|
const cmd = {
|
|
570
598
|
mac: `imagesnap "${tmpPath}"`,
|
|
571
599
|
win: `ffmpeg -f dshow -i video="Default" -frames:v 1 "${tmpPath}"`,
|
|
572
600
|
linux: `fswebcam -r 1280x720 "${tmpPath}"`
|
|
573
601
|
}[p];
|
|
574
|
-
|
|
575
|
-
|
|
602
|
+
try {
|
|
603
|
+
await execAsync3(cmd);
|
|
604
|
+
} catch (err) {
|
|
605
|
+
throw new Error(`\uCE74\uBA54\uB77C \uCD2C\uC601 \uC2E4\uD328: ${err.message}`);
|
|
606
|
+
}
|
|
607
|
+
const { readFileSync, unlinkSync } = await import("fs");
|
|
576
608
|
const data = readFileSync(tmpPath).toString("base64");
|
|
609
|
+
if (isTmp) try {
|
|
610
|
+
unlinkSync(tmpPath);
|
|
611
|
+
} catch {
|
|
612
|
+
}
|
|
577
613
|
return {
|
|
578
614
|
content: [{ type: "image", data, mimeType: "image/jpeg" }]
|
|
579
615
|
};
|
|
@@ -633,7 +669,7 @@ var DeviceTools = class {
|
|
|
633
669
|
async ({ action, output_path }) => {
|
|
634
670
|
const p = platform();
|
|
635
671
|
if (action === "start") {
|
|
636
|
-
if (
|
|
672
|
+
if (screenRecordPid) {
|
|
637
673
|
return { content: [{ type: "text", text: "\uC774\uBBF8 \uB179\uD654 \uC911\uC785\uB2C8\uB2E4." }] };
|
|
638
674
|
}
|
|
639
675
|
const tmpPath = output_path ?? `/tmp/junis_record_${Date.now()}.mp4`;
|
|
@@ -641,17 +677,17 @@ var DeviceTools = class {
|
|
|
641
677
|
const cmd = p === "mac" ? ["screencapture", ["-v", tmpPath]] : ["ffmpeg", ["-f", p === "win" ? "gdigrab" : "x11grab", "-i", p === "win" ? "desktop" : ":0.0", tmpPath]];
|
|
642
678
|
const child = spawn(cmd[0], cmd[1], { detached: true, stdio: "ignore" });
|
|
643
679
|
child.unref();
|
|
644
|
-
|
|
645
|
-
return { content: [{ type: "text", text: `\uB179\uD654 \uC2DC\uC791\uB428. \uC800\uC7A5 \uACBD\uB85C: ${tmpPath} (PID: ${
|
|
680
|
+
screenRecordPid = child.pid ?? null;
|
|
681
|
+
return { content: [{ type: "text", text: `\uB179\uD654 \uC2DC\uC791\uB428. \uC800\uC7A5 \uACBD\uB85C: ${tmpPath} (PID: ${screenRecordPid})` }] };
|
|
646
682
|
} else {
|
|
647
|
-
if (!
|
|
683
|
+
if (!screenRecordPid) {
|
|
648
684
|
return { content: [{ type: "text", text: "\uD604\uC7AC \uB179\uD654 \uC911\uC774 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
649
685
|
}
|
|
650
686
|
try {
|
|
651
|
-
process.kill(
|
|
687
|
+
process.kill(screenRecordPid, "SIGINT");
|
|
652
688
|
} catch {
|
|
653
689
|
}
|
|
654
|
-
|
|
690
|
+
screenRecordPid = null;
|
|
655
691
|
return { content: [{ type: "text", text: "\uB179\uD654 \uC911\uC9C0\uB428." }] };
|
|
656
692
|
}
|
|
657
693
|
}
|
|
@@ -670,12 +706,15 @@ var DeviceTools = class {
|
|
|
670
706
|
} catch {
|
|
671
707
|
}
|
|
672
708
|
}
|
|
673
|
-
const res = await fetch("
|
|
709
|
+
const res = await fetch("http://ip-api.com/json/");
|
|
674
710
|
const data = await res.json();
|
|
711
|
+
if (data.status !== "success") {
|
|
712
|
+
throw new Error(`IP \uC704\uCE58 \uC870\uD68C \uC2E4\uD328: ${data.message ?? data.status}`);
|
|
713
|
+
}
|
|
675
714
|
return {
|
|
676
715
|
content: [{
|
|
677
716
|
type: "text",
|
|
678
|
-
text: `\uC704\uB3C4: ${data.
|
|
717
|
+
text: `\uC704\uB3C4: ${data.lat}, \uACBD\uB3C4: ${data.lon}, \uB3C4\uC2DC: ${data.city}, \uAD6D\uAC00: ${data.country} (IP \uAE30\uBC18 \uCD94\uC815)`
|
|
679
718
|
}]
|
|
680
719
|
};
|
|
681
720
|
}
|
package/dist/server/stdio.js
CHANGED
|
@@ -35,6 +35,7 @@ var import_path = __toESM(require("path"));
|
|
|
35
35
|
var import_glob = require("glob");
|
|
36
36
|
var import_zod = require("zod");
|
|
37
37
|
var execAsync = (0, import_util.promisify)(import_child_process.exec);
|
|
38
|
+
var execFileAsync = (0, import_util.promisify)(import_child_process.execFile);
|
|
38
39
|
var FilesystemTools = class {
|
|
39
40
|
register(server) {
|
|
40
41
|
server.tool(
|
|
@@ -119,8 +120,9 @@ ${error.stderr ?? ""}`
|
|
|
119
120
|
},
|
|
120
121
|
async ({ pattern, directory, file_pattern }) => {
|
|
121
122
|
try {
|
|
122
|
-
const { stdout } = await
|
|
123
|
-
|
|
123
|
+
const { stdout } = await execFileAsync(
|
|
124
|
+
"rg",
|
|
125
|
+
["--no-heading", "-n", pattern, directory],
|
|
124
126
|
{ timeout: 1e4 }
|
|
125
127
|
);
|
|
126
128
|
return { content: [{ type: "text", text: stdout || "\uACB0\uACFC \uC5C6\uC74C" }] };
|
|
@@ -257,6 +259,17 @@ var import_zod2 = require("zod");
|
|
|
257
259
|
var BrowserTools = class {
|
|
258
260
|
browser = null;
|
|
259
261
|
page = null;
|
|
262
|
+
// 동시 요청 시 race condition 방지용 직렬화 락
|
|
263
|
+
lock = Promise.resolve();
|
|
264
|
+
withLock(fn) {
|
|
265
|
+
let release;
|
|
266
|
+
const next = new Promise((r) => {
|
|
267
|
+
release = r;
|
|
268
|
+
});
|
|
269
|
+
const current = this.lock;
|
|
270
|
+
this.lock = this.lock.then(() => next);
|
|
271
|
+
return current.then(() => fn()).finally(() => release());
|
|
272
|
+
}
|
|
260
273
|
async init() {
|
|
261
274
|
try {
|
|
262
275
|
this.browser = await import_playwright.chromium.launch({ headless: true });
|
|
@@ -279,22 +292,22 @@ var BrowserTools = class {
|
|
|
279
292
|
"browser_navigate",
|
|
280
293
|
"URL\uB85C \uC774\uB3D9",
|
|
281
294
|
{ url: import_zod2.z.string().describe("\uC774\uB3D9\uD560 URL") },
|
|
282
|
-
|
|
295
|
+
({ url }) => this.withLock(async () => {
|
|
283
296
|
const page = requirePage();
|
|
284
297
|
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
285
298
|
return {
|
|
286
299
|
content: [{ type: "text", text: `\uC774\uB3D9 \uC644\uB8CC: ${page.url()}` }]
|
|
287
300
|
};
|
|
288
|
-
}
|
|
301
|
+
})
|
|
289
302
|
);
|
|
290
303
|
server.tool(
|
|
291
304
|
"browser_click",
|
|
292
305
|
"\uC694\uC18C \uD074\uB9AD",
|
|
293
306
|
{ selector: import_zod2.z.string().describe("CSS \uC120\uD0DD\uC790") },
|
|
294
|
-
|
|
307
|
+
({ selector }) => this.withLock(async () => {
|
|
295
308
|
await requirePage().click(selector);
|
|
296
309
|
return { content: [{ type: "text", text: "\uD074\uB9AD \uC644\uB8CC" }] };
|
|
297
|
-
}
|
|
310
|
+
})
|
|
298
311
|
);
|
|
299
312
|
server.tool(
|
|
300
313
|
"browser_type",
|
|
@@ -304,12 +317,12 @@ var BrowserTools = class {
|
|
|
304
317
|
text: import_zod2.z.string().describe("\uC785\uB825\uD560 \uD14D\uC2A4\uD2B8"),
|
|
305
318
|
clear: import_zod2.z.boolean().optional().default(false).describe("\uAE30\uC874 \uB0B4\uC6A9 \uC0AD\uC81C \uD6C4 \uC785\uB825")
|
|
306
319
|
},
|
|
307
|
-
|
|
320
|
+
({ selector, text, clear }) => this.withLock(async () => {
|
|
308
321
|
const page = requirePage();
|
|
309
322
|
if (clear) await page.fill(selector, text);
|
|
310
323
|
else await page.type(selector, text);
|
|
311
324
|
return { content: [{ type: "text", text: "\uC785\uB825 \uC644\uB8CC" }] };
|
|
312
|
-
}
|
|
325
|
+
})
|
|
313
326
|
);
|
|
314
327
|
server.tool(
|
|
315
328
|
"browser_screenshot",
|
|
@@ -318,7 +331,7 @@ var BrowserTools = class {
|
|
|
318
331
|
path: import_zod2.z.string().optional().describe("\uC800\uC7A5 \uACBD\uB85C (\uC5C6\uC73C\uBA74 base64 \uBC18\uD658)"),
|
|
319
332
|
full_page: import_zod2.z.boolean().optional().default(false)
|
|
320
333
|
},
|
|
321
|
-
|
|
334
|
+
({ path: path2, full_page }) => this.withLock(async () => {
|
|
322
335
|
const page = requirePage();
|
|
323
336
|
const screenshot = await page.screenshot({
|
|
324
337
|
path: path2 ?? void 0,
|
|
@@ -336,13 +349,13 @@ var BrowserTools = class {
|
|
|
336
349
|
}
|
|
337
350
|
]
|
|
338
351
|
};
|
|
339
|
-
}
|
|
352
|
+
})
|
|
340
353
|
);
|
|
341
354
|
server.tool(
|
|
342
355
|
"browser_snapshot",
|
|
343
356
|
"\uD398\uC774\uC9C0 \uC811\uADFC\uC131 \uD2B8\uB9AC \uC870\uD68C (\uAD6C\uC870 \uD30C\uC545\uC6A9)",
|
|
344
357
|
{},
|
|
345
|
-
async () => {
|
|
358
|
+
() => this.withLock(async () => {
|
|
346
359
|
const page = requirePage();
|
|
347
360
|
const snapshot = await page.locator("body").ariaSnapshot();
|
|
348
361
|
return {
|
|
@@ -350,29 +363,29 @@ var BrowserTools = class {
|
|
|
350
363
|
{ type: "text", text: snapshot }
|
|
351
364
|
]
|
|
352
365
|
};
|
|
353
|
-
}
|
|
366
|
+
})
|
|
354
367
|
);
|
|
355
368
|
server.tool(
|
|
356
369
|
"browser_evaluate",
|
|
357
370
|
"JavaScript \uC2E4\uD589",
|
|
358
371
|
{ code: import_zod2.z.string().describe("\uC2E4\uD589\uD560 JavaScript \uCF54\uB4DC") },
|
|
359
|
-
|
|
372
|
+
({ code }) => this.withLock(async () => {
|
|
360
373
|
const result = await requirePage().evaluate(code);
|
|
361
374
|
return {
|
|
362
375
|
content: [
|
|
363
376
|
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
364
377
|
]
|
|
365
378
|
};
|
|
366
|
-
}
|
|
379
|
+
})
|
|
367
380
|
);
|
|
368
381
|
server.tool(
|
|
369
382
|
"browser_pdf",
|
|
370
383
|
"\uD604\uC7AC \uD398\uC774\uC9C0 PDF \uC800\uC7A5",
|
|
371
384
|
{ path: import_zod2.z.string().describe("\uC800\uC7A5 \uACBD\uB85C (.pdf)") },
|
|
372
|
-
|
|
385
|
+
({ path: path2 }) => this.withLock(async () => {
|
|
373
386
|
await requirePage().pdf({ path: path2 });
|
|
374
387
|
return { content: [{ type: "text", text: `PDF \uC800\uC7A5 \uC644\uB8CC: ${path2}` }] };
|
|
375
|
-
}
|
|
388
|
+
})
|
|
376
389
|
);
|
|
377
390
|
}
|
|
378
391
|
};
|
|
@@ -385,7 +398,11 @@ var import_util2 = require("util");
|
|
|
385
398
|
var execAsync2 = (0, import_util2.promisify)(import_child_process2.exec);
|
|
386
399
|
async function readNotebook(filePath) {
|
|
387
400
|
const raw = await import_promises2.default.readFile(filePath, "utf-8");
|
|
388
|
-
|
|
401
|
+
try {
|
|
402
|
+
return JSON.parse(raw);
|
|
403
|
+
} catch {
|
|
404
|
+
throw new Error(`\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 .ipynb \uD30C\uC77C: ${filePath}`);
|
|
405
|
+
}
|
|
389
406
|
}
|
|
390
407
|
async function writeNotebook(filePath, nb) {
|
|
391
408
|
await import_promises2.default.writeFile(filePath, JSON.stringify(nb, null, 1), "utf-8");
|
|
@@ -484,8 +501,9 @@ var NotebookTools = class {
|
|
|
484
501
|
nb.cells.push(newCell);
|
|
485
502
|
actualIndex = nb.cells.length - 1;
|
|
486
503
|
} else {
|
|
487
|
-
|
|
488
|
-
|
|
504
|
+
const clamped = Math.max(0, Math.min(position, nb.cells.length));
|
|
505
|
+
nb.cells.splice(clamped, 0, newCell);
|
|
506
|
+
actualIndex = clamped;
|
|
489
507
|
}
|
|
490
508
|
await writeNotebook(filePath, nb);
|
|
491
509
|
return { content: [{ type: "text", text: `\uC140 \uCD94\uAC00 \uC644\uB8CC (index: ${actualIndex})` }] };
|
|
@@ -516,13 +534,13 @@ var import_child_process3 = require("child_process");
|
|
|
516
534
|
var import_util3 = require("util");
|
|
517
535
|
var import_zod4 = require("zod");
|
|
518
536
|
var execAsync3 = (0, import_util3.promisify)(import_child_process3.exec);
|
|
537
|
+
var screenRecordPid = null;
|
|
519
538
|
function platform() {
|
|
520
539
|
if (process.platform === "darwin") return "mac";
|
|
521
540
|
if (process.platform === "win32") return "win";
|
|
522
541
|
return "linux";
|
|
523
542
|
}
|
|
524
543
|
var DeviceTools = class {
|
|
525
|
-
screenRecordPid = null;
|
|
526
544
|
register(server) {
|
|
527
545
|
server.tool(
|
|
528
546
|
"screen_capture",
|
|
@@ -532,15 +550,24 @@ var DeviceTools = class {
|
|
|
532
550
|
},
|
|
533
551
|
async ({ output_path }) => {
|
|
534
552
|
const p = platform();
|
|
553
|
+
const isTmp = !output_path;
|
|
535
554
|
const tmpPath = output_path ?? `/tmp/junis_screen_${Date.now()}.png`;
|
|
536
555
|
const cmd = {
|
|
537
556
|
mac: `screencapture -x "${tmpPath}"`,
|
|
538
557
|
win: `nircmd.exe savescreenshot "${tmpPath}"`,
|
|
539
558
|
linux: `scrot "${tmpPath}"`
|
|
540
559
|
}[p];
|
|
541
|
-
|
|
542
|
-
|
|
560
|
+
try {
|
|
561
|
+
await execAsync3(cmd);
|
|
562
|
+
} catch (err) {
|
|
563
|
+
throw new Error(`\uD654\uBA74 \uCEA1\uCC98 \uC2E4\uD328: ${err.message}`);
|
|
564
|
+
}
|
|
565
|
+
const { readFileSync, unlinkSync } = await import("fs");
|
|
543
566
|
const data = readFileSync(tmpPath).toString("base64");
|
|
567
|
+
if (isTmp) try {
|
|
568
|
+
unlinkSync(tmpPath);
|
|
569
|
+
} catch {
|
|
570
|
+
}
|
|
544
571
|
return {
|
|
545
572
|
content: [{ type: "image", data, mimeType: "image/png" }]
|
|
546
573
|
};
|
|
@@ -554,15 +581,24 @@ var DeviceTools = class {
|
|
|
554
581
|
},
|
|
555
582
|
async ({ output_path }) => {
|
|
556
583
|
const p = platform();
|
|
584
|
+
const isTmp = !output_path;
|
|
557
585
|
const tmpPath = output_path ?? `/tmp/junis_cam_${Date.now()}.jpg`;
|
|
558
586
|
const cmd = {
|
|
559
587
|
mac: `imagesnap "${tmpPath}"`,
|
|
560
588
|
win: `ffmpeg -f dshow -i video="Default" -frames:v 1 "${tmpPath}"`,
|
|
561
589
|
linux: `fswebcam -r 1280x720 "${tmpPath}"`
|
|
562
590
|
}[p];
|
|
563
|
-
|
|
564
|
-
|
|
591
|
+
try {
|
|
592
|
+
await execAsync3(cmd);
|
|
593
|
+
} catch (err) {
|
|
594
|
+
throw new Error(`\uCE74\uBA54\uB77C \uCD2C\uC601 \uC2E4\uD328: ${err.message}`);
|
|
595
|
+
}
|
|
596
|
+
const { readFileSync, unlinkSync } = await import("fs");
|
|
565
597
|
const data = readFileSync(tmpPath).toString("base64");
|
|
598
|
+
if (isTmp) try {
|
|
599
|
+
unlinkSync(tmpPath);
|
|
600
|
+
} catch {
|
|
601
|
+
}
|
|
566
602
|
return {
|
|
567
603
|
content: [{ type: "image", data, mimeType: "image/jpeg" }]
|
|
568
604
|
};
|
|
@@ -622,7 +658,7 @@ var DeviceTools = class {
|
|
|
622
658
|
async ({ action, output_path }) => {
|
|
623
659
|
const p = platform();
|
|
624
660
|
if (action === "start") {
|
|
625
|
-
if (
|
|
661
|
+
if (screenRecordPid) {
|
|
626
662
|
return { content: [{ type: "text", text: "\uC774\uBBF8 \uB179\uD654 \uC911\uC785\uB2C8\uB2E4." }] };
|
|
627
663
|
}
|
|
628
664
|
const tmpPath = output_path ?? `/tmp/junis_record_${Date.now()}.mp4`;
|
|
@@ -630,17 +666,17 @@ var DeviceTools = class {
|
|
|
630
666
|
const cmd = p === "mac" ? ["screencapture", ["-v", tmpPath]] : ["ffmpeg", ["-f", p === "win" ? "gdigrab" : "x11grab", "-i", p === "win" ? "desktop" : ":0.0", tmpPath]];
|
|
631
667
|
const child = spawn(cmd[0], cmd[1], { detached: true, stdio: "ignore" });
|
|
632
668
|
child.unref();
|
|
633
|
-
|
|
634
|
-
return { content: [{ type: "text", text: `\uB179\uD654 \uC2DC\uC791\uB428. \uC800\uC7A5 \uACBD\uB85C: ${tmpPath} (PID: ${
|
|
669
|
+
screenRecordPid = child.pid ?? null;
|
|
670
|
+
return { content: [{ type: "text", text: `\uB179\uD654 \uC2DC\uC791\uB428. \uC800\uC7A5 \uACBD\uB85C: ${tmpPath} (PID: ${screenRecordPid})` }] };
|
|
635
671
|
} else {
|
|
636
|
-
if (!
|
|
672
|
+
if (!screenRecordPid) {
|
|
637
673
|
return { content: [{ type: "text", text: "\uD604\uC7AC \uB179\uD654 \uC911\uC774 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
638
674
|
}
|
|
639
675
|
try {
|
|
640
|
-
process.kill(
|
|
676
|
+
process.kill(screenRecordPid, "SIGINT");
|
|
641
677
|
} catch {
|
|
642
678
|
}
|
|
643
|
-
|
|
679
|
+
screenRecordPid = null;
|
|
644
680
|
return { content: [{ type: "text", text: "\uB179\uD654 \uC911\uC9C0\uB428." }] };
|
|
645
681
|
}
|
|
646
682
|
}
|
|
@@ -659,12 +695,15 @@ var DeviceTools = class {
|
|
|
659
695
|
} catch {
|
|
660
696
|
}
|
|
661
697
|
}
|
|
662
|
-
const res = await fetch("
|
|
698
|
+
const res = await fetch("http://ip-api.com/json/");
|
|
663
699
|
const data = await res.json();
|
|
700
|
+
if (data.status !== "success") {
|
|
701
|
+
throw new Error(`IP \uC704\uCE58 \uC870\uD68C \uC2E4\uD328: ${data.message ?? data.status}`);
|
|
702
|
+
}
|
|
664
703
|
return {
|
|
665
704
|
content: [{
|
|
666
705
|
type: "text",
|
|
667
|
-
text: `\uC704\uB3C4: ${data.
|
|
706
|
+
text: `\uC704\uB3C4: ${data.lat}, \uACBD\uB3C4: ${data.lon}, \uB3C4\uC2DC: ${data.city}, \uAD6D\uAC00: ${data.country} (IP \uAE30\uBC18 \uCD94\uC815)`
|
|
668
707
|
}]
|
|
669
708
|
};
|
|
670
709
|
}
|