clay-server 2.6.0 → 2.7.0

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
@@ -1,5 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // --- Node version check (must run before any require that may use Node 20+ features) ---
4
+ var _nodeMajor = parseInt(process.versions.node.split(".")[0], 10);
5
+ if (_nodeMajor < 20) {
6
+ console.error("");
7
+ console.error("\x1b[31m[clay] Node.js 20+ is required (current: " + process.version + ")\x1b[0m");
8
+ console.error("[clay] The Claude Agent SDK 0.2.40+ requires Node 20 for Symbol.dispose support.");
9
+ console.error("[clay] If you cannot upgrade Node, use claude-relay@2.4.3 which supports Node 18.");
10
+ console.error("");
11
+ console.error(" Upgrade Node: nvm install 22 && nvm use 22");
12
+ console.error(" Or use older: npx claude-relay@2.4.3");
13
+ console.error("");
14
+ process.exit(78);
15
+ }
16
+
3
17
  var os = require("os");
4
18
  var fs = require("fs");
5
19
  var path = require("path");
@@ -33,12 +47,14 @@ var debugMode = false;
33
47
  var autoYes = false;
34
48
  var cliPin = null;
35
49
  var shutdownMode = false;
50
+ var restartMode = false;
36
51
  var addPath = null;
37
52
  var removePath = null;
38
53
  var listMode = false;
39
54
  var dangerouslySkipPermissions = false;
40
55
  var headlessMode = false;
41
56
  var watchMode = false;
57
+ var host = null;
42
58
 
