sweetspot-remote-agent 1.8.5 → 1.8.7
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/mcp-server.js +52 -12
- package/package.json +1 -1
package/mcp-server.js
CHANGED
|
@@ -273,20 +273,34 @@ if (!isStdio && !noRegister && !regName) {
|
|
|
273
273
|
process.exit(1);
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
-
// ──
|
|
276
|
+
// ── RSA 키페어 자동 생성/저장 ──
|
|
277
277
|
import fs from "fs";
|
|
278
278
|
|
|
279
|
-
const
|
|
279
|
+
const keypairFile = `${os.homedir()}/.sweetspot-keypair.json`;
|
|
280
280
|
|
|
281
|
-
function
|
|
282
|
-
if (fs.existsSync(
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
281
|
+
function loadOrCreateKeypair() {
|
|
282
|
+
if (fs.existsSync(keypairFile)) {
|
|
283
|
+
const { privateKey, publicKey } = JSON.parse(fs.readFileSync(keypairFile, "utf-8"));
|
|
284
|
+
return { privateKey, publicKey };
|
|
285
|
+
}
|
|
286
|
+
const { privateKey, publicKey } = crypto.generateKeyPairSync("rsa", {
|
|
287
|
+
modulusLength: 2048,
|
|
288
|
+
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
289
|
+
privateKeyEncoding: { type: "pkcs8", format: "pem" },
|
|
290
|
+
});
|
|
291
|
+
fs.writeFileSync(keypairFile, JSON.stringify({ privateKey, publicKey }), { mode: 0o600 });
|
|
292
|
+
console.error(`[remote-agent] RSA 키페어 생성 완료: ${keypairFile}`);
|
|
293
|
+
return { privateKey, publicKey };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function signChallenge(privateKey, name, timestamp) {
|
|
297
|
+
const payload = `${name}:${timestamp}`;
|
|
298
|
+
return crypto.sign("sha256", Buffer.from(payload), { key: privateKey, padding: crypto.constants.RSA_PKCS1_PSS_PADDING })
|
|
299
|
+
.toString("base64");
|
|
286
300
|
}
|
|
287
301
|
|
|
288
302
|
// ── WebSocket 클라이언트 (GCP 역방향 연결) ──
|
|
289
|
-
async function connectWs(name,
|
|
303
|
+
async function connectWs(name, keypair) {
|
|
290
304
|
const { default: WebSocket } = await import("ws");
|
|
291
305
|
const wsUrl = BOT_SERVER.replace(/^http/, "ws") + "/agent";
|
|
292
306
|
let alive = true;
|
|
@@ -296,7 +310,15 @@ async function connectWs(name, token) {
|
|
|
296
310
|
const ws = new WebSocket(wsUrl);
|
|
297
311
|
|
|
298
312
|
ws.on("open", () => {
|
|
299
|
-
|
|
313
|
+
const timestamp = Date.now();
|
|
314
|
+
const signature = signChallenge(keypair.privateKey, name, timestamp);
|
|
315
|
+
ws.send(JSON.stringify({
|
|
316
|
+
type: "register",
|
|
317
|
+
name,
|
|
318
|
+
publicKey: keypair.publicKey,
|
|
319
|
+
timestamp,
|
|
320
|
+
signature,
|
|
321
|
+
}));
|
|
300
322
|
console.error(`[remote-agent] GCP 서버에 연결 중... (${name})`);
|
|
301
323
|
});
|
|
302
324
|
|
|
@@ -377,6 +399,24 @@ async function executeAction(msg) {
|
|
|
377
399
|
try { unlinkSync(tmpFile); } catch {}
|
|
378
400
|
}
|
|
379
401
|
}
|
|
402
|
+
case "write_note": {
|
|
403
|
+
// 메모 앱 노트 작성 — body를 temp 파일로 전달해 인코딩 문제 방지
|
|
404
|
+
const { execSync } = await import("child_process");
|
|
405
|
+
const { writeFileSync, unlinkSync } = await import("fs");
|
|
406
|
+
const ts = Date.now();
|
|
407
|
+
const contentFile = `/tmp/sweetspot_note_${ts}.txt`;
|
|
408
|
+
const scriptFile = `/tmp/sweetspot_script_${ts}.applescript`;
|
|
409
|
+
const title = (msg.title || "Sophy 노트").replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
410
|
+
writeFileSync(contentFile, msg.body || "", "utf-8");
|
|
411
|
+
const script = `tell application "Notes"\nset noteBody to read POSIX file "${contentFile}" as «class utf8»\nmake new note with properties {name:"${title}", body:noteBody}\nend tell`;
|
|
412
|
+
writeFileSync(scriptFile, script, "utf-8");
|
|
413
|
+
try {
|
|
414
|
+
const output = execSync(`osascript '${scriptFile}'`, { timeout: 15000, encoding: "utf-8" });
|
|
415
|
+
return { action, success: true, output: output || "(노트 작성 완료)" };
|
|
416
|
+
} finally {
|
|
417
|
+
try { unlinkSync(scriptFile); unlinkSync(contentFile); } catch {}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
380
420
|
case "list_files": {
|
|
381
421
|
const { execSync } = await import("child_process");
|
|
382
422
|
const resolved = (msg.path || "~").replace(/^~/, process.env.HOME || "~");
|
|
@@ -414,9 +454,9 @@ async function main() {
|
|
|
414
454
|
console.error("[remote-agent] MCP 서버 시작 (stdio)");
|
|
415
455
|
} else {
|
|
416
456
|
// WebSocket 모드: GCP 역방향 연결 (ngrok 불필요)
|
|
417
|
-
const
|
|
418
|
-
console.error(`[remote-agent]
|
|
419
|
-
await connectWs(regName,
|
|
457
|
+
const keypair = loadOrCreateKeypair();
|
|
458
|
+
console.error(`[remote-agent] RSA 키페어 파일: ${keypairFile}`);
|
|
459
|
+
await connectWs(regName, keypair);
|
|
420
460
|
}
|
|
421
461
|
}
|
|
422
462
|
|