claude-relay 2.2.3 → 2.2.4

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/bin/cli.js CHANGED
@@ -10,6 +10,17 @@ var { loadConfig, saveConfig, configPath, socketPath, logPath, ensureConfigDir,
10
10
  var { sendIPCCommand } = require("../lib/ipc");
11
11
  var { generateAuthToken } = require("../lib/server");
12
12
 
13
+ function openUrl(url) {
14
+ try {
15
+ if (process.platform === "win32") {
16
+ spawn("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true, windowsHide: true }).unref();
17
+ } else {
18
+ var cmd = process.platform === "darwin" ? "open" : "xdg-open";
19
+ spawn(cmd, [url], { stdio: "ignore", detached: true }).unref();
20
+ }
21
+ } catch (e) {}
22
+ }
23
+
13
24
  var args = process.argv.slice(2);
14
25
  var port = 2633;
15
26
  var useHttps = true;
@@ -388,10 +399,8 @@ function ensureCerts(ip) {
388
399
  }
389
400
 
390
401
  try {
391
- execSync(
392
- "mkcert -key-file " + keyPath + " -cert-file " + certPath + " " + domains.join(" "),
393
- { stdio: "pipe" }
394
- );
402
+ var mkcertArgs = ["-key-file", keyPath, "-cert-file", certPath].concat(domains);
403
+ execFileSync("mkcert", mkcertArgs, { stdio: "pipe" });
395
404
  } catch (err) {
396
405
  return null;
397
406
  }
@@ -1026,9 +1035,13 @@ function setup(callback) {
1026
1035
  log(sym.bar);
1027
1036
 
1028
1037
  promptPin(function (pin) {
1029
- promptToggle("Keep awake", "Prevent system sleep while relay is running", false, function (keepAwake) {
1030
- callback(pin, keepAwake);
1031
- });
1038
+ if (process.platform === "darwin") {
1039
+ promptToggle("Keep awake", "Prevent system sleep while relay is running", false, function (keepAwake) {
1040
+ callback(pin, keepAwake);
1041
+ });
1042
+ } else {
1043
+ callback(pin, false);
1044
+ }
1032
1045
  });
1033
1046
  });
1034
1047
  });
