clay-server 2.6.0 → 2.7.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.
- package/bin/cli.js +53 -4
- package/lib/config.js +15 -6
- package/lib/daemon.js +47 -5
- package/lib/ipc.js +12 -0
- package/lib/notes.js +2 -2
- package/lib/project.js +883 -2
- package/lib/public/app.js +862 -14
- package/lib/public/css/diff.css +12 -0
- package/lib/public/css/filebrowser.css +1 -1
- package/lib/public/css/loop.css +841 -0
- package/lib/public/css/menus.css +5 -0
- package/lib/public/css/mobile-nav.css +15 -15
- package/lib/public/css/rewind.css +23 -0
- package/lib/public/css/scheduler-modal.css +546 -0
- package/lib/public/css/scheduler.css +944 -0
- package/lib/public/css/sidebar.css +1 -0
- package/lib/public/css/skills.css +59 -0
- package/lib/public/css/sticky-notes.css +486 -0
- package/lib/public/css/title-bar.css +83 -3
- package/lib/public/index.html +181 -3
- package/lib/public/modules/diff.js +3 -3
- package/lib/public/modules/filebrowser.js +169 -45
- package/lib/public/modules/input.js +17 -3
- package/lib/public/modules/markdown.js +10 -0
- package/lib/public/modules/qrcode.js +23 -26
- package/lib/public/modules/scheduler.js +1240 -0
- package/lib/public/modules/server-settings.js +40 -0
- package/lib/public/modules/sidebar.js +12 -0
- package/lib/public/modules/skills.js +84 -0
- package/lib/public/modules/sticky-notes.js +617 -52
- package/lib/public/modules/theme.js +9 -19
- package/lib/public/modules/tools.js +16 -2
- package/lib/public/style.css +3 -0
- package/lib/scheduler.js +362 -0
- package/lib/sdk-bridge.js +36 -0
- package/lib/sessions.js +9 -5
- package/lib/utils.js +49 -3
- package/package.json +1 -1
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");
|
|
@@ -7,8 +21,9 @@ var { execSync, execFileSync, spawn } = require("child_process");
|
|
|
7
21
|
var qrcode = require("qrcode-terminal");
|
|
8
22
|
var net = require("net");
|
|
9
23
|
|
|
10
|
-
// Detect dev mode
|
|
24
|
+
// Detect dev mode — dev and prod use separate daemon files so they can run simultaneously
|
|
11
25
|
var _isDev = (process.argv[1] && path.basename(process.argv[1]) === "clay-dev") || process.argv.includes("--dev");
|
|
26
|
+
if (_isDev) process.env.CLAY_DEV = "1";
|
|
12
27
|
|
|
13
28
|
var { loadConfig, saveConfig, configPath, socketPath, logPath, ensureConfigDir, isDaemonAlive, isDaemonAliveAsync, generateSlug, clearStaleConfig, loadClayrc, saveClayrc, readCrashInfo } = require("../lib/config");
|
|
14
29
|
var { sendIPCCommand } = require("../lib/ipc");
|
|
@@ -33,12 +48,14 @@ var debugMode = false;
|
|
|
33
48
|
var autoYes = false;
|
|
34
49
|
var cliPin = null;
|
|
35
50
|
var shutdownMode = false;
|
|
51
|
+
var restartMode = false;
|
|
36
52
|
var addPath = null;
|
|
37
53
|
var removePath = null;
|
|
38
54
|
var listMode = false;
|
|
39
55
|
var dangerouslySkipPermissions = false;
|
|
40
56
|
var headlessMode = false;
|
|
41
57
|
var watchMode = false;
|
|
58
|
+
var host = null;
|
|
42
59
|
|
|
43
60
|
for (var i = 0; i < args.length; i++) {
|
|
44
61
|
if (args[i] === "-p" || args[i] === "--port") {
|
|
@@ -48,6 +65,9 @@ for (var i = 0; i < args.length; i++) {
|
|
|
48
65
|
process.exit(1);
|
|
49
66
|
}
|
|
50
67
|
i++;
|
|
68
|
+
} else if (args[i] === "--host" || args[i] === "--bind") {
|
|
69
|
+
host = args[i + 1] || null;
|
|
70
|
+
i++;
|
|
51
71
|
} else if (args[i] === "--no-https") {
|
|
52
72
|
useHttps = false;
|
|
53
73
|
} else if (args[i] === "--no-update" || args[i] === "--skip-update") {
|
|
@@ -65,6 +85,8 @@ for (var i = 0; i < args.length; i++) {
|
|
|
65
85
|
i++;
|
|
66
86
|
} else if (args[i] === "--shutdown") {
|
|
67
87
|
shutdownMode = true;
|
|
88
|
+
} else if (args[i] === "--restart") {
|
|
89
|
+
restartMode = true;
|
|
68
90
|
} else if (args[i] === "--add") {
|
|
69
91
|
addPath = args[i + 1] || ".";
|
|
70
92
|
i++;
|
|
@@ -79,19 +101,21 @@ for (var i = 0; i < args.length; i++) {
|
|
|
79
101
|
} else if (args[i] === "--dangerously-skip-permissions") {
|
|
80
102
|
dangerouslySkipPermissions = true;
|
|
81
103
|
} 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]");
|
|
104
|
+
console.log("Usage: clay-server [-p|--port <port>] [--host <address>] [--no-https] [--no-update] [--debug] [-y|--yes] [--pin <pin>] [--shutdown] [--restart]");
|
|
83
105
|
console.log(" clay-server --add <path> Add a project to the running daemon");
|
|
84
106
|
console.log(" clay-server --remove <path> Remove a project from the running daemon");
|
|
85
107
|
console.log(" clay-server --list List registered projects");
|
|
86
108
|
console.log("");
|
|
87
109
|
console.log("Options:");
|
|
88
110
|
console.log(" -p, --port <port> Port to listen on (default: 2633)");
|
|
111
|
+
console.log(" --host <address> Address to bind to (default: 0.0.0.0)");
|
|
89
112
|
console.log(" --no-https Disable HTTPS (enabled by default via mkcert)");
|
|
90
113
|
console.log(" --no-update Skip auto-update check on startup");
|
|
91
114
|
console.log(" --debug Enable debug panel in the web UI");
|
|
92
115
|
console.log(" -y, --yes Skip interactive prompts (accept defaults)");
|
|
93
116
|
console.log(" --pin <pin> Set 6-digit PIN (use with --yes)");
|
|
94
117
|
console.log(" --shutdown Shut down the running relay daemon");
|
|
118
|
+
console.log(" --restart Restart the running relay daemon");
|
|
95
119
|
console.log(" --add <path> Add a project directory (use '.' for current)");
|
|
96
120
|
console.log(" --remove <path> Remove a project directory");
|
|
97
121
|
console.log(" --list List all registered projects");
|
|
@@ -128,6 +152,25 @@ if (shutdownMode) {
|
|
|
128
152
|
return;
|
|
129
153
|
}
|
|
130
154
|
|
|
155
|
+
// --- Handle --restart before anything else ---
|
|
156
|
+
if (restartMode) {
|
|
157
|
+
var restartConfig = loadConfig();
|
|
158
|
+
isDaemonAliveAsync(restartConfig).then(function (alive) {
|
|
159
|
+
if (!alive) {
|
|
160
|
+
console.error("No running daemon found.");
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
sendIPCCommand(socketPath(), { cmd: "restart" }).then(function () {
|
|
164
|
+
console.log("Server restarted.");
|
|
165
|
+
process.exit(0);
|
|
166
|
+
}).catch(function (err) {
|
|
167
|
+
console.error("Restart failed:", err.message);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
131
174
|
// --- Handle --add before anything else ---
|
|
132
175
|
if (addPath !== null) {
|
|
133
176
|
var absAdd = path.resolve(addPath);
|
|
@@ -1277,6 +1320,7 @@ async function forkDaemon(pin, keepAwake, extraProjects, addCwd) {
|
|
|
1277
1320
|
var config = {
|
|
1278
1321
|
pid: null,
|
|
1279
1322
|
port: port,
|
|
1323
|
+
host: host,
|
|
1280
1324
|
pinHash: pin ? generateAuthToken(pin) : null,
|
|
1281
1325
|
tls: hasTls,
|
|
1282
1326
|
debug: debugMode,
|
|
@@ -1386,6 +1430,7 @@ async function devMode(pin, keepAwake, existingPinHash) {
|
|
|
1386
1430
|
var config = {
|
|
1387
1431
|
pid: null,
|
|
1388
1432
|
port: port,
|
|
1433
|
+
host: host,
|
|
1389
1434
|
pinHash: existingPinHash || (pin ? generateAuthToken(pin) : null),
|
|
1390
1435
|
tls: hasTls,
|
|
1391
1436
|
debug: true,
|
|
@@ -2370,8 +2415,12 @@ var currentVersion = require("../package.json").version;
|
|
|
2370
2415
|
if (autoRestorable.length > 0) {
|
|
2371
2416
|
console.log(" " + sym.done + " Restoring " + autoRestorable.length + " previous project(s)");
|
|
2372
2417
|
}
|
|
2373
|
-
|
|
2374
|
-
|
|
2418
|
+
// Add cwd if it has history in .clayrc, or if there are no other projects to restore
|
|
2419
|
+
var cwdInRc = (autoRc.recentProjects || []).some(function (p) {
|
|
2420
|
+
return p.path === cwd;
|
|
2421
|
+
});
|
|
2422
|
+
var addCwd = cwdInRc || autoRestorable.length === 0;
|
|
2423
|
+
await forkDaemon(pin, false, autoRestorable.length > 0 ? autoRestorable : undefined, addCwd);
|
|
2375
2424
|
} else {
|
|
2376
2425
|
setup(function (pin, keepAwake) {
|
|
2377
2426
|
if (dangerouslySkipPermissions && !pin) {
|
package/lib/config.js
CHANGED
|
@@ -70,20 +70,23 @@ var CONFIG_DIR = CLAY_HOME;
|
|
|
70
70
|
var CLAYRC_PATH = path.join(os.homedir(), ".clayrc");
|
|
71
71
|
var CRASH_INFO_PATH = path.join(CONFIG_DIR, "crash.json");
|
|
72
72
|
|
|
73
|
+
// Dev mode uses separate daemon files so dev and prod can run simultaneously
|
|
74
|
+
var _devMode = !!process.env.CLAY_DEV;
|
|
75
|
+
|
|
73
76
|
function configPath() {
|
|
74
|
-
return path.join(CONFIG_DIR, "daemon.json");
|
|
77
|
+
return path.join(CONFIG_DIR, _devMode ? "daemon-dev.json" : "daemon.json");
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
function socketPath() {
|
|
78
81
|
if (process.platform === "win32") {
|
|
79
|
-
var pipeName = "clay-daemon";
|
|
82
|
+
var pipeName = _devMode ? "clay-daemon-dev" : "clay-daemon";
|
|
80
83
|
return "\\\\.\\pipe\\" + pipeName;
|
|
81
84
|
}
|
|
82
|
-
return path.join(CONFIG_DIR, "daemon.sock");
|
|
85
|
+
return path.join(CONFIG_DIR, _devMode ? "daemon-dev.sock" : "daemon.sock");
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
function logPath() {
|
|
86
|
-
return path.join(CONFIG_DIR, "daemon.log");
|
|
89
|
+
return path.join(CONFIG_DIR, _devMode ? "daemon-dev.log" : "daemon.log");
|
|
87
90
|
}
|
|
88
91
|
|
|
89
92
|
function ensureConfigDir() {
|
|
@@ -117,7 +120,10 @@ function isPidAlive(pid) {
|
|
|
117
120
|
|
|
118
121
|
function isDaemonAlive(config) {
|
|
119
122
|
if (!config || !config.pid) return false;
|
|
120
|
-
if (!isPidAlive(config.pid))
|
|
123
|
+
if (!isPidAlive(config.pid)) {
|
|
124
|
+
clearStaleConfig();
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
121
127
|
// Named pipes on Windows can't be stat'd, just check PID
|
|
122
128
|
if (process.platform === "win32") return true;
|
|
123
129
|
try {
|
|
@@ -131,7 +137,10 @@ function isDaemonAlive(config) {
|
|
|
131
137
|
function isDaemonAliveAsync(config) {
|
|
132
138
|
return new Promise(function (resolve) {
|
|
133
139
|
if (!config || !config.pid) return resolve(false);
|
|
134
|
-
if (!isPidAlive(config.pid))
|
|
140
|
+
if (!isPidAlive(config.pid)) {
|
|
141
|
+
clearStaleConfig();
|
|
142
|
+
return resolve(false);
|
|
143
|
+
}
|
|
135
144
|
|
|
136
145
|
var sock = socketPath();
|
|
137
146
|
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 + "://
|
|
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://
|
|
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
|
@@ -8,8 +8,8 @@ function createNotesManager(opts) {
|
|
|
8
8
|
var cwd = opts.cwd;
|
|
9
9
|
|
|
10
10
|
// Storage path: ~/.clay/notes/{encodedCwd}.json
|
|
11
|
-
var encodedCwd = utils.encodeCwd(cwd);
|
|
12
11
|
var notesDir = path.join(config.CONFIG_DIR, "notes");
|
|
12
|
+
var encodedCwd = utils.resolveEncodedFile(notesDir, cwd, ".json");
|
|
13
13
|
var notesFile = path.join(notesDir, encodedCwd + ".json");
|
|
14
14
|
|
|
15
15
|
// In-memory cache
|
|
@@ -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) {
|