clay-server 2.20.0 → 2.20.1-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/README.md +1 -1
  2. package/bin/cli.js +91 -35
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  <p align="center"><img src="media/hero.png" alt="Clay workspace" /></p>
10
10
 
11
- AI teammates who remember your decisions, challenge your thinking, and grow with your codebase. Runs on your machine.
11
+ A team workspace built on Claude Code. AI teammates who remember your decisions, challenge your thinking, and grow with your codebase. Runs on your machine.
12
12
 
13
13
  ```bash
14
14
  npx clay-server
package/bin/cli.js CHANGED
@@ -36,7 +36,7 @@ var crypto = require("crypto");
36
36
  var { loadConfig, saveConfig, configPath, socketPath, logPath, ensureConfigDir, isDaemonAlive, isDaemonAliveAsync, generateSlug, clearStaleConfig, loadClayrc, saveClayrc, readCrashInfo, REAL_HOME } = require("../lib/config");
37
37
  var { sendIPCCommand } = require("../lib/ipc");
38
38
  var { generateAuthToken } = require("../lib/server");
39
- var { enableMultiUser, disableMultiUser, hasAdmin, isMultiUser } = require("../lib/users");
39
+ var { enableMultiUser, disableMultiUser, hasAdmin, isMultiUser, getSetupCode } = require("../lib/users");
40
40
 
41
41
  function openUrl(url) {
42
42
  try {
@@ -1417,16 +1417,15 @@ function setup(callback) {
1417
1417
  }
1418
1418
  var isRoot = typeof process.getuid === "function" && process.getuid() === 0;
1419
1419
  if (!isRoot) {
1420
- // Save config so sudo clay can pick it up
1421
- var partialConfig = {
1422
- port: port,
1423
- host: host,
1424
- mode: "multi",
1425
- osUsers: true,
1426
- setupCompleted: true,
1427
- dangerouslySkipPermissions: dangerouslySkipPermissions,
1428
- };
1429
- saveConfig(partialConfig);
1420
+ // Merge into existing config (preserve projects, TLS, etc.)
1421
+ var existingCfg = loadConfig() || {};
1422
+ existingCfg.port = port;
1423
+ existingCfg.host = host;
1424
+ existingCfg.mode = "multi";
1425
+ existingCfg.osUsers = true;
1426
+ existingCfg.setupCompleted = true;
1427
+ if (dangerouslySkipPermissions) existingCfg.dangerouslySkipPermissions = true;
1428
+ saveConfig(existingCfg);
1430
1429
  log(sym.bar);
1431
1430
  log(sym.warn + " " + a.yellow + "OS user isolation requires root." + a.reset);
1432
1431
  log(sym.bar + " Run:");
@@ -2009,8 +2008,14 @@ function showMainMenu(config, ip, setupCode) {
2009
2008
  log("");
2010
2009
  }
2011
2010
 
2012
- if (setupCode) {
2013
- log(" " + a.yellow + sym.warn + " Setup code: " + a.bold + setupCode + a.reset);
2011
+ // Always check for pending setup code if multi-user is on and no admin exists
2012
+ var displayCode = setupCode;
2013
+ if (!displayCode && isMultiUser() && !hasAdmin()) {
2014
+ var pendingCode = getSetupCode();
2015
+ if (pendingCode) displayCode = pendingCode;
2016
+ }
2017
+ if (displayCode) {
2018
+ log(" " + a.yellow + sym.warn + " Setup code: " + a.bold + displayCode + a.reset);
2014
2019
  log(" " + a.dim + "Open Clay in your browser and enter this code to create the admin account." + a.reset);
2015
2020
  log("");
2016
2021
  }
@@ -2373,6 +2378,12 @@ function showSettingsMenu(config, ip) {
2373
2378
  } else {
2374
2379
  items.push({ label: "Enable multi-user mode", value: "multi_user" });
2375
2380
  }
2381
+ if (muEnabled && !hasAdmin()) {
2382
+ var pendingSetupCode = getSetupCode();
2383
+ if (pendingSetupCode) {
2384
+ items.push({ label: "Show setup code", value: "show_setup_code" });
2385
+ }
2386
+ }
2376
2387
  if (muEnabled && hasAdmin()) {
2377
2388
  items.push({ label: "Recover admin password", value: "recover_admin" });
2378
2389
  }
@@ -2478,7 +2489,9 @@ function showSettingsMenu(config, ip) {
2478
2489
  }
2479
2490
  if (process.getuid() !== 0) {
2480
2491
  log(sym.bar);
2481
- log(sym.bar + " " + a.red + "Requires running as root." + a.reset);
2492
+ log(sym.bar + " " + a.red + sym.warn + " OS user isolation requires root." + a.reset);
2493
+ log(sym.bar + " " + a.dim + "Shut down this server, then restart with:" + a.reset);
2494
+ log(sym.bar + " " + a.bold + "sudo npx clay-server" + a.reset);
2482
2495
  log(sym.bar);
2483
2496
  promptSelect("Back?", [{ label: "Back", value: "back" }], function () {
2484
2497
  showSettingsMenu(config, ip);
@@ -2603,33 +2616,48 @@ function showSettingsMenu(config, ip) {
2603
2616
  { label: "Cancel", value: "cancel" },
2604
2617
  ], function (confirmChoice) {
2605
2618
  if (confirmChoice === "confirm") {
2606
- // Clear setupCompleted so setup() runs fresh
2619
+ // Save old PID before clearing, so we can force-kill if needed
2607
2620
  var cfg = loadConfig() || {};
2621
+ var oldPid = cfg.pid;
2622
+ // Clear setupCompleted so setup() runs fresh
2608
2623
  delete cfg.setupCompleted;
2609
2624
  delete cfg.mode;
2610
2625
  cfg.pid = null;
2611
2626
  saveConfig(cfg);
2612
- // Shut down the daemon
2613
- sendIPCCommand(socketPath(), { cmd: "shutdown" }).then(function () {
2614
- clearStaleConfig();
2615
- // Run the setup wizard, then fork a new daemon
2616
- setup(function (mode, keepAwake, wantOsUsers) {
2617
- var rc = loadClayrc();
2618
- var restorable = (rc.recentProjects || []).filter(function (p) {
2619
- return p.path !== cwd && fs.existsSync(p.path);
2627
+
2628
+ // Helper: wait for port to be free, force-kill if needed
2629
+ function waitForPortFree(cb) {
2630
+ var attempts = 0;
2631
+ var maxAttempts = 12; // 6 seconds total
2632
+ function check() {
2633
+ isPortFree(port).then(function (free) {
2634
+ if (free) return cb();
2635
+ attempts++;
2636
+ if (attempts >= maxAttempts) {
2637
+ // Port still busy, force-kill old daemon
2638
+ if (oldPid) {
2639
+ try { process.kill(oldPid, "SIGKILL"); } catch (e) {}
2640
+ }
2641
+ // Wait a bit more after SIGKILL
2642
+ setTimeout(function () {
2643
+ isPortFree(port).then(function (free2) {
2644
+ if (!free2) {
2645
+ log(sym.warn + " " + a.yellow + "Port " + port + " still in use. Kill the process manually:" + a.reset);
2646
+ log(sym.bar + " " + a.bold + "lsof -ti:" + port + " | xargs kill -9" + a.reset);
2647
+ }
2648
+ cb();
2649
+ });
2650
+ }, 1000);
2651
+ return;
2652
+ }
2653
+ setTimeout(check, 500);
2620
2654
  });
2621
- if (restorable.length > 0) {
2622
- promptRestoreProjects(restorable, function (selected) {
2623
- forkDaemon(mode, keepAwake, selected, false, wantOsUsers);
2624
- });
2625
- } else {
2626
- log(sym.bar);
2627
- log(sym.end + " " + a.dim + "Starting relay..." + a.reset);
2628
- log("");
2629
- forkDaemon(mode, keepAwake, undefined, true, wantOsUsers);
2630
- }
2631
- });
2632
- }).catch(function () {
2655
+ }
2656
+ check();
2657
+ }
2658
+
2659
+ // Helper: run setup wizard after daemon is dead
2660
+ function proceedWithSetup() {
2633
2661
  clearStaleConfig();
2634
2662
  setup(function (mode, keepAwake, wantOsUsers) {
2635
2663
  var rc = loadClayrc();
@@ -2647,6 +2675,17 @@ function showSettingsMenu(config, ip) {
2647
2675
  forkDaemon(mode, keepAwake, undefined, true, wantOsUsers);
2648
2676
  }
2649
2677
  });
2678
+ }
2679
+
2680
+ // Shut down the daemon, then wait for port to be free
2681
+ sendIPCCommand(socketPath(), { cmd: "shutdown" }).then(function () {
2682
+ waitForPortFree(proceedWithSetup);
2683
+ }).catch(function () {
2684
+ // IPC failed, daemon may be unresponsive. Try SIGTERM, then wait.
2685
+ if (oldPid) {
2686
+ try { process.kill(oldPid, "SIGTERM"); } catch (e) {}
2687
+ }
2688
+ waitForPortFree(proceedWithSetup);
2650
2689
  });
2651
2690
  } else {
2652
2691
  showSettingsMenu(config, ip);
@@ -2654,6 +2693,23 @@ function showSettingsMenu(config, ip) {
2654
2693
  });
2655
2694
  break;
2656
2695
 
2696
+ case "show_setup_code":
2697
+ var currentCode = getSetupCode();
2698
+ if (currentCode) {
2699
+ log(sym.bar);
2700
+ log(sym.bar + " " + a.yellow + sym.warn + " Setup code: " + a.bold + currentCode + a.reset);
2701
+ log(sym.bar + " " + a.dim + "Open Clay in your browser and enter this code to create the admin account." + a.reset);
2702
+ log(sym.bar);
2703
+ } else {
2704
+ log(sym.bar);
2705
+ log(sym.bar + " " + a.dim + "No pending setup code (admin already exists)." + a.reset);
2706
+ log(sym.bar);
2707
+ }
2708
+ promptSelect("Back?", [{ label: "Back", value: "back" }], function () {
2709
+ showSettingsMenu(config, ip);
2710
+ });
2711
+ break;
2712
+
2657
2713
  case "logs":
2658
2714
  console.clear();
2659
2715
  log(a.bold + "Daemon logs" + a.reset + " " + a.dim + "(" + logPath() + ")" + a.reset);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.20.0",
3
+ "version": "2.20.1-beta.1",
4
4
  "description": "Web UI for Claude Code. Any device. Push notifications.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",