clay-server 2.20.0 → 2.20.1-beta.2
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/README.md +1 -1
- package/bin/cli.js +85 -35
- package/lib/daemon.js +5 -0
- package/lib/users.js +9 -1
- 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
|
-
//
|
|
1421
|
-
var
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
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,10 @@ function showMainMenu(config, ip, setupCode) {
|
|
|
2009
2008
|
log("");
|
|
2010
2009
|
}
|
|
2011
2010
|
|
|
2012
|
-
if (
|
|
2013
|
-
|
|
2011
|
+
// Always show setup code if one exists (persists until admin is created)
|
|
2012
|
+
var displayCode = setupCode || getSetupCode();
|
|
2013
|
+
if (displayCode) {
|
|
2014
|
+
log(" " + a.yellow + sym.warn + " Setup code: " + a.bold + displayCode + a.reset);
|
|
2014
2015
|
log(" " + a.dim + "Open Clay in your browser and enter this code to create the admin account." + a.reset);
|
|
2015
2016
|
log("");
|
|
2016
2017
|
}
|
|
@@ -2373,6 +2374,10 @@ function showSettingsMenu(config, ip) {
|
|
|
2373
2374
|
} else {
|
|
2374
2375
|
items.push({ label: "Enable multi-user mode", value: "multi_user" });
|
|
2375
2376
|
}
|
|
2377
|
+
var pendingSetupCode = getSetupCode();
|
|
2378
|
+
if (muEnabled && pendingSetupCode) {
|
|
2379
|
+
items.push({ label: "Show setup code", value: "show_setup_code" });
|
|
2380
|
+
}
|
|
2376
2381
|
if (muEnabled && hasAdmin()) {
|
|
2377
2382
|
items.push({ label: "Recover admin password", value: "recover_admin" });
|
|
2378
2383
|
}
|
|
@@ -2478,7 +2483,9 @@ function showSettingsMenu(config, ip) {
|
|
|
2478
2483
|
}
|
|
2479
2484
|
if (process.getuid() !== 0) {
|
|
2480
2485
|
log(sym.bar);
|
|
2481
|
-
log(sym.bar + " " + a.red + "
|
|
2486
|
+
log(sym.bar + " " + a.red + sym.warn + " OS user isolation requires root." + a.reset);
|
|
2487
|
+
log(sym.bar + " " + a.dim + "Shut down this server, then restart with:" + a.reset);
|
|
2488
|
+
log(sym.bar + " " + a.bold + "sudo npx clay-server" + a.reset);
|
|
2482
2489
|
log(sym.bar);
|
|
2483
2490
|
promptSelect("Back?", [{ label: "Back", value: "back" }], function () {
|
|
2484
2491
|
showSettingsMenu(config, ip);
|
|
@@ -2603,33 +2610,48 @@ function showSettingsMenu(config, ip) {
|
|
|
2603
2610
|
{ label: "Cancel", value: "cancel" },
|
|
2604
2611
|
], function (confirmChoice) {
|
|
2605
2612
|
if (confirmChoice === "confirm") {
|
|
2606
|
-
//
|
|
2613
|
+
// Save old PID before clearing, so we can force-kill if needed
|
|
2607
2614
|
var cfg = loadConfig() || {};
|
|
2615
|
+
var oldPid = cfg.pid;
|
|
2616
|
+
// Clear setupCompleted so setup() runs fresh
|
|
2608
2617
|
delete cfg.setupCompleted;
|
|
2609
2618
|
delete cfg.mode;
|
|
2610
2619
|
cfg.pid = null;
|
|
2611
2620
|
saveConfig(cfg);
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2621
|
+
|
|
2622
|
+
// Helper: wait for port to be free, force-kill if needed
|
|
2623
|
+
function waitForPortFree(cb) {
|
|
2624
|
+
var attempts = 0;
|
|
2625
|
+
var maxAttempts = 12; // 6 seconds total
|
|
2626
|
+
function check() {
|
|
2627
|
+
isPortFree(port).then(function (free) {
|
|
2628
|
+
if (free) return cb();
|
|
2629
|
+
attempts++;
|
|
2630
|
+
if (attempts >= maxAttempts) {
|
|
2631
|
+
// Port still busy, force-kill old daemon
|
|
2632
|
+
if (oldPid) {
|
|
2633
|
+
try { process.kill(oldPid, "SIGKILL"); } catch (e) {}
|
|
2634
|
+
}
|
|
2635
|
+
// Wait a bit more after SIGKILL
|
|
2636
|
+
setTimeout(function () {
|
|
2637
|
+
isPortFree(port).then(function (free2) {
|
|
2638
|
+
if (!free2) {
|
|
2639
|
+
log(sym.warn + " " + a.yellow + "Port " + port + " still in use. Kill the process manually:" + a.reset);
|
|
2640
|
+
log(sym.bar + " " + a.bold + "lsof -ti:" + port + " | xargs kill -9" + a.reset);
|
|
2641
|
+
}
|
|
2642
|
+
cb();
|
|
2643
|
+
});
|
|
2644
|
+
}, 1000);
|
|
2645
|
+
return;
|
|
2646
|
+
}
|
|
2647
|
+
setTimeout(check, 500);
|
|
2620
2648
|
});
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
log(sym.end + " " + a.dim + "Starting relay..." + a.reset);
|
|
2628
|
-
log("");
|
|
2629
|
-
forkDaemon(mode, keepAwake, undefined, true, wantOsUsers);
|
|
2630
|
-
}
|
|
2631
|
-
});
|
|
2632
|
-
}).catch(function () {
|
|
2649
|
+
}
|
|
2650
|
+
check();
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
// Helper: run setup wizard after daemon is dead
|
|
2654
|
+
function proceedWithSetup() {
|
|
2633
2655
|
clearStaleConfig();
|
|
2634
2656
|
setup(function (mode, keepAwake, wantOsUsers) {
|
|
2635
2657
|
var rc = loadClayrc();
|
|
@@ -2647,6 +2669,17 @@ function showSettingsMenu(config, ip) {
|
|
|
2647
2669
|
forkDaemon(mode, keepAwake, undefined, true, wantOsUsers);
|
|
2648
2670
|
}
|
|
2649
2671
|
});
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
// Shut down the daemon, then wait for port to be free
|
|
2675
|
+
sendIPCCommand(socketPath(), { cmd: "shutdown" }).then(function () {
|
|
2676
|
+
waitForPortFree(proceedWithSetup);
|
|
2677
|
+
}).catch(function () {
|
|
2678
|
+
// IPC failed, daemon may be unresponsive. Try SIGTERM, then wait.
|
|
2679
|
+
if (oldPid) {
|
|
2680
|
+
try { process.kill(oldPid, "SIGTERM"); } catch (e) {}
|
|
2681
|
+
}
|
|
2682
|
+
waitForPortFree(proceedWithSetup);
|
|
2650
2683
|
});
|
|
2651
2684
|
} else {
|
|
2652
2685
|
showSettingsMenu(config, ip);
|
|
@@ -2654,6 +2687,23 @@ function showSettingsMenu(config, ip) {
|
|
|
2654
2687
|
});
|
|
2655
2688
|
break;
|
|
2656
2689
|
|
|
2690
|
+
case "show_setup_code":
|
|
2691
|
+
var currentCode = getSetupCode();
|
|
2692
|
+
if (currentCode) {
|
|
2693
|
+
log(sym.bar);
|
|
2694
|
+
log(sym.bar + " " + a.yellow + sym.warn + " Setup code: " + a.bold + currentCode + a.reset);
|
|
2695
|
+
log(sym.bar + " " + a.dim + "Open Clay in your browser and enter this code to create the admin account." + a.reset);
|
|
2696
|
+
log(sym.bar);
|
|
2697
|
+
} else {
|
|
2698
|
+
log(sym.bar);
|
|
2699
|
+
log(sym.bar + " " + a.dim + "No pending setup code (admin already exists)." + a.reset);
|
|
2700
|
+
log(sym.bar);
|
|
2701
|
+
}
|
|
2702
|
+
promptSelect("Back?", [{ label: "Back", value: "back" }], function () {
|
|
2703
|
+
showSettingsMenu(config, ip);
|
|
2704
|
+
});
|
|
2705
|
+
break;
|
|
2706
|
+
|
|
2657
2707
|
case "logs":
|
|
2658
2708
|
console.clear();
|
|
2659
2709
|
log(a.bold + "Daemon logs" + a.reset + " " + a.dim + "(" + logPath() + ")" + a.reset);
|
package/lib/daemon.js
CHANGED
|
@@ -43,6 +43,11 @@ try {
|
|
|
43
43
|
process.exit(1);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
console.log("[daemon] Config: " + configFile);
|
|
47
|
+
console.log("[daemon] Users: " + usersModule.USERS_FILE);
|
|
48
|
+
if (process.env.SUDO_USER) console.log("[daemon] SUDO_USER: " + process.env.SUDO_USER);
|
|
49
|
+
console.log("[daemon] UID: " + (typeof process.getuid === "function" ? process.getuid() : "N/A"));
|
|
50
|
+
|
|
46
51
|
// --- OS users mode: check required system dependencies ---
|
|
47
52
|
if (config.osUsers) {
|
|
48
53
|
var { execSync: checkExec } = require("child_process");
|
package/lib/users.js
CHANGED
|
@@ -115,7 +115,15 @@ function generateSetupCode() {
|
|
|
115
115
|
|
|
116
116
|
function getSetupCode() {
|
|
117
117
|
var data = loadUsers();
|
|
118
|
-
|
|
118
|
+
if (data.setupCode) return data.setupCode;
|
|
119
|
+
// Defensive: if multi-user is on, no admin, and no code, auto-generate one
|
|
120
|
+
if (data.multiUser && !findAdmin(data)) {
|
|
121
|
+
var code = generateSetupCode();
|
|
122
|
+
data.setupCode = code;
|
|
123
|
+
saveUsers(data);
|
|
124
|
+
return code;
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
119
127
|
}
|
|
120
128
|
|
|
121
129
|
function clearSetupCode() {
|