junis 0.4.1 → 0.4.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 +113 -25
- package/dist/server/mcp.js +88 -23
- package/dist/server/stdio.js +82 -22
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -222,6 +222,14 @@ var RelayClient = class {
|
|
|
222
222
|
heartbeatTimer = null;
|
|
223
223
|
destroyed = false;
|
|
224
224
|
lastPongTime = 0;
|
|
225
|
+
_currentRequestId = null;
|
|
226
|
+
_currentSessionId = null;
|
|
227
|
+
get currentRequestId() {
|
|
228
|
+
return this._currentRequestId;
|
|
229
|
+
}
|
|
230
|
+
get currentSessionId() {
|
|
231
|
+
return this._currentSessionId;
|
|
232
|
+
}
|
|
225
233
|
// upload_url_response 대기용 pending 맵
|
|
226
234
|
pendingUploadRequests = /* @__PURE__ */ new Map();
|
|
227
235
|
// signed_url_response 대기용 pending 맵
|
|
@@ -274,11 +282,17 @@ var RelayClient = class {
|
|
|
274
282
|
return;
|
|
275
283
|
}
|
|
276
284
|
if (msg.type === "mcp_request") {
|
|
285
|
+
this._currentRequestId = msg.id;
|
|
286
|
+
this._currentSessionId = msg.session_id || null;
|
|
277
287
|
try {
|
|
278
288
|
let result = await this.onMCPRequest(msg.id, msg.payload);
|
|
289
|
+
this._currentRequestId = null;
|
|
290
|
+
this._currentSessionId = null;
|
|
279
291
|
result = await this.processLargeFiles(result);
|
|
280
292
|
this.send({ type: "mcp_response", id: msg.id, payload: result });
|
|
281
293
|
} catch (err) {
|
|
294
|
+
this._currentRequestId = null;
|
|
295
|
+
this._currentSessionId = null;
|
|
282
296
|
this.send({
|
|
283
297
|
type: "mcp_response",
|
|
284
298
|
id: msg.id,
|
|
@@ -331,6 +345,14 @@ var RelayClient = class {
|
|
|
331
345
|
this.ws.send(JSON.stringify(data));
|
|
332
346
|
}
|
|
333
347
|
}
|
|
348
|
+
sendProgress(requestId, line) {
|
|
349
|
+
this.send({
|
|
350
|
+
type: "mcp_stdout",
|
|
351
|
+
request_id: requestId,
|
|
352
|
+
session_id: this._currentSessionId || "",
|
|
353
|
+
line
|
|
354
|
+
});
|
|
355
|
+
}
|
|
334
356
|
/**
|
|
335
357
|
* 서버에 presigned PUT URL 요청.
|
|
336
358
|
* WebSocket으로 upload_url_request 전송 → upload_url_response 대기.
|
|
@@ -488,7 +510,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
488
510
|
import { createServer } from "http";
|
|
489
511
|
|
|
490
512
|
// src/tools/filesystem.ts
|
|
491
|
-
import { exec, execFile } from "child_process";
|
|
513
|
+
import { exec, execFile, spawn } from "child_process";
|
|
492
514
|
import { promisify } from "util";
|
|
493
515
|
import fs2 from "fs/promises";
|
|
494
516
|
import path2 from "path";
|
|
@@ -559,7 +581,9 @@ function checkPermission(toolName) {
|
|
|
559
581
|
var execAsync = promisify(exec);
|
|
560
582
|
var execFileAsync = promisify(execFile);
|
|
561
583
|
var FilesystemTools = class {
|
|
562
|
-
|
|
584
|
+
relayClient;
|
|
585
|
+
register(server, relayClient) {
|
|
586
|
+
this.relayClient = relayClient;
|
|
563
587
|
server.tool(
|
|
564
588
|
"execute_command",
|
|
565
589
|
[
|
|
@@ -595,26 +619,84 @@ var FilesystemTools = class {
|
|
|
595
619
|
exec(command);
|
|
596
620
|
return { content: [{ type: "text", text: "Background execution started" }] };
|
|
597
621
|
}
|
|
598
|
-
|
|
599
|
-
|
|
622
|
+
const requestId = this.relayClient?.currentRequestId ?? null;
|
|
623
|
+
if (requestId) {
|
|
624
|
+
this.relayClient?.sendProgress(requestId, `$ ${command}`);
|
|
625
|
+
}
|
|
626
|
+
return new Promise((resolve) => {
|
|
627
|
+
const child = spawn("sh", ["-c", command], {
|
|
600
628
|
timeout: timeout_ms
|
|
601
629
|
});
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
630
|
+
let stdoutBuf = "";
|
|
631
|
+
let stderrBuf = "";
|
|
632
|
+
let stdoutLineBuffer = "";
|
|
633
|
+
let stderrLineBuffer = "";
|
|
634
|
+
const flushLines = (buffer, newChunk) => {
|
|
635
|
+
const combined = buffer + newChunk;
|
|
636
|
+
const lines = combined.split("\n");
|
|
637
|
+
const incomplete = lines.pop() ?? "";
|
|
638
|
+
for (const line of lines) {
|
|
639
|
+
if (requestId) {
|
|
640
|
+
this.relayClient?.sendProgress(requestId, line);
|
|
613
641
|
}
|
|
614
|
-
|
|
615
|
-
|
|
642
|
+
}
|
|
643
|
+
return incomplete;
|
|
616
644
|
};
|
|
617
|
-
|
|
645
|
+
child.stdout.on("data", (chunk) => {
|
|
646
|
+
const text = chunk.toString();
|
|
647
|
+
stdoutBuf += text;
|
|
648
|
+
stdoutLineBuffer = flushLines(stdoutLineBuffer, text);
|
|
649
|
+
});
|
|
650
|
+
child.stderr.on("data", (chunk) => {
|
|
651
|
+
const text = chunk.toString();
|
|
652
|
+
stderrBuf += text;
|
|
653
|
+
stderrLineBuffer = flushLines(stderrLineBuffer, text);
|
|
654
|
+
});
|
|
655
|
+
child.on("close", (code, signal) => {
|
|
656
|
+
if (stdoutLineBuffer && requestId) {
|
|
657
|
+
this.relayClient?.sendProgress(requestId, stdoutLineBuffer);
|
|
658
|
+
}
|
|
659
|
+
if (stderrLineBuffer && requestId) {
|
|
660
|
+
this.relayClient?.sendProgress(requestId, stderrLineBuffer);
|
|
661
|
+
}
|
|
662
|
+
if (signal) {
|
|
663
|
+
resolve({
|
|
664
|
+
content: [
|
|
665
|
+
{
|
|
666
|
+
type: "text",
|
|
667
|
+
text: `Killed by signal ${signal} (timeout: ${timeout_ms}ms)`
|
|
668
|
+
}
|
|
669
|
+
],
|
|
670
|
+
isError: true
|
|
671
|
+
});
|
|
672
|
+
} else if (code !== 0 && code !== null) {
|
|
673
|
+
resolve({
|
|
674
|
+
content: [
|
|
675
|
+
{
|
|
676
|
+
type: "text",
|
|
677
|
+
text: `Error (exit ${code}): ${stderrBuf || stdoutBuf || "(no output)"}`
|
|
678
|
+
}
|
|
679
|
+
],
|
|
680
|
+
isError: true
|
|
681
|
+
});
|
|
682
|
+
} else {
|
|
683
|
+
resolve({
|
|
684
|
+
content: [{ type: "text", text: stdoutBuf || stderrBuf || "(no output)" }]
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
child.on("error", (err) => {
|
|
689
|
+
resolve({
|
|
690
|
+
content: [
|
|
691
|
+
{
|
|
692
|
+
type: "text",
|
|
693
|
+
text: `Error: ${err.message}`
|
|
694
|
+
}
|
|
695
|
+
],
|
|
696
|
+
isError: true
|
|
697
|
+
});
|
|
698
|
+
});
|
|
699
|
+
});
|
|
618
700
|
}
|
|
619
701
|
);
|
|
620
702
|
server.tool(
|
|
@@ -1782,14 +1864,14 @@ Cause: ${e.message}${hint}` }],
|
|
|
1782
1864
|
},
|
|
1783
1865
|
async ({ text }) => {
|
|
1784
1866
|
const p = platform();
|
|
1785
|
-
const { spawn:
|
|
1867
|
+
const { spawn: spawn3 } = await import("child_process");
|
|
1786
1868
|
const cmd = {
|
|
1787
1869
|
mac: { bin: "pbcopy", args: [] },
|
|
1788
1870
|
win: { bin: "powershell", args: ["-Command", "$input | Set-Clipboard"] },
|
|
1789
1871
|
linux: { bin: "xclip", args: ["-selection", "clipboard"] }
|
|
1790
1872
|
}[p];
|
|
1791
1873
|
await new Promise((resolve, reject) => {
|
|
1792
|
-
const proc =
|
|
1874
|
+
const proc = spawn3(cmd.bin, cmd.args, { stdio: ["pipe", "ignore", "ignore"] });
|
|
1793
1875
|
proc.on("error", reject);
|
|
1794
1876
|
proc.on("close", (code) => code === 0 ? resolve() : reject(new Error(`${cmd.bin} exited ${code}`)));
|
|
1795
1877
|
proc.stdin.end(text);
|
|
@@ -1821,9 +1903,9 @@ Cause: ${e.message}${hint}` }],
|
|
|
1821
1903
|
return { content: [{ type: "text", text: "Already recording." }] };
|
|
1822
1904
|
}
|
|
1823
1905
|
const tmpPath = output_path ?? `/tmp/junis_record_${Date.now()}.mp4`;
|
|
1824
|
-
const { spawn:
|
|
1906
|
+
const { spawn: spawn3 } = await import("child_process");
|
|
1825
1907
|
const cmd = p === "mac" ? ["screencapture", ["-v", tmpPath]] : ["ffmpeg", ["-f", p === "win" ? "gdigrab" : "x11grab", "-i", p === "win" ? "desktop" : ":0.0", tmpPath]];
|
|
1826
|
-
const child =
|
|
1908
|
+
const child = spawn3(cmd[0], cmd[1], { detached: true, stdio: "ignore" });
|
|
1827
1909
|
child.unref();
|
|
1828
1910
|
screenRecordPid = child.pid ?? null;
|
|
1829
1911
|
return { content: [{ type: "text", text: `Recording started. Output path: ${tmpPath} (PID: ${screenRecordPid})` }] };
|
|
@@ -2761,13 +2843,17 @@ var DesktopTools = class {
|
|
|
2761
2843
|
var mcpPort = 3e3;
|
|
2762
2844
|
var globalBrowserTools = null;
|
|
2763
2845
|
var desktopToolsEnabled = false;
|
|
2846
|
+
var globalRelayClient = null;
|
|
2847
|
+
function setRelayClient(client) {
|
|
2848
|
+
globalRelayClient = client;
|
|
2849
|
+
}
|
|
2764
2850
|
function createMcpServer() {
|
|
2765
2851
|
const server = new McpServer({
|
|
2766
2852
|
name: "junis",
|
|
2767
2853
|
version: "0.1.0"
|
|
2768
2854
|
});
|
|
2769
2855
|
const fsTools = new FilesystemTools();
|
|
2770
|
-
fsTools.register(server);
|
|
2856
|
+
fsTools.register(server, globalRelayClient ?? void 0);
|
|
2771
2857
|
if (globalBrowserTools) {
|
|
2772
2858
|
globalBrowserTools.register(server);
|
|
2773
2859
|
}
|
|
@@ -3090,7 +3176,7 @@ import { createRequire } from "module";
|
|
|
3090
3176
|
import fs6 from "fs";
|
|
3091
3177
|
import path3 from "path";
|
|
3092
3178
|
import os2 from "os";
|
|
3093
|
-
import { execSync, spawn } from "child_process";
|
|
3179
|
+
import { execSync, spawn as spawn2 } from "child_process";
|
|
3094
3180
|
var CONFIG_DIR2 = path3.join(os2.homedir(), ".junis");
|
|
3095
3181
|
var PID_FILE = path3.join(CONFIG_DIR2, "junis.pid");
|
|
3096
3182
|
var LOG_DIR = path3.join(CONFIG_DIR2, "logs");
|
|
@@ -3128,7 +3214,7 @@ function startDaemon(port) {
|
|
|
3128
3214
|
const scriptPath = process.argv[1];
|
|
3129
3215
|
const out = fs6.openSync(LOG_FILE, "a");
|
|
3130
3216
|
const err = fs6.openSync(LOG_FILE, "a");
|
|
3131
|
-
const child =
|
|
3217
|
+
const child = spawn2(nodePath, [scriptPath, "start", "--daemon", "--port", String(port)], {
|
|
3132
3218
|
detached: true,
|
|
3133
3219
|
stdio: ["ignore", out, err],
|
|
3134
3220
|
env: { ...process.env }
|
|
@@ -3367,6 +3453,7 @@ async function runForeground(config, port) {
|
|
|
3367
3453
|
process.exit(1);
|
|
3368
3454
|
}
|
|
3369
3455
|
});
|
|
3456
|
+
setRelayClient(relay);
|
|
3370
3457
|
await relay.connect();
|
|
3371
3458
|
const webUrl = process.env.JUNIS_WEB_URL ?? "https://junis.ai";
|
|
3372
3459
|
console.log(" \u25C9 Relay connected");
|
|
@@ -3585,6 +3672,7 @@ program.command("start", { isDefault: true }).description("Start Junis agent con
|
|
|
3585
3672
|
process.exit(1);
|
|
3586
3673
|
}
|
|
3587
3674
|
});
|
|
3675
|
+
setRelayClient(relay);
|
|
3588
3676
|
await relay.connect();
|
|
3589
3677
|
console.log("[junis daemon] relay connected");
|
|
3590
3678
|
return;
|
package/dist/server/mcp.js
CHANGED
|
@@ -4,7 +4,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
4
4
|
import { createServer } from "http";
|
|
5
5
|
|
|
6
6
|
// src/tools/filesystem.ts
|
|
7
|
-
import { exec, execFile } from "child_process";
|
|
7
|
+
import { exec, execFile, spawn } from "child_process";
|
|
8
8
|
import { promisify } from "util";
|
|
9
9
|
import fs from "fs/promises";
|
|
10
10
|
import path from "path";
|
|
@@ -75,7 +75,9 @@ function checkPermission(toolName) {
|
|
|
75
75
|
var execAsync = promisify(exec);
|
|
76
76
|
var execFileAsync = promisify(execFile);
|
|
77
77
|
var FilesystemTools = class {
|
|
78
|
-
|
|
78
|
+
relayClient;
|
|
79
|
+
register(server, relayClient) {
|
|
80
|
+
this.relayClient = relayClient;
|
|
79
81
|
server.tool(
|
|
80
82
|
"execute_command",
|
|
81
83
|
[
|
|
@@ -111,26 +113,84 @@ var FilesystemTools = class {
|
|
|
111
113
|
exec(command);
|
|
112
114
|
return { content: [{ type: "text", text: "Background execution started" }] };
|
|
113
115
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
const requestId = this.relayClient?.currentRequestId ?? null;
|
|
117
|
+
if (requestId) {
|
|
118
|
+
this.relayClient?.sendProgress(requestId, `$ ${command}`);
|
|
119
|
+
}
|
|
120
|
+
return new Promise((resolve) => {
|
|
121
|
+
const child = spawn("sh", ["-c", command], {
|
|
116
122
|
timeout: timeout_ms
|
|
117
123
|
});
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
124
|
+
let stdoutBuf = "";
|
|
125
|
+
let stderrBuf = "";
|
|
126
|
+
let stdoutLineBuffer = "";
|
|
127
|
+
let stderrLineBuffer = "";
|
|
128
|
+
const flushLines = (buffer, newChunk) => {
|
|
129
|
+
const combined = buffer + newChunk;
|
|
130
|
+
const lines = combined.split("\n");
|
|
131
|
+
const incomplete = lines.pop() ?? "";
|
|
132
|
+
for (const line of lines) {
|
|
133
|
+
if (requestId) {
|
|
134
|
+
this.relayClient?.sendProgress(requestId, line);
|
|
129
135
|
}
|
|
130
|
-
|
|
131
|
-
|
|
136
|
+
}
|
|
137
|
+
return incomplete;
|
|
132
138
|
};
|
|
133
|
-
|
|
139
|
+
child.stdout.on("data", (chunk) => {
|
|
140
|
+
const text = chunk.toString();
|
|
141
|
+
stdoutBuf += text;
|
|
142
|
+
stdoutLineBuffer = flushLines(stdoutLineBuffer, text);
|
|
143
|
+
});
|
|
144
|
+
child.stderr.on("data", (chunk) => {
|
|
145
|
+
const text = chunk.toString();
|
|
146
|
+
stderrBuf += text;
|
|
147
|
+
stderrLineBuffer = flushLines(stderrLineBuffer, text);
|
|
148
|
+
});
|
|
149
|
+
child.on("close", (code, signal) => {
|
|
150
|
+
if (stdoutLineBuffer && requestId) {
|
|
151
|
+
this.relayClient?.sendProgress(requestId, stdoutLineBuffer);
|
|
152
|
+
}
|
|
153
|
+
if (stderrLineBuffer && requestId) {
|
|
154
|
+
this.relayClient?.sendProgress(requestId, stderrLineBuffer);
|
|
155
|
+
}
|
|
156
|
+
if (signal) {
|
|
157
|
+
resolve({
|
|
158
|
+
content: [
|
|
159
|
+
{
|
|
160
|
+
type: "text",
|
|
161
|
+
text: `Killed by signal ${signal} (timeout: ${timeout_ms}ms)`
|
|
162
|
+
}
|
|
163
|
+
],
|
|
164
|
+
isError: true
|
|
165
|
+
});
|
|
166
|
+
} else if (code !== 0 && code !== null) {
|
|
167
|
+
resolve({
|
|
168
|
+
content: [
|
|
169
|
+
{
|
|
170
|
+
type: "text",
|
|
171
|
+
text: `Error (exit ${code}): ${stderrBuf || stdoutBuf || "(no output)"}`
|
|
172
|
+
}
|
|
173
|
+
],
|
|
174
|
+
isError: true
|
|
175
|
+
});
|
|
176
|
+
} else {
|
|
177
|
+
resolve({
|
|
178
|
+
content: [{ type: "text", text: stdoutBuf || stderrBuf || "(no output)" }]
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
child.on("error", (err) => {
|
|
183
|
+
resolve({
|
|
184
|
+
content: [
|
|
185
|
+
{
|
|
186
|
+
type: "text",
|
|
187
|
+
text: `Error: ${err.message}`
|
|
188
|
+
}
|
|
189
|
+
],
|
|
190
|
+
isError: true
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
});
|
|
134
194
|
}
|
|
135
195
|
);
|
|
136
196
|
server.tool(
|
|
@@ -1298,14 +1358,14 @@ Cause: ${e.message}${hint}` }],
|
|
|
1298
1358
|
},
|
|
1299
1359
|
async ({ text }) => {
|
|
1300
1360
|
const p = platform();
|
|
1301
|
-
const { spawn } = await import("child_process");
|
|
1361
|
+
const { spawn: spawn2 } = await import("child_process");
|
|
1302
1362
|
const cmd = {
|
|
1303
1363
|
mac: { bin: "pbcopy", args: [] },
|
|
1304
1364
|
win: { bin: "powershell", args: ["-Command", "$input | Set-Clipboard"] },
|
|
1305
1365
|
linux: { bin: "xclip", args: ["-selection", "clipboard"] }
|
|
1306
1366
|
}[p];
|
|
1307
1367
|
await new Promise((resolve, reject) => {
|
|
1308
|
-
const proc =
|
|
1368
|
+
const proc = spawn2(cmd.bin, cmd.args, { stdio: ["pipe", "ignore", "ignore"] });
|
|
1309
1369
|
proc.on("error", reject);
|
|
1310
1370
|
proc.on("close", (code) => code === 0 ? resolve() : reject(new Error(`${cmd.bin} exited ${code}`)));
|
|
1311
1371
|
proc.stdin.end(text);
|
|
@@ -1337,9 +1397,9 @@ Cause: ${e.message}${hint}` }],
|
|
|
1337
1397
|
return { content: [{ type: "text", text: "Already recording." }] };
|
|
1338
1398
|
}
|
|
1339
1399
|
const tmpPath = output_path ?? `/tmp/junis_record_${Date.now()}.mp4`;
|
|
1340
|
-
const { spawn } = await import("child_process");
|
|
1400
|
+
const { spawn: spawn2 } = await import("child_process");
|
|
1341
1401
|
const cmd = p === "mac" ? ["screencapture", ["-v", tmpPath]] : ["ffmpeg", ["-f", p === "win" ? "gdigrab" : "x11grab", "-i", p === "win" ? "desktop" : ":0.0", tmpPath]];
|
|
1342
|
-
const child =
|
|
1402
|
+
const child = spawn2(cmd[0], cmd[1], { detached: true, stdio: "ignore" });
|
|
1343
1403
|
child.unref();
|
|
1344
1404
|
screenRecordPid = child.pid ?? null;
|
|
1345
1405
|
return { content: [{ type: "text", text: `Recording started. Output path: ${tmpPath} (PID: ${screenRecordPid})` }] };
|
|
@@ -2277,13 +2337,17 @@ var DesktopTools = class {
|
|
|
2277
2337
|
var mcpPort = 3e3;
|
|
2278
2338
|
var globalBrowserTools = null;
|
|
2279
2339
|
var desktopToolsEnabled = false;
|
|
2340
|
+
var globalRelayClient = null;
|
|
2341
|
+
function setRelayClient(client) {
|
|
2342
|
+
globalRelayClient = client;
|
|
2343
|
+
}
|
|
2280
2344
|
function createMcpServer() {
|
|
2281
2345
|
const server = new McpServer({
|
|
2282
2346
|
name: "junis",
|
|
2283
2347
|
version: "0.1.0"
|
|
2284
2348
|
});
|
|
2285
2349
|
const fsTools = new FilesystemTools();
|
|
2286
|
-
fsTools.register(server);
|
|
2350
|
+
fsTools.register(server, globalRelayClient ?? void 0);
|
|
2287
2351
|
if (globalBrowserTools) {
|
|
2288
2352
|
globalBrowserTools.register(server);
|
|
2289
2353
|
}
|
|
@@ -2574,6 +2638,7 @@ async function handleMCPRequest(id, payload) {
|
|
|
2574
2638
|
export {
|
|
2575
2639
|
checkPermission,
|
|
2576
2640
|
handleMCPRequest,
|
|
2641
|
+
setRelayClient,
|
|
2577
2642
|
startMCPServer,
|
|
2578
2643
|
toolPermissions
|
|
2579
2644
|
};
|
package/dist/server/stdio.js
CHANGED
|
@@ -5,7 +5,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
6
|
|
|
7
7
|
// src/tools/filesystem.ts
|
|
8
|
-
import { exec, execFile } from "child_process";
|
|
8
|
+
import { exec, execFile, spawn } from "child_process";
|
|
9
9
|
import { promisify } from "util";
|
|
10
10
|
import fs from "fs/promises";
|
|
11
11
|
import path from "path";
|
|
@@ -76,7 +76,9 @@ function checkPermission(toolName) {
|
|
|
76
76
|
var execAsync = promisify(exec);
|
|
77
77
|
var execFileAsync = promisify(execFile);
|
|
78
78
|
var FilesystemTools = class {
|
|
79
|
-
|
|
79
|
+
relayClient;
|
|
80
|
+
register(server, relayClient) {
|
|
81
|
+
this.relayClient = relayClient;
|
|
80
82
|
server.tool(
|
|
81
83
|
"execute_command",
|
|
82
84
|
[
|
|
@@ -112,26 +114,84 @@ var FilesystemTools = class {
|
|
|
112
114
|
exec(command);
|
|
113
115
|
return { content: [{ type: "text", text: "Background execution started" }] };
|
|
114
116
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
const requestId = this.relayClient?.currentRequestId ?? null;
|
|
118
|
+
if (requestId) {
|
|
119
|
+
this.relayClient?.sendProgress(requestId, `$ ${command}`);
|
|
120
|
+
}
|
|
121
|
+
return new Promise((resolve) => {
|
|
122
|
+
const child = spawn("sh", ["-c", command], {
|
|
117
123
|
timeout: timeout_ms
|
|
118
124
|
});
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
125
|
+
let stdoutBuf = "";
|
|
126
|
+
let stderrBuf = "";
|
|
127
|
+
let stdoutLineBuffer = "";
|
|
128
|
+
let stderrLineBuffer = "";
|
|
129
|
+
const flushLines = (buffer, newChunk) => {
|
|
130
|
+
const combined = buffer + newChunk;
|
|
131
|
+
const lines = combined.split("\n");
|
|
132
|
+
const incomplete = lines.pop() ?? "";
|
|
133
|
+
for (const line of lines) {
|
|
134
|
+
if (requestId) {
|
|
135
|
+
this.relayClient?.sendProgress(requestId, line);
|
|
130
136
|
}
|
|
131
|
-
|
|
132
|
-
|
|
137
|
+
}
|
|
138
|
+
return incomplete;
|
|
133
139
|
};
|
|
134
|
-
|
|
140
|
+
child.stdout.on("data", (chunk) => {
|
|
141
|
+
const text = chunk.toString();
|
|
142
|
+
stdoutBuf += text;
|
|
143
|
+
stdoutLineBuffer = flushLines(stdoutLineBuffer, text);
|
|
144
|
+
});
|
|
145
|
+
child.stderr.on("data", (chunk) => {
|
|
146
|
+
const text = chunk.toString();
|
|
147
|
+
stderrBuf += text;
|
|
148
|
+
stderrLineBuffer = flushLines(stderrLineBuffer, text);
|
|
149
|
+
});
|
|
150
|
+
child.on("close", (code, signal) => {
|
|
151
|
+
if (stdoutLineBuffer && requestId) {
|
|
152
|
+
this.relayClient?.sendProgress(requestId, stdoutLineBuffer);
|
|
153
|
+
}
|
|
154
|
+
if (stderrLineBuffer && requestId) {
|
|
155
|
+
this.relayClient?.sendProgress(requestId, stderrLineBuffer);
|
|
156
|
+
}
|
|
157
|
+
if (signal) {
|
|
158
|
+
resolve({
|
|
159
|
+
content: [
|
|
160
|
+
{
|
|
161
|
+
type: "text",
|
|
162
|
+
text: `Killed by signal ${signal} (timeout: ${timeout_ms}ms)`
|
|
163
|
+
}
|
|
164
|
+
],
|
|
165
|
+
isError: true
|
|
166
|
+
});
|
|
167
|
+
} else if (code !== 0 && code !== null) {
|
|
168
|
+
resolve({
|
|
169
|
+
content: [
|
|
170
|
+
{
|
|
171
|
+
type: "text",
|
|
172
|
+
text: `Error (exit ${code}): ${stderrBuf || stdoutBuf || "(no output)"}`
|
|
173
|
+
}
|
|
174
|
+
],
|
|
175
|
+
isError: true
|
|
176
|
+
});
|
|
177
|
+
} else {
|
|
178
|
+
resolve({
|
|
179
|
+
content: [{ type: "text", text: stdoutBuf || stderrBuf || "(no output)" }]
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
child.on("error", (err) => {
|
|
184
|
+
resolve({
|
|
185
|
+
content: [
|
|
186
|
+
{
|
|
187
|
+
type: "text",
|
|
188
|
+
text: `Error: ${err.message}`
|
|
189
|
+
}
|
|
190
|
+
],
|
|
191
|
+
isError: true
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
});
|
|
135
195
|
}
|
|
136
196
|
);
|
|
137
197
|
server.tool(
|
|
@@ -1299,14 +1359,14 @@ Cause: ${e.message}${hint}` }],
|
|
|
1299
1359
|
},
|
|
1300
1360
|
async ({ text }) => {
|
|
1301
1361
|
const p = platform();
|
|
1302
|
-
const { spawn } = await import("child_process");
|
|
1362
|
+
const { spawn: spawn2 } = await import("child_process");
|
|
1303
1363
|
const cmd = {
|
|
1304
1364
|
mac: { bin: "pbcopy", args: [] },
|
|
1305
1365
|
win: { bin: "powershell", args: ["-Command", "$input | Set-Clipboard"] },
|
|
1306
1366
|
linux: { bin: "xclip", args: ["-selection", "clipboard"] }
|
|
1307
1367
|
}[p];
|
|
1308
1368
|
await new Promise((resolve, reject) => {
|
|
1309
|
-
const proc =
|
|
1369
|
+
const proc = spawn2(cmd.bin, cmd.args, { stdio: ["pipe", "ignore", "ignore"] });
|
|
1310
1370
|
proc.on("error", reject);
|
|
1311
1371
|
proc.on("close", (code) => code === 0 ? resolve() : reject(new Error(`${cmd.bin} exited ${code}`)));
|
|
1312
1372
|
proc.stdin.end(text);
|
|
@@ -1338,9 +1398,9 @@ Cause: ${e.message}${hint}` }],
|
|
|
1338
1398
|
return { content: [{ type: "text", text: "Already recording." }] };
|
|
1339
1399
|
}
|
|
1340
1400
|
const tmpPath = output_path ?? `/tmp/junis_record_${Date.now()}.mp4`;
|
|
1341
|
-
const { spawn } = await import("child_process");
|
|
1401
|
+
const { spawn: spawn2 } = await import("child_process");
|
|
1342
1402
|
const cmd = p === "mac" ? ["screencapture", ["-v", tmpPath]] : ["ffmpeg", ["-f", p === "win" ? "gdigrab" : "x11grab", "-i", p === "win" ? "desktop" : ":0.0", tmpPath]];
|
|
1343
|
-
const child =
|
|
1403
|
+
const child = spawn2(cmd[0], cmd[1], { detached: true, stdio: "ignore" });
|
|
1344
1404
|
child.unref();
|
|
1345
1405
|
screenRecordPid = child.pid ?? null;
|
|
1346
1406
|
return { content: [{ type: "text", text: `Recording started. Output path: ${tmpPath} (PID: ${screenRecordPid})` }] };
|