clay-server 2.16.0 → 2.17.0-beta.10
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 +20 -7
- package/bin/cli.js +158 -239
- package/lib/certs/fullchain.pem +47 -0
- package/lib/certs/privkey.pem +5 -0
- package/lib/daemon.js +20 -3
- package/lib/pages.js +22 -20
- package/lib/project.js +9 -5
- package/lib/public/app.js +45 -25
- package/lib/public/css/command-palette.css +1 -1
- package/lib/public/css/mates.css +13 -14
- package/lib/public/css/menus.css +19 -0
- package/lib/public/css/overlays.css +44 -18
- package/lib/public/css/profile.css +128 -0
- package/lib/public/css/title-bar.css +0 -4
- package/lib/public/index.html +9 -5
- package/lib/public/modules/avatar.js +36 -0
- package/lib/public/modules/command-palette.js +4 -7
- package/lib/public/modules/mate-sidebar.js +5 -8
- package/lib/public/modules/profile.js +351 -24
- package/lib/public/modules/qrcode.js +23 -3
- package/lib/public/modules/sidebar.js +26 -9
- package/lib/public/sw.js +4 -1
- package/lib/server.js +224 -3
- package/lib/sessions.js +4 -4
- package/package.json +1 -1
- package/lib/themes/clay-light.json +0 -10
- package/lib/themes/clay.json +0 -10
package/lib/public/sw.js
CHANGED
|
@@ -38,8 +38,11 @@ self.addEventListener("fetch", function (event) {
|
|
|
38
38
|
// Only handle GET requests
|
|
39
39
|
if (request.method !== "GET") return;
|
|
40
40
|
|
|
41
|
-
// Skip
|
|
41
|
+
// Skip cross-origin requests (external images, fonts, etc.)
|
|
42
42
|
var url = new URL(request.url);
|
|
43
|
+
if (url.origin !== self.location.origin) return;
|
|
44
|
+
|
|
45
|
+
// Skip WebSocket upgrade requests and API/data endpoints
|
|
43
46
|
if (url.pathname.indexOf("/ws") !== -1) return;
|
|
44
47
|
if (url.pathname.indexOf("/api/") !== -1) return;
|
|
45
48
|
|
package/lib/server.js
CHANGED
|
@@ -525,7 +525,7 @@ function createServer(opts) {
|
|
|
525
525
|
var securityHeaders = {
|
|
526
526
|
"X-Content-Type-Options": "nosniff",
|
|
527
527
|
"X-Frame-Options": "DENY",
|
|
528
|
-
"Content-Security-Policy": "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://esm.sh; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net; img-src * data: blob:; connect-src 'self' ws: wss: https://cdn.jsdelivr.net https://esm.sh https://api.dicebear.com https://api.open-meteo.com; font-src 'self' data: https://fonts.gstatic.com https://cdn.jsdelivr.net;",
|
|
528
|
+
"Content-Security-Policy": "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://esm.sh; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net; img-src * data: blob:; connect-src 'self' ws: wss: https://cdn.jsdelivr.net https://esm.sh https://api.dicebear.com https://api.open-meteo.com https://ipapi.co; font-src 'self' data: https://fonts.gstatic.com https://cdn.jsdelivr.net;",
|
|
529
529
|
};
|
|
530
530
|
if (tlsOptions) {
|
|
531
531
|
securityHeaders["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains";
|
|
@@ -1017,6 +1017,19 @@ function createServer(opts) {
|
|
|
1017
1017
|
return;
|
|
1018
1018
|
}
|
|
1019
1019
|
|
|
1020
|
+
// PWA install guide (builtin cert mode, no CA step needed)
|
|
1021
|
+
if (fullUrl === "/pwa" && req.method === "GET") {
|
|
1022
|
+
var host = req.headers.host || "localhost";
|
|
1023
|
+
var hostname = host.split(":")[0];
|
|
1024
|
+
var protocol = tlsOptions ? "https" : "http";
|
|
1025
|
+
var pwaUrl = protocol + "://" + hostname + ":" + portNum;
|
|
1026
|
+
res.writeHead(200, {
|
|
1027
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
1028
|
+
});
|
|
1029
|
+
res.end(setupPageHtml(pwaUrl, pwaUrl, false, true));
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1020
1033
|
// Global push endpoints (used by setup page)
|
|
1021
1034
|
if (req.method === "GET" && fullUrl === "/api/vapid-public-key" && pushModule) {
|
|
1022
1035
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -1086,7 +1099,7 @@ function createServer(opts) {
|
|
|
1086
1099
|
res.end('{"error":"unauthorized"}');
|
|
1087
1100
|
return;
|
|
1088
1101
|
}
|
|
1089
|
-
var profile = mu.profile || { name: "", lang: "en-US", avatarColor: "#7c3aed", avatarStyle: "thumbs", avatarSeed: "" };
|
|
1102
|
+
var profile = mu.profile || { name: "", lang: "en-US", avatarColor: "#7c3aed", avatarStyle: "thumbs", avatarSeed: "", avatarCustom: "" };
|
|
1090
1103
|
profile.username = mu.username;
|
|
1091
1104
|
profile.userId = mu.id;
|
|
1092
1105
|
profile.role = mu.role;
|
|
@@ -1094,7 +1107,7 @@ function createServer(opts) {
|
|
|
1094
1107
|
res.end(JSON.stringify(profile));
|
|
1095
1108
|
return;
|
|
1096
1109
|
}
|
|
1097
|
-
var profile = { name: "", lang: "en-US", avatarColor: "#7c3aed", avatarStyle: "thumbs", avatarSeed: "" };
|
|
1110
|
+
var profile = { name: "", lang: "en-US", avatarColor: "#7c3aed", avatarStyle: "thumbs", avatarSeed: "", avatarCustom: "" };
|
|
1098
1111
|
try {
|
|
1099
1112
|
var raw = fs.readFileSync(profilePath, "utf8");
|
|
1100
1113
|
var saved = JSON.parse(raw);
|
|
@@ -1103,7 +1116,18 @@ function createServer(opts) {
|
|
|
1103
1116
|
if (saved.avatarColor) profile.avatarColor = saved.avatarColor;
|
|
1104
1117
|
if (saved.avatarStyle) profile.avatarStyle = saved.avatarStyle;
|
|
1105
1118
|
if (saved.avatarSeed) profile.avatarSeed = saved.avatarSeed;
|
|
1119
|
+
if (saved.avatarCustom) profile.avatarCustom = saved.avatarCustom;
|
|
1106
1120
|
} catch (e) { /* file doesn't exist yet */ }
|
|
1121
|
+
// Check if custom avatar file exists
|
|
1122
|
+
try {
|
|
1123
|
+
var avatarFiles = fs.readdirSync(path.join(CONFIG_DIR, "avatars"));
|
|
1124
|
+
for (var afi = 0; afi < avatarFiles.length; afi++) {
|
|
1125
|
+
if (avatarFiles[afi].startsWith("default.")) {
|
|
1126
|
+
profile.avatarCustom = "/api/avatar/default?v=" + fs.statSync(path.join(CONFIG_DIR, "avatars", avatarFiles[afi])).mtimeMs;
|
|
1127
|
+
break;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
} catch (e) {}
|
|
1107
1131
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1108
1132
|
res.end(JSON.stringify(profile));
|
|
1109
1133
|
return;
|
|
@@ -1123,6 +1147,8 @@ function createServer(opts) {
|
|
|
1123
1147
|
}
|
|
1124
1148
|
if (typeof data.avatarStyle === "string") profile.avatarStyle = data.avatarStyle.substring(0, 30);
|
|
1125
1149
|
if (typeof data.avatarSeed === "string") profile.avatarSeed = data.avatarSeed.substring(0, 30);
|
|
1150
|
+
if (typeof data.avatarCustom === "string") profile.avatarCustom = data.avatarCustom;
|
|
1151
|
+
if (data.avatarCustom === null || data.avatarCustom === "") profile.avatarCustom = undefined;
|
|
1126
1152
|
if (users.isMultiUser()) {
|
|
1127
1153
|
var mu = getMultiUserFromReq(req);
|
|
1128
1154
|
if (!mu) {
|
|
@@ -1151,6 +1177,196 @@ function createServer(opts) {
|
|
|
1151
1177
|
return;
|
|
1152
1178
|
}
|
|
1153
1179
|
|
|
1180
|
+
// Upload custom avatar image
|
|
1181
|
+
if (req.method === "POST" && fullUrl === "/api/avatar") {
|
|
1182
|
+
var chunks = [];
|
|
1183
|
+
var totalSize = 0;
|
|
1184
|
+
var maxSize = 2 * 1024 * 1024; // 2MB
|
|
1185
|
+
req.on("data", function (chunk) {
|
|
1186
|
+
totalSize += chunk.length;
|
|
1187
|
+
if (totalSize <= maxSize) chunks.push(chunk);
|
|
1188
|
+
});
|
|
1189
|
+
req.on("end", function () {
|
|
1190
|
+
if (totalSize > maxSize) {
|
|
1191
|
+
res.writeHead(413, { "Content-Type": "application/json" });
|
|
1192
|
+
res.end('{"error":"File too large (max 2MB)"}');
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
var raw = Buffer.concat(chunks);
|
|
1196
|
+
// Detect content type from magic bytes
|
|
1197
|
+
var ct = null;
|
|
1198
|
+
if (raw[0] === 0xFF && raw[1] === 0xD8) ct = "image/jpeg";
|
|
1199
|
+
else if (raw[0] === 0x89 && raw[1] === 0x50) ct = "image/png";
|
|
1200
|
+
else if (raw[0] === 0x47 && raw[1] === 0x49) ct = "image/gif";
|
|
1201
|
+
else if (raw[0] === 0x52 && raw[1] === 0x49) ct = "image/webp";
|
|
1202
|
+
if (!ct) {
|
|
1203
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1204
|
+
res.end('{"error":"Unsupported image format"}');
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
var ext = ct.split("/")[1] === "jpeg" ? "jpg" : ct.split("/")[1];
|
|
1208
|
+
var avatarDir = path.join(CONFIG_DIR, "avatars");
|
|
1209
|
+
fs.mkdirSync(avatarDir, { recursive: true });
|
|
1210
|
+
|
|
1211
|
+
var userId = "default";
|
|
1212
|
+
if (users.isMultiUser()) {
|
|
1213
|
+
var mu = getMultiUserFromReq(req);
|
|
1214
|
+
if (!mu) {
|
|
1215
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
1216
|
+
res.end('{"error":"unauthorized"}');
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
userId = mu.id;
|
|
1220
|
+
}
|
|
1221
|
+
var filename = userId + "." + ext;
|
|
1222
|
+
// Remove old avatar files for this user
|
|
1223
|
+
try {
|
|
1224
|
+
var existing = fs.readdirSync(avatarDir);
|
|
1225
|
+
for (var ei = 0; ei < existing.length; ei++) {
|
|
1226
|
+
if (existing[ei].startsWith(userId + ".")) {
|
|
1227
|
+
fs.unlinkSync(path.join(avatarDir, existing[ei]));
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
} catch (e) {}
|
|
1231
|
+
fs.writeFileSync(path.join(avatarDir, filename), raw);
|
|
1232
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1233
|
+
res.end(JSON.stringify({ ok: true, avatar: "/api/avatar/" + userId + "?v=" + Date.now() }));
|
|
1234
|
+
});
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
// Serve custom avatar image
|
|
1239
|
+
if (req.method === "GET" && fullUrl.startsWith("/api/avatar/")) {
|
|
1240
|
+
var avatarUserId = fullUrl.split("/api/avatar/")[1].split("?")[0];
|
|
1241
|
+
var avatarDir = path.join(CONFIG_DIR, "avatars");
|
|
1242
|
+
try {
|
|
1243
|
+
var files = fs.readdirSync(avatarDir);
|
|
1244
|
+
var match = null;
|
|
1245
|
+
for (var fi = 0; fi < files.length; fi++) {
|
|
1246
|
+
if (files[fi].startsWith(avatarUserId + ".")) {
|
|
1247
|
+
match = files[fi];
|
|
1248
|
+
break;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
if (match) {
|
|
1252
|
+
var ext = match.split(".").pop();
|
|
1253
|
+
var ctMap = { jpg: "image/jpeg", png: "image/png", gif: "image/gif", webp: "image/webp" };
|
|
1254
|
+
res.writeHead(200, {
|
|
1255
|
+
"Content-Type": ctMap[ext] || "application/octet-stream",
|
|
1256
|
+
"Cache-Control": "public, max-age=31536000, immutable",
|
|
1257
|
+
});
|
|
1258
|
+
res.end(fs.readFileSync(path.join(avatarDir, match)));
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
} catch (e) {}
|
|
1262
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1263
|
+
res.end('{"error":"not found"}');
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
// Upload custom avatar for a mate
|
|
1268
|
+
if (req.method === "POST" && fullUrl.startsWith("/api/mate-avatar/")) {
|
|
1269
|
+
var mateIdFromUrl = fullUrl.split("/api/mate-avatar/")[1].split("?")[0];
|
|
1270
|
+
if (!mateIdFromUrl) {
|
|
1271
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1272
|
+
res.end('{"error":"Missing mate ID"}');
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
var chunks = [];
|
|
1276
|
+
var totalSize = 0;
|
|
1277
|
+
var maxSize = 2 * 1024 * 1024; // 2MB
|
|
1278
|
+
req.on("data", function (chunk) {
|
|
1279
|
+
totalSize += chunk.length;
|
|
1280
|
+
if (totalSize <= maxSize) chunks.push(chunk);
|
|
1281
|
+
});
|
|
1282
|
+
req.on("end", function () {
|
|
1283
|
+
if (totalSize > maxSize) {
|
|
1284
|
+
res.writeHead(413, { "Content-Type": "application/json" });
|
|
1285
|
+
res.end('{"error":"File too large (max 2MB)"}');
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
var raw = Buffer.concat(chunks);
|
|
1289
|
+
var ct = null;
|
|
1290
|
+
if (raw[0] === 0xFF && raw[1] === 0xD8) ct = "image/jpeg";
|
|
1291
|
+
else if (raw[0] === 0x89 && raw[1] === 0x50) ct = "image/png";
|
|
1292
|
+
else if (raw[0] === 0x47 && raw[1] === 0x49) ct = "image/gif";
|
|
1293
|
+
else if (raw[0] === 0x52 && raw[1] === 0x49) ct = "image/webp";
|
|
1294
|
+
if (!ct) {
|
|
1295
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1296
|
+
res.end('{"error":"Unsupported image format"}');
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
var userId = null;
|
|
1300
|
+
if (users.isMultiUser()) {
|
|
1301
|
+
var mu = getMultiUserFromReq(req);
|
|
1302
|
+
if (!mu) {
|
|
1303
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
1304
|
+
res.end('{"error":"unauthorized"}');
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
userId = mu.id;
|
|
1308
|
+
}
|
|
1309
|
+
var mateCtx = mates.buildMateCtx(userId);
|
|
1310
|
+
var mate = mates.getMate(mateCtx, mateIdFromUrl);
|
|
1311
|
+
if (!mate) {
|
|
1312
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1313
|
+
res.end('{"error":"Mate not found"}');
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
var ext = ct.split("/")[1] === "jpeg" ? "jpg" : ct.split("/")[1];
|
|
1317
|
+
var avatarDir = path.join(CONFIG_DIR, "mate-avatars");
|
|
1318
|
+
fs.mkdirSync(avatarDir, { recursive: true });
|
|
1319
|
+
var filename = mateIdFromUrl + "." + ext;
|
|
1320
|
+
// Remove old avatar files for this mate
|
|
1321
|
+
try {
|
|
1322
|
+
var existing = fs.readdirSync(avatarDir);
|
|
1323
|
+
for (var ei = 0; ei < existing.length; ei++) {
|
|
1324
|
+
if (existing[ei].startsWith(mateIdFromUrl + ".")) {
|
|
1325
|
+
fs.unlinkSync(path.join(avatarDir, existing[ei]));
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
} catch (e) {}
|
|
1329
|
+
fs.writeFileSync(path.join(avatarDir, filename), raw);
|
|
1330
|
+
var avatarPath = "/api/mate-avatar/" + mateIdFromUrl + "?v=" + Date.now();
|
|
1331
|
+
// Update mate profile with custom avatar URL
|
|
1332
|
+
var profile = mate.profile || {};
|
|
1333
|
+
profile.avatarCustom = avatarPath;
|
|
1334
|
+
mates.updateMate(mateCtx, mateIdFromUrl, { profile: profile });
|
|
1335
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1336
|
+
res.end(JSON.stringify({ ok: true, avatar: avatarPath }));
|
|
1337
|
+
});
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
// Serve custom mate avatar image
|
|
1342
|
+
if (req.method === "GET" && fullUrl.startsWith("/api/mate-avatar/")) {
|
|
1343
|
+
var mateAvatarId = fullUrl.split("/api/mate-avatar/")[1].split("?")[0];
|
|
1344
|
+
var mateAvatarDir = path.join(CONFIG_DIR, "mate-avatars");
|
|
1345
|
+
try {
|
|
1346
|
+
var files = fs.readdirSync(mateAvatarDir);
|
|
1347
|
+
var match = null;
|
|
1348
|
+
for (var fi = 0; fi < files.length; fi++) {
|
|
1349
|
+
if (files[fi].startsWith(mateAvatarId + ".")) {
|
|
1350
|
+
match = files[fi];
|
|
1351
|
+
break;
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
if (match) {
|
|
1355
|
+
var ext = match.split(".").pop();
|
|
1356
|
+
var ctMap = { jpg: "image/jpeg", png: "image/png", gif: "image/gif", webp: "image/webp" };
|
|
1357
|
+
res.writeHead(200, {
|
|
1358
|
+
"Content-Type": ctMap[ext] || "application/octet-stream",
|
|
1359
|
+
"Cache-Control": "public, max-age=31536000, immutable",
|
|
1360
|
+
});
|
|
1361
|
+
res.end(fs.readFileSync(path.join(mateAvatarDir, match)));
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
} catch (e) {}
|
|
1365
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1366
|
+
res.end('{"error":"not found"}');
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1154
1370
|
// Change own PIN (multi-user mode)
|
|
1155
1371
|
if (req.method === "PUT" && fullUrl === "/api/user/pin") {
|
|
1156
1372
|
if (!users.isMultiUser()) {
|
|
@@ -2613,6 +2829,7 @@ function createServer(opts) {
|
|
|
2613
2829
|
avatarStyle: p.avatarStyle || "thumbs",
|
|
2614
2830
|
avatarSeed: p.avatarSeed || otherUser.username,
|
|
2615
2831
|
avatarColor: p.avatarColor || "#7c3aed",
|
|
2832
|
+
avatarCustom: p.avatarCustom || "",
|
|
2616
2833
|
};
|
|
2617
2834
|
}
|
|
2618
2835
|
}
|
|
@@ -2675,6 +2892,7 @@ function createServer(opts) {
|
|
|
2675
2892
|
avatarStyle: tp.avatarStyle || "thumbs",
|
|
2676
2893
|
avatarSeed: tp.avatarSeed || targetUser.username,
|
|
2677
2894
|
avatarColor: tp.avatarColor || "#7c3aed",
|
|
2895
|
+
avatarCustom: tp.avatarCustom || "",
|
|
2678
2896
|
} : null,
|
|
2679
2897
|
}));
|
|
2680
2898
|
return;
|
|
@@ -2746,6 +2964,7 @@ function createServer(opts) {
|
|
|
2746
2964
|
avatarStyle: p.avatarStyle || "thumbs",
|
|
2747
2965
|
avatarSeed: p.avatarSeed || u.username,
|
|
2748
2966
|
avatarColor: p.avatarColor || "#7c3aed",
|
|
2967
|
+
avatarCustom: p.avatarCustom || "",
|
|
2749
2968
|
};
|
|
2750
2969
|
});
|
|
2751
2970
|
ws.send(JSON.stringify({
|
|
@@ -2913,6 +3132,7 @@ function createServer(opts) {
|
|
|
2913
3132
|
username: u.username,
|
|
2914
3133
|
avatarStyle: p.avatarStyle || "thumbs",
|
|
2915
3134
|
avatarSeed: p.avatarSeed || u.username,
|
|
3135
|
+
avatarCustom: p.avatarCustom || "",
|
|
2916
3136
|
});
|
|
2917
3137
|
});
|
|
2918
3138
|
});
|
|
@@ -2946,6 +3166,7 @@ function createServer(opts) {
|
|
|
2946
3166
|
avatarStyle: p.avatarStyle || "thumbs",
|
|
2947
3167
|
avatarSeed: p.avatarSeed || u.username,
|
|
2948
3168
|
avatarColor: p.avatarColor || "#7c3aed",
|
|
3169
|
+
avatarCustom: p.avatarCustom || "",
|
|
2949
3170
|
};
|
|
2950
3171
|
});
|
|
2951
3172
|
// Build per-user filtered lists, send individually
|
package/lib/sessions.js
CHANGED
|
@@ -315,7 +315,7 @@ function createSessionManager(opts) {
|
|
|
315
315
|
return 0;
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
-
function replayHistory(session, fromIndex, targetWs) {
|
|
318
|
+
function replayHistory(session, fromIndex, targetWs, transform) {
|
|
319
319
|
var _send = (targetWs && sendTo) ? function (obj) { sendTo(targetWs, obj); } : send;
|
|
320
320
|
var total = session.history.length;
|
|
321
321
|
if (typeof fromIndex !== "number") {
|
|
@@ -329,7 +329,7 @@ function createSessionManager(opts) {
|
|
|
329
329
|
_send({ type: "history_meta", total: total, from: fromIndex });
|
|
330
330
|
|
|
331
331
|
for (var i = fromIndex; i < total; i++) {
|
|
332
|
-
_send(session.history[i]);
|
|
332
|
+
_send(transform ? transform(session.history[i]) : session.history[i]);
|
|
333
333
|
}
|
|
334
334
|
|
|
335
335
|
// Find the last result message in the full history for accurate context data
|
|
@@ -351,7 +351,7 @@ function createSessionManager(opts) {
|
|
|
351
351
|
_send({ type: "history_done", lastUsage: lastUsage, lastModelUsage: lastModelUsage, lastCost: lastCost, lastStreamInputTokens: lastStreamInputTokens });
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
-
function switchSession(localId, targetWs) {
|
|
354
|
+
function switchSession(localId, targetWs, transform) {
|
|
355
355
|
var session = sessions.get(localId);
|
|
356
356
|
if (!session) return;
|
|
357
357
|
|
|
@@ -374,7 +374,7 @@ function createSessionManager(opts) {
|
|
|
374
374
|
|
|
375
375
|
_send({ type: "session_switched", id: localId, cliSessionId: session.cliSessionId || null, loop: session.loop || null });
|
|
376
376
|
broadcastSessionList();
|
|
377
|
-
replayHistory(session, undefined, targetWs);
|
|
377
|
+
replayHistory(session, undefined, targetWs, transform);
|
|
378
378
|
|
|
379
379
|
if (session.isProcessing) {
|
|
380
380
|
_send({ type: "status", status: "processing" });
|
package/package.json
CHANGED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "Clay Light",
|
|
3
|
-
"author": "Clay",
|
|
4
|
-
"variant": "light",
|
|
5
|
-
"base00": "F3EBE7", "base01": "EBE1DC", "base02": "D8CCC6", "base03": "A09590",
|
|
6
|
-
"base04": "786D67", "base05": "504541", "base06": "332925", "base07": "1A1412",
|
|
7
|
-
"base08": "C83520", "base09": "F74728", "base0A": "C08520", "base0B": "008F6B",
|
|
8
|
-
"base0C": "1C8575", "base0D": "3560B0", "base0E": "8C4E8E", "base0F": "A57C45",
|
|
9
|
-
"accent2": "2A26E5"
|
|
10
|
-
}
|
package/lib/themes/clay.json
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "Clay Dark",
|
|
3
|
-
"author": "Clay",
|
|
4
|
-
"variant": "dark",
|
|
5
|
-
"base00": "1F1B1B", "base01": "2A2525", "base02": "352F2F", "base03": "7D7370",
|
|
6
|
-
"base04": "A09590", "base05": "C2BAB4", "base06": "E5DED8", "base07": "FFFFFF",
|
|
7
|
-
"base08": "F74728", "base09": "FE7150", "base0A": "E5A040", "base0B": "09E5A3",
|
|
8
|
-
"base0C": "4EC9B0", "base0D": "6BA0E5", "base0E": "D085CC", "base0F": "D09558",
|
|
9
|
-
"accent2": "5857FC"
|
|
10
|
-
}
|