clay-server 2.5.1 → 2.7.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/claude-relay.js +6 -0
- package/bin/cli.js +77 -11
- package/lib/cli-sessions.js +2 -7
- package/lib/config.js +86 -7
- package/lib/daemon.js +279 -6
- package/lib/ipc.js +12 -0
- package/lib/notes.js +4 -3
- package/lib/project.js +1174 -28
- package/lib/public/app.js +879 -31
- package/lib/public/css/diff.css +15 -4
- package/lib/public/css/filebrowser.css +363 -3
- package/lib/public/css/icon-strip.css +317 -1
- package/lib/public/css/input.css +127 -50
- package/lib/public/css/loop.css +780 -0
- package/lib/public/css/messages.css +1 -1
- package/lib/public/css/mobile-nav.css +6 -2
- package/lib/public/css/rewind.css +23 -0
- package/lib/public/css/server-settings.css +67 -20
- package/lib/public/css/sidebar.css +10 -4
- package/lib/public/css/skills.css +789 -0
- package/lib/public/css/sticky-notes.css +486 -0
- package/lib/public/css/title-bar.css +157 -7
- package/lib/public/index.html +366 -55
- package/lib/public/modules/diff.js +3 -3
- package/lib/public/modules/filebrowser.js +169 -45
- package/lib/public/modules/input.js +123 -56
- package/lib/public/modules/project-settings.js +906 -0
- package/lib/public/modules/qrcode.js +23 -26
- package/lib/public/modules/server-settings.js +449 -53
- package/lib/public/modules/sidebar.js +732 -1
- package/lib/public/modules/skills.js +794 -0
- package/lib/public/modules/sticky-notes.js +617 -52
- package/lib/public/modules/terminal.js +7 -0
- package/lib/public/modules/theme.js +9 -19
- package/lib/public/modules/tools.js +16 -2
- package/lib/public/style.css +2 -0
- package/lib/sdk-bridge.js +46 -7
- package/lib/server.js +305 -1
- package/lib/sessions.js +11 -5
- package/lib/utils.js +18 -0
- package/package.json +3 -2
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,11 +21,8 @@ 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 (no separate storage — dev and prod share ~/.clay)
|
|
11
25
|
var _isDev = (process.argv[1] && path.basename(process.argv[1]) === "clay-dev") || process.argv.includes("--dev");
|
|
12
|
-
if (_isDev) {
|
|
13
|
-
process.env.CLAY_HOME = path.join(os.homedir(), ".clay-dev");
|
|
14
|
-
}
|
|
15
26
|
|
|
16
27
|
var { loadConfig, saveConfig, configPath, socketPath, logPath, ensureConfigDir, isDaemonAlive, isDaemonAliveAsync, generateSlug, clearStaleConfig, loadClayrc, saveClayrc, readCrashInfo } = require("../lib/config");
|
|
17
28
|
var { sendIPCCommand } = require("../lib/ipc");
|
|
@@ -36,12 +47,14 @@ var debugMode = false;
|
|
|
36
47
|
var autoYes = false;
|
|
37
48
|
var cliPin = null;
|
|
38
49
|
var shutdownMode = false;
|
|
50
|
+
var restartMode = false;
|
|
39
51
|
var addPath = null;
|
|
40
52
|
var removePath = null;
|
|
41
53
|
var listMode = false;
|
|
42
54
|
var dangerouslySkipPermissions = false;
|
|
43
55
|
var headlessMode = false;
|
|
44
56
|
var watchMode = false;
|
|
57
|
+
var host = null;
|
|
45
58
|
|
|
46
59
|
for (var i = 0; i < args.length; i++) {
|
|
47
60
|
if (args[i] === "-p" || args[i] === "--port") {
|
|
@@ -51,6 +64,9 @@ for (var i = 0; i < args.length; i++) {
|
|
|
51
64
|
process.exit(1);
|
|
52
65
|
}
|
|
53
66
|
i++;
|
|
67
|
+
} else if (args[i] === "--host" || args[i] === "--bind") {
|
|
68
|
+
host = args[i + 1] || null;
|
|
69
|
+
i++;
|
|
54
70
|
} else if (args[i] === "--no-https") {
|
|
55
71
|
useHttps = false;
|
|
56
72
|
} else if (args[i] === "--no-update" || args[i] === "--skip-update") {
|
|
@@ -68,6 +84,8 @@ for (var i = 0; i < args.length; i++) {
|
|
|
68
84
|
i++;
|
|
69
85
|
} else if (args[i] === "--shutdown") {
|
|
70
86
|
shutdownMode = true;
|
|
87
|
+
} else if (args[i] === "--restart") {
|
|
88
|
+
restartMode = true;
|
|
71
89
|
} else if (args[i] === "--add") {
|
|
72
90
|
addPath = args[i + 1] || ".";
|
|
73
91
|
i++;
|
|
@@ -82,19 +100,21 @@ for (var i = 0; i < args.length; i++) {
|
|
|
82
100
|
} else if (args[i] === "--dangerously-skip-permissions") {
|
|
83
101
|
dangerouslySkipPermissions = true;
|
|
84
102
|
} else if (args[i] === "-h" || args[i] === "--help") {
|
|
85
|
-
console.log("Usage: clay-server [-p|--port <port>] [--no-https] [--no-update] [--debug] [-y|--yes] [--pin <pin>] [--shutdown]");
|
|
103
|
+
console.log("Usage: clay-server [-p|--port <port>] [--host <address>] [--no-https] [--no-update] [--debug] [-y|--yes] [--pin <pin>] [--shutdown] [--restart]");
|
|
86
104
|
console.log(" clay-server --add <path> Add a project to the running daemon");
|
|
87
105
|
console.log(" clay-server --remove <path> Remove a project from the running daemon");
|
|
88
106
|
console.log(" clay-server --list List registered projects");
|
|
89
107
|
console.log("");
|
|
90
108
|
console.log("Options:");
|
|
91
109
|
console.log(" -p, --port <port> Port to listen on (default: 2633)");
|
|
110
|
+
console.log(" --host <address> Address to bind to (default: 0.0.0.0)");
|
|
92
111
|
console.log(" --no-https Disable HTTPS (enabled by default via mkcert)");
|
|
93
112
|
console.log(" --no-update Skip auto-update check on startup");
|
|
94
113
|
console.log(" --debug Enable debug panel in the web UI");
|
|
95
114
|
console.log(" -y, --yes Skip interactive prompts (accept defaults)");
|
|
96
115
|
console.log(" --pin <pin> Set 6-digit PIN (use with --yes)");
|
|
97
116
|
console.log(" --shutdown Shut down the running relay daemon");
|
|
117
|
+
console.log(" --restart Restart the running relay daemon");
|
|
98
118
|
console.log(" --add <path> Add a project directory (use '.' for current)");
|
|
99
119
|
console.log(" --remove <path> Remove a project directory");
|
|
100
120
|
console.log(" --list List all registered projects");
|
|
@@ -131,6 +151,25 @@ if (shutdownMode) {
|
|
|
131
151
|
return;
|
|
132
152
|
}
|
|
133
153
|
|
|
154
|
+
// --- Handle --restart before anything else ---
|
|
155
|
+
if (restartMode) {
|
|
156
|
+
var restartConfig = loadConfig();
|
|
157
|
+
isDaemonAliveAsync(restartConfig).then(function (alive) {
|
|
158
|
+
if (!alive) {
|
|
159
|
+
console.error("No running daemon found.");
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
sendIPCCommand(socketPath(), { cmd: "restart" }).then(function () {
|
|
163
|
+
console.log("Server restarted.");
|
|
164
|
+
process.exit(0);
|
|
165
|
+
}).catch(function (err) {
|
|
166
|
+
console.error("Restart failed:", err.message);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
134
173
|
// --- Handle --add before anything else ---
|
|
135
174
|
if (addPath !== null) {
|
|
136
175
|
var absAdd = path.resolve(addPath);
|
|
@@ -1250,7 +1289,18 @@ async function forkDaemon(pin, keepAwake, extraProjects, addCwd) {
|
|
|
1250
1289
|
// Only include cwd if explicitly requested
|
|
1251
1290
|
if (addCwd) {
|
|
1252
1291
|
var slug = generateSlug(cwd, []);
|
|
1253
|
-
|
|
1292
|
+
var cwdEntry = { path: cwd, slug: slug, addedAt: Date.now() };
|
|
1293
|
+
// Restore title/icon from .clayrc if available
|
|
1294
|
+
var cwdRc = loadClayrc();
|
|
1295
|
+
var cwdRecent = cwdRc.recentProjects || [];
|
|
1296
|
+
for (var cr = 0; cr < cwdRecent.length; cr++) {
|
|
1297
|
+
if (cwdRecent[cr].path === cwd) {
|
|
1298
|
+
if (cwdRecent[cr].title) cwdEntry.title = cwdRecent[cr].title;
|
|
1299
|
+
if (cwdRecent[cr].icon) cwdEntry.icon = cwdRecent[cr].icon;
|
|
1300
|
+
break;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
allProjects.push(cwdEntry);
|
|
1254
1304
|
usedSlugs.push(slug);
|
|
1255
1305
|
}
|
|
1256
1306
|
|
|
@@ -1262,13 +1312,14 @@ async function forkDaemon(pin, keepAwake, extraProjects, addCwd) {
|
|
|
1262
1312
|
if (!fs.existsSync(rp.path)) continue; // skip missing directories
|
|
1263
1313
|
var rpSlug = generateSlug(rp.path, usedSlugs);
|
|
1264
1314
|
usedSlugs.push(rpSlug);
|
|
1265
|
-
allProjects.push({ path: rp.path, slug: rpSlug, title: rp.title || undefined, addedAt: rp.addedAt || Date.now() });
|
|
1315
|
+
allProjects.push({ path: rp.path, slug: rpSlug, title: rp.title || undefined, icon: rp.icon || undefined, addedAt: rp.addedAt || Date.now() });
|
|
1266
1316
|
}
|
|
1267
1317
|
}
|
|
1268
1318
|
|
|
1269
1319
|
var config = {
|
|
1270
1320
|
pid: null,
|
|
1271
1321
|
port: port,
|
|
1322
|
+
host: host,
|
|
1272
1323
|
pinHash: pin ? generateAuthToken(pin) : null,
|
|
1273
1324
|
tls: hasTls,
|
|
1274
1325
|
debug: debugMode,
|
|
@@ -1350,24 +1401,35 @@ async function devMode(pin, keepAwake, existingPinHash) {
|
|
|
1350
1401
|
}
|
|
1351
1402
|
|
|
1352
1403
|
var slug = generateSlug(cwd, []);
|
|
1353
|
-
var
|
|
1404
|
+
var cwdDevEntry = { path: cwd, slug: slug, addedAt: Date.now() };
|
|
1354
1405
|
|
|
1355
1406
|
// Restore previous projects
|
|
1356
1407
|
var rc = loadClayrc();
|
|
1357
1408
|
var restorable = (rc.recentProjects || []).filter(function (p) {
|
|
1358
1409
|
return p.path !== cwd && fs.existsSync(p.path);
|
|
1359
1410
|
});
|
|
1411
|
+
// Restore title/icon for cwd from .clayrc
|
|
1412
|
+
var rcAll = rc.recentProjects || [];
|
|
1413
|
+
for (var ci = 0; ci < rcAll.length; ci++) {
|
|
1414
|
+
if (rcAll[ci].path === cwd) {
|
|
1415
|
+
if (rcAll[ci].title) cwdDevEntry.title = rcAll[ci].title;
|
|
1416
|
+
if (rcAll[ci].icon) cwdDevEntry.icon = rcAll[ci].icon;
|
|
1417
|
+
break;
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
var allProjects = [cwdDevEntry];
|
|
1360
1421
|
var usedSlugs = [slug];
|
|
1361
1422
|
for (var ri = 0; ri < restorable.length; ri++) {
|
|
1362
1423
|
var rp = restorable[ri];
|
|
1363
1424
|
var rpSlug = generateSlug(rp.path, usedSlugs);
|
|
1364
1425
|
usedSlugs.push(rpSlug);
|
|
1365
|
-
allProjects.push({ path: rp.path, slug: rpSlug, title: rp.title || undefined, addedAt: rp.addedAt || Date.now() });
|
|
1426
|
+
allProjects.push({ path: rp.path, slug: rpSlug, title: rp.title || undefined, icon: rp.icon || undefined, addedAt: rp.addedAt || Date.now() });
|
|
1366
1427
|
}
|
|
1367
1428
|
|
|
1368
1429
|
var config = {
|
|
1369
1430
|
pid: null,
|
|
1370
1431
|
port: port,
|
|
1432
|
+
host: host,
|
|
1371
1433
|
pinHash: existingPinHash || (pin ? generateAuthToken(pin) : null),
|
|
1372
1434
|
tls: hasTls,
|
|
1373
1435
|
debug: true,
|
|
@@ -2352,8 +2414,12 @@ var currentVersion = require("../package.json").version;
|
|
|
2352
2414
|
if (autoRestorable.length > 0) {
|
|
2353
2415
|
console.log(" " + sym.done + " Restoring " + autoRestorable.length + " previous project(s)");
|
|
2354
2416
|
}
|
|
2355
|
-
|
|
2356
|
-
|
|
2417
|
+
// Add cwd if it has history in .clayrc, or if there are no other projects to restore
|
|
2418
|
+
var cwdInRc = (autoRc.recentProjects || []).some(function (p) {
|
|
2419
|
+
return p.path === cwd;
|
|
2420
|
+
});
|
|
2421
|
+
var addCwd = cwdInRc || autoRestorable.length === 0;
|
|
2422
|
+
await forkDaemon(pin, false, autoRestorable.length > 0 ? autoRestorable : undefined, addCwd);
|
|
2357
2423
|
} else {
|
|
2358
2424
|
setup(function (pin, keepAwake) {
|
|
2359
2425
|
if (dangerouslySkipPermissions && !pin) {
|
package/lib/cli-sessions.js
CHANGED
|
@@ -2,14 +2,9 @@ var fs = require("fs");
|
|
|
2
2
|
var path = require("path");
|
|
3
3
|
var os = require("os");
|
|
4
4
|
var readline = require("readline");
|
|
5
|
+
var utils = require("./utils");
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
* Compute the encoded project directory name used by the Claude CLI.
|
|
8
|
-
* Replaces all "/" with "-", e.g. "/Users/foo/project" -> "-Users-foo-project"
|
|
9
|
-
*/
|
|
10
|
-
function encodeCwd(cwd) {
|
|
11
|
-
return cwd.replace(/\//g, "-");
|
|
12
|
-
}
|
|
7
|
+
var encodeCwd = utils.encodeCwd;
|
|
13
8
|
|
|
14
9
|
/**
|
|
15
10
|
* Parse the first ~20 lines of a CLI session JSONL file to extract metadata.
|
package/lib/config.js
CHANGED
|
@@ -18,6 +18,54 @@ if (!fs.existsSync(CLAY_HOME) && fs.existsSync(LEGACY_HOME)) {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// Auto-migrate dev sessions: merge ~/.clay-dev/sessions/ into ~/.clay/sessions/
|
|
22
|
+
var CLAY_DEV_HOME = path.join(os.homedir(), ".clay-dev");
|
|
23
|
+
var devSessionsRoot = path.join(CLAY_DEV_HOME, "sessions");
|
|
24
|
+
if (fs.existsSync(devSessionsRoot)) {
|
|
25
|
+
try {
|
|
26
|
+
var prodSessionsRoot = path.join(CLAY_HOME, "sessions");
|
|
27
|
+
fs.mkdirSync(prodSessionsRoot, { recursive: true });
|
|
28
|
+
var projectDirs = fs.readdirSync(devSessionsRoot);
|
|
29
|
+
var totalMigrated = 0;
|
|
30
|
+
for (var di = 0; di < projectDirs.length; di++) {
|
|
31
|
+
var srcDir = path.join(devSessionsRoot, projectDirs[di]);
|
|
32
|
+
if (!fs.statSync(srcDir).isDirectory()) continue;
|
|
33
|
+
var destDir = path.join(prodSessionsRoot, projectDirs[di]);
|
|
34
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
35
|
+
var files = fs.readdirSync(srcDir);
|
|
36
|
+
for (var fi = 0; fi < files.length; fi++) {
|
|
37
|
+
if (!files[fi].endsWith(".jsonl")) continue;
|
|
38
|
+
var srcFile = path.join(srcDir, files[fi]);
|
|
39
|
+
var destFile = path.join(destDir, files[fi]);
|
|
40
|
+
if (fs.existsSync(destFile)) continue;
|
|
41
|
+
try {
|
|
42
|
+
fs.renameSync(srcFile, destFile);
|
|
43
|
+
totalMigrated++;
|
|
44
|
+
} catch (e2) {
|
|
45
|
+
try {
|
|
46
|
+
fs.copyFileSync(srcFile, destFile);
|
|
47
|
+
fs.unlinkSync(srcFile);
|
|
48
|
+
totalMigrated++;
|
|
49
|
+
} catch (e3) {}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Clean up empty project dir
|
|
53
|
+
try {
|
|
54
|
+
if (fs.readdirSync(srcDir).length === 0) fs.rmdirSync(srcDir);
|
|
55
|
+
} catch (e4) {}
|
|
56
|
+
}
|
|
57
|
+
if (totalMigrated > 0) {
|
|
58
|
+
console.log("[config] Migrated " + totalMigrated + " dev session(s) from " + devSessionsRoot + " → " + prodSessionsRoot);
|
|
59
|
+
}
|
|
60
|
+
// Clean up empty dev sessions root
|
|
61
|
+
try {
|
|
62
|
+
if (fs.readdirSync(devSessionsRoot).length === 0) fs.rmdirSync(devSessionsRoot);
|
|
63
|
+
} catch (e5) {}
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.error("[config] Dev session migration failed:", e.message);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
21
69
|
var CONFIG_DIR = CLAY_HOME;
|
|
22
70
|
var CLAYRC_PATH = path.join(os.homedir(), ".clayrc");
|
|
23
71
|
var CRASH_INFO_PATH = path.join(CONFIG_DIR, "crash.json");
|
|
@@ -28,7 +76,7 @@ function configPath() {
|
|
|
28
76
|
|
|
29
77
|
function socketPath() {
|
|
30
78
|
if (process.platform === "win32") {
|
|
31
|
-
var pipeName =
|
|
79
|
+
var pipeName = "clay-daemon";
|
|
32
80
|
return "\\\\.\\pipe\\" + pipeName;
|
|
33
81
|
}
|
|
34
82
|
return path.join(CONFIG_DIR, "daemon.sock");
|
|
@@ -69,7 +117,10 @@ function isPidAlive(pid) {
|
|
|
69
117
|
|
|
70
118
|
function isDaemonAlive(config) {
|
|
71
119
|
if (!config || !config.pid) return false;
|
|
72
|
-
if (!isPidAlive(config.pid))
|
|
120
|
+
if (!isPidAlive(config.pid)) {
|
|
121
|
+
clearStaleConfig();
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
73
124
|
// Named pipes on Windows can't be stat'd, just check PID
|
|
74
125
|
if (process.platform === "win32") return true;
|
|
75
126
|
try {
|
|
@@ -83,7 +134,10 @@ function isDaemonAlive(config) {
|
|
|
83
134
|
function isDaemonAliveAsync(config) {
|
|
84
135
|
return new Promise(function (resolve) {
|
|
85
136
|
if (!config || !config.pid) return resolve(false);
|
|
86
|
-
if (!isPidAlive(config.pid))
|
|
137
|
+
if (!isPidAlive(config.pid)) {
|
|
138
|
+
clearStaleConfig();
|
|
139
|
+
return resolve(false);
|
|
140
|
+
}
|
|
87
141
|
|
|
88
142
|
var sock = socketPath();
|
|
89
143
|
var client = net.connect(sock);
|
|
@@ -191,28 +245,52 @@ function syncClayrc(projects) {
|
|
|
191
245
|
byPath[p.path].slug = p.slug;
|
|
192
246
|
byPath[p.path].lastUsed = Date.now();
|
|
193
247
|
if (p.title) byPath[p.path].title = p.title;
|
|
194
|
-
else delete byPath[p.path].title;
|
|
248
|
+
else if ("title" in p) delete byPath[p.path].title;
|
|
249
|
+
if (p.icon) byPath[p.path].icon = p.icon;
|
|
250
|
+
else if ("icon" in p) delete byPath[p.path].icon;
|
|
195
251
|
} else {
|
|
196
252
|
// New entry
|
|
197
253
|
byPath[p.path] = {
|
|
198
254
|
path: p.path,
|
|
199
255
|
slug: p.slug,
|
|
200
256
|
title: p.title || undefined,
|
|
257
|
+
icon: p.icon || undefined,
|
|
201
258
|
addedAt: p.addedAt || Date.now(),
|
|
202
259
|
lastUsed: Date.now(),
|
|
203
260
|
};
|
|
204
261
|
}
|
|
205
262
|
}
|
|
206
263
|
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
264
|
+
// Active projects first, preserving config order (user's drag-and-drop order),
|
|
265
|
+
// then inactive recent projects sorted by lastUsed descending
|
|
266
|
+
var activePaths = {};
|
|
267
|
+
var ordered = [];
|
|
268
|
+
for (var k = 0; k < projects.length; k++) {
|
|
269
|
+
activePaths[projects[k].path] = true;
|
|
270
|
+
if (byPath[projects[k].path]) ordered.push(byPath[projects[k].path]);
|
|
271
|
+
}
|
|
272
|
+
var inactive = [];
|
|
273
|
+
var paths = Object.keys(byPath);
|
|
274
|
+
for (var m = 0; m < paths.length; m++) {
|
|
275
|
+
if (!activePaths[paths[m]]) inactive.push(byPath[paths[m]]);
|
|
276
|
+
}
|
|
277
|
+
inactive.sort(function (a, b) { return (b.lastUsed || 0) - (a.lastUsed || 0); });
|
|
278
|
+
var all = ordered.concat(inactive);
|
|
210
279
|
|
|
211
280
|
// Keep at most 20 recent projects
|
|
212
281
|
rc.recentProjects = all.slice(0, 20);
|
|
213
282
|
saveClayrc(rc);
|
|
214
283
|
}
|
|
215
284
|
|
|
285
|
+
function removeFromClayrc(projectPath) {
|
|
286
|
+
var rc = loadClayrc();
|
|
287
|
+
var before = (rc.recentProjects || []).length;
|
|
288
|
+
rc.recentProjects = (rc.recentProjects || []).filter(function (p) {
|
|
289
|
+
return p.path !== projectPath;
|
|
290
|
+
});
|
|
291
|
+
if (rc.recentProjects.length !== before) saveClayrc(rc);
|
|
292
|
+
}
|
|
293
|
+
|
|
216
294
|
module.exports = {
|
|
217
295
|
CONFIG_DIR: CONFIG_DIR,
|
|
218
296
|
configPath: configPath,
|
|
@@ -234,4 +312,5 @@ module.exports = {
|
|
|
234
312
|
loadClayrc: loadClayrc,
|
|
235
313
|
saveClayrc: saveClayrc,
|
|
236
314
|
syncClayrc: syncClayrc,
|
|
315
|
+
removeFromClayrc: removeFromClayrc,
|
|
237
316
|
};
|