diff --git a/native/cli.cjs b/native/cli.cjs index 0e7b2ef..d98e4de 100755 --- a/native/cli.cjs +++ b/native/cli.cjs @@ -10,7 +10,10 @@ const networkStore = require("./network-store.cjs"); const { parseDoCommands } = require("./do-parser.cjs"); const { executeDoSteps } = require("./do-executor.cjs"); -const SOCKET_PATH = "/tmp/surf.sock"; +const IS_WIN = process.platform === "win32"; +const SURF_TMP = IS_WIN ? path.join(os.tmpdir(), "surf") : "/tmp"; +const SOCKET_PATH = IS_WIN ? "//./pipe/surf" : "/tmp/surf.sock"; +if (IS_WIN) { try { fs.mkdirSync(SURF_TMP, { recursive: true }); } catch {} } // ============================================================================ // Workflow Resolution and Management @@ -268,12 +271,14 @@ function resizeImage(filePath, maxSize) { const height = parseInt(sizeInfo.match(/pixelHeight:\s*(\d+)/)?.[1] || "0", 10); return { success: true, width, height }; } else { - // Linux/other: use ImageMagick (try IM6 first, then IM7) + // Linux/Windows: use ImageMagick (try IM6 first, then IM7) + // On Windows, \> is interpreted as redirect by cmd.exe — quote the geometry + const resizeArg = IS_WIN ? `"${maxSize}x${maxSize}>"` : `${maxSize}x${maxSize}\\>`; try { - execSync(`convert "${filePath}" -resize ${maxSize}x${maxSize}\\> "${filePath}"`, { stdio: "pipe" }); + execSync(`convert "${filePath}" -resize ${resizeArg} "${filePath}"`, { stdio: "pipe" }); } catch { // IM7 uses 'magick' as main command - execSync(`magick "${filePath}" -resize ${maxSize}x${maxSize}\\> "${filePath}"`, { stdio: "pipe" }); + execSync(`magick "${filePath}" -resize ${resizeArg} "${filePath}"`, { stdio: "pipe" }); } // Get dimensions (IM7 may need 'magick identify' instead of just 'identify') let sizeInfo; @@ -2449,7 +2454,7 @@ tool = ALIASES[tool] || tool; const config = loadConfig(); const autoSaveEnabled = config.autoSaveScreenshots !== false && !options["no-save"]; if (tool === "screenshot" && !options.output && !options.savePath && autoSaveEnabled) { - options.savePath = `/tmp/surf-snap-${Date.now()}.png`; + options.savePath = path.join(SURF_TMP, `surf-snap-${Date.now()}.png`); } if (tool === "smoke") { @@ -2813,7 +2818,7 @@ const sendRequest = (toolName, toolArgs = {}) => { const performAutoCapture = async () => { const timestamp = Date.now(); - const screenshotPath = `/tmp/surf-error-${timestamp}.png`; + const screenshotPath = path.join(SURF_TMP, `surf-error-${timestamp}.png`); try { const [screenshotResp, consoleResp] = await Promise.all([ diff --git a/native/do-executor.cjs b/native/do-executor.cjs index cad9152..85ada96 100644 --- a/native/do-executor.cjs +++ b/native/do-executor.cjs @@ -12,7 +12,7 @@ const net = require("net"); -const SOCKET_PATH = "/tmp/surf.sock"; +const SOCKET_PATH = process.platform === "win32" ? "//./pipe/surf" : "/tmp/surf.sock"; // Maximum iterations for loops (safety cap) const MAX_LOOP_ITERATIONS = 100; diff --git a/native/host.cjs b/native/host.cjs index cdb7ded..97415a4 100755 --- a/native/host.cjs +++ b/native/host.cjs @@ -12,7 +12,11 @@ const perplexityClient = require("./perplexity-client.cjs"); const grokClient = require("./grok-client.cjs"); const { mapToolToMessage, mapComputerAction, formatToolContent } = require("./host-helpers.cjs"); -const SOCKET_PATH = "/tmp/surf.sock"; +const IS_WIN = process.platform === "win32"; +const SURF_TMP = IS_WIN ? path.join(os.tmpdir(), "surf") : "/tmp"; +const SOCKET_PATH = IS_WIN ? "//./pipe/surf" : "/tmp/surf.sock"; +// Ensure tmp dir exists on Windows +if (IS_WIN) { try { fs.mkdirSync(SURF_TMP, { recursive: true }); } catch {} } // Cross-platform image resize (macOS: sips, Linux: ImageMagick) function resizeImage(filePath, maxSize) { @@ -27,12 +31,14 @@ function resizeImage(filePath, maxSize) { const height = parseInt(sizeInfo.match(/pixelHeight:\s*(\d+)/)?.[1] || "0", 10); return { success: true, width, height }; } else { - // Linux/other: use ImageMagick (try IM6 first, then IM7) + // Linux/Windows: use ImageMagick (try IM6 first, then IM7) + // On Windows, \> is interpreted as redirect by cmd.exe — quote the geometry + const resizeArg = IS_WIN ? `"${maxSize}x${maxSize}>"` : `${maxSize}x${maxSize}\\>`; try { - execSync(`convert "${filePath}" -resize ${maxSize}x${maxSize}\\> "${filePath}"`, { stdio: "pipe" }); + execSync(`convert "${filePath}" -resize ${resizeArg} "${filePath}"`, { stdio: "pipe" }); } catch { // IM7 uses 'magick' as main command - execSync(`magick "${filePath}" -resize ${maxSize}x${maxSize}\\> "${filePath}"`, { stdio: "pipe" }); + execSync(`magick "${filePath}" -resize ${resizeArg} "${filePath}"`, { stdio: "pipe" }); } // Get dimensions (IM7 may need 'magick identify' instead of just 'identify') let sizeInfo; @@ -73,7 +79,7 @@ async function processAiQueue() { setTimeout(processAiQueue, 2000); } } -const LOG_FILE = "/tmp/surf-host.log"; +const LOG_FILE = path.join(SURF_TMP, "surf-host.log"); const AUTH_FILE = path.join(os.homedir(), ".pi", "agent", "auth.json"); const DEFAULT_RETRY_OPTIONS = { @@ -288,9 +294,7 @@ const log = (msg) => { log("Host starting..."); -try { - fs.unlinkSync(SOCKET_PATH); -} catch {} +if (!IS_WIN) { try { fs.unlinkSync(SOCKET_PATH); } catch {} } const pendingRequests = new Map(); const pendingToolRequests = new Map(); @@ -1204,15 +1208,15 @@ function processInput() { } else if (autoScreenshot && tabId && !msg.error && !msg.base64) { const screenshotId = ++requestCounter; - const screenshotPath = `/tmp/pi-auto-${Date.now()}.png`; + const screenshotPath = path.join(SURF_TMP, `pi-auto-${Date.now()}.png`); - const autoFiles = fs.readdirSync("/tmp") + const autoFiles = fs.readdirSync(SURF_TMP) .filter(f => f.startsWith("pi-auto-") && f.endsWith(".png")) .map(f => ({ name: f, time: parseInt(f.match(/pi-auto-(\d+)\.png/)?.[1] || "0", 10) })) .sort((a, b) => b.time - a.time); if (autoFiles.length >= 10) { autoFiles.slice(9).forEach(f => { - try { fs.unlinkSync(path.join("/tmp", f.name)); } catch (e) {} + try { fs.unlinkSync(path.join(SURF_TMP, f.name)); } catch (e) {} }); } pendingToolRequests.set(screenshotId, { @@ -1440,7 +1444,7 @@ const server = net.createServer((socket) => { server.listen(SOCKET_PATH, () => { log("Socket server listening on " + SOCKET_PATH); - fs.chmodSync(SOCKET_PATH, 0o600); + if (!IS_WIN) { try { fs.chmodSync(SOCKET_PATH, 0o600); } catch {} } writeMessage({ type: "HOST_READY" }); log("Sent HOST_READY to extension"); }); @@ -1452,14 +1456,14 @@ server.on("error", (err) => { process.on("SIGTERM", () => { log("SIGTERM received"); server.close(); - try { fs.unlinkSync(SOCKET_PATH); } catch {} + if (!IS_WIN) { try { fs.unlinkSync(SOCKET_PATH); } catch {} } process.exit(0); }); process.on("SIGINT", () => { log("SIGINT received"); server.close(); - try { fs.unlinkSync(SOCKET_PATH); } catch {} + if (!IS_WIN) { try { fs.unlinkSync(SOCKET_PATH); } catch {} } process.exit(0); }); diff --git a/native/mcp-server.cjs b/native/mcp-server.cjs index cf8cae9..5652f9d 100644 --- a/native/mcp-server.cjs +++ b/native/mcp-server.cjs @@ -4,7 +4,7 @@ const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js"); const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js"); const { z } = require("zod"); -const SOCKET_PATH = "/tmp/surf.sock"; +const SOCKET_PATH = process.platform === "win32" ? "//./pipe/surf" : "/tmp/surf.sock"; const REQUEST_TIMEOUT = 30000; const TOOL_SCHEMAS = { diff --git a/native/network-store.cjs b/native/network-store.cjs index 98f3308..8b182b8 100644 --- a/native/network-store.cjs +++ b/native/network-store.cjs @@ -13,7 +13,9 @@ const crypto = require("crypto"); const readline = require("readline"); // Configuration -const DEFAULT_BASE = "/tmp/surf"; +const DEFAULT_BASE = process.platform === "win32" + ? path.join(require("os").tmpdir(), "surf") + : "/tmp/surf"; const DEFAULT_TTL = 24 * 60 * 60 * 1000; // 24 hours const DEFAULT_MAX_SIZE = 200 * 1024 * 1024; // 200MB const AUTO_CLEANUP_INTERVAL = 60 * 60 * 1000; // 1 hour diff --git a/package-lock.json b/package-lock.json index 96813e5..8ecdfed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "surf-cli", - "version": "2.4.2", + "version": "2.5.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "surf-cli", - "version": "2.4.2", + "version": "2.5.2", "license": "MIT", "dependencies": { "@google/generative-ai": "^0.24.1", @@ -1305,6 +1305,7 @@ "integrity": "sha512-rkoPH+RqWopVxDnCBE/ysIdfQ2A7j1eDmW8tCxxrR9nnFBa9jKf86VgsSAzxBd1x+ny0GC4JgiD3SNfRHv3pOg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/utils": "4.0.16", "fflate": "^0.8.2", @@ -2181,6 +2182,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -3623,6 +3625,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -4259,6 +4262,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -4350,6 +4354,7 @@ "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.0.16", "@vitest/mocker": "4.0.16", @@ -4513,6 +4518,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" }