#!/usr/bin/env node import { spawn } from "node:child_process"; function writeJson(payload) { process.stdout.write(`${JSON.stringify(payload)}\n`); } async function readStdin() { const chunks = []; for await (const chunk of process.stdin) { chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8")); } return chunks.join("").trim(); } function parsePayload(raw) { try { const parsed = JSON.parse(raw || "{}"); if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { throw new Error("expected object"); } return parsed; } catch { return null; } } function envString(name) { return String(process.env[name] || "").trim(); } function envBoolean(name) { return envString(name).toLowerCase() === "true"; } function detectTargetApp(objective) { const text = String(objective || "").toLowerCase(); const candidates = [ ["Chrome", ["chrome", "谷歌浏览器"]], ["Safari", ["safari"]], ["Finder", ["finder", "访达"]], ["System Settings", ["system settings", "系统设置", "设置"]], ["QQ", ["qq"]], ["WeChat", ["wechat", "微信"]], ["Telegram", ["telegram"]], ]; for (const [name, aliases] of candidates) { if (aliases.some((alias) => text.includes(alias.toLowerCase()))) { return name; } } return "Finder"; } function extractQuotedText(objective) { const text = String(objective || ""); const patterns = [ /[“"]([^“”"]+)[”"]/, /[「『]([^」』]+)[」』]/, /输入[::]\s*([^\n。;;]+)/, /打字[::]\s*([^\n。;;]+)/, ]; for (const pattern of patterns) { const match = text.match(pattern); const value = match?.[1]?.trim(); if (value) return value; } return undefined; } function shouldSubmitAfterTyping(objective) { const text = String(objective || "").toLowerCase(); return text.includes("发送") || text.includes("提交") || text.includes("回车") || text.includes("enter"); } function escapeAppleScriptString(value) { return String(value || "").replaceAll("\\", "\\\\").replaceAll('"', '\\"'); } function buildAppleScript(targetApp, objective) { const lines = [ `tell application "${escapeAppleScriptString(targetApp)}"`, "activate", "end tell", ]; const typedText = extractQuotedText(objective); if (typedText) { lines.push("delay 0.2"); lines.push('tell application "System Events"'); lines.push(`keystroke "${escapeAppleScriptString(typedText)}"`); if (shouldSubmitAfterTyping(objective)) { lines.push("key code 36"); } lines.push("end tell"); } return lines.join("\n"); } function runCommand(command, args, env = {}) { return new Promise((resolve, reject) => { const child = spawn(command, args, { env: { ...process.env, ...env, }, stdio: ["ignore", "pipe", "pipe"], }); let stdout = ""; let stderr = ""; child.stdout.setEncoding("utf8"); child.stderr.setEncoding("utf8"); child.stdout.on("data", (chunk) => { stdout += chunk; }); child.stderr.on("data", (chunk) => { stderr += chunk; }); child.on("error", reject); child.on("close", (code) => { if (code !== 0) { reject(new Error(stderr.trim() || `ssh computer use exited with ${code}`)); return; } resolve({ stdout: stdout.trim(), stderr: stderr.trim() }); }); }); } async function runRemoteAppleScript(script) { const host = envString("BOSS_SSH_CONTROL_HOST"); const user = envString("BOSS_SSH_CONTROL_USER"); const port = envString("BOSS_SSH_CONTROL_PORT") || "22"; const password = envString("BOSS_SSH_CONTROL_PASSWORD"); if (!host) throw new Error("SSH_CONTROL_HOST_REQUIRED"); if (!user) throw new Error("SSH_CONTROL_USER_REQUIRED"); const sshTarget = `${user}@${host}`; const encodedScript = Buffer.from(script, "utf8").toString("base64"); const remoteCommand = `printf '%s' '${encodedScript}' | base64 -D | osascript`; const sshArgs = [ "-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=8", "-o", "PreferredAuthentications=password", "-o", "PubkeyAuthentication=no", "-o", "NumberOfPasswordPrompts=1", "-p", port, sshTarget, remoteCommand, ]; if (password) { await runCommand("sshpass", ["-e", "ssh", ...sshArgs], { SSHPASS: password }); return; } await runCommand("ssh", sshArgs); } const payload = parsePayload(await readStdin()); if (!payload) { writeJson({ status: "failed", error: "INVALID_SSH_COMPUTER_USE_PAYLOAD" }); process.exit(0); } const requestId = typeof payload.requestId === "string" ? payload.requestId : undefined; const objective = typeof payload.objective === "string" && payload.objective.trim() ? payload.objective.trim() : "远程桌面控制 smoke 链路测试"; const targetApp = detectTargetApp(objective); const typedText = extractQuotedText(objective); try { if (!envString("BOSS_SSH_CONTROL_HOST")) { throw new Error("SSH_CONTROL_HOST_REQUIRED"); } if (!envString("BOSS_SSH_CONTROL_USER")) { throw new Error("SSH_CONTROL_USER_REQUIRED"); } const appleScript = buildAppleScript(targetApp, objective); if (!envBoolean("BOSS_SSH_CONTROL_DRY_RUN")) { await runRemoteAppleScript(appleScript); } writeJson({ status: "completed", requestId, replyBody: `SSH 桌面控制已完成:${objective}`, executionSummary: `ssh osascript ${envBoolean("BOSS_SSH_CONTROL_DRY_RUN") ? "dry-run" : "executed"} (${targetApp})`, targetApp, typedText, }); } catch (error) { writeJson({ status: "failed", requestId, error: error instanceof Error ? error.message : "SSH_COMPUTER_USE_FAILED", }); }