43
59
  for (var i = 0; i < args.length; i++) {
44
60
  if (args[i] === "-p" || args[i] === "--port") {
@@ -48,6 +64,9 @@ for (var i = 0; i < args.length; i++) {
48
64
  process.exit(1);
49
65
  }
50
66
  i++;
67
+ } else if (args[i] === "--host" || args[i] === "--bind") {
68
+ host = args[i + 1] || null;
69
+ i++;
51
70
  } else if (args[i] === "--no-https") {
52
71
  useHttps = false;
53
72
  } else if (args[i] === "--no-update" || args[i] === "--skip-update") {
@@ -65,6 +84,8 @@ for (var i = 0; i < args.length; i++) {
65
84
  i++;
66
85
  } else if (args[i] === "--shutdown") {
67
86
  shutdownMode = true;
87
+ } else if (args[i] === "--restart") {
88
+ restartMode = true;
68
89
  } else if (args[i] === "--add") {
69
90
  addPath = args[i + 1] || ".";
70
91
  i++;
@@ -79,19 +100,21 @@ for (var i = 0; i < args.length; i++) {
79
100
  } else if (args[i] === "--dangerously-skip-permissions") {
80
101
  dangerouslySkipPermissions = true;
81
102
  } else if (args[i] === "-h" || args[i] === "--help") {
82
- console.log("Usage: clay-server [-p|--port <port>] [--no-https] [--no-update] [--debug] [-y|--yes] [--pin <pin>] [--shutdown]");
103
+ console.log("Usage: clay-server [-p|--port <port>] [--host <address>] [--no-https] [--no-update] [--debug] [-y|--yes] [--pin <pin>] [--shutdown] [--restart]");
83
104
  console.log(" clay-server --add <path> Add a project to the running daemon");
84
105
  console.log(" clay-server --remove <path> Remove a project from the running daemon");
85
106
  console.log(" clay-server --list List registered projects");
86
107
  console.log("");
87
108
  console.log("Options:");
88
109
  console.log(" -p, --port <port> Port to listen on (default: 2633)");
110
+ console.log(" --host <address> Address to bind to (default: 0.0.0.0)");
89
111
  console.log(" --no-https Disable HTTPS (enabled by default via mkcert)");
90
112
  console.log(" --no-update Skip auto-update check on startup");
91
113
  console.log(" --debug Enable debug panel in the web UI");
92
114
  console.log(" -y, --yes Skip interactive prompts (accept defaults)");
93
115
  console.log(" --pin <pin> Set 6-digit PIN (use with --yes)");
94
116
  console.log(" --shutdown Shut down the running relay daemon");
117
+ console.log(" --restart Restart the running relay daemon");
95
118
  console.log(" --add <path> Add a project directory (use '.' for current)");
96
119
  console.log(" --remove <path> Remove a project directory");
97
120
  console.log(" --list List all registered projects");
@@ -128,6 +151,25 @@ if (shutdownMode) {
128
151
  return;
129
152
  }
130
153
 
154
+ // --- Handle --restart before anything else ---
155
+ if (restartMode) {
156
+ var restartConfig = loadConfig();
157
+ isDaemonAliveAsync(restartConfig).then(function (alive) {
158
+ if (!alive) {
159
+ console.error("No running daemon found.");
160
+ process.exit(1);
161
+ }
162
+ sendIPCCommand(socketPath(), { cmd: "restart" }).then(function () {
163
+ console.log("Server restarted.");
164
+ process.exit(0);
165
+ }).catch(function (err) {
166
+ console.error("Restart failed:", err.message);
167
+ process.exit(1);
168
+ });
169
+ });
170
+ return;
171
+ }
172
+
131
173
  // --- Handle --add before anything else ---
132
174
  if (addPath !== null) {
133
175
  var absAdd = path.resolve(addPath);
@@ -1277,6 +1319,7 @@ async function forkDaemon(pin, keepAwake, extraProjects, addCwd) {
1277
1319
  var config = {
1278
1320
  pid: null,
1279
1321
  port: port,
1322
+ host: host,
1280
1323
  pinHash: pin ? generateAuthToken(pin) : null,
1281
1324
  tls: hasTls,
1282
1325
  debug: debugMode,
@@ -1386,6 +1429,7 @@ async function devMode(pin, keepAwake, existingPinHash) {
1386
1429
  var config = {
1387
1430
  pid: null,
1388
1431
  port: port,
1432
+ host: host,
1389
1433
  pinHash: existingPinHash || (pin ? generateAuthToken(pin) : null),
1390
1434
  tls: hasTls,
1391
1435
  debug: true,
@@ -2370,8 +2414,12 @@ var currentVersion = require("../package.json").version;
2370
2414
  if (autoRestorable.length > 0) {
2371
2415
  console.log(" " + sym.done + " Restoring " + autoRestorable.length + " previous project(s)");
2372
2416
  }
2373
- var hasRestorable = autoRestorable.length > 0;
2374
- await forkDaemon(pin, false, hasRestorable ? autoRestorable : undefined, !hasRestorable);
2417
+ // Add cwd if it has history in .clayrc, or if there are no other projects to restore
2418
+ var cwdInRc = (autoRc.recentProjects || []).some(function (p) {
2419
+ return p.path === cwd;
2420
+ });
2421
+ var addCwd = cwdInRc || autoRestorable.length === 0;
2422
+ await forkDaemon(pin, false, autoRestorable.length > 0 ? autoRestorable : undefined, addCwd);
2375
2423
  } else {
2376
2424
  setup(function (pin, keepAwake) {
2377
2425
  if (dangerouslySkipPermissions && !pin) {
package/lib/config.js CHANGED
@@ -117,7 +117,10 @@ function isPidAlive(pid) {
117
117
 
118
118
  function isDaemonAlive(config) {
119
119
  if (!config || !config.pid) return false;
120
- if (!isPidAlive(config.pid)) return false;
120
+ if (!isPidAlive(config.pid)) {
121
+ clearStaleConfig();
122
+ return false;
123
+ }
121
124
  // Named pipes on Windows can't be stat'd, just check PID
122
125
  if (process.platform === "win32") return true;
123
126
  try {
@@ -131,7 +134,10 @@ function isDaemonAlive(config) {
131
134
  function isDaemonAliveAsync(config) {
132
135
  return new Promise(function (resolve) {
133
136
  if (!config || !config.pid) return resolve(false);
134
- if (!isPidAlive(config.pid)) return resolve(false);
137
+ if (!isPidAlive(config.pid)) {
138
+ clearStaleConfig();
139
+ return resolve(false);
140
+ }
135
141
 
136
142
  var sock = socketPath();
137
143
  var client = net.connect(sock);
package/lib/daemon.js CHANGED
@@ -22,7 +22,7 @@ delete process.env.CLAUDECODE;
22
22
 
23
23
  var fs = require("fs");
24
24
  var path = require("path");
25
- var { loadConfig, saveConfig, socketPath, generateSlug, syncClayrc, removeFromClayrc, writeCrashInfo, readCrashInfo, clearCrashInfo } = require("./config");
25
+ var { loadConfig, saveConfig, socketPath, generateSlug, syncClayrc, removeFromClayrc, writeCrashInfo, readCrashInfo, clearCrashInfo, isPidAlive, clearStaleConfig } = require("./config");
26
26
  var { createIPCServer } = require("./ipc");
27
27
  var { createServer, generateAuthToken } = require("./server");
28
28
 
@@ -81,6 +81,8 @@ var lanIp = (function () {
81
81
  })();
82
82
 
83
83
  // --- Create multi-project server ---
84
+ var listenHost = config.host || "0.0.0.0";
85
+
84
86
  var relay = createServer({
85
87
  tlsOptions: tlsOptions,
86
88
  caPath: caRoot,
@@ -387,6 +389,10 @@ var relay = createServer({
387
389
  console.log("[daemon] Shutdown requested via web UI");
388
390
  gracefulShutdown();
389
391
  },
392
+ onRestart: function () {
393
+ console.log("[daemon] Restart requested via web UI");
394
+ spawnAndRestart();
395
+ },
390
396
  });
391
397
 
392
398
  // --- Register projects ---
@@ -405,6 +411,14 @@ for (var i = 0; i < projects.length; i++) {
405
411
  try { syncClayrc(config.projects); } catch (e) {}
406
412
 
407
413
  // --- IPC server ---
414
+ // Clean up stale socket/config left by a previously killed daemon
415
+ var existingConfig = loadConfig();
416
+ if (existingConfig && existingConfig.pid && existingConfig.pid !== process.pid) {
417
+ if (!isPidAlive(existingConfig.pid)) {
418
+ console.log("[daemon] Clearing stale config from dead PID " + existingConfig.pid);
419
+ clearStaleConfig();
420
+ }
421
+ }
408
422
  var ipc = createIPCServer(socketPath(), function (msg) {
409
423
  switch (msg.cmd) {
410
424
  case "add_project": {
@@ -523,6 +537,11 @@ var ipc = createIPCServer(socketPath(), function (msg) {
523
537
  gracefulShutdown();
524
538
  return { ok: true };
525
539
 
540
+ case "restart":
541
+ console.log("[daemon] Restart requested via IPC");
542
+ spawnAndRestart();
543
+ return { ok: true };
544
+
526
545
  case "update": {
527
546
  console.log("[daemon] Update & restart requested via IPC");
528
547
 
@@ -584,9 +603,9 @@ var listenRetries = 0;
584
603
  var MAX_LISTEN_RETRIES = 15;
585
604
 
586
605
  function startListening() {
587
- relay.server.listen(config.port, function () {
606
+ relay.server.listen(config.port, listenHost, function () {
588
607
  var protocol = tlsOptions ? "https" : "http";
589
- console.log("[daemon] Listening on " + protocol + "://0.0.0.0:" + config.port);
608
+ console.log("[daemon] Listening on " + protocol + "://" + listenHost + ":" + config.port);
590
609
  console.log("[daemon] PID:", process.pid);
591
610
  console.log("[daemon] Projects:", config.projects.length);
592
611
 
@@ -637,8 +656,8 @@ if (relay.onboardingServer) {
637
656
  relay.onboardingServer.on("error", function (err) {
638
657
  console.error("[daemon] Onboarding HTTP server error:", err.message);
639
658
  });
640
- relay.onboardingServer.listen(onboardingPort, function () {
641
- console.log("[daemon] Onboarding HTTP on http://0.0.0.0:" + onboardingPort);
659
+ relay.onboardingServer.listen(onboardingPort, listenHost, function () {
660
+ console.log("[daemon] Onboarding HTTP on http://" + listenHost + ":" + onboardingPort);
642
661
  });
643
662
  }
644
663
 
@@ -652,6 +671,29 @@ if (config.keepAwake && process.platform === "darwin") {
652
671
  } catch (e) {}
653
672
  }
654
673
 
674
+ // --- Spawn new daemon and graceful restart ---
675
+ function spawnAndRestart() {
676
+ var { spawn: spawnRestart } = require("child_process");
677
+ var { logPath: restartLogPath, configPath: restartConfigPath } = require("./config");
678
+ var daemonScript = path.join(__dirname, "daemon.js");
679
+ var logFd = fs.openSync(restartLogPath(), "a");
680
+ var child = spawnRestart(process.execPath, [daemonScript], {
681
+ detached: true,
682
+ windowsHide: true,
683
+ stdio: ["ignore", logFd, logFd],
684
+ env: Object.assign({}, process.env, {
685
+ CLAY_CONFIG: restartConfigPath(),
686
+ }),
687
+ });
688
+ child.unref();
689
+ fs.closeSync(logFd);
690
+ config.pid = child.pid;
691
+ saveConfig(config);
692
+ console.log("[daemon] Spawned new daemon (PID " + child.pid + "), shutting down...");
693
+ updateHandoff = true;
694
+ setTimeout(function () { gracefulShutdown(); }, 100);
695
+ }
696
+
655
697
  // --- Graceful shutdown ---
656
698
  var updateHandoff = false; // true when shutting down for update (new daemon already spawned)
657
699
 
package/lib/ipc.js CHANGED
@@ -46,6 +46,18 @@ function createIPCServer(sockPath, handler) {
46
46
  conn.on("error", function () {});
47
47
  });
48
48
 
49
+ var retried = false;
50
+ server.on("error", function (err) {
51
+ if (err.code === "EADDRINUSE" && !retried) {
52
+ retried = true;
53
+ console.log("[ipc] Socket in use, removing stale socket and retrying...");
54
+ try { fs.unlinkSync(sockPath); } catch (e) {}
55
+ server.listen(sockPath);
56
+ } else {
57
+ console.error("[ipc] Failed to bind socket:", err.message);
58
+ process.exit(1);
59
+ }
60
+ });
49
61
  server.listen(sockPath);
50
62
 
51
63
  return {
package/lib/notes.js CHANGED
@@ -67,7 +67,7 @@ function createNotesManager(opts) {
67
67
  function update(id, changes) {
68
68
  for (var i = 0; i < notes.length; i++) {
69
69
  if (notes[i].id === id) {
70
- var allowed = ["text", "x", "y", "w", "h", "color", "minimized", "zIndex"];
70
+ var allowed = ["text", "x", "y", "w", "h", "color", "minimized", "hidden", "zIndex"];
71
71
  for (var j = 0; j < allowed.length; j++) {
72
72
  var key = allowed[j];
73
73
  if (changes[key] !== undefined) {