clay-server 2.8.0 → 2.9.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/README.md +2 -0
- package/bin/cli.js +151 -18
- package/lib/config.js +20 -1
- package/lib/daemon.js +40 -0
- package/lib/pages.js +670 -27
- package/lib/project.js +267 -16
- package/lib/public/app.js +78 -21
- package/lib/public/css/admin.css +576 -0
- package/lib/public/css/base.css +8 -1
- package/lib/public/css/icon-strip.css +1 -0
- package/lib/public/css/menus.css +16 -11
- package/lib/public/css/overlays.css +2 -4
- package/lib/public/css/sidebar.css +49 -0
- package/lib/public/css/title-bar.css +45 -1
- package/lib/public/index.html +40 -11
- package/lib/public/modules/admin.js +631 -0
- package/lib/public/modules/ascii-logo.js +84 -31
- package/lib/public/modules/filebrowser.js +1 -2
- package/lib/public/modules/markdown.js +6 -102
- package/lib/public/modules/profile.js +21 -0
- package/lib/public/modules/project-settings.js +1 -1
- package/lib/public/modules/server-settings.js +13 -0
- package/lib/public/modules/sidebar.js +109 -6
- package/lib/public/modules/sticky-notes.js +0 -2
- package/lib/public/modules/tools.js +1 -2
- package/lib/public/style.css +1 -0
- package/lib/push.js +6 -0
- package/lib/server.js +1075 -27
- package/lib/sessions.js +127 -41
- package/lib/smtp.js +221 -0
- package/lib/users.js +459 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -276,6 +276,8 @@ If you are using claude-relay, let us know how you are using it in Discussions:
|
|
|
276
276
|
|
|
277
277
|
This is an independent project and is not affiliated with Anthropic. Claude is a trademark of Anthropic.
|
|
278
278
|
|
|
279
|
+
Clay is provided "as is" without warranty of any kind. Users are responsible for ensuring their use of this software complies with all applicable terms of service of the underlying AI providers (e.g., Anthropic, OpenAI) and any other third-party services. Features such as multi-user mode are experimental and may involve sharing access to API-backed services — please review your provider's usage policies regarding account sharing, acceptable use, and any applicable rate limits or restrictions before enabling such features. The authors assume no liability for any misuse or violations arising from the use of this software.
|
|
280
|
+
|
|
279
281
|
## License
|
|
280
282
|
|
|
281
283
|
MIT
|
package/bin/cli.js
CHANGED
|
@@ -28,6 +28,7 @@ if (_isDev) process.env.CLAY_DEV = "1";
|
|
|
28
28
|
var { loadConfig, saveConfig, configPath, socketPath, logPath, ensureConfigDir, isDaemonAlive, isDaemonAliveAsync, generateSlug, clearStaleConfig, loadClayrc, saveClayrc, readCrashInfo } = require("../lib/config");
|
|
29
29
|
var { sendIPCCommand } = require("../lib/ipc");
|
|
30
30
|
var { generateAuthToken } = require("../lib/server");
|
|
31
|
+
var { enableMultiUser, hasAdmin, isMultiUser } = require("../lib/users");
|
|
31
32
|
|
|
32
33
|
function openUrl(url) {
|
|
33
34
|
try {
|
|
@@ -56,6 +57,7 @@ var dangerouslySkipPermissions = false;
|
|
|
56
57
|
var headlessMode = false;
|
|
57
58
|
var watchMode = false;
|
|
58
59
|
var host = null;
|
|
60
|
+
var multiUserMode = false;
|
|
59
61
|
|
|
60
62
|
for (var i = 0; i < args.length; i++) {
|
|
61
63
|
if (args[i] === "-p" || args[i] === "--port") {
|
|
@@ -100,6 +102,8 @@ for (var i = 0; i < args.length; i++) {
|
|
|
100
102
|
autoYes = true;
|
|
101
103
|
} else if (args[i] === "--dangerously-skip-permissions") {
|
|
102
104
|
dangerouslySkipPermissions = true;
|
|
105
|
+
} else if (args[i] === "--multi-user") {
|
|
106
|
+
multiUserMode = true;
|
|
103
107
|
} else if (args[i] === "-h" || args[i] === "--help") {
|
|
104
108
|
console.log("Usage: clay-server [-p|--port <port>] [--host <address>] [--no-https] [--no-update] [--debug] [-y|--yes] [--pin <pin>] [--shutdown] [--restart]");
|
|
105
109
|
console.log(" clay-server --add <path> Add a project to the running daemon");
|
|
@@ -120,8 +124,9 @@ for (var i = 0; i < args.length; i++) {
|
|
|
120
124
|
console.log(" --remove <path> Remove a project directory");
|
|
121
125
|
console.log(" --list List all registered projects");
|
|
122
126
|
console.log(" --headless Start daemon and exit immediately (implies --yes)");
|
|
127
|
+
console.log(" --multi-user Enable multi-user mode (generates setup code)");
|
|
123
128
|
console.log(" --dangerously-skip-permissions");
|
|
124
|
-
console.log(" Bypass all permission prompts
|
|
129
|
+
console.log(" Bypass all permission prompts");
|
|
125
130
|
process.exit(0);
|
|
126
131
|
}
|
|
127
132
|
}
|
|
@@ -258,6 +263,34 @@ if (listMode) {
|
|
|
258
263
|
return;
|
|
259
264
|
}
|
|
260
265
|
|
|
266
|
+
// --- Handle --multi-user before anything else ---
|
|
267
|
+
if (multiUserMode) {
|
|
268
|
+
var muResult = enableMultiUser();
|
|
269
|
+
if (muResult.alreadyEnabled && muResult.hasAdmin) {
|
|
270
|
+
console.log("");
|
|
271
|
+
console.log("Multi-user mode is already enabled and an admin account exists.");
|
|
272
|
+
console.log("No changes made.");
|
|
273
|
+
console.log("");
|
|
274
|
+
} else if (muResult.setupCode) {
|
|
275
|
+
console.log("");
|
|
276
|
+
console.log("\x1b[33m⚠ Experimental Feature\x1b[0m");
|
|
277
|
+
console.log("");
|
|
278
|
+
console.log(" Multi-user mode is experimental and may change in future releases.");
|
|
279
|
+
console.log(" Sharing access to AI-powered tools may be subject to your provider's");
|
|
280
|
+
console.log(" terms of service. Please review the applicable usage policies before");
|
|
281
|
+
console.log(" granting access to other users.");
|
|
282
|
+
console.log("");
|
|
283
|
+
console.log("\x1b[32mMulti-user mode enabled.\x1b[0m");
|
|
284
|
+
console.log("");
|
|
285
|
+
console.log("Setup code: \x1b[1m" + muResult.setupCode + "\x1b[0m");
|
|
286
|
+
console.log("");
|
|
287
|
+
console.log("Open Clay in your browser and enter this code to create the admin account.");
|
|
288
|
+
console.log("The code is single-use and will be cleared once the admin is set up.");
|
|
289
|
+
console.log("");
|
|
290
|
+
}
|
|
291
|
+
process.exit(0);
|
|
292
|
+
}
|
|
293
|
+
|
|
261
294
|
var cwd = process.cwd();
|
|
262
295
|
|
|
263
296
|
// --- ANSI helpers ---
|
|
@@ -1243,7 +1276,30 @@ function setup(callback) {
|
|
|
1243
1276
|
port = p;
|
|
1244
1277
|
log(sym.bar);
|
|
1245
1278
|
|
|
1246
|
-
|
|
1279
|
+
function askPin() {
|
|
1280
|
+
promptPin(function (pin) {
|
|
1281
|
+
if (dangerouslySkipPermissions && !pin) {
|
|
1282
|
+
log(sym.bar);
|
|
1283
|
+
log(sym.warn + " " + a.yellow + "WARNING: No PIN + skip permissions = anyone with the URL" + a.reset);
|
|
1284
|
+
log(sym.bar + " " + a.yellow + "can execute any command without approval." + a.reset);
|
|
1285
|
+
log(sym.bar);
|
|
1286
|
+
promptToggle("Continue without PIN?", null, false, function (confirmed) {
|
|
1287
|
+
if (!confirmed) {
|
|
1288
|
+
clearUp(6);
|
|
1289
|
+
log(sym.done + " PIN protection " + a.dim + "·" + a.reset + " " + a.yellow + "Required for skip permissions" + a.reset);
|
|
1290
|
+
log(sym.bar);
|
|
1291
|
+
askPin();
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
afterPin(pin);
|
|
1295
|
+
});
|
|
1296
|
+
} else {
|
|
1297
|
+
afterPin(pin);
|
|
1298
|
+
}
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
function afterPin(pin) {
|
|
1247
1303
|
if (process.platform === "darwin") {
|
|
1248
1304
|
promptToggle("Keep awake", "Prevent system sleep while relay is running", false, function (keepAwake) {
|
|
1249
1305
|
callback(pin, keepAwake);
|
|
@@ -1251,7 +1307,9 @@ function setup(callback) {
|
|
|
1251
1307
|
} else {
|
|
1252
1308
|
callback(pin, false);
|
|
1253
1309
|
}
|
|
1254
|
-
}
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
askPin();
|
|
1255
1313
|
});
|
|
1256
1314
|
});
|
|
1257
1315
|
}
|
|
@@ -1287,6 +1345,15 @@ async function forkDaemon(pin, keepAwake, extraProjects, addCwd) {
|
|
|
1287
1345
|
var allProjects = [];
|
|
1288
1346
|
var usedSlugs = [];
|
|
1289
1347
|
|
|
1348
|
+
// Load previous config to preserve per-project settings (visibility, allowedUsers)
|
|
1349
|
+
var prevConfig = loadConfig();
|
|
1350
|
+
var prevProjectMap = {};
|
|
1351
|
+
if (prevConfig && prevConfig.projects) {
|
|
1352
|
+
for (var pi = 0; pi < prevConfig.projects.length; pi++) {
|
|
1353
|
+
prevProjectMap[prevConfig.projects[pi].path] = prevConfig.projects[pi];
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1290
1357
|
// Only include cwd if explicitly requested
|
|
1291
1358
|
if (addCwd) {
|
|
1292
1359
|
var slug = generateSlug(cwd, []);
|
|
@@ -1301,6 +1368,11 @@ async function forkDaemon(pin, keepAwake, extraProjects, addCwd) {
|
|
|
1301
1368
|
break;
|
|
1302
1369
|
}
|
|
1303
1370
|
}
|
|
1371
|
+
// Restore access settings from previous config
|
|
1372
|
+
if (prevProjectMap[cwd]) {
|
|
1373
|
+
if (prevProjectMap[cwd].visibility) cwdEntry.visibility = prevProjectMap[cwd].visibility;
|
|
1374
|
+
if (prevProjectMap[cwd].allowedUsers) cwdEntry.allowedUsers = prevProjectMap[cwd].allowedUsers;
|
|
1375
|
+
}
|
|
1304
1376
|
allProjects.push(cwdEntry);
|
|
1305
1377
|
usedSlugs.push(slug);
|
|
1306
1378
|
}
|
|
@@ -1313,7 +1385,13 @@ async function forkDaemon(pin, keepAwake, extraProjects, addCwd) {
|
|
|
1313
1385
|
if (!fs.existsSync(rp.path)) continue; // skip missing directories
|
|
1314
1386
|
var rpSlug = generateSlug(rp.path, usedSlugs);
|
|
1315
1387
|
usedSlugs.push(rpSlug);
|
|
1316
|
-
|
|
1388
|
+
var rpEntry = { path: rp.path, slug: rpSlug, title: rp.title || undefined, icon: rp.icon || undefined, addedAt: rp.addedAt || Date.now() };
|
|
1389
|
+
// Restore access settings from previous config
|
|
1390
|
+
if (prevProjectMap[rp.path]) {
|
|
1391
|
+
if (prevProjectMap[rp.path].visibility) rpEntry.visibility = prevProjectMap[rp.path].visibility;
|
|
1392
|
+
if (prevProjectMap[rp.path].allowedUsers) rpEntry.allowedUsers = prevProjectMap[rp.path].allowedUsers;
|
|
1393
|
+
}
|
|
1394
|
+
allProjects.push(rpEntry);
|
|
1317
1395
|
}
|
|
1318
1396
|
}
|
|
1319
1397
|
|
|
@@ -1404,6 +1482,15 @@ async function devMode(pin, keepAwake, existingPinHash) {
|
|
|
1404
1482
|
var slug = generateSlug(cwd, []);
|
|
1405
1483
|
var cwdDevEntry = { path: cwd, slug: slug, addedAt: Date.now() };
|
|
1406
1484
|
|
|
1485
|
+
// Load previous config to preserve per-project settings (visibility, allowedUsers)
|
|
1486
|
+
var prevDevConfig = loadConfig();
|
|
1487
|
+
var prevDevProjectMap = {};
|
|
1488
|
+
if (prevDevConfig && prevDevConfig.projects) {
|
|
1489
|
+
for (var pdi = 0; pdi < prevDevConfig.projects.length; pdi++) {
|
|
1490
|
+
prevDevProjectMap[prevDevConfig.projects[pdi].path] = prevDevConfig.projects[pdi];
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1407
1494
|
// Restore previous projects
|
|
1408
1495
|
var rc = loadClayrc();
|
|
1409
1496
|
var restorable = (rc.recentProjects || []).filter(function (p) {
|
|
@@ -1418,13 +1505,24 @@ async function devMode(pin, keepAwake, existingPinHash) {
|
|
|
1418
1505
|
break;
|
|
1419
1506
|
}
|
|
1420
1507
|
}
|
|
1508
|
+
// Restore access settings for cwd from previous config
|
|
1509
|
+
if (prevDevProjectMap[cwd]) {
|
|
1510
|
+
if (prevDevProjectMap[cwd].visibility) cwdDevEntry.visibility = prevDevProjectMap[cwd].visibility;
|
|
1511
|
+
if (prevDevProjectMap[cwd].allowedUsers) cwdDevEntry.allowedUsers = prevDevProjectMap[cwd].allowedUsers;
|
|
1512
|
+
}
|
|
1421
1513
|
var allProjects = [cwdDevEntry];
|
|
1422
1514
|
var usedSlugs = [slug];
|
|
1423
1515
|
for (var ri = 0; ri < restorable.length; ri++) {
|
|
1424
1516
|
var rp = restorable[ri];
|
|
1425
1517
|
var rpSlug = generateSlug(rp.path, usedSlugs);
|
|
1426
1518
|
usedSlugs.push(rpSlug);
|
|
1427
|
-
|
|
1519
|
+
var rpDevEntry = { path: rp.path, slug: rpSlug, title: rp.title || undefined, icon: rp.icon || undefined, addedAt: rp.addedAt || Date.now() };
|
|
1520
|
+
// Restore access settings from previous config
|
|
1521
|
+
if (prevDevProjectMap[rp.path]) {
|
|
1522
|
+
if (prevDevProjectMap[rp.path].visibility) rpDevEntry.visibility = prevDevProjectMap[rp.path].visibility;
|
|
1523
|
+
if (prevDevProjectMap[rp.path].allowedUsers) rpDevEntry.allowedUsers = prevDevProjectMap[rp.path].allowedUsers;
|
|
1524
|
+
}
|
|
1525
|
+
allProjects.push(rpDevEntry);
|
|
1428
1526
|
}
|
|
1429
1527
|
|
|
1430
1528
|
var config = {
|
|
@@ -2197,7 +2295,13 @@ function showSettingsMenu(config, ip) {
|
|
|
2197
2295
|
log(sym.bar + " Tailscale " + tsStatus);
|
|
2198
2296
|
log(sym.bar + " mkcert " + mcStatus);
|
|
2199
2297
|
log(sym.bar + " HTTPS " + tlsStatus);
|
|
2298
|
+
var muEnabled = isMultiUser();
|
|
2299
|
+
var muStatus = muEnabled
|
|
2300
|
+
? a.green + "Enabled" + a.reset
|
|
2301
|
+
: a.dim + "Off" + a.reset;
|
|
2302
|
+
|
|
2200
2303
|
log(sym.bar + " PIN " + pinStatus);
|
|
2304
|
+
log(sym.bar + " Multi-user " + muStatus);
|
|
2201
2305
|
if (process.platform === "darwin") {
|
|
2202
2306
|
log(sym.bar + " Keep awake " + awakeStatus);
|
|
2203
2307
|
}
|
|
@@ -2214,6 +2318,11 @@ function showSettingsMenu(config, ip) {
|
|
|
2214
2318
|
} else {
|
|
2215
2319
|
items.push({ label: "Set PIN", value: "pin" });
|
|
2216
2320
|
}
|
|
2321
|
+
if (muEnabled) {
|
|
2322
|
+
items.push({ label: "Multi-user mode (enabled)", value: "multi_user" });
|
|
2323
|
+
} else {
|
|
2324
|
+
items.push({ label: "Enable multi-user mode", value: "multi_user" });
|
|
2325
|
+
}
|
|
2217
2326
|
if (process.platform === "darwin") {
|
|
2218
2327
|
items.push({ label: isAwake ? "Disable keep awake" : "Enable keep awake", value: "awake" });
|
|
2219
2328
|
}
|
|
@@ -2255,6 +2364,42 @@ function showSettingsMenu(config, ip) {
|
|
|
2255
2364
|
});
|
|
2256
2365
|
break;
|
|
2257
2366
|
|
|
2367
|
+
case "multi_user":
|
|
2368
|
+
if (muEnabled && hasAdmin()) {
|
|
2369
|
+
log(sym.bar);
|
|
2370
|
+
log(sym.bar + " " + a.dim + "Multi-user mode is already enabled and an admin account exists." + a.reset);
|
|
2371
|
+
log(sym.bar + " " + a.dim + "No changes made." + a.reset);
|
|
2372
|
+
log(sym.bar);
|
|
2373
|
+
promptSelect("Back?", [{ label: "Back", value: "back" }], function () {
|
|
2374
|
+
showSettingsMenu(config, ip);
|
|
2375
|
+
});
|
|
2376
|
+
} else {
|
|
2377
|
+
var muResult = enableMultiUser();
|
|
2378
|
+
log(sym.bar);
|
|
2379
|
+
log(sym.bar + " " + a.yellow + sym.warn + " Experimental Feature" + a.reset);
|
|
2380
|
+
log(sym.bar);
|
|
2381
|
+
log(sym.bar + " " + a.dim + "Multi-user mode is experimental and may change in future releases." + a.reset);
|
|
2382
|
+
log(sym.bar + " " + a.dim + "Sharing access to AI-powered tools may be subject to your provider's" + a.reset);
|
|
2383
|
+
log(sym.bar + " " + a.dim + "terms of service. Please review the applicable usage policies before" + a.reset);
|
|
2384
|
+
log(sym.bar + " " + a.dim + "granting access to other users." + a.reset);
|
|
2385
|
+
log(sym.bar);
|
|
2386
|
+
if (muResult.setupCode) {
|
|
2387
|
+
log(sym.bar + " " + a.green + "Multi-user mode enabled." + a.reset);
|
|
2388
|
+
log(sym.bar);
|
|
2389
|
+
log(sym.bar + " Setup code: " + a.bold + muResult.setupCode + a.reset);
|
|
2390
|
+
log(sym.bar);
|
|
2391
|
+
log(sym.bar + " " + a.dim + "Open Clay in your browser and enter this code to create the admin account." + a.reset);
|
|
2392
|
+
log(sym.bar + " " + a.dim + "The code is single-use and will be cleared once the admin is set up." + a.reset);
|
|
2393
|
+
} else {
|
|
2394
|
+
log(sym.bar + " " + a.dim + "Multi-user mode is already enabled." + a.reset);
|
|
2395
|
+
}
|
|
2396
|
+
log(sym.bar);
|
|
2397
|
+
promptSelect("Back?", [{ label: "Back", value: "back" }], function () {
|
|
2398
|
+
showSettingsMenu(config, ip);
|
|
2399
|
+
});
|
|
2400
|
+
}
|
|
2401
|
+
break;
|
|
2402
|
+
|
|
2258
2403
|
case "logs":
|
|
2259
2404
|
console.clear();
|
|
2260
2405
|
log(a.bold + "Daemon logs" + a.reset + " " + a.dim + "(" + logPath() + ")" + a.reset);
|
|
@@ -2398,15 +2543,10 @@ var currentVersion = require("../package.json").version;
|
|
|
2398
2543
|
// No daemon running — first-time setup
|
|
2399
2544
|
if (autoYes) {
|
|
2400
2545
|
var pin = cliPin || null;
|
|
2401
|
-
if (dangerouslySkipPermissions && !pin) {
|
|
2402
|
-
console.error(" " + sym.warn + " " + a.red + "--dangerously-skip-permissions requires --pin <pin>" + a.reset);
|
|
2403
|
-
process.exit(1);
|
|
2404
|
-
return;
|
|
2405
|
-
}
|
|
2406
2546
|
console.log(" " + sym.done + " Auto-accepted disclaimer");
|
|
2407
2547
|
console.log(" " + sym.done + " PIN: " + (pin ? "Enabled" : "Skipped"));
|
|
2408
2548
|
if (dangerouslySkipPermissions) {
|
|
2409
|
-
console.log(" " + sym.warn + " " + a.yellow + "Skip permissions mode enabled" + a.reset);
|
|
2549
|
+
console.log(" " + sym.warn + " " + a.yellow + "Skip permissions mode enabled" + (pin ? "" : " (no PIN)") + a.reset);
|
|
2410
2550
|
}
|
|
2411
2551
|
var autoRc = loadClayrc();
|
|
2412
2552
|
var autoRestorable = (autoRc.recentProjects || []).filter(function (p) {
|
|
@@ -2423,13 +2563,6 @@ var currentVersion = require("../package.json").version;
|
|
|
2423
2563
|
await forkDaemon(pin, false, autoRestorable.length > 0 ? autoRestorable : undefined, addCwd);
|
|
2424
2564
|
} else {
|
|
2425
2565
|
setup(function (pin, keepAwake) {
|
|
2426
|
-
if (dangerouslySkipPermissions && !pin) {
|
|
2427
|
-
log(sym.warn + " " + a.red + "--dangerously-skip-permissions requires a PIN." + a.reset);
|
|
2428
|
-
log(a.dim + " Please set a PIN to use skip permissions mode." + a.reset);
|
|
2429
|
-
process.exit(1);
|
|
2430
|
-
return;
|
|
2431
|
-
}
|
|
2432
|
-
|
|
2433
2566
|
// Check ~/.clayrc for previous projects to restore
|
|
2434
2567
|
var rc = loadClayrc();
|
|
2435
2568
|
var restorable = (rc.recentProjects || []).filter(function (p) {
|
package/lib/config.js
CHANGED
|
@@ -89,8 +89,14 @@ function logPath() {
|
|
|
89
89
|
return path.join(CONFIG_DIR, _devMode ? "daemon-dev.log" : "daemon.log");
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
function chmodSafe(filePath, mode) {
|
|
93
|
+
if (process.platform === "win32") return;
|
|
94
|
+
try { fs.chmodSync(filePath, mode); } catch (e) {}
|
|
95
|
+
}
|
|
96
|
+
|
|
92
97
|
function ensureConfigDir() {
|
|
93
98
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
99
|
+
chmodSafe(CONFIG_DIR, 0o700);
|
|
94
100
|
}
|
|
95
101
|
|
|
96
102
|
function loadConfig() {
|
|
@@ -106,7 +112,9 @@ function saveConfig(config) {
|
|
|
106
112
|
ensureConfigDir();
|
|
107
113
|
var tmpPath = configPath() + ".tmp";
|
|
108
114
|
fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2));
|
|
115
|
+
chmodSafe(tmpPath, 0o600);
|
|
109
116
|
fs.renameSync(tmpPath, configPath());
|
|
117
|
+
chmodSafe(configPath(), 0o600);
|
|
110
118
|
}
|
|
111
119
|
|
|
112
120
|
function isPidAlive(pid) {
|
|
@@ -173,7 +181,17 @@ function generateSlug(projectPath, existingSlugs) {
|
|
|
173
181
|
}
|
|
174
182
|
|
|
175
183
|
function clearStaleConfig() {
|
|
176
|
-
|
|
184
|
+
// Clear pid from config instead of deleting the file (preserves project settings)
|
|
185
|
+
try {
|
|
186
|
+
var data = fs.readFileSync(configPath(), "utf8");
|
|
187
|
+
var cfg = JSON.parse(data);
|
|
188
|
+
cfg.pid = null;
|
|
189
|
+
var tmpPath = configPath() + ".tmp";
|
|
190
|
+
fs.writeFileSync(tmpPath, JSON.stringify(cfg, null, 2));
|
|
191
|
+
chmodSafe(tmpPath, 0o600);
|
|
192
|
+
fs.renameSync(tmpPath, configPath());
|
|
193
|
+
chmodSafe(configPath(), 0o600);
|
|
194
|
+
} catch (e) {}
|
|
177
195
|
if (process.platform !== "win32") {
|
|
178
196
|
try { fs.unlinkSync(socketPath()); } catch (e) {}
|
|
179
197
|
}
|
|
@@ -316,4 +334,5 @@ module.exports = {
|
|
|
316
334
|
saveClayrc: saveClayrc,
|
|
317
335
|
syncClayrc: syncClayrc,
|
|
318
336
|
removeFromClayrc: removeFromClayrc,
|
|
337
|
+
chmodSafe: chmodSafe,
|
|
319
338
|
};
|
package/lib/daemon.js
CHANGED
|
@@ -368,6 +368,12 @@ var relay = createServer({
|
|
|
368
368
|
console.log("[daemon] PIN", pin ? "set" : "removed", "(web)");
|
|
369
369
|
return { ok: true, pinEnabled: !!config.pinHash };
|
|
370
370
|
},
|
|
371
|
+
onUpgradePin: function (newHash) {
|
|
372
|
+
config.pinHash = newHash;
|
|
373
|
+
relay.setAuthToken(newHash);
|
|
374
|
+
saveConfig(config);
|
|
375
|
+
console.log("[daemon] PIN hash auto-upgraded to scrypt");
|
|
376
|
+
},
|
|
371
377
|
onSetKeepAwake: function (value) {
|
|
372
378
|
var want = !!value;
|
|
373
379
|
config.keepAwake = want;
|
|
@@ -393,6 +399,40 @@ var relay = createServer({
|
|
|
393
399
|
console.log("[daemon] Restart requested via web UI");
|
|
394
400
|
spawnAndRestart();
|
|
395
401
|
},
|
|
402
|
+
onSetProjectVisibility: function (slug, visibility) {
|
|
403
|
+
for (var i = 0; i < config.projects.length; i++) {
|
|
404
|
+
if (config.projects[i].slug === slug) {
|
|
405
|
+
config.projects[i].visibility = visibility;
|
|
406
|
+
saveConfig(config);
|
|
407
|
+
console.log("[daemon] Set project visibility:", slug, "→", visibility);
|
|
408
|
+
return { ok: true };
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return { error: "Project not found" };
|
|
412
|
+
},
|
|
413
|
+
onSetProjectAllowedUsers: function (slug, allowedUsers) {
|
|
414
|
+
for (var i = 0; i < config.projects.length; i++) {
|
|
415
|
+
if (config.projects[i].slug === slug) {
|
|
416
|
+
config.projects[i].allowedUsers = allowedUsers;
|
|
417
|
+
saveConfig(config);
|
|
418
|
+
console.log("[daemon] Set project allowed users:", slug, "→", allowedUsers.length, "users");
|
|
419
|
+
return { ok: true };
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return { error: "Project not found" };
|
|
423
|
+
},
|
|
424
|
+
onGetProjectAccess: function (slug) {
|
|
425
|
+
for (var i = 0; i < config.projects.length; i++) {
|
|
426
|
+
if (config.projects[i].slug === slug) {
|
|
427
|
+
return {
|
|
428
|
+
slug: slug,
|
|
429
|
+
visibility: config.projects[i].visibility || "public",
|
|
430
|
+
allowedUsers: config.projects[i].allowedUsers || [],
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return { error: "Project not found" };
|
|
435
|
+
},
|
|
396
436
|
});
|
|
397
437
|
|
|
398
438
|
// --- Register projects ---
|