junis 0.1.8 → 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 +167 -35
- package/dist/server/mcp.js +156 -30
- package/dist/server/stdio.js +156 -30
- 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.
|
|
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)) {
|
|
@@ -182,9 +186,11 @@ var RelayClient = class {
|
|
|
182
186
|
destroyed = false;
|
|
183
187
|
async connect() {
|
|
184
188
|
if (this.destroyed) return;
|
|
185
|
-
const url = `${JUNIS_WS}/ws/devices/${this.config.device_key}
|
|
189
|
+
const url = `${JUNIS_WS}/ws/devices/${this.config.device_key}`;
|
|
186
190
|
console.log(`\u{1F517} \uB9B4\uB808\uC774 \uC11C\uBC84 \uC5F0\uACB0 \uC911...`);
|
|
187
|
-
this.ws = new import_ws.default(url
|
|
191
|
+
this.ws = new import_ws.default(url, {
|
|
192
|
+
headers: { Authorization: `Bearer ${this.config.token}` }
|
|
193
|
+
});
|
|
188
194
|
this.ws.on("open", () => {
|
|
189
195
|
console.log("\u2705 \uB9B4\uB808\uC774 \uC11C\uBC84 \uC5F0\uACB0\uB428");
|
|
190
196
|
this.reconnectDelay = 1e3;
|
|
@@ -274,6 +280,7 @@ var import_path2 = __toESM(require("path"));
|
|
|
274
280
|
var import_glob = require("glob");
|
|
275
281
|
var import_zod = require("zod");
|
|
276
282
|
var execAsync = (0, import_util.promisify)(import_child_process.exec);
|
|
283
|
+
var execFileAsync = (0, import_util.promisify)(import_child_process.execFile);
|
|
277
284
|
var FilesystemTools = class {
|
|
278
285
|
register(server) {
|
|
279
286
|
server.tool(
|
|
@@ -358,8 +365,9 @@ ${error.stderr ?? ""}`
|
|
|
358
365
|
},
|
|
359
366
|
async ({ pattern, directory, file_pattern }) => {
|
|
360
367
|
try {
|
|
361
|
-
const { stdout } = await
|
|
362
|
-
|
|
368
|
+
const { stdout } = await execFileAsync(
|
|
369
|
+
"rg",
|
|
370
|
+
["--no-heading", "-n", pattern, directory],
|
|
363
371
|
{ timeout: 1e4 }
|
|
364
372
|
);
|
|
365
373
|
return { content: [{ type: "text", text: stdout || "\uACB0\uACFC \uC5C6\uC74C" }] };
|
|
@@ -447,6 +455,46 @@ ${error.stderr ?? ""}`
|
|
|
447
455
|
};
|
|
448
456
|
}
|
|
449
457
|
);
|
|
458
|
+
server.tool(
|
|
459
|
+
"edit_block",
|
|
460
|
+
"\uD30C\uC77C\uC758 \uD2B9\uC815 \uD14D\uC2A4\uD2B8 \uBE14\uB85D\uC744 \uC0C8 \uD14D\uC2A4\uD2B8\uB85C \uAD50\uCCB4 (diff \uAE30\uBC18 \uBD80\uBD84 \uC218\uC815)",
|
|
461
|
+
{
|
|
462
|
+
path: import_zod.z.string().describe("\uD30C\uC77C \uACBD\uB85C"),
|
|
463
|
+
old_string: import_zod.z.string().describe("\uAD50\uCCB4\uD560 \uAE30\uC874 \uD14D\uC2A4\uD2B8 (\uC815\uD655\uD788 \uC77C\uCE58\uD574\uC57C \uD568)"),
|
|
464
|
+
new_string: import_zod.z.string().describe("\uC0C8 \uD14D\uC2A4\uD2B8"),
|
|
465
|
+
replace_all: import_zod.z.boolean().optional().default(false).describe("true\uBA74 \uBAA8\uB4E0 \uB9E4\uCE6D \uAD50\uCCB4, false\uBA74 \uCCAB \uBC88\uC9F8\uB9CC")
|
|
466
|
+
},
|
|
467
|
+
async ({ path: filePath, old_string, new_string, replace_all }) => {
|
|
468
|
+
const content = await import_promises.default.readFile(filePath, "utf-8");
|
|
469
|
+
if (!content.includes(old_string)) {
|
|
470
|
+
throw new Error(`old_string\uC744 \uD30C\uC77C\uC5D0\uC11C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${filePath}`);
|
|
471
|
+
}
|
|
472
|
+
let count = 0;
|
|
473
|
+
let pos = 0;
|
|
474
|
+
while ((pos = content.indexOf(old_string, pos)) !== -1) {
|
|
475
|
+
count++;
|
|
476
|
+
pos += old_string.length;
|
|
477
|
+
}
|
|
478
|
+
if (!replace_all && count > 1) {
|
|
479
|
+
throw new Error(
|
|
480
|
+
`\uB9E4\uCE6D\uC774 ${count}\uAC1C\uC785\uB2C8\uB2E4. replace_all\uC744 true\uB85C \uD558\uAC70\uB098 \uB354 \uB113\uC740 \uCEE8\uD14D\uC2A4\uD2B8\uB97C \uD3EC\uD568\uD558\uC138\uC694.`
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
let result;
|
|
484
|
+
let replaced;
|
|
485
|
+
if (replace_all) {
|
|
486
|
+
result = content.split(old_string).join(new_string);
|
|
487
|
+
replaced = count;
|
|
488
|
+
} else {
|
|
489
|
+
result = content.replace(old_string, new_string);
|
|
490
|
+
replaced = 1;
|
|
491
|
+
}
|
|
492
|
+
await import_promises.default.writeFile(filePath, result, "utf-8");
|
|
493
|
+
return {
|
|
494
|
+
content: [{ type: "text", text: `\uAD50\uCCB4 \uC644\uB8CC (${replaced}\uAC1C \uBCC0\uACBD\uB428)` }]
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
);
|
|
450
498
|
}
|
|
451
499
|
};
|
|
452
500
|
|
|
@@ -456,6 +504,17 @@ var import_zod2 = require("zod");
|
|
|
456
504
|
var BrowserTools = class {
|
|
457
505
|
browser = null;
|
|
458
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
|
+
}
|
|
459
518
|
async init() {
|
|
460
519
|
try {
|
|
461
520
|
this.browser = await import_playwright.chromium.launch({ headless: true });
|
|
@@ -478,22 +537,22 @@ var BrowserTools = class {
|
|
|
478
537
|
"browser_navigate",
|
|
479
538
|
"URL\uB85C \uC774\uB3D9",
|
|
480
539
|
{ url: import_zod2.z.string().describe("\uC774\uB3D9\uD560 URL") },
|
|
481
|
-
|
|
540
|
+
({ url }) => this.withLock(async () => {
|
|
482
541
|
const page = requirePage();
|
|
483
542
|
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
484
543
|
return {
|
|
485
544
|
content: [{ type: "text", text: `\uC774\uB3D9 \uC644\uB8CC: ${page.url()}` }]
|
|
486
545
|
};
|
|
487
|
-
}
|
|
546
|
+
})
|
|
488
547
|
);
|
|
489
548
|
server.tool(
|
|
490
549
|
"browser_click",
|
|
491
550
|
"\uC694\uC18C \uD074\uB9AD",
|
|
492
551
|
{ selector: import_zod2.z.string().describe("CSS \uC120\uD0DD\uC790") },
|
|
493
|
-
|
|
552
|
+
({ selector }) => this.withLock(async () => {
|
|
494
553
|
await requirePage().click(selector);
|
|
495
554
|
return { content: [{ type: "text", text: "\uD074\uB9AD \uC644\uB8CC" }] };
|
|
496
|
-
}
|
|
555
|
+
})
|
|
497
556
|
);
|
|
498
557
|
server.tool(
|
|
499
558
|
"browser_type",
|
|
@@ -503,12 +562,12 @@ var BrowserTools = class {
|
|
|
503
562
|
text: import_zod2.z.string().describe("\uC785\uB825\uD560 \uD14D\uC2A4\uD2B8"),
|
|
504
563
|
clear: import_zod2.z.boolean().optional().default(false).describe("\uAE30\uC874 \uB0B4\uC6A9 \uC0AD\uC81C \uD6C4 \uC785\uB825")
|
|
505
564
|
},
|
|
506
|
-
|
|
565
|
+
({ selector, text, clear }) => this.withLock(async () => {
|
|
507
566
|
const page = requirePage();
|
|
508
567
|
if (clear) await page.fill(selector, text);
|
|
509
568
|
else await page.type(selector, text);
|
|
510
569
|
return { content: [{ type: "text", text: "\uC785\uB825 \uC644\uB8CC" }] };
|
|
511
|
-
}
|
|
570
|
+
})
|
|
512
571
|
);
|
|
513
572
|
server.tool(
|
|
514
573
|
"browser_screenshot",
|
|
@@ -517,7 +576,7 @@ var BrowserTools = class {
|
|
|
517
576
|
path: import_zod2.z.string().optional().describe("\uC800\uC7A5 \uACBD\uB85C (\uC5C6\uC73C\uBA74 base64 \uBC18\uD658)"),
|
|
518
577
|
full_page: import_zod2.z.boolean().optional().default(false)
|
|
519
578
|
},
|
|
520
|
-
|
|
579
|
+
({ path: path3, full_page }) => this.withLock(async () => {
|
|
521
580
|
const page = requirePage();
|
|
522
581
|
const screenshot = await page.screenshot({
|
|
523
582
|
path: path3 ?? void 0,
|
|
@@ -535,13 +594,13 @@ var BrowserTools = class {
|
|
|
535
594
|
}
|
|
536
595
|
]
|
|
537
596
|
};
|
|
538
|
-
}
|
|
597
|
+
})
|
|
539
598
|
);
|
|
540
599
|
server.tool(
|
|
541
600
|
"browser_snapshot",
|
|
542
601
|
"\uD398\uC774\uC9C0 \uC811\uADFC\uC131 \uD2B8\uB9AC \uC870\uD68C (\uAD6C\uC870 \uD30C\uC545\uC6A9)",
|
|
543
602
|
{},
|
|
544
|
-
async () => {
|
|
603
|
+
() => this.withLock(async () => {
|
|
545
604
|
const page = requirePage();
|
|
546
605
|
const snapshot = await page.locator("body").ariaSnapshot();
|
|
547
606
|
return {
|
|
@@ -549,29 +608,29 @@ var BrowserTools = class {
|
|
|
549
608
|
{ type: "text", text: snapshot }
|
|
550
609
|
]
|
|
551
610
|
};
|
|
552
|
-
}
|
|
611
|
+
})
|
|
553
612
|
);
|
|
554
613
|
server.tool(
|
|
555
614
|
"browser_evaluate",
|
|
556
615
|
"JavaScript \uC2E4\uD589",
|
|
557
616
|
{ code: import_zod2.z.string().describe("\uC2E4\uD589\uD560 JavaScript \uCF54\uB4DC") },
|
|
558
|
-
|
|
617
|
+
({ code }) => this.withLock(async () => {
|
|
559
618
|
const result = await requirePage().evaluate(code);
|
|
560
619
|
return {
|
|
561
620
|
content: [
|
|
562
621
|
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
563
622
|
]
|
|
564
623
|
};
|
|
565
|
-
}
|
|
624
|
+
})
|
|
566
625
|
);
|
|
567
626
|
server.tool(
|
|
568
627
|
"browser_pdf",
|
|
569
628
|
"\uD604\uC7AC \uD398\uC774\uC9C0 PDF \uC800\uC7A5",
|
|
570
629
|
{ path: import_zod2.z.string().describe("\uC800\uC7A5 \uACBD\uB85C (.pdf)") },
|
|
571
|
-
|
|
630
|
+
({ path: path3 }) => this.withLock(async () => {
|
|
572
631
|
await requirePage().pdf({ path: path3 });
|
|
573
632
|
return { content: [{ type: "text", text: `PDF \uC800\uC7A5 \uC644\uB8CC: ${path3}` }] };
|
|
574
|
-
}
|
|
633
|
+
})
|
|
575
634
|
);
|
|
576
635
|
}
|
|
577
636
|
};
|
|
@@ -584,7 +643,11 @@ var import_util2 = require("util");
|
|
|
584
643
|
var execAsync2 = (0, import_util2.promisify)(import_child_process2.exec);
|
|
585
644
|
async function readNotebook(filePath) {
|
|
586
645
|
const raw = await import_promises2.default.readFile(filePath, "utf-8");
|
|
587
|
-
|
|
646
|
+
try {
|
|
647
|
+
return JSON.parse(raw);
|
|
648
|
+
} catch {
|
|
649
|
+
throw new Error(`\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 .ipynb \uD30C\uC77C: ${filePath}`);
|
|
650
|
+
}
|
|
588
651
|
}
|
|
589
652
|
async function writeNotebook(filePath, nb) {
|
|
590
653
|
await import_promises2.default.writeFile(filePath, JSON.stringify(nb, null, 1), "utf-8");
|
|
@@ -660,6 +723,54 @@ var NotebookTools = class {
|
|
|
660
723
|
throw new Error("jupyter\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC124\uCE58 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694: pip install jupyter");
|
|
661
724
|
}
|
|
662
725
|
);
|
|
726
|
+
server.tool(
|
|
727
|
+
"notebook_add_cell",
|
|
728
|
+
"\uB178\uD2B8\uBD81\uC5D0 \uC0C8 \uC140 \uCD94\uAC00",
|
|
729
|
+
{
|
|
730
|
+
path: import_zod3.z.string().describe(".ipynb \uD30C\uC77C \uACBD\uB85C"),
|
|
731
|
+
cell_type: import_zod3.z.enum(["code", "markdown"]).describe("\uC140 \uD0C0\uC785"),
|
|
732
|
+
source: import_zod3.z.string().describe("\uC140 \uC18C\uC2A4 \uB0B4\uC6A9"),
|
|
733
|
+
position: import_zod3.z.number().optional().describe("\uC0BD\uC785 \uC704\uCE58(0-based). \uC5C6\uC73C\uBA74 \uB9E8 \uB05D\uC5D0 \uCD94\uAC00")
|
|
734
|
+
},
|
|
735
|
+
async ({ path: filePath, cell_type: cellType, source, position }) => {
|
|
736
|
+
const nb = await readNotebook(filePath);
|
|
737
|
+
const newCell = {
|
|
738
|
+
cell_type: cellType,
|
|
739
|
+
source: source.split("\n").map((l, i, arr) => i < arr.length - 1 ? l + "\n" : l),
|
|
740
|
+
metadata: {},
|
|
741
|
+
outputs: cellType === "code" ? [] : void 0,
|
|
742
|
+
execution_count: cellType === "code" ? null : void 0
|
|
743
|
+
};
|
|
744
|
+
let actualIndex;
|
|
745
|
+
if (position === void 0 || position === null) {
|
|
746
|
+
nb.cells.push(newCell);
|
|
747
|
+
actualIndex = nb.cells.length - 1;
|
|
748
|
+
} else {
|
|
749
|
+
const clamped = Math.max(0, Math.min(position, nb.cells.length));
|
|
750
|
+
nb.cells.splice(clamped, 0, newCell);
|
|
751
|
+
actualIndex = clamped;
|
|
752
|
+
}
|
|
753
|
+
await writeNotebook(filePath, nb);
|
|
754
|
+
return { content: [{ type: "text", text: `\uC140 \uCD94\uAC00 \uC644\uB8CC (index: ${actualIndex})` }] };
|
|
755
|
+
}
|
|
756
|
+
);
|
|
757
|
+
server.tool(
|
|
758
|
+
"notebook_delete_cell",
|
|
759
|
+
"\uB178\uD2B8\uBD81 \uD2B9\uC815 \uC140 \uC0AD\uC81C",
|
|
760
|
+
{
|
|
761
|
+
path: import_zod3.z.string().describe(".ipynb \uD30C\uC77C \uACBD\uB85C"),
|
|
762
|
+
cell_index: import_zod3.z.number().describe("\uC0AD\uC81C\uD560 \uC140 \uC778\uB371\uC2A4 (0-based)")
|
|
763
|
+
},
|
|
764
|
+
async ({ path: filePath, cell_index }) => {
|
|
765
|
+
const nb = await readNotebook(filePath);
|
|
766
|
+
if (cell_index < 0 || cell_index >= nb.cells.length) {
|
|
767
|
+
throw new Error(`\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uC140 \uC778\uB371\uC2A4: ${cell_index}`);
|
|
768
|
+
}
|
|
769
|
+
nb.cells.splice(cell_index, 1);
|
|
770
|
+
await writeNotebook(filePath, nb);
|
|
771
|
+
return { content: [{ type: "text", text: `\uC140 \uC0AD\uC81C \uC644\uB8CC (index: ${cell_index})` }] };
|
|
772
|
+
}
|
|
773
|
+
);
|
|
663
774
|
}
|
|
664
775
|
};
|
|
665
776
|
|
|
@@ -668,13 +779,13 @@ var import_child_process3 = require("child_process");
|
|
|
668
779
|
var import_util3 = require("util");
|
|
669
780
|
var import_zod4 = require("zod");
|
|
670
781
|
var execAsync3 = (0, import_util3.promisify)(import_child_process3.exec);
|
|
782
|
+
var screenRecordPid = null;
|
|
671
783
|
function platform() {
|
|
672
784
|
if (process.platform === "darwin") return "mac";
|
|
673
785
|
if (process.platform === "win32") return "win";
|
|
674
786
|
return "linux";
|
|
675
787
|
}
|
|
676
788
|
var DeviceTools = class {
|
|
677
|
-
screenRecordPid = null;
|
|
678
789
|
register(server) {
|
|
679
790
|
server.tool(
|
|
680
791
|
"screen_capture",
|
|
@@ -684,15 +795,24 @@ var DeviceTools = class {
|
|
|
684
795
|
},
|
|
685
796
|
async ({ output_path }) => {
|
|
686
797
|
const p = platform();
|
|
798
|
+
const isTmp = !output_path;
|
|
687
799
|
const tmpPath = output_path ?? `/tmp/junis_screen_${Date.now()}.png`;
|
|
688
800
|
const cmd = {
|
|
689
801
|
mac: `screencapture -x "${tmpPath}"`,
|
|
690
802
|
win: `nircmd.exe savescreenshot "${tmpPath}"`,
|
|
691
803
|
linux: `scrot "${tmpPath}"`
|
|
692
804
|
}[p];
|
|
693
|
-
|
|
694
|
-
|
|
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");
|
|
695
811
|
const data = readFileSync(tmpPath).toString("base64");
|
|
812
|
+
if (isTmp) try {
|
|
813
|
+
unlinkSync(tmpPath);
|
|
814
|
+
} catch {
|
|
815
|
+
}
|
|
696
816
|
return {
|
|
697
817
|
content: [{ type: "image", data, mimeType: "image/png" }]
|
|
698
818
|
};
|
|
@@ -706,15 +826,24 @@ var DeviceTools = class {
|
|
|
706
826
|
},
|
|
707
827
|
async ({ output_path }) => {
|
|
708
828
|
const p = platform();
|
|
829
|
+
const isTmp = !output_path;
|
|
709
830
|
const tmpPath = output_path ?? `/tmp/junis_cam_${Date.now()}.jpg`;
|
|
710
831
|
const cmd = {
|
|
711
832
|
mac: `imagesnap "${tmpPath}"`,
|
|
712
833
|
win: `ffmpeg -f dshow -i video="Default" -frames:v 1 "${tmpPath}"`,
|
|
713
834
|
linux: `fswebcam -r 1280x720 "${tmpPath}"`
|
|
714
835
|
}[p];
|
|
715
|
-
|
|
716
|
-
|
|
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");
|
|
717
842
|
const data = readFileSync(tmpPath).toString("base64");
|
|
843
|
+
if (isTmp) try {
|
|
844
|
+
unlinkSync(tmpPath);
|
|
845
|
+
} catch {
|
|
846
|
+
}
|
|
718
847
|
return {
|
|
719
848
|
content: [{ type: "image", data, mimeType: "image/jpeg" }]
|
|
720
849
|
};
|
|
@@ -774,7 +903,7 @@ var DeviceTools = class {
|
|
|
774
903
|
async ({ action, output_path }) => {
|
|
775
904
|
const p = platform();
|
|
776
905
|
if (action === "start") {
|
|
777
|
-
if (
|
|
906
|
+
if (screenRecordPid) {
|
|
778
907
|
return { content: [{ type: "text", text: "\uC774\uBBF8 \uB179\uD654 \uC911\uC785\uB2C8\uB2E4." }] };
|
|
779
908
|
}
|
|
780
909
|
const tmpPath = output_path ?? `/tmp/junis_record_${Date.now()}.mp4`;
|
|
@@ -782,17 +911,17 @@ var DeviceTools = class {
|
|
|
782
911
|
const cmd = p === "mac" ? ["screencapture", ["-v", tmpPath]] : ["ffmpeg", ["-f", p === "win" ? "gdigrab" : "x11grab", "-i", p === "win" ? "desktop" : ":0.0", tmpPath]];
|
|
783
912
|
const child = spawn(cmd[0], cmd[1], { detached: true, stdio: "ignore" });
|
|
784
913
|
child.unref();
|
|
785
|
-
|
|
786
|
-
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})` }] };
|
|
787
916
|
} else {
|
|
788
|
-
if (!
|
|
917
|
+
if (!screenRecordPid) {
|
|
789
918
|
return { content: [{ type: "text", text: "\uD604\uC7AC \uB179\uD654 \uC911\uC774 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
790
919
|
}
|
|
791
920
|
try {
|
|
792
|
-
process.kill(
|
|
921
|
+
process.kill(screenRecordPid, "SIGINT");
|
|
793
922
|
} catch {
|
|
794
923
|
}
|
|
795
|
-
|
|
924
|
+
screenRecordPid = null;
|
|
796
925
|
return { content: [{ type: "text", text: "\uB179\uD654 \uC911\uC9C0\uB428." }] };
|
|
797
926
|
}
|
|
798
927
|
}
|
|
@@ -811,12 +940,15 @@ var DeviceTools = class {
|
|
|
811
940
|
} catch {
|
|
812
941
|
}
|
|
813
942
|
}
|
|
814
|
-
const res = await fetch("
|
|
943
|
+
const res = await fetch("http://ip-api.com/json/");
|
|
815
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
|
+
}
|
|
816
948
|
return {
|
|
817
949
|
content: [{
|
|
818
950
|
type: "text",
|
|
819
|
-
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)`
|
|
820
952
|
}]
|
|
821
953
|
};
|
|
822
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" }] };
|
|
@@ -219,6 +221,46 @@ ${error.stderr ?? ""}`
|
|
|
219
221
|
};
|
|
220
222
|
}
|
|
221
223
|
);
|
|
224
|
+
server.tool(
|
|
225
|
+
"edit_block",
|
|
226
|
+
"\uD30C\uC77C\uC758 \uD2B9\uC815 \uD14D\uC2A4\uD2B8 \uBE14\uB85D\uC744 \uC0C8 \uD14D\uC2A4\uD2B8\uB85C \uAD50\uCCB4 (diff \uAE30\uBC18 \uBD80\uBD84 \uC218\uC815)",
|
|
227
|
+
{
|
|
228
|
+
path: import_zod.z.string().describe("\uD30C\uC77C \uACBD\uB85C"),
|
|
229
|
+
old_string: import_zod.z.string().describe("\uAD50\uCCB4\uD560 \uAE30\uC874 \uD14D\uC2A4\uD2B8 (\uC815\uD655\uD788 \uC77C\uCE58\uD574\uC57C \uD568)"),
|
|
230
|
+
new_string: import_zod.z.string().describe("\uC0C8 \uD14D\uC2A4\uD2B8"),
|
|
231
|
+
replace_all: import_zod.z.boolean().optional().default(false).describe("true\uBA74 \uBAA8\uB4E0 \uB9E4\uCE6D \uAD50\uCCB4, false\uBA74 \uCCAB \uBC88\uC9F8\uB9CC")
|
|
232
|
+
},
|
|
233
|
+
async ({ path: filePath, old_string, new_string, replace_all }) => {
|
|
234
|
+
const content = await import_promises.default.readFile(filePath, "utf-8");
|
|
235
|
+
if (!content.includes(old_string)) {
|
|
236
|
+
throw new Error(`old_string\uC744 \uD30C\uC77C\uC5D0\uC11C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${filePath}`);
|
|
237
|
+
}
|
|
238
|
+
let count = 0;
|
|
239
|
+
let pos = 0;
|
|
240
|
+
while ((pos = content.indexOf(old_string, pos)) !== -1) {
|
|
241
|
+
count++;
|
|
242
|
+
pos += old_string.length;
|
|
243
|
+
}
|
|
244
|
+
if (!replace_all && count > 1) {
|
|
245
|
+
throw new Error(
|
|
246
|
+
`\uB9E4\uCE6D\uC774 ${count}\uAC1C\uC785\uB2C8\uB2E4. replace_all\uC744 true\uB85C \uD558\uAC70\uB098 \uB354 \uB113\uC740 \uCEE8\uD14D\uC2A4\uD2B8\uB97C \uD3EC\uD568\uD558\uC138\uC694.`
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
let result;
|
|
250
|
+
let replaced;
|
|
251
|
+
if (replace_all) {
|
|
252
|
+
result = content.split(old_string).join(new_string);
|
|
253
|
+
replaced = count;
|
|
254
|
+
} else {
|
|
255
|
+
result = content.replace(old_string, new_string);
|
|
256
|
+
replaced = 1;
|
|
257
|
+
}
|
|
258
|
+
await import_promises.default.writeFile(filePath, result, "utf-8");
|
|
259
|
+
return {
|
|
260
|
+
content: [{ type: "text", text: `\uAD50\uCCB4 \uC644\uB8CC (${replaced}\uAC1C \uBCC0\uACBD\uB428)` }]
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
);
|
|
222
264
|
}
|
|
223
265
|
};
|
|
224
266
|
|
|
@@ -228,6 +270,17 @@ var import_zod2 = require("zod");
|
|
|
228
270
|
var BrowserTools = class {
|
|
229
271
|
browser = null;
|
|
230
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
|
+
}
|
|
231
284
|
async init() {
|
|
232
285
|
try {
|
|
233
286
|
this.browser = await import_playwright.chromium.launch({ headless: true });
|
|
@@ -250,22 +303,22 @@ var BrowserTools = class {
|
|
|
250
303
|
"browser_navigate",
|
|
251
304
|
"URL\uB85C \uC774\uB3D9",
|
|
252
305
|
{ url: import_zod2.z.string().describe("\uC774\uB3D9\uD560 URL") },
|
|
253
|
-
|
|
306
|
+
({ url }) => this.withLock(async () => {
|
|
254
307
|
const page = requirePage();
|
|
255
308
|
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
256
309
|
return {
|
|
257
310
|
content: [{ type: "text", text: `\uC774\uB3D9 \uC644\uB8CC: ${page.url()}` }]
|
|
258
311
|
};
|
|
259
|
-
}
|
|
312
|
+
})
|
|
260
313
|
);
|
|
261
314
|
server.tool(
|
|
262
315
|
"browser_click",
|
|
263
316
|
"\uC694\uC18C \uD074\uB9AD",
|
|
264
317
|
{ selector: import_zod2.z.string().describe("CSS \uC120\uD0DD\uC790") },
|
|
265
|
-
|
|
318
|
+
({ selector }) => this.withLock(async () => {
|
|
266
319
|
await requirePage().click(selector);
|
|
267
320
|
return { content: [{ type: "text", text: "\uD074\uB9AD \uC644\uB8CC" }] };
|
|
268
|
-
}
|
|
321
|
+
})
|
|
269
322
|
);
|
|
270
323
|
server.tool(
|
|
271
324
|
"browser_type",
|
|
@@ -275,12 +328,12 @@ var BrowserTools = class {
|
|
|
275
328
|
text: import_zod2.z.string().describe("\uC785\uB825\uD560 \uD14D\uC2A4\uD2B8"),
|
|
276
329
|
clear: import_zod2.z.boolean().optional().default(false).describe("\uAE30\uC874 \uB0B4\uC6A9 \uC0AD\uC81C \uD6C4 \uC785\uB825")
|
|
277
330
|
},
|
|
278
|
-
|
|
331
|
+
({ selector, text, clear }) => this.withLock(async () => {
|
|
279
332
|
const page = requirePage();
|
|
280
333
|
if (clear) await page.fill(selector, text);
|
|
281
334
|
else await page.type(selector, text);
|
|
282
335
|
return { content: [{ type: "text", text: "\uC785\uB825 \uC644\uB8CC" }] };
|
|
283
|
-
}
|
|
336
|
+
})
|
|
284
337
|
);
|
|
285
338
|
server.tool(
|
|
286
339
|
"browser_screenshot",
|
|
@@ -289,7 +342,7 @@ var BrowserTools = class {
|
|
|
289
342
|
path: import_zod2.z.string().optional().describe("\uC800\uC7A5 \uACBD\uB85C (\uC5C6\uC73C\uBA74 base64 \uBC18\uD658)"),
|
|
290
343
|
full_page: import_zod2.z.boolean().optional().default(false)
|
|
291
344
|
},
|
|
292
|
-
|
|
345
|
+
({ path: path2, full_page }) => this.withLock(async () => {
|
|
293
346
|
const page = requirePage();
|
|
294
347
|
const screenshot = await page.screenshot({
|
|
295
348
|
path: path2 ?? void 0,
|
|
@@ -307,13 +360,13 @@ var BrowserTools = class {
|
|
|
307
360
|
}
|
|
308
361
|
]
|
|
309
362
|
};
|
|
310
|
-
}
|
|
363
|
+
})
|
|
311
364
|
);
|
|
312
365
|
server.tool(
|
|
313
366
|
"browser_snapshot",
|
|
314
367
|
"\uD398\uC774\uC9C0 \uC811\uADFC\uC131 \uD2B8\uB9AC \uC870\uD68C (\uAD6C\uC870 \uD30C\uC545\uC6A9)",
|
|
315
368
|
{},
|
|
316
|
-
async () => {
|
|
369
|
+
() => this.withLock(async () => {
|
|
317
370
|
const page = requirePage();
|
|
318
371
|
const snapshot = await page.locator("body").ariaSnapshot();
|
|
319
372
|
return {
|
|
@@ -321,29 +374,29 @@ var BrowserTools = class {
|
|
|
321
374
|
{ type: "text", text: snapshot }
|
|
322
375
|
]
|
|
323
376
|
};
|
|
324
|
-
}
|
|
377
|
+
})
|
|
325
378
|
);
|
|
326
379
|
server.tool(
|
|
327
380
|
"browser_evaluate",
|
|
328
381
|
"JavaScript \uC2E4\uD589",
|
|
329
382
|
{ code: import_zod2.z.string().describe("\uC2E4\uD589\uD560 JavaScript \uCF54\uB4DC") },
|
|
330
|
-
|
|
383
|
+
({ code }) => this.withLock(async () => {
|
|
331
384
|
const result = await requirePage().evaluate(code);
|
|
332
385
|
return {
|
|
333
386
|
content: [
|
|
334
387
|
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
335
388
|
]
|
|
336
389
|
};
|
|
337
|
-
}
|
|
390
|
+
})
|
|
338
391
|
);
|
|
339
392
|
server.tool(
|
|
340
393
|
"browser_pdf",
|
|
341
394
|
"\uD604\uC7AC \uD398\uC774\uC9C0 PDF \uC800\uC7A5",
|
|
342
395
|
{ path: import_zod2.z.string().describe("\uC800\uC7A5 \uACBD\uB85C (.pdf)") },
|
|
343
|
-
|
|
396
|
+
({ path: path2 }) => this.withLock(async () => {
|
|
344
397
|
await requirePage().pdf({ path: path2 });
|
|
345
398
|
return { content: [{ type: "text", text: `PDF \uC800\uC7A5 \uC644\uB8CC: ${path2}` }] };
|
|
346
|
-
}
|
|
399
|
+
})
|
|
347
400
|
);
|
|
348
401
|
}
|
|
349
402
|
};
|
|
@@ -356,7 +409,11 @@ var import_util2 = require("util");
|
|
|
356
409
|
var execAsync2 = (0, import_util2.promisify)(import_child_process2.exec);
|
|
357
410
|
async function readNotebook(filePath) {
|
|
358
411
|
const raw = await import_promises2.default.readFile(filePath, "utf-8");
|
|
359
|
-
|
|
412
|
+
try {
|
|
413
|
+
return JSON.parse(raw);
|
|
414
|
+
} catch {
|
|
415
|
+
throw new Error(`\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 .ipynb \uD30C\uC77C: ${filePath}`);
|
|
416
|
+
}
|
|
360
417
|
}
|
|
361
418
|
async function writeNotebook(filePath, nb) {
|
|
362
419
|
await import_promises2.default.writeFile(filePath, JSON.stringify(nb, null, 1), "utf-8");
|
|
@@ -432,6 +489,54 @@ var NotebookTools = class {
|
|
|
432
489
|
throw new Error("jupyter\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC124\uCE58 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694: pip install jupyter");
|
|
433
490
|
}
|
|
434
491
|
);
|
|
492
|
+
server.tool(
|
|
493
|
+
"notebook_add_cell",
|
|
494
|
+
"\uB178\uD2B8\uBD81\uC5D0 \uC0C8 \uC140 \uCD94\uAC00",
|
|
495
|
+
{
|
|
496
|
+
path: import_zod3.z.string().describe(".ipynb \uD30C\uC77C \uACBD\uB85C"),
|
|
497
|
+
cell_type: import_zod3.z.enum(["code", "markdown"]).describe("\uC140 \uD0C0\uC785"),
|
|
498
|
+
source: import_zod3.z.string().describe("\uC140 \uC18C\uC2A4 \uB0B4\uC6A9"),
|
|
499
|
+
position: import_zod3.z.number().optional().describe("\uC0BD\uC785 \uC704\uCE58(0-based). \uC5C6\uC73C\uBA74 \uB9E8 \uB05D\uC5D0 \uCD94\uAC00")
|
|
500
|
+
},
|
|
501
|
+
async ({ path: filePath, cell_type: cellType, source, position }) => {
|
|
502
|
+
const nb = await readNotebook(filePath);
|
|
503
|
+
const newCell = {
|
|
504
|
+
cell_type: cellType,
|
|
505
|
+
source: source.split("\n").map((l, i, arr) => i < arr.length - 1 ? l + "\n" : l),
|
|
506
|
+
metadata: {},
|
|
507
|
+
outputs: cellType === "code" ? [] : void 0,
|
|
508
|
+
execution_count: cellType === "code" ? null : void 0
|
|
509
|
+
};
|
|
510
|
+
let actualIndex;
|
|
511
|
+
if (position === void 0 || position === null) {
|
|
512
|
+
nb.cells.push(newCell);
|
|
513
|
+
actualIndex = nb.cells.length - 1;
|
|
514
|
+
} else {
|
|
515
|
+
const clamped = Math.max(0, Math.min(position, nb.cells.length));
|
|
516
|
+
nb.cells.splice(clamped, 0, newCell);
|
|
517
|
+
actualIndex = clamped;
|
|
518
|
+
}
|
|
519
|
+
await writeNotebook(filePath, nb);
|
|
520
|
+
return { content: [{ type: "text", text: `\uC140 \uCD94\uAC00 \uC644\uB8CC (index: ${actualIndex})` }] };
|
|
521
|
+
}
|
|
522
|
+
);
|
|
523
|
+
server.tool(
|
|
524
|
+
"notebook_delete_cell",
|
|
525
|
+
"\uB178\uD2B8\uBD81 \uD2B9\uC815 \uC140 \uC0AD\uC81C",
|
|
526
|
+
{
|
|
527
|
+
path: import_zod3.z.string().describe(".ipynb \uD30C\uC77C \uACBD\uB85C"),
|
|
528
|
+
cell_index: import_zod3.z.number().describe("\uC0AD\uC81C\uD560 \uC140 \uC778\uB371\uC2A4 (0-based)")
|
|
529
|
+
},
|
|
530
|
+
async ({ path: filePath, cell_index }) => {
|
|
531
|
+
const nb = await readNotebook(filePath);
|
|
532
|
+
if (cell_index < 0 || cell_index >= nb.cells.length) {
|
|
533
|
+
throw new Error(`\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uC140 \uC778\uB371\uC2A4: ${cell_index}`);
|
|
534
|
+
}
|
|
535
|
+
nb.cells.splice(cell_index, 1);
|
|
536
|
+
await writeNotebook(filePath, nb);
|
|
537
|
+
return { content: [{ type: "text", text: `\uC140 \uC0AD\uC81C \uC644\uB8CC (index: ${cell_index})` }] };
|
|
538
|
+
}
|
|
539
|
+
);
|
|
435
540
|
}
|
|
436
541
|
};
|
|
437
542
|
|
|
@@ -440,13 +545,13 @@ var import_child_process3 = require("child_process");
|
|
|
440
545
|
var import_util3 = require("util");
|
|
441
546
|
var import_zod4 = require("zod");
|
|
442
547
|
var execAsync3 = (0, import_util3.promisify)(import_child_process3.exec);
|
|
548
|
+
var screenRecordPid = null;
|
|
443
549
|
function platform() {
|
|
444
550
|
if (process.platform === "darwin") return "mac";
|
|
445
551
|
if (process.platform === "win32") return "win";
|
|
446
552
|
return "linux";
|
|
447
553
|
}
|
|
448
554
|
var DeviceTools = class {
|
|
449
|
-
screenRecordPid = null;
|
|
450
555
|
register(server) {
|
|
451
556
|
server.tool(
|
|
452
557
|
"screen_capture",
|
|
@@ -456,15 +561,24 @@ var DeviceTools = class {
|
|
|
456
561
|
},
|
|
457
562
|
async ({ output_path }) => {
|
|
458
563
|
const p = platform();
|
|
564
|
+
const isTmp = !output_path;
|
|
459
565
|
const tmpPath = output_path ?? `/tmp/junis_screen_${Date.now()}.png`;
|
|
460
566
|
const cmd = {
|
|
461
567
|
mac: `screencapture -x "${tmpPath}"`,
|
|
462
568
|
win: `nircmd.exe savescreenshot "${tmpPath}"`,
|
|
463
569
|
linux: `scrot "${tmpPath}"`
|
|
464
570
|
}[p];
|
|
465
|
-
|
|
466
|
-
|
|
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");
|
|
467
577
|
const data = readFileSync(tmpPath).toString("base64");
|
|
578
|
+
if (isTmp) try {
|
|
579
|
+
unlinkSync(tmpPath);
|
|
580
|
+
} catch {
|
|
581
|
+
}
|
|
468
582
|
return {
|
|
469
583
|
content: [{ type: "image", data, mimeType: "image/png" }]
|
|
470
584
|
};
|
|
@@ -478,15 +592,24 @@ var DeviceTools = class {
|
|
|
478
592
|
},
|
|
479
593
|
async ({ output_path }) => {
|
|
480
594
|
const p = platform();
|
|
595
|
+
const isTmp = !output_path;
|
|
481
596
|
const tmpPath = output_path ?? `/tmp/junis_cam_${Date.now()}.jpg`;
|
|
482
597
|
const cmd = {
|
|
483
598
|
mac: `imagesnap "${tmpPath}"`,
|
|
484
599
|
win: `ffmpeg -f dshow -i video="Default" -frames:v 1 "${tmpPath}"`,
|
|
485
600
|
linux: `fswebcam -r 1280x720 "${tmpPath}"`
|
|
486
601
|
}[p];
|
|
487
|
-
|
|
488
|
-
|
|
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");
|
|
489
608
|
const data = readFileSync(tmpPath).toString("base64");
|
|
609
|
+
if (isTmp) try {
|
|
610
|
+
unlinkSync(tmpPath);
|
|
611
|
+
} catch {
|
|
612
|
+
}
|
|
490
613
|
return {
|
|
491
614
|
content: [{ type: "image", data, mimeType: "image/jpeg" }]
|
|
492
615
|
};
|
|
@@ -546,7 +669,7 @@ var DeviceTools = class {
|
|
|
546
669
|
async ({ action, output_path }) => {
|
|
547
670
|
const p = platform();
|
|
548
671
|
if (action === "start") {
|
|
549
|
-
if (
|
|
672
|
+
if (screenRecordPid) {
|
|
550
673
|
return { content: [{ type: "text", text: "\uC774\uBBF8 \uB179\uD654 \uC911\uC785\uB2C8\uB2E4." }] };
|
|
551
674
|
}
|
|
552
675
|
const tmpPath = output_path ?? `/tmp/junis_record_${Date.now()}.mp4`;
|
|
@@ -554,17 +677,17 @@ var DeviceTools = class {
|
|
|
554
677
|
const cmd = p === "mac" ? ["screencapture", ["-v", tmpPath]] : ["ffmpeg", ["-f", p === "win" ? "gdigrab" : "x11grab", "-i", p === "win" ? "desktop" : ":0.0", tmpPath]];
|
|
555
678
|
const child = spawn(cmd[0], cmd[1], { detached: true, stdio: "ignore" });
|
|
556
679
|
child.unref();
|
|
557
|
-
|
|
558
|
-
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})` }] };
|
|
559
682
|
} else {
|
|
560
|
-
if (!
|
|
683
|
+
if (!screenRecordPid) {
|
|
561
684
|
return { content: [{ type: "text", text: "\uD604\uC7AC \uB179\uD654 \uC911\uC774 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
562
685
|
}
|
|
563
686
|
try {
|
|
564
|
-
process.kill(
|
|
687
|
+
process.kill(screenRecordPid, "SIGINT");
|
|
565
688
|
} catch {
|
|
566
689
|
}
|
|
567
|
-
|
|
690
|
+
screenRecordPid = null;
|
|
568
691
|
return { content: [{ type: "text", text: "\uB179\uD654 \uC911\uC9C0\uB428." }] };
|
|
569
692
|
}
|
|
570
693
|
}
|
|
@@ -583,12 +706,15 @@ var DeviceTools = class {
|
|
|
583
706
|
} catch {
|
|
584
707
|
}
|
|
585
708
|
}
|
|
586
|
-
const res = await fetch("
|
|
709
|
+
const res = await fetch("http://ip-api.com/json/");
|
|
587
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
|
+
}
|
|
588
714
|
return {
|
|
589
715
|
content: [{
|
|
590
716
|
type: "text",
|
|
591
|
-
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)`
|
|
592
718
|
}]
|
|
593
719
|
};
|
|
594
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" }] };
|
|
@@ -208,6 +210,46 @@ ${error.stderr ?? ""}`
|
|
|
208
210
|
};
|
|
209
211
|
}
|
|
210
212
|
);
|
|
213
|
+
server.tool(
|
|
214
|
+
"edit_block",
|
|
215
|
+
"\uD30C\uC77C\uC758 \uD2B9\uC815 \uD14D\uC2A4\uD2B8 \uBE14\uB85D\uC744 \uC0C8 \uD14D\uC2A4\uD2B8\uB85C \uAD50\uCCB4 (diff \uAE30\uBC18 \uBD80\uBD84 \uC218\uC815)",
|
|
216
|
+
{
|
|
217
|
+
path: import_zod.z.string().describe("\uD30C\uC77C \uACBD\uB85C"),
|
|
218
|
+
old_string: import_zod.z.string().describe("\uAD50\uCCB4\uD560 \uAE30\uC874 \uD14D\uC2A4\uD2B8 (\uC815\uD655\uD788 \uC77C\uCE58\uD574\uC57C \uD568)"),
|
|
219
|
+
new_string: import_zod.z.string().describe("\uC0C8 \uD14D\uC2A4\uD2B8"),
|
|
220
|
+
replace_all: import_zod.z.boolean().optional().default(false).describe("true\uBA74 \uBAA8\uB4E0 \uB9E4\uCE6D \uAD50\uCCB4, false\uBA74 \uCCAB \uBC88\uC9F8\uB9CC")
|
|
221
|
+
},
|
|
222
|
+
async ({ path: filePath, old_string, new_string, replace_all }) => {
|
|
223
|
+
const content = await import_promises.default.readFile(filePath, "utf-8");
|
|
224
|
+
if (!content.includes(old_string)) {
|
|
225
|
+
throw new Error(`old_string\uC744 \uD30C\uC77C\uC5D0\uC11C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${filePath}`);
|
|
226
|
+
}
|
|
227
|
+
let count = 0;
|
|
228
|
+
let pos = 0;
|
|
229
|
+
while ((pos = content.indexOf(old_string, pos)) !== -1) {
|
|
230
|
+
count++;
|
|
231
|
+
pos += old_string.length;
|
|
232
|
+
}
|
|
233
|
+
if (!replace_all && count > 1) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
`\uB9E4\uCE6D\uC774 ${count}\uAC1C\uC785\uB2C8\uB2E4. replace_all\uC744 true\uB85C \uD558\uAC70\uB098 \uB354 \uB113\uC740 \uCEE8\uD14D\uC2A4\uD2B8\uB97C \uD3EC\uD568\uD558\uC138\uC694.`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
let result;
|
|
239
|
+
let replaced;
|
|
240
|
+
if (replace_all) {
|
|
241
|
+
result = content.split(old_string).join(new_string);
|
|
242
|
+
replaced = count;
|
|
243
|
+
} else {
|
|
244
|
+
result = content.replace(old_string, new_string);
|
|
245
|
+
replaced = 1;
|
|
246
|
+
}
|
|
247
|
+
await import_promises.default.writeFile(filePath, result, "utf-8");
|
|
248
|
+
return {
|
|
249
|
+
content: [{ type: "text", text: `\uAD50\uCCB4 \uC644\uB8CC (${replaced}\uAC1C \uBCC0\uACBD\uB428)` }]
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
);
|
|
211
253
|
}
|
|
212
254
|
};
|
|
213
255
|
|
|
@@ -217,6 +259,17 @@ var import_zod2 = require("zod");
|
|
|
217
259
|
var BrowserTools = class {
|
|
218
260
|
browser = null;
|
|
219
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
|
+
}
|
|
220
273
|
async init() {
|
|
221
274
|
try {
|
|
222
275
|
this.browser = await import_playwright.chromium.launch({ headless: true });
|
|
@@ -239,22 +292,22 @@ var BrowserTools = class {
|
|
|
239
292
|
"browser_navigate",
|
|
240
293
|
"URL\uB85C \uC774\uB3D9",
|
|
241
294
|
{ url: import_zod2.z.string().describe("\uC774\uB3D9\uD560 URL") },
|
|
242
|
-
|
|
295
|
+
({ url }) => this.withLock(async () => {
|
|
243
296
|
const page = requirePage();
|
|
244
297
|
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
245
298
|
return {
|
|
246
299
|
content: [{ type: "text", text: `\uC774\uB3D9 \uC644\uB8CC: ${page.url()}` }]
|
|
247
300
|
};
|
|
248
|
-
}
|
|
301
|
+
})
|
|
249
302
|
);
|
|
250
303
|
server.tool(
|
|
251
304
|
"browser_click",
|
|
252
305
|
"\uC694\uC18C \uD074\uB9AD",
|
|
253
306
|
{ selector: import_zod2.z.string().describe("CSS \uC120\uD0DD\uC790") },
|
|
254
|
-
|
|
307
|
+
({ selector }) => this.withLock(async () => {
|
|
255
308
|
await requirePage().click(selector);
|
|
256
309
|
return { content: [{ type: "text", text: "\uD074\uB9AD \uC644\uB8CC" }] };
|
|
257
|
-
}
|
|
310
|
+
})
|
|
258
311
|
);
|
|
259
312
|
server.tool(
|
|
260
313
|
"browser_type",
|
|
@@ -264,12 +317,12 @@ var BrowserTools = class {
|
|
|
264
317
|
text: import_zod2.z.string().describe("\uC785\uB825\uD560 \uD14D\uC2A4\uD2B8"),
|
|
265
318
|
clear: import_zod2.z.boolean().optional().default(false).describe("\uAE30\uC874 \uB0B4\uC6A9 \uC0AD\uC81C \uD6C4 \uC785\uB825")
|
|
266
319
|
},
|
|
267
|
-
|
|
320
|
+
({ selector, text, clear }) => this.withLock(async () => {
|
|
268
321
|
const page = requirePage();
|
|
269
322
|
if (clear) await page.fill(selector, text);
|
|
270
323
|
else await page.type(selector, text);
|
|
271
324
|
return { content: [{ type: "text", text: "\uC785\uB825 \uC644\uB8CC" }] };
|
|
272
|
-
}
|
|
325
|
+
})
|
|
273
326
|
);
|
|
274
327
|
server.tool(
|
|
275
328
|
"browser_screenshot",
|
|
@@ -278,7 +331,7 @@ var BrowserTools = class {
|
|
|
278
331
|
path: import_zod2.z.string().optional().describe("\uC800\uC7A5 \uACBD\uB85C (\uC5C6\uC73C\uBA74 base64 \uBC18\uD658)"),
|
|
279
332
|
full_page: import_zod2.z.boolean().optional().default(false)
|
|
280
333
|
},
|
|
281
|
-
|
|
334
|
+
({ path: path2, full_page }) => this.withLock(async () => {
|
|
282
335
|
const page = requirePage();
|
|
283
336
|
const screenshot = await page.screenshot({
|
|
284
337
|
path: path2 ?? void 0,
|
|
@@ -296,13 +349,13 @@ var BrowserTools = class {
|
|
|
296
349
|
}
|
|
297
350
|
]
|
|
298
351
|
};
|
|
299
|
-
}
|
|
352
|
+
})
|
|
300
353
|
);
|
|
301
354
|
server.tool(
|
|
302
355
|
"browser_snapshot",
|
|
303
356
|
"\uD398\uC774\uC9C0 \uC811\uADFC\uC131 \uD2B8\uB9AC \uC870\uD68C (\uAD6C\uC870 \uD30C\uC545\uC6A9)",
|
|
304
357
|
{},
|
|
305
|
-
async () => {
|
|
358
|
+
() => this.withLock(async () => {
|
|
306
359
|
const page = requirePage();
|
|
307
360
|
const snapshot = await page.locator("body").ariaSnapshot();
|
|
308
361
|
return {
|
|
@@ -310,29 +363,29 @@ var BrowserTools = class {
|
|
|
310
363
|
{ type: "text", text: snapshot }
|
|
311
364
|
]
|
|
312
365
|
};
|
|
313
|
-
}
|
|
366
|
+
})
|
|
314
367
|
);
|
|
315
368
|
server.tool(
|
|
316
369
|
"browser_evaluate",
|
|
317
370
|
"JavaScript \uC2E4\uD589",
|
|
318
371
|
{ code: import_zod2.z.string().describe("\uC2E4\uD589\uD560 JavaScript \uCF54\uB4DC") },
|
|
319
|
-
|
|
372
|
+
({ code }) => this.withLock(async () => {
|
|
320
373
|
const result = await requirePage().evaluate(code);
|
|
321
374
|
return {
|
|
322
375
|
content: [
|
|
323
376
|
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
324
377
|
]
|
|
325
378
|
};
|
|
326
|
-
}
|
|
379
|
+
})
|
|
327
380
|
);
|
|
328
381
|
server.tool(
|
|
329
382
|
"browser_pdf",
|
|
330
383
|
"\uD604\uC7AC \uD398\uC774\uC9C0 PDF \uC800\uC7A5",
|
|
331
384
|
{ path: import_zod2.z.string().describe("\uC800\uC7A5 \uACBD\uB85C (.pdf)") },
|
|
332
|
-
|
|
385
|
+
({ path: path2 }) => this.withLock(async () => {
|
|
333
386
|
await requirePage().pdf({ path: path2 });
|
|
334
387
|
return { content: [{ type: "text", text: `PDF \uC800\uC7A5 \uC644\uB8CC: ${path2}` }] };
|
|
335
|
-
}
|
|
388
|
+
})
|
|
336
389
|
);
|
|
337
390
|
}
|
|
338
391
|
};
|
|
@@ -345,7 +398,11 @@ var import_util2 = require("util");
|
|
|
345
398
|
var execAsync2 = (0, import_util2.promisify)(import_child_process2.exec);
|
|
346
399
|
async function readNotebook(filePath) {
|
|
347
400
|
const raw = await import_promises2.default.readFile(filePath, "utf-8");
|
|
348
|
-
|
|
401
|
+
try {
|
|
402
|
+
return JSON.parse(raw);
|
|
403
|
+
} catch {
|
|
404
|
+
throw new Error(`\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 .ipynb \uD30C\uC77C: ${filePath}`);
|
|
405
|
+
}
|
|
349
406
|
}
|
|
350
407
|
async function writeNotebook(filePath, nb) {
|
|
351
408
|
await import_promises2.default.writeFile(filePath, JSON.stringify(nb, null, 1), "utf-8");
|
|
@@ -421,6 +478,54 @@ var NotebookTools = class {
|
|
|
421
478
|
throw new Error("jupyter\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC124\uCE58 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694: pip install jupyter");
|
|
422
479
|
}
|
|
423
480
|
);
|
|
481
|
+
server.tool(
|
|
482
|
+
"notebook_add_cell",
|
|
483
|
+
"\uB178\uD2B8\uBD81\uC5D0 \uC0C8 \uC140 \uCD94\uAC00",
|
|
484
|
+
{
|
|
485
|
+
path: import_zod3.z.string().describe(".ipynb \uD30C\uC77C \uACBD\uB85C"),
|
|
486
|
+
cell_type: import_zod3.z.enum(["code", "markdown"]).describe("\uC140 \uD0C0\uC785"),
|
|
487
|
+
source: import_zod3.z.string().describe("\uC140 \uC18C\uC2A4 \uB0B4\uC6A9"),
|
|
488
|
+
position: import_zod3.z.number().optional().describe("\uC0BD\uC785 \uC704\uCE58(0-based). \uC5C6\uC73C\uBA74 \uB9E8 \uB05D\uC5D0 \uCD94\uAC00")
|
|
489
|
+
},
|
|
490
|
+
async ({ path: filePath, cell_type: cellType, source, position }) => {
|
|
491
|
+
const nb = await readNotebook(filePath);
|
|
492
|
+
const newCell = {
|
|
493
|
+
cell_type: cellType,
|
|
494
|
+
source: source.split("\n").map((l, i, arr) => i < arr.length - 1 ? l + "\n" : l),
|
|
495
|
+
metadata: {},
|
|
496
|
+
outputs: cellType === "code" ? [] : void 0,
|
|
497
|
+
execution_count: cellType === "code" ? null : void 0
|
|
498
|
+
};
|
|
499
|
+
let actualIndex;
|
|
500
|
+
if (position === void 0 || position === null) {
|
|
501
|
+
nb.cells.push(newCell);
|
|
502
|
+
actualIndex = nb.cells.length - 1;
|
|
503
|
+
} else {
|
|
504
|
+
const clamped = Math.max(0, Math.min(position, nb.cells.length));
|
|
505
|
+
nb.cells.splice(clamped, 0, newCell);
|
|
506
|
+
actualIndex = clamped;
|
|
507
|
+
}
|
|
508
|
+
await writeNotebook(filePath, nb);
|
|
509
|
+
return { content: [{ type: "text", text: `\uC140 \uCD94\uAC00 \uC644\uB8CC (index: ${actualIndex})` }] };
|
|
510
|
+
}
|
|
511
|
+
);
|
|
512
|
+
server.tool(
|
|
513
|
+
"notebook_delete_cell",
|
|
514
|
+
"\uB178\uD2B8\uBD81 \uD2B9\uC815 \uC140 \uC0AD\uC81C",
|
|
515
|
+
{
|
|
516
|
+
path: import_zod3.z.string().describe(".ipynb \uD30C\uC77C \uACBD\uB85C"),
|
|
517
|
+
cell_index: import_zod3.z.number().describe("\uC0AD\uC81C\uD560 \uC140 \uC778\uB371\uC2A4 (0-based)")
|
|
518
|
+
},
|
|
519
|
+
async ({ path: filePath, cell_index }) => {
|
|
520
|
+
const nb = await readNotebook(filePath);
|
|
521
|
+
if (cell_index < 0 || cell_index >= nb.cells.length) {
|
|
522
|
+
throw new Error(`\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uC140 \uC778\uB371\uC2A4: ${cell_index}`);
|
|
523
|
+
}
|
|
524
|
+
nb.cells.splice(cell_index, 1);
|
|
525
|
+
await writeNotebook(filePath, nb);
|
|
526
|
+
return { content: [{ type: "text", text: `\uC140 \uC0AD\uC81C \uC644\uB8CC (index: ${cell_index})` }] };
|
|
527
|
+
}
|
|
528
|
+
);
|
|
424
529
|
}
|
|
425
530
|
};
|
|
426
531
|
|
|
@@ -429,13 +534,13 @@ var import_child_process3 = require("child_process");
|
|
|
429
534
|
var import_util3 = require("util");
|
|
430
535
|
var import_zod4 = require("zod");
|
|
431
536
|
var execAsync3 = (0, import_util3.promisify)(import_child_process3.exec);
|
|
537
|
+
var screenRecordPid = null;
|
|
432
538
|
function platform() {
|
|
433
539
|
if (process.platform === "darwin") return "mac";
|
|
434
540
|
if (process.platform === "win32") return "win";
|
|
435
541
|
return "linux";
|
|
436
542
|
}
|
|
437
543
|
var DeviceTools = class {
|
|
438
|
-
screenRecordPid = null;
|
|
439
544
|
register(server) {
|
|
440
545
|
server.tool(
|
|
441
546
|
"screen_capture",
|
|
@@ -445,15 +550,24 @@ var DeviceTools = class {
|
|
|
445
550
|
},
|
|
446
551
|
async ({ output_path }) => {
|
|
447
552
|
const p = platform();
|
|
553
|
+
const isTmp = !output_path;
|
|
448
554
|
const tmpPath = output_path ?? `/tmp/junis_screen_${Date.now()}.png`;
|
|
449
555
|
const cmd = {
|
|
450
556
|
mac: `screencapture -x "${tmpPath}"`,
|
|
451
557
|
win: `nircmd.exe savescreenshot "${tmpPath}"`,
|
|
452
558
|
linux: `scrot "${tmpPath}"`
|
|
453
559
|
}[p];
|
|
454
|
-
|
|
455
|
-
|
|
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");
|
|
456
566
|
const data = readFileSync(tmpPath).toString("base64");
|
|
567
|
+
if (isTmp) try {
|
|
568
|
+
unlinkSync(tmpPath);
|
|
569
|
+
} catch {
|
|
570
|
+
}
|
|
457
571
|
return {
|
|
458
572
|
content: [{ type: "image", data, mimeType: "image/png" }]
|
|
459
573
|
};
|
|
@@ -467,15 +581,24 @@ var DeviceTools = class {
|
|
|
467
581
|
},
|
|
468
582
|
async ({ output_path }) => {
|
|
469
583
|
const p = platform();
|
|
584
|
+
const isTmp = !output_path;
|
|
470
585
|
const tmpPath = output_path ?? `/tmp/junis_cam_${Date.now()}.jpg`;
|
|
471
586
|
const cmd = {
|
|
472
587
|
mac: `imagesnap "${tmpPath}"`,
|
|
473
588
|
win: `ffmpeg -f dshow -i video="Default" -frames:v 1 "${tmpPath}"`,
|
|
474
589
|
linux: `fswebcam -r 1280x720 "${tmpPath}"`
|
|
475
590
|
}[p];
|
|
476
|
-
|
|
477
|
-
|
|
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");
|
|
478
597
|
const data = readFileSync(tmpPath).toString("base64");
|
|
598
|
+
if (isTmp) try {
|
|
599
|
+
unlinkSync(tmpPath);
|
|
600
|
+
} catch {
|
|
601
|
+
}
|
|
479
602
|
return {
|
|
480
603
|
content: [{ type: "image", data, mimeType: "image/jpeg" }]
|
|
481
604
|
};
|
|
@@ -535,7 +658,7 @@ var DeviceTools = class {
|
|
|
535
658
|
async ({ action, output_path }) => {
|
|
536
659
|
const p = platform();
|
|
537
660
|
if (action === "start") {
|
|
538
|
-
if (
|
|
661
|
+
if (screenRecordPid) {
|
|
539
662
|
return { content: [{ type: "text", text: "\uC774\uBBF8 \uB179\uD654 \uC911\uC785\uB2C8\uB2E4." }] };
|
|
540
663
|
}
|
|
541
664
|
const tmpPath = output_path ?? `/tmp/junis_record_${Date.now()}.mp4`;
|
|
@@ -543,17 +666,17 @@ var DeviceTools = class {
|
|
|
543
666
|
const cmd = p === "mac" ? ["screencapture", ["-v", tmpPath]] : ["ffmpeg", ["-f", p === "win" ? "gdigrab" : "x11grab", "-i", p === "win" ? "desktop" : ":0.0", tmpPath]];
|
|
544
667
|
const child = spawn(cmd[0], cmd[1], { detached: true, stdio: "ignore" });
|
|
545
668
|
child.unref();
|
|
546
|
-
|
|
547
|
-
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})` }] };
|
|
548
671
|
} else {
|
|
549
|
-
if (!
|
|
672
|
+
if (!screenRecordPid) {
|
|
550
673
|
return { content: [{ type: "text", text: "\uD604\uC7AC \uB179\uD654 \uC911\uC774 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
551
674
|
}
|
|
552
675
|
try {
|
|
553
|
-
process.kill(
|
|
676
|
+
process.kill(screenRecordPid, "SIGINT");
|
|
554
677
|
} catch {
|
|
555
678
|
}
|
|
556
|
-
|
|
679
|
+
screenRecordPid = null;
|
|
557
680
|
return { content: [{ type: "text", text: "\uB179\uD654 \uC911\uC9C0\uB428." }] };
|
|
558
681
|
}
|
|
559
682
|
}
|
|
@@ -572,12 +695,15 @@ var DeviceTools = class {
|
|
|
572
695
|
} catch {
|
|
573
696
|
}
|
|
574
697
|
}
|
|
575
|
-
const res = await fetch("
|
|
698
|
+
const res = await fetch("http://ip-api.com/json/");
|
|
576
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
|
+
}
|
|
577
703
|
return {
|
|
578
704
|
content: [{
|
|
579
705
|
type: "text",
|
|
580
|
-
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)`
|
|
581
707
|
}]
|
|
582
708
|
};
|
|
583
709
|
}
|