clay-server 2.22.1 → 2.22.2-beta.1

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 (3) hide show
  1. package/bin/cli.js +1 -222
  2. package/lib/daemon.js +36 -18
  3. package/package.json +1 -1
package/bin/cli.js CHANGED
@@ -2011,14 +2011,6 @@ function showMainMenu(config, ip, setupCode) {
2011
2011
  log("");
2012
2012
  }
2013
2013
 
2014
- // Always show setup code if one exists (persists until admin is created)
2015
- var displayCode = setupCode || getSetupCode();
2016
- if (displayCode) {
2017
- log(" " + a.yellow + sym.warn + " Setup code: " + a.bold + displayCode + a.reset);
2018
- log(" " + a.dim + "Open Clay in your browser and enter this code to create the admin account." + a.reset);
2019
- log("");
2020
- }
2021
-
2022
2014
  showMenuItems();
2023
2015
  }
2024
2016
 
@@ -2036,7 +2028,6 @@ function showMainMenu(config, ip, setupCode) {
2036
2028
 
2037
2029
  function showMenuItems() {
2038
2030
  var items = [
2039
- { label: "Setup notifications", value: "notifications" },
2040
2031
  { label: "Settings", value: "settings" },
2041
2032
  { label: "Shut down server", value: "shutdown" },
2042
2033
  { label: "Keep server alive & exit", value: "exit" },
@@ -2044,13 +2035,6 @@ function showMainMenu(config, ip, setupCode) {
2044
2035
 
2045
2036
  promptSelect("What would you like to do?", items, function (choice) {
2046
2037
  switch (choice) {
2047
- case "notifications":
2048
- showSetupGuide(config, ip, function () {
2049
- config = loadConfig() || config;
2050
- showMainMenu(config, ip);
2051
- });
2052
- break;
2053
-
2054
2038
  case "settings":
2055
2039
  showSettingsMenu(config, ip);
2056
2040
  break;
@@ -2109,190 +2093,6 @@ function showMainMenu(config, ip, setupCode) {
2109
2093
  // ==============================
2110
2094
  // Setup guide (2x2 toggle flow)
2111
2095
  // ==============================
2112
- function showSetupGuide(config, ip, goBack) {
2113
- var protocol = config.tls ? "https" : "http";
2114
- var wantRemote = false;
2115
- var wantPush = false;
2116
-
2117
- console.clear();
2118
- printLogo();
2119
- log("");
2120
- log(sym.pointer + " " + a.bold + "Setup Notifications" + a.reset);
2121
- log(sym.bar);
2122
-
2123
- function redraw(renderFn) {
2124
- console.clear();
2125
- printLogo();
2126
- log("");
2127
- log(sym.pointer + " " + a.bold + "Setup Notifications" + a.reset);
2128
- log(sym.bar);
2129
- if (wantRemote) log(sym.done + " Access from outside your network? " + a.dim + "·" + a.reset + " " + a.green + "Yes" + a.reset);
2130
- else log(sym.done + " Access from outside your network? " + a.dim + "· No" + a.reset);
2131
- log(sym.bar);
2132
- if (wantPush) log(sym.done + " Want push notifications? " + a.dim + "·" + a.reset + " " + a.green + "Yes" + a.reset);
2133
- else log(sym.done + " Want push notifications? " + a.dim + "· No" + a.reset);
2134
- log(sym.bar);
2135
- renderFn();
2136
- }
2137
-
2138
- promptToggle("Access from outside your network?", "Requires Tailscale on both devices", false, function (remote) {
2139
- wantRemote = remote;
2140
- log(sym.bar);
2141
- promptToggle("Want push notifications?", "Requires HTTPS", false, function (push) {
2142
- wantPush = push;
2143
- log(sym.bar);
2144
- afterToggles();
2145
- });
2146
- });
2147
-
2148
- function afterToggles() {
2149
- if (!wantRemote && !wantPush) {
2150
- log(sym.done + " " + a.green + "All set!" + a.reset + a.dim + " · No additional setup needed." + a.reset);
2151
- log(sym.end);
2152
- log("");
2153
- promptSelect("Back?", [{ label: "Back", value: "back" }], function () {
2154
- goBack();
2155
- });
2156
- return;
2157
- }
2158
- if (wantRemote) {
2159
- renderTailscale();
2160
- } else {
2161
- renderHttps();
2162
- }
2163
- }
2164
-
2165
- function renderTailscale() {
2166
- var tsIP = getTailscaleIP();
2167
-
2168
- log(sym.pointer + " " + a.bold + "Tailscale Setup" + a.reset);
2169
- if (tsIP) {
2170
- log(sym.bar + " " + a.green + "Tailscale is running" + a.reset + a.dim + " · " + tsIP + a.reset);
2171
- log(sym.bar);
2172
- log(sym.bar + " On your phone/tablet:");
2173
- log(sym.bar + " " + a.dim + "1. Install Tailscale (App Store / Google Play)" + a.reset);
2174
- log(sym.bar + " " + a.dim + "2. Sign in with the same account" + a.reset);
2175
- log(sym.bar);
2176
- renderHttps();
2177
- } else {
2178
- log(sym.bar + " " + a.yellow + "Tailscale not found on this machine." + a.reset);
2179
- log(sym.bar + " " + a.dim + "Install: " + a.reset + "https://tailscale.com/download");
2180
- log(sym.bar + " " + a.dim + "Then run: " + a.reset + "tailscale up");
2181
- log(sym.bar);
2182
- log(sym.bar + " On your phone/tablet:");
2183
- log(sym.bar + " " + a.dim + "1. Install Tailscale (App Store / Google Play)" + a.reset);
2184
- log(sym.bar + " " + a.dim + "2. Sign in with the same account" + a.reset);
2185
- log(sym.bar);
2186
- promptSelect("Select", [
2187
- { label: "Re-check", value: "recheck" },
2188
- { label: "Back", value: "back" },
2189
- ], function (choice) {
2190
- if (choice === "recheck") {
2191
- redraw(renderTailscale);
2192
- } else {
2193
- goBack();
2194
- }
2195
- });
2196
- }
2197
- }
2198
-
2199
- function renderHttps() {
2200
- if (!wantPush) {
2201
- showSetupQR();
2202
- return;
2203
- }
2204
-
2205
- // Builtin cert: HTTPS already active, skip mkcert flow entirely
2206
- if (config.builtinCert) {
2207
- log(sym.pointer + " " + a.bold + "HTTPS" + a.reset + a.dim + " · Enabled (builtin certificate)" + a.reset);
2208
- log(sym.bar);
2209
- showSetupQR();
2210
- return;
2211
- }
2212
-
2213
- // mkcert flow (--mkcert or fallback)
2214
- var mcReady = hasMkcert();
2215
- log(sym.pointer + " " + a.bold + "HTTPS Setup (for push notifications)" + a.reset);
2216
- if (mcReady) {
2217
- log(sym.bar + " " + a.green + "mkcert is installed" + a.reset);
2218
- if (!config.tls) {
2219
- log(sym.bar + " " + a.dim + "Restarting server with HTTPS..." + a.reset);
2220
- restartDaemonWithTLS(config, function (newConfig) {
2221
- config = newConfig;
2222
- log(sym.bar);
2223
- showSetupQR();
2224
- });
2225
- return;
2226
- }
2227
- log(sym.bar);
2228
- showSetupQR();
2229
- } else {
2230
- log(sym.bar + " " + a.yellow + "mkcert not found." + a.reset);
2231
- var mkcertHint = process.platform === "win32"
2232
- ? "choco install mkcert && mkcert -install"
2233
- : process.platform === "darwin"
2234
- ? "brew install mkcert && mkcert -install"
2235
- : "apt install mkcert && mkcert -install";
2236
- log(sym.bar + " " + a.dim + "Install: " + a.reset + mkcertHint);
2237
- log(sym.bar);
2238
- promptSelect("Select", [
2239
- { label: "Re-check", value: "recheck" },
2240
- { label: "Back", value: "back" },
2241
- ], function (choice) {
2242
- if (choice === "recheck") {
2243
- redraw(renderHttps);
2244
- } else {
2245
- goBack();
2246
- }
2247
- });
2248
- }
2249
- }
2250
-
2251
- function showSetupQR() {
2252
- var tsIP = getTailscaleIP();
2253
- var lanIP = null;
2254
- if (!wantRemote) {
2255
- var allIPs = getAllIPs();
2256
- for (var j = 0; j < allIPs.length; j++) {
2257
- if (!allIPs[j].startsWith("100.")) { lanIP = allIPs[j]; break; }
2258
- }
2259
- }
2260
- var setupIP = wantRemote ? (tsIP || ip) : (lanIP || ip);
2261
- var setupQuery = wantRemote ? "" : "?mode=lan";
2262
- // Builtin cert: link directly to the app with push notification guide
2263
- // mkcert: use HTTP onboarding server for CA install flow
2264
- var setupUrl;
2265
- if (config.builtinCert) {
2266
- setupUrl = toClayStudioUrl(setupIP, config.port, "https") + "/pwa";
2267
- } else if (config.tls) {
2268
- setupUrl = "http://" + setupIP + ":" + (config.port + 1) + "/setup" + setupQuery;
2269
- } else {
2270
- setupUrl = "http://" + setupIP + ":" + config.port + "/setup" + setupQuery;
2271
- }
2272
- log(sym.pointer + " " + a.bold + "Continue on your device" + a.reset);
2273
- log(sym.bar + " " + a.dim + "Scan the QR code or open:" + a.reset);
2274
- log(sym.bar + " " + a.bold + setupUrl + a.reset);
2275
- log(sym.bar);
2276
- qrcode.generate(setupUrl, { small: !isBasicTerm }, function (code) {
2277
- var lines = code.split("\n").map(function (l) { return " " + sym.bar + " " + l; }).join("\n");
2278
- console.log(lines);
2279
- log(sym.bar);
2280
- if (wantRemote) {
2281
- log(sym.bar + " " + a.dim + "Can't connect? Make sure Tailscale is installed on your phone too." + a.reset);
2282
- } else {
2283
- log(sym.bar + " " + a.dim + "Can't connect? Your phone must be on the same Wi-Fi network." + a.reset);
2284
- }
2285
- log(sym.bar);
2286
- log(sym.done + " " + a.dim + "Setup complete." + a.reset);
2287
- log(sym.end);
2288
- log("");
2289
- promptSelect("Back?", [{ label: "Back", value: "back" }], function () {
2290
- goBack();
2291
- });
2292
- });
2293
- }
2294
- }
2295
-
2296
2096
  // ==============================
2297
2097
  // Settings sub-menu
2298
2098
  // ==============================
@@ -2308,16 +2108,6 @@ function showSettingsMenu(config, ip) {
2308
2108
  log(sym.bar);
2309
2109
 
2310
2110
  // Detect current state
2311
- var tsIP = getTailscaleIP();
2312
- var tsOk = tsIP !== null;
2313
- var mcOk = hasMkcert();
2314
-
2315
- var tsStatus = tsOk
2316
- ? a.green + "Connected" + a.reset + a.dim + " · " + tsIP + a.reset
2317
- : a.dim + "Not detected" + a.reset;
2318
- var mcStatus = mcOk
2319
- ? a.green + "Installed" + a.reset
2320
- : a.dim + "Not found" + a.reset;
2321
2111
  var tlsStatus = config.tls
2322
2112
  ? a.green + "Enabled" + a.reset
2323
2113
  : a.dim + "Disabled" + a.reset;
@@ -2328,8 +2118,6 @@ function showSettingsMenu(config, ip) {
2328
2118
  ? a.green + "On" + a.reset
2329
2119
  : a.dim + "Off" + a.reset;
2330
2120
 
2331
- log(sym.bar + " Tailscale " + tsStatus);
2332
- log(sym.bar + " mkcert " + mcStatus);
2333
2121
  log(sym.bar + " HTTPS " + tlsStatus);
2334
2122
  var muEnabled = isMultiUser();
2335
2123
  var muStatus = muEnabled
@@ -2355,9 +2143,7 @@ function showSettingsMenu(config, ip) {
2355
2143
  log(sym.bar);
2356
2144
 
2357
2145
  // Build items
2358
- var items = [
2359
- { label: "Setup notifications", value: "guide" },
2360
- ];
2146
+ var items = [];
2361
2147
 
2362
2148
  if (!muEnabled) {
2363
2149
  if (config.pinHash) {
@@ -2392,13 +2178,6 @@ function showSettingsMenu(config, ip) {
2392
2178
 
2393
2179
  promptSelect("Select", items, function (choice) {
2394
2180
  switch (choice) {
2395
- case "guide":
2396
- showSetupGuide(config, ip, function () {
2397
- config = loadConfig() || config;
2398
- showSettingsMenu(config, ip);
2399
- });
2400
- break;
2401
-
2402
2181
  case "pin":
2403
2182
  log(sym.bar);
2404
2183
  promptPin(function (pin) {
package/lib/daemon.js CHANGED
@@ -1343,25 +1343,43 @@ if (config.keepAwake && process.platform === "darwin") {
1343
1343
  // --- Spawn new daemon and graceful restart ---
1344
1344
  function spawnAndRestart() {
1345
1345
  try {
1346
- var { spawn: spawnRestart } = require("child_process");
1347
- var { logPath: restartLogPath, configPath: restartConfigPath } = require("./config");
1348
- var daemonScript = path.join(__dirname, "daemon.js");
1349
- var logFd = fs.openSync(restartLogPath(), "a");
1350
- var child = spawnRestart(process.execPath, [daemonScript], {
1351
- detached: true,
1352
- windowsHide: true,
1353
- stdio: ["ignore", logFd, logFd],
1354
- env: Object.assign({}, process.env, {
1355
- CLAY_CONFIG: restartConfigPath(),
1356
- }),
1357
- });
1358
- child.unref();
1359
- fs.closeSync(logFd);
1360
- config.pid = child.pid;
1361
- saveConfig(config);
1362
- console.log("[daemon] Spawned new daemon (PID " + child.pid + "), shutting down...");
1363
1346
  updateHandoff = true;
1364
- setTimeout(function () { gracefulShutdown(); }, 100);
1347
+ ipc.close();
1348
+ relay.destroyAll();
1349
+ if (relay.onboardingServer) relay.onboardingServer.close();
1350
+
1351
+ // Close the server first so the port is released before spawning the new daemon
1352
+ relay.server.close(function () {
1353
+ try {
1354
+ var { spawn: spawnRestart } = require("child_process");
1355
+ var { logPath: restartLogPath, configPath: restartConfigPath } = require("./config");
1356
+ var daemonScript = path.join(__dirname, "daemon.js");
1357
+ var logFd = fs.openSync(restartLogPath(), "a");
1358
+ var child = spawnRestart(process.execPath, [daemonScript], {
1359
+ detached: true,
1360
+ windowsHide: true,
1361
+ stdio: ["ignore", logFd, logFd],
1362
+ env: Object.assign({}, process.env, {
1363
+ CLAY_CONFIG: restartConfigPath(),
1364
+ }),
1365
+ });
1366
+ child.unref();
1367
+ fs.closeSync(logFd);
1368
+ config.pid = child.pid;
1369
+ saveConfig(config);
1370
+ console.log("[daemon] Spawned new daemon (PID " + child.pid + "), exiting.");
1371
+ process.exit(120);
1372
+ } catch (e) {
1373
+ console.error("[daemon] Restart failed:", e.message);
1374
+ process.exit(1);
1375
+ }
1376
+ });
1377
+
1378
+ // Force exit after 5 seconds if server.close hangs
1379
+ setTimeout(function () {
1380
+ console.error("[daemon] Forced exit after timeout during restart");
1381
+ process.exit(1);
1382
+ }, 5000);
1365
1383
  } catch (e) {
1366
1384
  console.error("[daemon] Restart failed:", e.message);
1367
1385
  relay.broadcastAll({ type: "toast", level: "error", message: "Restart failed: " + e.message });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.22.1",
3
+ "version": "2.22.2-beta.1",
4
4
  "description": "Self-hosted Claude Code in your browser. Multi-session, multi-user, push notifications.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",