@@ -1098,6 +1111,7 @@ async function forkDaemon(pin, keepAwake, extraProjects) {
1098
1111
 
1099
1112
  var child = spawn(process.execPath, [daemonScript], {
1100
1113
  detached: true,
1114
+ windowsHide: true,
1101
1115
  stdio: ["ignore", logFd, logFd],
1102
1116
  env: Object.assign({}, process.env, {
1103
1117
  CLAUDE_RELAY_CONFIG: configPath(),
@@ -1127,6 +1141,96 @@ async function forkDaemon(pin, keepAwake, extraProjects) {
1127
1141
  showServerStarted(config, ip);
1128
1142
  }
1129
1143
 
1144
+ // ==============================
1145
+ // Restart daemon with TLS enabled
1146
+ // ==============================
1147
+ async function restartDaemonWithTLS(config, callback) {
1148
+ var ip = getLocalIP();
1149
+ var certPaths = ensureCerts(ip);
1150
+ if (!certPaths) {
1151
+ callback(config);
1152
+ return;
1153
+ }
1154
+
1155
+ // Shut down old daemon
1156
+ stopDaemonWatcher();
1157
+ try {
1158
+ await sendIPCCommand(socketPath(), { cmd: "shutdown" });
1159
+ } catch (e) {}
1160
+
1161
+ // Wait for port to be released
1162
+ var waited = 0;
1163
+ while (waited < 5000) {
1164
+ await new Promise(function (resolve) { setTimeout(resolve, 300); });
1165
+ waited += 300;
1166
+ var free = await isPortFree(config.port);
1167
+ if (free) break;
1168
+ }
1169
+ clearStaleConfig();
1170
+
1171
+ // Re-fork with TLS
1172
+ var newConfig = {
1173
+ pid: null,
1174
+ port: config.port,
1175
+ pinHash: config.pinHash || null,
1176
+ tls: true,
1177
+ debug: config.debug || false,
1178
+ keepAwake: config.keepAwake || false,
1179
+ projects: config.projects || [],
1180
+ };
1181
+
1182
+ ensureConfigDir();
1183
+ saveConfig(newConfig);
1184
+
1185
+ var daemonScript = path.join(__dirname, "..", "lib", "daemon.js");
1186
+ var logFile = logPath();
1187
+ var logFd = fs.openSync(logFile, "a");
1188
+
1189
+ var child = spawn(process.execPath, [daemonScript], {
1190
+ detached: true,
1191
+ windowsHide: true,
1192
+ stdio: ["ignore", logFd, logFd],
1193
+ env: Object.assign({}, process.env, {
1194
+ CLAUDE_RELAY_CONFIG: configPath(),
1195
+ }),
1196
+ });
1197
+ child.unref();
1198
+ fs.closeSync(logFd);
1199
+
1200
+ newConfig.pid = child.pid;
1201
+ saveConfig(newConfig);
1202
+
1203
+ await new Promise(function (resolve) { setTimeout(resolve, 800); });
1204
+
1205
+ var alive = await isDaemonAliveAsync(newConfig);
1206
+ if (!alive) {
1207
+ log(sym.warn + " " + a.yellow + "Failed to restart with HTTPS, falling back to HTTP..." + a.reset);
1208
+ // Re-fork without TLS so the server is at least running
1209
+ newConfig.tls = false;
1210
+ saveConfig(newConfig);
1211
+ var logFd2 = fs.openSync(logFile, "a");
1212
+ var child2 = spawn(process.execPath, [daemonScript], {
1213
+ detached: true,
1214
+ windowsHide: true,
1215
+ stdio: ["ignore", logFd2, logFd2],
1216
+ env: Object.assign({}, process.env, {
1217
+ CLAUDE_RELAY_CONFIG: configPath(),
1218
+ }),
1219
+ });
1220
+ child2.unref();
1221
+ fs.closeSync(logFd2);
1222
+ newConfig.pid = child2.pid;
1223
+ saveConfig(newConfig);
1224
+ await new Promise(function (resolve) { setTimeout(resolve, 800); });
1225
+ startDaemonWatcher();
1226
+ callback(newConfig);
1227
+ return;
1228
+ }
1229
+
1230
+ startDaemonWatcher();
1231
+ callback(newConfig);
1232
+ }
1233
+
1130
1234
  // ==============================
1131
1235
  // Show server started info
1132
1236
  // ==============================
@@ -1196,6 +1300,7 @@ function showMainMenu(config, ip) {
1196
1300
  switch (choice) {
1197
1301
  case "notifications":
1198
1302
  showSetupGuide(config, ip, function () {
1303
+ config = loadConfig() || config;
1199
1304
  showMainMenu(config, ip);
1200
1305
  });
1201
1306
  break;
@@ -1246,17 +1351,11 @@ function showMainMenu(config, ip) {
1246
1351
  ],
1247
1352
  keys: [
1248
1353
  { key: "o", onKey: function () {
1249
- try {
1250
- var openCmd = process.platform === "darwin" ? "open" : "xdg-open";
1251
- spawn(openCmd, [url], { stdio: "ignore", detached: true }).unref();
1252
- } catch (e) {}
1354
+ openUrl(url);
1253
1355
  showMainMenu(config, ip);
1254
1356
  }},
1255
1357
  { key: "s", onKey: function () {
1256
- try {
1257
- var openCmd = process.platform === "darwin" ? "open" : "xdg-open";
1258
- spawn(openCmd, ["https://github.com/chadbyte/claude-relay"], { stdio: "ignore", detached: true }).unref();
1259
- } catch (e) {}
1358
+ openUrl("https://github.com/chadbyte/claude-relay");
1260
1359
  showMainMenu(config, ip);
1261
1360
  }},
1262
1361
  ],
@@ -1562,11 +1661,25 @@ function showSetupGuide(config, ip, goBack) {
1562
1661
  log(sym.pointer + " " + a.bold + "HTTPS Setup (for push notifications)" + a.reset);
1563
1662
  if (mcReady) {
1564
1663
  log(sym.bar + " " + a.green + "mkcert is installed" + a.reset);
1664
+ if (!config.tls) {
1665
+ log(sym.bar + " " + a.dim + "Restarting server with HTTPS..." + a.reset);
1666
+ restartDaemonWithTLS(config, function (newConfig) {
1667
+ config = newConfig;
1668
+ log(sym.bar);
1669
+ showSetupQR();
1670
+ });
1671
+ return;
1672
+ }
1565
1673
  log(sym.bar);
1566
1674
  showSetupQR();
1567
1675
  } else {
1568
1676
  log(sym.bar + " " + a.yellow + "mkcert not found." + a.reset);
1569
- log(sym.bar + " " + a.dim + "Install: " + a.reset + "brew install mkcert && mkcert -install");
1677
+ var mkcertHint = process.platform === "win32"
1678
+ ? "choco install mkcert && mkcert -install"
1679
+ : process.platform === "darwin"
1680
+ ? "brew install mkcert && mkcert -install"
1681
+ : "apt install mkcert && mkcert -install";
1682
+ log(sym.bar + " " + a.dim + "Install: " + a.reset + mkcertHint);
1570
1683
  log(sym.bar);
1571
1684
  promptSelect("Select", [
1572
1685
  { label: "Re-check", value: "recheck" },
@@ -1658,7 +1771,9 @@ function showSettingsMenu(config, ip) {
1658
1771
  log(sym.bar + " mkcert " + mcStatus);
1659
1772
  log(sym.bar + " HTTPS " + tlsStatus);
1660
1773
  log(sym.bar + " PIN " + pinStatus);
1661
- log(sym.bar + " Keep awake " + awakeStatus);
1774
+ if (process.platform === "darwin") {
1775
+ log(sym.bar + " Keep awake " + awakeStatus);
1776
+ }
1662
1777
  log(sym.bar);
1663
1778
 
1664
1779
  // Build items
@@ -1672,7 +1787,9 @@ function showSettingsMenu(config, ip) {
1672
1787
  } else {
1673
1788
  items.push({ label: "Set PIN", value: "pin" });
1674
1789
  }
1675
- items.push({ label: isAwake ? "Disable keep awake" : "Enable keep awake", value: "awake" });
1790
+ if (process.platform === "darwin") {
1791
+ items.push({ label: isAwake ? "Disable keep awake" : "Enable keep awake", value: "awake" });
1792
+ }
1676
1793
  items.push({ label: "View logs", value: "logs" });
1677
1794
  items.push({ label: "Back", value: "back" });
1678
1795
 
@@ -1680,6 +1797,7 @@ function showSettingsMenu(config, ip) {
1680
1797
  switch (choice) {
1681
1798
  case "guide":
1682
1799
  showSetupGuide(config, ip, function () {
1800
+ config = loadConfig() || config;
1683
1801
  showSettingsMenu(config, ip);
1684
1802
  });
1685
1803
  break;
package/lib/config.js CHANGED
@@ -11,6 +11,9 @@ function configPath() {
11
11
  }
12
12
 
13
13
  function socketPath() {
14
+ if (process.platform === "win32") {
15
+ return "\\\\.\\pipe\\claude-relay-daemon";
16
+ }
14
17
  return path.join(CONFIG_DIR, "daemon.sock");
15
18
  }
16
19
 
@@ -50,7 +53,8 @@ function isPidAlive(pid) {
50
53
  function isDaemonAlive(config) {
51
54
  if (!config || !config.pid) return false;
52
55
  if (!isPidAlive(config.pid)) return false;
53
- // Also check socket exists
56
+ // Named pipes on Windows can't be stat'd, just check PID
57
+ if (process.platform === "win32") return true;
54
58
  try {
55
59
  fs.statSync(socketPath());
56
60
  return true;
@@ -96,7 +100,9 @@ function generateSlug(projectPath, existingSlugs) {
96
100
 
97
101
  function clearStaleConfig() {
98
102
  try { fs.unlinkSync(configPath()); } catch (e) {}
99
- try { fs.unlinkSync(socketPath()); } catch (e) {}
103
+ if (process.platform !== "win32") {
104
+ try { fs.unlinkSync(socketPath()); } catch (e) {}
105
+ }
100
106
  }
101
107
 
102
108
  // --- ~/.clayrc (recent projects persistence) ---
package/lib/daemon.js CHANGED
@@ -251,6 +251,10 @@ function gracefulShutdown() {
251
251
 
252
252
  process.on("SIGTERM", gracefulShutdown);
253
253
  process.on("SIGINT", gracefulShutdown);
254
+ // Windows emits SIGHUP when console window closes
255
+ if (process.platform === "win32") {
256
+ process.on("SIGHUP", gracefulShutdown);
257
+ }
254
258
 
255
259
  process.on("uncaughtException", function (err) {
256
260
  console.error("[daemon] Uncaught exception:", err);
package/lib/ipc.js CHANGED
@@ -6,8 +6,10 @@ var fs = require("fs");
6
6
  * handler(msg) should return a response object (or a Promise of one).
7
7
  */
8
8
  function createIPCServer(sockPath, handler) {
9
- // Remove stale socket file
10
- try { fs.unlinkSync(sockPath); } catch (e) {}
9
+ // Remove stale socket file (not needed for Windows named pipes)
10
+ if (process.platform !== "win32") {
11
+ try { fs.unlinkSync(sockPath); } catch (e) {}
12
+ }
11
13
 
12
14
  var server = net.createServer(function (conn) {
13
15
  var buffer = "";
@@ -49,7 +51,9 @@ function createIPCServer(sockPath, handler) {
49
51
  return {
50
52
  close: function () {
51
53
  server.close();
52
- try { fs.unlinkSync(sockPath); } catch (e) {}
54
+ if (process.platform !== "win32") {
55
+ try { fs.unlinkSync(sockPath); } catch (e) {}
56
+ }
53
57
  },
54
58
  };
55
59
  }
package/lib/project.js CHANGED
@@ -478,7 +478,7 @@ function createProjectContext(opts) {
478
478
  entries.push({
479
479
  name: item.name,
480
480
  type: item.isDirectory() ? "dir" : "file",
481
- path: path.relative(cwd, path.join(fsDir, item.name)),
481
+ path: path.relative(cwd, path.join(fsDir, item.name)).split(path.sep).join("/"),
482
482
  });
483
483
  }
484
484
  sendTo(ws, { type: "fs_list_result", path: msg.path || ".", entries: entries });
package/lib/sdk-bridge.js CHANGED
@@ -466,7 +466,7 @@ function createSDKBridge(opts) {
466
466
 
467
467
  function permissionPushTitle(toolName, input) {
468
468
  if (!input) return "Claude wants to use " + toolName;
469
- var file = input.file_path ? input.file_path.split("/").pop() : "";
469
+ var file = input.file_path ? input.file_path.split(/[/\\]/).pop() : "";
470
470
  switch (toolName) {
471
471
  case "Bash": return "Claude wants to run a command";
472
472
  case "Edit": return "Claude wants to edit " + (file || "a file");
@@ -487,7 +487,7 @@ function createSDKBridge(opts) {
487
487
  if (toolName === "Bash" && input.command) {
488
488
  text = input.command;
489
489
  } else if (toolName === "Edit" && input.file_path) {
490
- text = input.file_path.split("/").pop() + ": " + (input.old_string || "").substring(0, 40) + " \u2192 " + (input.new_string || "").substring(0, 40);
490
+ text = input.file_path.split(/[/\\]/).pop() + ": " + (input.old_string || "").substring(0, 40) + " \u2192 " + (input.new_string || "").substring(0, 40);
491
491
  } else if (toolName === "Write" && input.file_path) {
492
492
  text = input.file_path;
493
493
  } else if (input.file_path) {
package/lib/terminal.js CHANGED
@@ -8,7 +8,8 @@ try {
8
8
  function createTerminal(cwd, cols, rows) {
9
9
  if (!pty) return null;
10
10
 
11
- var shell = process.env.SHELL || "/bin/bash";
11
+ var shell = process.env.SHELL
12
+ || (process.platform === "win32" ? process.env.COMSPEC || "cmd.exe" : "/bin/bash");
12
13
  var term = pty.spawn(shell, [], {
13
14
  name: "xterm-256color",
14
15
  cols: cols || 80,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-relay",
3
- "version": "2.2.3",
3
+ "version": "2.2.4",
4
4
  "description": "Web UI for Claude Code. Any device. Push notifications.",
5
5
  "bin": {
6
6
  "claude-relay": "./bin/cli.js"