clay-server 2.11.0-beta.9 → 2.11.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/bin/cli.js +282 -115
- package/lib/daemon.js +109 -37
- package/lib/os-users.js +58 -1
- package/lib/pages.js +31 -29
- package/lib/project.js +27 -8
- package/lib/public/app.js +158 -16
- package/lib/public/css/filebrowser.css +6 -0
- package/lib/public/css/icon-strip.css +123 -1
- package/lib/public/css/messages.css +1 -1
- package/lib/public/css/mobile-nav.css +17 -0
- package/lib/public/css/overlays.css +49 -0
- package/lib/public/css/sidebar.css +26 -0
- package/lib/public/css/sticky-notes.css +3 -0
- package/lib/public/index.html +2 -0
- package/lib/public/modules/admin.js +53 -5
- package/lib/public/modules/sidebar.js +299 -21
- package/lib/public/modules/sticky-notes.js +27 -5
- package/lib/public/modules/terminal.js +161 -25
- package/lib/sdk-bridge.js +53 -7
- package/lib/server.js +156 -17
- package/lib/sessions.js +48 -7
- package/lib/terminal-manager.js +4 -2
- package/lib/terminal.js +2 -1
- package/lib/users.js +92 -0
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -25,7 +25,10 @@ var net = require("net");
|
|
|
25
25
|
var _isDev = (process.argv[1] && path.basename(process.argv[1]) === "clay-dev") || process.argv.includes("--dev");
|
|
26
26
|
if (_isDev) {
|
|
27
27
|
process.env.CLAY_DEV = "1";
|
|
28
|
-
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Preserve console output in dev/debug mode so logs remain readable
|
|
31
|
+
if (_isDev || process.argv.includes("--debug")) {
|
|
29
32
|
console.clear = function() {};
|
|
30
33
|
}
|
|
31
34
|
|
|
@@ -131,8 +134,8 @@ for (var i = 0; i < args.length; i++) {
|
|
|
131
134
|
console.log(" --remove <path> Remove a project directory");
|
|
132
135
|
console.log(" --list List all registered projects");
|
|
133
136
|
console.log(" --headless Start daemon and exit immediately (implies --yes)");
|
|
134
|
-
console.log(" --multi-user
|
|
135
|
-
console.log(" --os-users Enable OS-level user isolation (Linux
|
|
137
|
+
console.log(" --multi-user Start in multi-user mode (use with --yes for headless)");
|
|
138
|
+
console.log(" --os-users Enable OS-level user isolation (Linux, requires root + --multi-user)");
|
|
136
139
|
console.log(" --dangerously-skip-permissions");
|
|
137
140
|
console.log(" Bypass all permission prompts");
|
|
138
141
|
process.exit(0);
|
|
@@ -271,59 +274,8 @@ if (listMode) {
|
|
|
271
274
|
return;
|
|
272
275
|
}
|
|
273
276
|
|
|
274
|
-
//
|
|
275
|
-
|
|
276
|
-
var muResult = enableMultiUser();
|
|
277
|
-
if (muResult.alreadyEnabled && muResult.hasAdmin) {
|
|
278
|
-
console.log("");
|
|
279
|
-
console.log("Multi-user mode is already enabled and an admin account exists.");
|
|
280
|
-
console.log("No changes made.");
|
|
281
|
-
console.log("");
|
|
282
|
-
} else if (muResult.setupCode) {
|
|
283
|
-
console.log("");
|
|
284
|
-
console.log("\x1b[33m⚠ Experimental Feature\x1b[0m");
|
|
285
|
-
console.log("");
|
|
286
|
-
console.log(" Multi-user mode is experimental and may change in future releases.");
|
|
287
|
-
console.log(" Sharing access to AI-powered tools may be subject to your provider's");
|
|
288
|
-
console.log(" terms of service. Please review the applicable usage policies before");
|
|
289
|
-
console.log(" granting access to other users.");
|
|
290
|
-
console.log("");
|
|
291
|
-
console.log("\x1b[32mMulti-user mode enabled.\x1b[0m");
|
|
292
|
-
console.log("");
|
|
293
|
-
console.log("Setup code: \x1b[1m" + muResult.setupCode + "\x1b[0m");
|
|
294
|
-
console.log("");
|
|
295
|
-
console.log("Open Clay in your browser and enter this code to create the admin account.");
|
|
296
|
-
console.log("The code is single-use and will be cleared once the admin is set up.");
|
|
297
|
-
console.log("");
|
|
298
|
-
}
|
|
299
|
-
if (!osUsersMode) {
|
|
300
|
-
process.exit(0);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// --- Handle --os-users validation ---
|
|
305
|
-
if (osUsersMode) {
|
|
306
|
-
if (process.platform !== "linux") {
|
|
307
|
-
console.error("\x1b[31mError: --os-users requires Linux.\x1b[0m");
|
|
308
|
-
console.error("OS-level user isolation depends on setfacl, getent, and uid/gid process spawning.");
|
|
309
|
-
process.exit(1);
|
|
310
|
-
}
|
|
311
|
-
if (typeof process.getuid === "function" && process.getuid() !== 0) {
|
|
312
|
-
console.error("\x1b[31mError: --os-users requires running as root.\x1b[0m");
|
|
313
|
-
console.error("The daemon must run as root to spawn worker processes as different users.");
|
|
314
|
-
console.error("Use: sudo clay --multi-user --os-users");
|
|
315
|
-
process.exit(1);
|
|
316
|
-
}
|
|
317
|
-
if (!isMultiUser()) {
|
|
318
|
-
console.error("\x1b[31mError: --os-users requires --multi-user mode.\x1b[0m");
|
|
319
|
-
console.error("Enable multi-user mode first: clay --multi-user");
|
|
320
|
-
process.exit(1);
|
|
321
|
-
}
|
|
322
|
-
console.log("\x1b[32mOS-level user isolation enabled.\x1b[0m");
|
|
323
|
-
console.log("Worker processes will run as mapped Linux users.");
|
|
324
|
-
console.log("Linux accounts will be auto-provisioned for existing users on startup.");
|
|
325
|
-
console.log("");
|
|
326
|
-
}
|
|
277
|
+
// --multi-user / --os-users are now handled in the main entry flow (setup wizard or repeat run)
|
|
278
|
+
// Flags are parsed above and applied during forkDaemon()
|
|
327
279
|
|
|
328
280
|
var cwd = process.cwd();
|
|
329
281
|
|
|
@@ -494,12 +446,23 @@ async function restartDaemonFromConfig() {
|
|
|
494
446
|
projects: (lastConfig.projects || []).filter(function (p) {
|
|
495
447
|
return fs.existsSync(p.path);
|
|
496
448
|
}),
|
|
449
|
+
removedProjects: lastConfig.removedProjects || [],
|
|
497
450
|
};
|
|
498
451
|
|
|
499
452
|
ensureConfigDir();
|
|
500
453
|
saveConfig(newConfig);
|
|
501
454
|
|
|
502
455
|
var daemonScript = path.join(__dirname, "..", "lib", "daemon.js");
|
|
456
|
+
|
|
457
|
+
// Debug mode: run in foreground with logs to stdout
|
|
458
|
+
if (debugMode) {
|
|
459
|
+
process.env.CLAY_CONFIG = configPath();
|
|
460
|
+
newConfig.pid = process.pid;
|
|
461
|
+
saveConfig(newConfig);
|
|
462
|
+
require(daemonScript);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
503
466
|
var logFile = logPath();
|
|
504
467
|
var logFd = fs.openSync(logFile, "a");
|
|
505
468
|
|
|
@@ -1274,13 +1237,29 @@ function setup(callback) {
|
|
|
1274
1237
|
log("");
|
|
1275
1238
|
log(sym.pointer + " " + a.bold + "Clay" + a.reset + a.dim + " · Unofficial, open-source project" + a.reset);
|
|
1276
1239
|
log(sym.bar);
|
|
1277
|
-
log(sym.bar + " " + a.
|
|
1278
|
-
log(sym.bar
|
|
1279
|
-
log(sym.bar + " " + a.dim + "
|
|
1240
|
+
log(sym.bar + " " + a.yellow + sym.warn + " Disclaimer" + a.reset);
|
|
1241
|
+
log(sym.bar);
|
|
1242
|
+
log(sym.bar + " " + a.dim + "This is an independent project and is not affiliated with Anthropic." + a.reset);
|
|
1243
|
+
log(sym.bar + " " + a.dim + "Claude is a trademark of Anthropic." + a.reset);
|
|
1244
|
+
log(sym.bar);
|
|
1245
|
+
log(sym.bar + " " + a.dim + "Clay is provided \"as is\" without warranty of any kind. Users are" + a.reset);
|
|
1246
|
+
log(sym.bar + " " + a.dim + "responsible for complying with the terms of service of underlying AI" + a.reset);
|
|
1247
|
+
log(sym.bar + " " + a.dim + "providers (e.g., Anthropic, OpenAI) and all applicable terms of any" + a.reset);
|
|
1248
|
+
log(sym.bar + " " + a.dim + "third-party services." + a.reset);
|
|
1249
|
+
log(sym.bar);
|
|
1250
|
+
log(sym.bar + " " + a.dim + "Features such as multi-user mode are experimental and may involve" + a.reset);
|
|
1251
|
+
log(sym.bar + " " + a.dim + "sharing access to API-based services. Before enabling such features," + a.reset);
|
|
1252
|
+
log(sym.bar + " " + a.dim + "review your provider's usage policies regarding account sharing," + a.reset);
|
|
1253
|
+
log(sym.bar + " " + a.dim + "acceptable use, and any applicable rate limits or restrictions." + a.reset);
|
|
1254
|
+
log(sym.bar);
|
|
1255
|
+
log(sym.bar + " " + a.dim + "The authors assume no liability for misuse or violations arising" + a.reset);
|
|
1256
|
+
log(sym.bar + " " + a.dim + "from the use of this software." + a.reset);
|
|
1257
|
+
log(sym.bar);
|
|
1258
|
+
log(sym.bar + " Type " + a.bold + "agree" + a.reset + " to accept and continue.");
|
|
1280
1259
|
log(sym.bar);
|
|
1281
1260
|
|
|
1282
|
-
|
|
1283
|
-
if (!
|
|
1261
|
+
promptText("", "", function (val) {
|
|
1262
|
+
if (!val || val.trim().toLowerCase() !== "agree") {
|
|
1284
1263
|
log(sym.end + " " + a.dim + "Aborted." + a.reset);
|
|
1285
1264
|
log("");
|
|
1286
1265
|
process.exit(0);
|
|
@@ -1310,44 +1289,99 @@ function setup(callback) {
|
|
|
1310
1289
|
}
|
|
1311
1290
|
port = p;
|
|
1312
1291
|
log(sym.bar);
|
|
1292
|
+
askMode();
|
|
1293
|
+
});
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1313
1296
|
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
askPin();
|
|
1327
|
-
return;
|
|
1328
|
-
}
|
|
1329
|
-
afterPin(pin);
|
|
1330
|
-
});
|
|
1331
|
-
} else {
|
|
1332
|
-
afterPin(pin);
|
|
1333
|
-
}
|
|
1334
|
-
});
|
|
1335
|
-
}
|
|
1297
|
+
function askMode() {
|
|
1298
|
+
promptSelect("How will you use Clay?", [
|
|
1299
|
+
{ label: "Just me (single user)", value: "single" },
|
|
1300
|
+
{ label: "Multiple users", value: "multi" },
|
|
1301
|
+
], function (mode) {
|
|
1302
|
+
if (mode === "single") {
|
|
1303
|
+
finishSetup(mode, false);
|
|
1304
|
+
} else {
|
|
1305
|
+
askOsUsers(mode);
|
|
1306
|
+
}
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1336
1309
|
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1310
|
+
function askOsUsers(mode) {
|
|
1311
|
+
// Only offer OS user isolation on Linux
|
|
1312
|
+
if (process.platform !== "linux") {
|
|
1313
|
+
finishSetup(mode, false);
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
log(sym.bar);
|
|
1317
|
+
promptSelect("Enable OS-level user isolation?", [
|
|
1318
|
+
{ label: "Yes", value: "yes" },
|
|
1319
|
+
{ label: "No", value: "no" },
|
|
1320
|
+
], function (choice) {
|
|
1321
|
+
if (choice !== "yes") {
|
|
1322
|
+
finishSetup(mode, false);
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
log(sym.bar);
|
|
1326
|
+
log(sym.bar + " " + a.yellow + sym.warn + " OS-Level User Isolation" + a.reset);
|
|
1327
|
+
log(sym.bar);
|
|
1328
|
+
log(sym.bar + " " + a.dim + "This feature maps each Clay user to a Linux OS user account." + a.reset);
|
|
1329
|
+
log(sym.bar + " " + a.dim + "The daemon must run as root and will spawn processes (SDK workers," + a.reset);
|
|
1330
|
+
log(sym.bar + " " + a.dim + "terminals, file operations) as the mapped Linux user." + a.reset);
|
|
1331
|
+
log(sym.bar);
|
|
1332
|
+
log(sym.bar + " " + a.dim + "What this means:" + a.reset);
|
|
1333
|
+
log(sym.bar + " " + a.dim + "- Each mapped user uses their own ~/.claude/ credentials" + a.reset);
|
|
1334
|
+
log(sym.bar + " " + a.dim + "- Terminals and file access follow Linux permissions" + a.reset);
|
|
1335
|
+
log(sym.bar + " " + a.dim + "- Linux user accounts are created automatically (clay-username)" + a.reset);
|
|
1336
|
+
log(sym.bar);
|
|
1337
|
+
log(sym.bar + " " + a.dim + "Recommended: Run on a dedicated Clay server or cloud instance," + a.reset);
|
|
1338
|
+
log(sym.bar + " " + a.dim + "not on a personal computer or general-purpose server." + a.reset);
|
|
1339
|
+
log(sym.bar);
|
|
1340
|
+
promptSelect("Confirm", [
|
|
1341
|
+
{ label: "Enable OS-level user isolation", value: "confirm" },
|
|
1342
|
+
{ label: "Cancel", value: "cancel" },
|
|
1343
|
+
], function (confirmChoice) {
|
|
1344
|
+
if (confirmChoice !== "confirm") {
|
|
1345
|
+
finishSetup(mode, false);
|
|
1346
|
+
return;
|
|
1345
1347
|
}
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
+
var isRoot = typeof process.getuid === "function" && process.getuid() === 0;
|
|
1349
|
+
if (!isRoot) {
|
|
1350
|
+
// Save config so sudo clay can pick it up
|
|
1351
|
+
var partialConfig = {
|
|
1352
|
+
port: port,
|
|
1353
|
+
host: host,
|
|
1354
|
+
mode: "multi",
|
|
1355
|
+
osUsers: true,
|
|
1356
|
+
setupCompleted: true,
|
|
1357
|
+
dangerouslySkipPermissions: dangerouslySkipPermissions,
|
|
1358
|
+
};
|
|
1359
|
+
saveConfig(partialConfig);
|
|
1360
|
+
log(sym.bar);
|
|
1361
|
+
log(sym.warn + " " + a.yellow + "OS user isolation requires root." + a.reset);
|
|
1362
|
+
log(sym.bar + " Run:");
|
|
1363
|
+
log(sym.bar + " " + a.bold + "sudo npx clay-server" + a.reset);
|
|
1364
|
+
log(sym.end);
|
|
1365
|
+
log("");
|
|
1366
|
+
process.exit(0);
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
finishSetup(mode, true);
|
|
1348
1370
|
});
|
|
1349
1371
|
});
|
|
1350
1372
|
}
|
|
1373
|
+
|
|
1374
|
+
function finishSetup(mode, wantOsUsers) {
|
|
1375
|
+
if (process.platform === "darwin") {
|
|
1376
|
+
log(sym.bar);
|
|
1377
|
+
promptToggle("Keep awake", "Prevent system sleep while relay is running", false, function (keepAwake) {
|
|
1378
|
+
callback(mode, keepAwake, wantOsUsers);
|
|
1379
|
+
});
|
|
1380
|
+
} else {
|
|
1381
|
+
callback(mode, false, wantOsUsers);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1351
1385
|
askPort();
|
|
1352
1386
|
});
|
|
1353
1387
|
}
|
|
@@ -1355,7 +1389,7 @@ function setup(callback) {
|
|
|
1355
1389
|
// ==============================
|
|
1356
1390
|
// Fork the daemon process
|
|
1357
1391
|
// ==============================
|
|
1358
|
-
async function forkDaemon(
|
|
1392
|
+
async function forkDaemon(mode, keepAwake, extraProjects, addCwd, wantOsUsers) {
|
|
1359
1393
|
var ip = getLocalIP();
|
|
1360
1394
|
var hasTls = false;
|
|
1361
1395
|
|
|
@@ -1434,12 +1468,14 @@ async function forkDaemon(pin, keepAwake, extraProjects, addCwd) {
|
|
|
1434
1468
|
pid: null,
|
|
1435
1469
|
port: port,
|
|
1436
1470
|
host: host,
|
|
1437
|
-
pinHash:
|
|
1471
|
+
pinHash: mode === "multi" && cliPin ? generateAuthToken(cliPin) : null,
|
|
1438
1472
|
tls: hasTls,
|
|
1439
1473
|
debug: debugMode,
|
|
1440
1474
|
keepAwake: keepAwake,
|
|
1441
1475
|
dangerouslySkipPermissions: dangerouslySkipPermissions,
|
|
1442
|
-
osUsers: osUsersMode,
|
|
1476
|
+
osUsers: wantOsUsers || osUsersMode,
|
|
1477
|
+
mode: mode || "single",
|
|
1478
|
+
setupCompleted: true,
|
|
1443
1479
|
projects: allProjects,
|
|
1444
1480
|
};
|
|
1445
1481
|
|
|
@@ -1448,6 +1484,16 @@ async function forkDaemon(pin, keepAwake, extraProjects, addCwd) {
|
|
|
1448
1484
|
|
|
1449
1485
|
// Fork daemon
|
|
1450
1486
|
var daemonScript = path.join(__dirname, "..", "lib", "daemon.js");
|
|
1487
|
+
|
|
1488
|
+
// Debug mode: run in foreground with logs to stdout
|
|
1489
|
+
if (debugMode) {
|
|
1490
|
+
process.env.CLAY_CONFIG = configPath();
|
|
1491
|
+
config.pid = process.pid;
|
|
1492
|
+
saveConfig(config);
|
|
1493
|
+
require(daemonScript);
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1451
1497
|
var logFile = logPath();
|
|
1452
1498
|
var logFd = fs.openSync(logFile, "a");
|
|
1453
1499
|
|
|
@@ -1481,6 +1527,18 @@ async function forkDaemon(pin, keepAwake, extraProjects, addCwd) {
|
|
|
1481
1527
|
return;
|
|
1482
1528
|
}
|
|
1483
1529
|
|
|
1530
|
+
// Enable multi-user mode if requested
|
|
1531
|
+
if (config.mode === "multi") {
|
|
1532
|
+
var muResult = enableMultiUser();
|
|
1533
|
+
if (muResult.setupCode) {
|
|
1534
|
+
log("");
|
|
1535
|
+
log(sym.done + " " + a.green + "Multi-user mode enabled." + a.reset);
|
|
1536
|
+
log(sym.bar + " Setup code: " + a.bold + muResult.setupCode + a.reset);
|
|
1537
|
+
log(sym.bar + " Open Clay in your browser and enter this code to create the admin account.");
|
|
1538
|
+
log("");
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1484
1542
|
// Headless mode — print status and exit immediately
|
|
1485
1543
|
if (headlessMode) {
|
|
1486
1544
|
var protocol = config.tls ? "https" : "http";
|
|
@@ -1499,7 +1557,7 @@ async function forkDaemon(pin, keepAwake, extraProjects, addCwd) {
|
|
|
1499
1557
|
// ==============================
|
|
1500
1558
|
// Dev mode — foreground daemon with file watching
|
|
1501
1559
|
// ==============================
|
|
1502
|
-
async function devMode(
|
|
1560
|
+
async function devMode(mode, keepAwake, existingPinHash) {
|
|
1503
1561
|
var ip = getLocalIP();
|
|
1504
1562
|
var hasTls = false;
|
|
1505
1563
|
|
|
@@ -1565,11 +1623,13 @@ async function devMode(pin, keepAwake, existingPinHash) {
|
|
|
1565
1623
|
pid: null,
|
|
1566
1624
|
port: port,
|
|
1567
1625
|
host: host,
|
|
1568
|
-
pinHash: existingPinHash ||
|
|
1626
|
+
pinHash: existingPinHash || null,
|
|
1569
1627
|
tls: hasTls,
|
|
1570
1628
|
debug: true,
|
|
1571
1629
|
keepAwake: keepAwake || false,
|
|
1572
1630
|
dangerouslySkipPermissions: dangerouslySkipPermissions,
|
|
1631
|
+
mode: mode || "single",
|
|
1632
|
+
setupCompleted: true,
|
|
1573
1633
|
projects: allProjects,
|
|
1574
1634
|
};
|
|
1575
1635
|
|
|
@@ -2337,6 +2397,11 @@ function showSettingsMenu(config, ip) {
|
|
|
2337
2397
|
? a.green + "Enabled" + a.reset
|
|
2338
2398
|
: a.dim + "Off" + a.reset;
|
|
2339
2399
|
|
|
2400
|
+
var modeLabel = config.mode === "multi" ? "Multi-user" : "Single user";
|
|
2401
|
+
var modeStatus = config.mode === "multi"
|
|
2402
|
+
? a.clay + modeLabel + a.reset
|
|
2403
|
+
: a.dim + modeLabel + a.reset;
|
|
2404
|
+
log(sym.bar + " Mode " + modeStatus);
|
|
2340
2405
|
log(sym.bar + " PIN " + pinStatus);
|
|
2341
2406
|
log(sym.bar + " Multi-user " + muStatus);
|
|
2342
2407
|
var osUsersStatus = isOsUsers
|
|
@@ -2375,6 +2440,7 @@ function showSettingsMenu(config, ip) {
|
|
|
2375
2440
|
items.push({ label: isAwake ? "Disable keep awake" : "Enable keep awake", value: "awake" });
|
|
2376
2441
|
}
|
|
2377
2442
|
items.push({ label: "View logs", value: "logs" });
|
|
2443
|
+
items.push({ label: "Re-run setup wizard", value: "rerun_setup" });
|
|
2378
2444
|
items.push({ label: "Back", value: "back" });
|
|
2379
2445
|
|
|
2380
2446
|
promptSelect("Select", items, function (choice) {
|
|
@@ -2572,6 +2638,70 @@ function showSettingsMenu(config, ip) {
|
|
|
2572
2638
|
});
|
|
2573
2639
|
break;
|
|
2574
2640
|
|
|
2641
|
+
case "rerun_setup":
|
|
2642
|
+
log(sym.bar);
|
|
2643
|
+
log(sym.bar + " " + a.yellow + sym.warn + " Re-run setup wizard?" + a.reset);
|
|
2644
|
+
log(sym.bar);
|
|
2645
|
+
log(sym.bar + " " + a.dim + "This will shut down the running daemon, reset your setup" + a.reset);
|
|
2646
|
+
log(sym.bar + " " + a.dim + "preferences (mode, port), and walk you through the wizard again." + a.reset);
|
|
2647
|
+
log(sym.bar + " " + a.dim + "Your projects and user accounts will be preserved." + a.reset);
|
|
2648
|
+
log(sym.bar);
|
|
2649
|
+
promptSelect("Confirm", [
|
|
2650
|
+
{ label: "Re-run setup wizard", value: "confirm" },
|
|
2651
|
+
{ label: "Cancel", value: "cancel" },
|
|
2652
|
+
], function (confirmChoice) {
|
|
2653
|
+
if (confirmChoice === "confirm") {
|
|
2654
|
+
// Clear setupCompleted so setup() runs fresh
|
|
2655
|
+
var cfg = loadConfig() || {};
|
|
2656
|
+
delete cfg.setupCompleted;
|
|
2657
|
+
delete cfg.mode;
|
|
2658
|
+
cfg.pid = null;
|
|
2659
|
+
saveConfig(cfg);
|
|
2660
|
+
// Shut down the daemon
|
|
2661
|
+
sendIPCCommand(socketPath(), { cmd: "shutdown" }).then(function () {
|
|
2662
|
+
clearStaleConfig();
|
|
2663
|
+
// Run the setup wizard, then fork a new daemon
|
|
2664
|
+
setup(function (mode, keepAwake, wantOsUsers) {
|
|
2665
|
+
var rc = loadClayrc();
|
|
2666
|
+
var restorable = (rc.recentProjects || []).filter(function (p) {
|
|
2667
|
+
return p.path !== cwd && fs.existsSync(p.path);
|
|
2668
|
+
});
|
|
2669
|
+
if (restorable.length > 0) {
|
|
2670
|
+
promptRestoreProjects(restorable, function (selected) {
|
|
2671
|
+
forkDaemon(mode, keepAwake, selected, false, wantOsUsers);
|
|
2672
|
+
});
|
|
2673
|
+
} else {
|
|
2674
|
+
log(sym.bar);
|
|
2675
|
+
log(sym.end + " " + a.dim + "Starting relay..." + a.reset);
|
|
2676
|
+
log("");
|
|
2677
|
+
forkDaemon(mode, keepAwake, undefined, true, wantOsUsers);
|
|
2678
|
+
}
|
|
2679
|
+
});
|
|
2680
|
+
}).catch(function () {
|
|
2681
|
+
clearStaleConfig();
|
|
2682
|
+
setup(function (mode, keepAwake, wantOsUsers) {
|
|
2683
|
+
var rc = loadClayrc();
|
|
2684
|
+
var restorable = (rc.recentProjects || []).filter(function (p) {
|
|
2685
|
+
return p.path !== cwd && fs.existsSync(p.path);
|
|
2686
|
+
});
|
|
2687
|
+
if (restorable.length > 0) {
|
|
2688
|
+
promptRestoreProjects(restorable, function (selected) {
|
|
2689
|
+
forkDaemon(mode, keepAwake, selected, false, wantOsUsers);
|
|
2690
|
+
});
|
|
2691
|
+
} else {
|
|
2692
|
+
log(sym.bar);
|
|
2693
|
+
log(sym.end + " " + a.dim + "Starting relay..." + a.reset);
|
|
2694
|
+
log("");
|
|
2695
|
+
forkDaemon(mode, keepAwake, undefined, true, wantOsUsers);
|
|
2696
|
+
}
|
|
2697
|
+
});
|
|
2698
|
+
});
|
|
2699
|
+
} else {
|
|
2700
|
+
showSettingsMenu(config, ip);
|
|
2701
|
+
}
|
|
2702
|
+
});
|
|
2703
|
+
break;
|
|
2704
|
+
|
|
2575
2705
|
case "logs":
|
|
2576
2706
|
console.clear();
|
|
2577
2707
|
log(a.bold + "Daemon logs" + a.reset + " " + a.dim + "(" + logPath() + ")" + a.reset);
|
|
@@ -2633,14 +2763,14 @@ var currentVersion = require("../package.json").version;
|
|
|
2633
2763
|
if (devConfig.pid) clearStaleConfig();
|
|
2634
2764
|
devConfig = null;
|
|
2635
2765
|
}
|
|
2636
|
-
// No config — go through setup (disclaimer, port,
|
|
2766
|
+
// No config — go through setup (disclaimer, port, mode, etc.)
|
|
2637
2767
|
if (!devConfig) {
|
|
2638
|
-
setup(function (
|
|
2639
|
-
devMode(
|
|
2768
|
+
setup(function (mode, keepAwake, wantOsUsers) {
|
|
2769
|
+
devMode(mode, keepAwake, null);
|
|
2640
2770
|
});
|
|
2641
2771
|
} else {
|
|
2642
|
-
// Reuse existing
|
|
2643
|
-
await devMode(
|
|
2772
|
+
// Reuse existing config (repeat run)
|
|
2773
|
+
await devMode(devConfig.mode || "single", devConfig.keepAwake || false, devConfig.pinHash || null);
|
|
2644
2774
|
}
|
|
2645
2775
|
return;
|
|
2646
2776
|
}
|
|
@@ -2717,19 +2847,55 @@ var currentVersion = require("../package.json").version;
|
|
|
2717
2847
|
showMainMenu(config || { pid: status.pid, port: status.port, tls: status.tls }, ip);
|
|
2718
2848
|
}
|
|
2719
2849
|
} else {
|
|
2720
|
-
// No daemon running —
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2850
|
+
// No daemon running — check for saved config (repeat run)
|
|
2851
|
+
var savedConfig = loadConfig();
|
|
2852
|
+
var isRepeatRun = savedConfig && savedConfig.setupCompleted;
|
|
2853
|
+
|
|
2854
|
+
// --multi-user / --os-users CLI flags set config directly for headless/scripted usage
|
|
2855
|
+
if (multiUserMode) {
|
|
2856
|
+
if (!savedConfig) savedConfig = {};
|
|
2857
|
+
savedConfig.mode = "multi";
|
|
2858
|
+
savedConfig.setupCompleted = true;
|
|
2859
|
+
}
|
|
2860
|
+
if (osUsersMode) {
|
|
2861
|
+
if (!savedConfig) savedConfig = {};
|
|
2862
|
+
savedConfig.osUsers = true;
|
|
2863
|
+
savedConfig.mode = "multi";
|
|
2864
|
+
savedConfig.setupCompleted = true;
|
|
2865
|
+
}
|
|
2866
|
+
isRepeatRun = savedConfig && savedConfig.setupCompleted;
|
|
2867
|
+
|
|
2868
|
+
if (isRepeatRun || autoYes) {
|
|
2869
|
+
// Repeat run or --yes: skip wizard, reuse saved config
|
|
2870
|
+
var savedMode = (savedConfig && savedConfig.mode) || "single";
|
|
2871
|
+
var savedKeepAwake = (savedConfig && savedConfig.keepAwake) || false;
|
|
2872
|
+
var savedOsUsers = (savedConfig && savedConfig.osUsers) || false;
|
|
2873
|
+
|
|
2874
|
+
// os-users requires root
|
|
2875
|
+
if (savedOsUsers && typeof process.getuid === "function" && process.getuid() !== 0) {
|
|
2876
|
+
console.error(a.red + "OS user isolation requires root." + a.reset);
|
|
2877
|
+
console.error("Run: " + a.bold + "sudo npx clay-server" + a.reset);
|
|
2878
|
+
process.exit(1);
|
|
2879
|
+
return;
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
if (savedConfig && savedConfig.port) port = savedConfig.port;
|
|
2883
|
+
if (savedConfig && savedConfig.host) host = savedConfig.host;
|
|
2884
|
+
if (savedConfig && savedConfig.dangerouslySkipPermissions) dangerouslySkipPermissions = true;
|
|
2885
|
+
|
|
2886
|
+
if (autoYes) {
|
|
2887
|
+
console.log(" " + sym.done + " Auto-accepted disclaimer");
|
|
2888
|
+
console.log(" " + sym.done + " Mode: " + savedMode);
|
|
2889
|
+
if (dangerouslySkipPermissions) {
|
|
2890
|
+
console.log(" " + sym.warn + " " + a.yellow + "Skip permissions mode enabled" + a.reset);
|
|
2891
|
+
}
|
|
2727
2892
|
}
|
|
2893
|
+
|
|
2728
2894
|
var autoRc = loadClayrc();
|
|
2729
2895
|
var autoRestorable = (autoRc.recentProjects || []).filter(function (p) {
|
|
2730
2896
|
return p.path !== cwd && fs.existsSync(p.path);
|
|
2731
2897
|
});
|
|
2732
|
-
if (autoRestorable.length > 0) {
|
|
2898
|
+
if (autoRestorable.length > 0 && autoYes) {
|
|
2733
2899
|
console.log(" " + sym.done + " Restoring " + autoRestorable.length + " previous project(s)");
|
|
2734
2900
|
}
|
|
2735
2901
|
// Add cwd if it has history in .clayrc, or if there are no other projects to restore
|
|
@@ -2737,9 +2903,10 @@ var currentVersion = require("../package.json").version;
|
|
|
2737
2903
|
return p.path === cwd;
|
|
2738
2904
|
});
|
|
2739
2905
|
var addCwd = cwdInRc || autoRestorable.length === 0;
|
|
2740
|
-
await forkDaemon(
|
|
2906
|
+
await forkDaemon(savedMode, savedKeepAwake, autoRestorable.length > 0 ? autoRestorable : undefined, addCwd, savedOsUsers);
|
|
2741
2907
|
} else {
|
|
2742
|
-
|
|
2908
|
+
// First run: interactive wizard
|
|
2909
|
+
setup(function (mode, keepAwake, wantOsUsers) {
|
|
2743
2910
|
// Check ~/.clayrc for previous projects to restore
|
|
2744
2911
|
var rc = loadClayrc();
|
|
2745
2912
|
var restorable = (rc.recentProjects || []).filter(function (p) {
|
|
@@ -2748,13 +2915,13 @@ var currentVersion = require("../package.json").version;
|
|
|
2748
2915
|
|
|
2749
2916
|
if (restorable.length > 0) {
|
|
2750
2917
|
promptRestoreProjects(restorable, function (selected) {
|
|
2751
|
-
forkDaemon(
|
|
2918
|
+
forkDaemon(mode, keepAwake, selected, false, wantOsUsers);
|
|
2752
2919
|
});
|
|
2753
2920
|
} else {
|
|
2754
2921
|
log(sym.bar);
|
|
2755
2922
|
log(sym.end + " " + a.dim + "Starting relay..." + a.reset);
|
|
2756
2923
|
log("");
|
|
2757
|
-
forkDaemon(
|
|
2924
|
+
forkDaemon(mode, keepAwake, undefined, true, wantOsUsers);
|
|
2758
2925
|
}
|
|
2759
2926
|
});
|
|
2760
2927
|
}
|