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.
Files changed (2) hide show
  1. package/mcp-server.js +52 -12
  2. 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 tokenFile = `${os.homedir()}/.sweetspot-token`;
279
+ const keypairFile = `${os.homedir()}/.sweetspot-keypair.json`;
280
280
 
281
- function loadOrCreateToken() {
282
- if (fs.existsSync(tokenFile)) return fs.readFileSync(tokenFile, "utf-8").trim();
283
- const token = crypto.randomUUID();
284
- fs.writeFileSync(tokenFile, token, "utf-8");
285
- return token;
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, token) {
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
- ws.send(JSON.stringify({ type: "register", name, token }));
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 token = loadOrCreateToken();
418
- console.error(`[remote-agent] 토큰 파일: ${tokenFile}`);
419
- await connectWs(regName, token);
457
+ const keypair = loadOrCreateKeypair();
458
+ console.error(`[remote-agent] RSA 키페어 파일: ${keypairFile}`);
459
+ await connectWs(regName, keypair);
420
460
  }
421
461
  }
422
462
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sweetspot-remote-agent",
3
- "version": "1.8.5",
3
+ "version": "1.8.7",
4
4
  "description": "Sweetspot 원격 제어 MCP 서버 — 스크린샷, 마우스/키보드, 앱 제어, 파일 탐색, 셸 실행",
5
5
  "type": "module",
6
6
  "main": "mcp-server.js",