clay-server 2.20.0-beta.1 → 2.20.0-beta.2

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/lib/server.js CHANGED
@@ -326,9 +326,11 @@ function serveStatic(urlPath, res) {
326
326
  var content = fs.readFileSync(filePath);
327
327
  var ext = path.extname(filePath);
328
328
  var mime = MIME_TYPES[ext] || "application/octet-stream";
329
+ var isImage = ext === ".png" || ext === ".jpg" || ext === ".jpeg" || ext === ".gif" || ext === ".svg" || ext === ".webp" || ext === ".ico";
330
+ var cacheControl = isImage ? "public, max-age=86400, immutable" : "no-cache";
329
331
  res.writeHead(200, {
330
- "Content-Type": mime + "; charset=utf-8",
331
- "Cache-Control": "no-cache",
332
+ "Content-Type": mime + (isImage ? "" : "; charset=utf-8"),
333
+ "Cache-Control": cacheControl,
332
334
  });
333
335
  res.end(content);
334
336
  return true;
@@ -2465,8 +2467,8 @@ function createServer(opts) {
2465
2467
  return;
2466
2468
  }
2467
2469
 
2468
- // Static files at root (favicon, manifest, icons, sw.js, etc.)
2469
- if (fullUrl.lastIndexOf("/") === 0 && !fullUrl.includes("..")) {
2470
+ // Static files (favicon, manifest, icons, sw.js, mate avatars, etc.)
2471
+ if (!fullUrl.includes("..") && !fullUrl.startsWith("/p/") && !fullUrl.startsWith("/api/")) {
2470
2472
  if (serveStatic(fullUrl, res)) return;
2471
2473
  }
2472
2474
 
@@ -2973,6 +2975,7 @@ function createServer(opts) {
2973
2975
  avatarStyle: mp.avatarStyle || "bottts",
2974
2976
  avatarSeed: mp.avatarSeed || mate.id,
2975
2977
  avatarColor: mp.avatarColor || "#6c5ce7",
2978
+ avatarCustom: mp.avatarCustom || "",
2976
2979
  isMate: true,
2977
2980
  mateStatus: mate.status,
2978
2981
  seedData: mate.seedData || {},
@@ -3101,6 +3104,8 @@ function createServer(opts) {
3101
3104
  var mateSlug = "mate-" + mate.id;
3102
3105
  var mateName = (mate.profile && mate.profile.displayName) || mate.name || "New Mate";
3103
3106
  addProject(mateDir, mateSlug, mateName, null, mate.createdBy, null, { isMate: true, mateDisplayName: mateName });
3107
+ // Auto-add to favorites so it shows in sidebar
3108
+ users.addDmFavorite(userId, mate.id);
3104
3109
  ws.send(JSON.stringify({ type: "mate_created", mate: mate, projectSlug: mateSlug }));
3105
3110
  } catch (e) {
3106
3111
  ws.send(JSON.stringify({ type: "mate_error", error: "Failed to create mate: " + e.message }));
@@ -3110,7 +3115,30 @@ function createServer(opts) {
3110
3115
 
3111
3116
  if (msg.type === "mate_list") {
3112
3117
  var mateCtx5 = mates.buildMateCtx(userId);
3118
+ // Backfill built-in mates for existing users
3119
+ try {
3120
+ var deletedKeys = users.getDeletedBuiltinKeys(userId);
3121
+ var newBuiltins = mates.ensureBuiltinMates(mateCtx5, deletedKeys);
3122
+ for (var bi = 0; bi < newBuiltins.length; bi++) {
3123
+ var nb = newBuiltins[bi];
3124
+ var nbSlug = "mate-" + nb.id;
3125
+ var nbDir = mates.getMateDir(mateCtx5, nb.id);
3126
+ var nbName = (nb.profile && nb.profile.displayName) || nb.name || "New Mate";
3127
+ addProject(nbDir, nbSlug, nbName, null, nb.createdBy || userId, null, { isMate: true, mateDisplayName: nbName });
3128
+ users.addDmFavorite(userId, nb.id);
3129
+ }
3130
+ } catch (e) {
3131
+ console.error("[server] Failed to ensure built-in mates:", e.message);
3132
+ }
3133
+ // Ensure built-in mates are in favorites (unless user explicitly removed them)
3113
3134
  var mateList = mates.getAllMates(mateCtx5);
3135
+ var currentFavs = users.getDmFavorites(userId);
3136
+ var hiddenIds = users.getDmHidden(userId);
3137
+ for (var bfi = 0; bfi < mateList.length; bfi++) {
3138
+ if (mateList[bfi].builtinKey && currentFavs.indexOf(mateList[bfi].id) === -1 && hiddenIds.indexOf(mateList[bfi].id) === -1) {
3139
+ users.addDmFavorite(userId, mateList[bfi].id);
3140
+ }
3141
+ }
3114
3142
  // Ensure all mate projects are registered (survives server restarts)
3115
3143
  for (var mi = 0; mi < mateList.length; mi++) {
3116
3144
  var m = mateList[mi];
@@ -3122,31 +3150,121 @@ function createServer(opts) {
3122
3150
  addProject(mDir, mSlug, mName, null, m.createdBy || userId, null, { isMate: true, mateDisplayName: mName });
3123
3151
  }
3124
3152
  }
3125
- ws.send(JSON.stringify({ type: "mate_list", mates: mateList }));
3153
+ // Include deleted built-in mates for re-add UI
3154
+ var builtinDefs2 = require("./builtin-mates");
3155
+ var missingKeys2 = mates.getMissingBuiltinKeys(mateCtx5);
3156
+ var availableBuiltins2 = [];
3157
+ for (var abk2 = 0; abk2 < missingKeys2.length; abk2++) {
3158
+ var bDef2 = builtinDefs2.getBuiltinByKey(missingKeys2[abk2]);
3159
+ if (bDef2) {
3160
+ availableBuiltins2.push({
3161
+ key: bDef2.key,
3162
+ displayName: bDef2.displayName,
3163
+ bio: bDef2.bio,
3164
+ avatarCustom: bDef2.avatarCustom || "",
3165
+ });
3166
+ }
3167
+ }
3168
+ ws.send(JSON.stringify({ type: "mate_list", mates: mateList, availableBuiltins: availableBuiltins2 }));
3126
3169
  return;
3127
3170
  }
3128
3171
 
3129
3172
  if (msg.type === "mate_delete") {
3130
3173
  if (!msg.mateId) return;
3131
3174
  var mateCtx6 = mates.buildMateCtx(userId);
3175
+ // Track deleted built-in mate key so it doesn't auto-recreate
3176
+ var mateToDelete = mates.getMate(mateCtx6, msg.mateId);
3177
+ if (mateToDelete && mateToDelete.builtinKey) {
3178
+ users.addDeletedBuiltinKey(userId, mateToDelete.builtinKey);
3179
+ }
3132
3180
  var result = mates.deleteMate(mateCtx6, msg.mateId);
3133
3181
  if (result.error) {
3134
3182
  ws.send(JSON.stringify({ type: "mate_error", error: result.error }));
3135
3183
  } else {
3136
3184
  removeProject("mate-" + msg.mateId);
3137
- ws.send(JSON.stringify({ type: "mate_deleted", mateId: msg.mateId }));
3185
+ // Build updated available builtins list
3186
+ var builtinDefs3 = require("./builtin-mates");
3187
+ var missingKeys3 = mates.getMissingBuiltinKeys(mateCtx6);
3188
+ var availableBuiltins3 = [];
3189
+ for (var abk3 = 0; abk3 < missingKeys3.length; abk3++) {
3190
+ var bDef3 = builtinDefs3.getBuiltinByKey(missingKeys3[abk3]);
3191
+ if (bDef3) {
3192
+ availableBuiltins3.push({
3193
+ key: bDef3.key,
3194
+ displayName: bDef3.displayName,
3195
+ bio: bDef3.bio,
3196
+ avatarCustom: bDef3.avatarCustom || "",
3197
+ });
3198
+ }
3199
+ }
3200
+ ws.send(JSON.stringify({ type: "mate_deleted", mateId: msg.mateId, availableBuiltins: availableBuiltins3 }));
3138
3201
  // Broadcast to all clients so strips update
3139
3202
  projects.forEach(function (ctx) {
3140
3203
  ctx.forEachClient(function (otherWs) {
3141
3204
  if (otherWs === ws) return;
3142
3205
  if (otherWs.readyState !== 1) return;
3143
- otherWs.send(JSON.stringify({ type: "mate_deleted", mateId: msg.mateId }));
3206
+ otherWs.send(JSON.stringify({ type: "mate_deleted", mateId: msg.mateId, availableBuiltins: availableBuiltins3 }));
3144
3207
  });
3145
3208
  });
3146
3209
  }
3147
3210
  return;
3148
3211
  }
3149
3212
 
3213
+ if (msg.type === "mate_readd_builtin") {
3214
+ if (!msg.builtinKey) return;
3215
+ try {
3216
+ var mateCtxR = mates.buildMateCtx(userId);
3217
+ var missingKeys = mates.getMissingBuiltinKeys(mateCtxR);
3218
+ if (missingKeys.indexOf(msg.builtinKey) === -1) {
3219
+ ws.send(JSON.stringify({ type: "mate_error", error: "This built-in mate already exists" }));
3220
+ return;
3221
+ }
3222
+ var newMate = mates.createBuiltinMate(mateCtxR, msg.builtinKey);
3223
+ users.removeDeletedBuiltinKey(userId, msg.builtinKey);
3224
+ var updatedFavsR = users.addDmFavorite(userId, newMate.id);
3225
+ var readdSlug = "mate-" + newMate.id;
3226
+ var readdDir = mates.getMateDir(mateCtxR, newMate.id);
3227
+ var readdName = (newMate.profile && newMate.profile.displayName) || newMate.name || "New Mate";
3228
+ addProject(readdDir, readdSlug, readdName, null, newMate.createdBy || userId, null, { isMate: true, mateDisplayName: readdName });
3229
+ // Build updated available builtins
3230
+ var builtinDefsR = require("./builtin-mates");
3231
+ var missingKeysR = mates.getMissingBuiltinKeys(mateCtxR);
3232
+ var availableBuiltinsR = [];
3233
+ for (var abkR = 0; abkR < missingKeysR.length; abkR++) {
3234
+ var bDefR = builtinDefsR.getBuiltinByKey(missingKeysR[abkR]);
3235
+ if (bDefR) {
3236
+ availableBuiltinsR.push({ key: bDefR.key, displayName: bDefR.displayName, bio: bDefR.bio, avatarCustom: bDefR.avatarCustom || "" });
3237
+ }
3238
+ }
3239
+ ws.send(JSON.stringify({ type: "mate_created", mate: newMate, projectSlug: readdSlug, availableBuiltins: availableBuiltinsR, dmFavorites: updatedFavsR }));
3240
+ } catch (e) {
3241
+ ws.send(JSON.stringify({ type: "mate_error", error: "Failed to re-add built-in mate: " + e.message }));
3242
+ }
3243
+ return;
3244
+ }
3245
+
3246
+ if (msg.type === "mate_list_available_builtins") {
3247
+ var mateCtxAB = mates.buildMateCtx(userId);
3248
+ var missingBuiltinKeys = mates.getMissingBuiltinKeys(mateCtxAB);
3249
+ var builtinDefs = require("./builtin-mates");
3250
+ var availableBuiltins = [];
3251
+ for (var abk = 0; abk < missingBuiltinKeys.length; abk++) {
3252
+ var bDef = builtinDefs.getBuiltinByKey(missingBuiltinKeys[abk]);
3253
+ if (bDef) {
3254
+ availableBuiltins.push({
3255
+ key: bDef.key,
3256
+ displayName: bDef.displayName,
3257
+ bio: bDef.bio,
3258
+ avatarColor: bDef.avatarColor,
3259
+ avatarStyle: bDef.avatarStyle,
3260
+ avatarCustom: bDef.avatarCustom || "",
3261
+ });
3262
+ }
3263
+ }
3264
+ ws.send(JSON.stringify({ type: "mate_available_builtins", builtins: availableBuiltins }));
3265
+ return;
3266
+ }
3267
+
3150
3268
  if (msg.type === "mate_update") {
3151
3269
  if (!msg.mateId || !msg.updates) return;
3152
3270
  var mateCtx7 = mates.buildMateCtx(userId);
package/lib/users.js CHANGED
@@ -176,6 +176,16 @@ function createUser(opts) {
176
176
  };
177
177
  data.users.push(user);
178
178
  saveUsers(data);
179
+
180
+ // Seed built-in mates for the new user
181
+ try {
182
+ var mates = require("./mates");
183
+ var mateCtx = mates.buildMateCtx(user.id);
184
+ mates.ensureBuiltinMates(mateCtx);
185
+ } catch (e) {
186
+ console.error("[users] Failed to seed built-in mates for user " + user.id + ":", e.message);
187
+ }
188
+
179
189
  return { ok: true, user: user };
180
190
  }
181
191
 
@@ -422,6 +432,16 @@ function createUserWithoutPin(opts) {
422
432
  };
423
433
  data.users.push(user);
424
434
  saveUsers(data);
435
+
436
+ // Seed built-in mates for the new user
437
+ try {
438
+ var mates = require("./mates");
439
+ var mateCtx = mates.buildMateCtx(user.id);
440
+ mates.ensureBuiltinMates(mateCtx);
441
+ } catch (e) {
442
+ console.error("[users] Failed to seed built-in mates for user " + user.id + ":", e.message);
443
+ }
444
+
425
445
  return { ok: true, user: user };
426
446
  }
427
447
 
@@ -563,6 +583,46 @@ function removeDmHidden(userId, targetUserId) {
563
583
  return [];
564
584
  }
565
585
 
586
+ // --- Deleted built-in mate keys tracking ---
587
+
588
+ function getDeletedBuiltinKeys(userId) {
589
+ var data = loadUsers();
590
+ for (var i = 0; i < data.users.length; i++) {
591
+ if (data.users[i].id === userId) {
592
+ return data.users[i].deletedBuiltinKeys || [];
593
+ }
594
+ }
595
+ return [];
596
+ }
597
+
598
+ function addDeletedBuiltinKey(userId, key) {
599
+ var data = loadUsers();
600
+ for (var i = 0; i < data.users.length; i++) {
601
+ if (data.users[i].id === userId) {
602
+ if (!data.users[i].deletedBuiltinKeys) data.users[i].deletedBuiltinKeys = [];
603
+ if (data.users[i].deletedBuiltinKeys.indexOf(key) === -1) {
604
+ data.users[i].deletedBuiltinKeys.push(key);
605
+ saveUsers(data);
606
+ }
607
+ return;
608
+ }
609
+ }
610
+ }
611
+
612
+ function removeDeletedBuiltinKey(userId, key) {
613
+ var data = loadUsers();
614
+ for (var i = 0; i < data.users.length; i++) {
615
+ if (data.users[i].id === userId) {
616
+ if (!data.users[i].deletedBuiltinKeys) return;
617
+ data.users[i].deletedBuiltinKeys = data.users[i].deletedBuiltinKeys.filter(function (k) {
618
+ return k !== key;
619
+ });
620
+ saveUsers(data);
621
+ return;
622
+ }
623
+ }
624
+ }
625
+
566
626
  // --- RBAC permissions ---
567
627
 
568
628
  function getEffectivePermissions(user, osUsersMode) {
@@ -717,4 +777,7 @@ module.exports = {
717
777
  removeDmHidden: removeDmHidden,
718
778
  getAutoContinue: getAutoContinue,
719
779
  setAutoContinue: setAutoContinue,
780
+ getDeletedBuiltinKeys: getDeletedBuiltinKeys,
781
+ addDeletedBuiltinKey: addDeletedBuiltinKey,
782
+ removeDeletedBuiltinKey: removeDeletedBuiltinKey,
720
783
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.20.0-beta.1",
3
+ "version": "2.20.0-beta.2",
4
4
  "description": "Web UI for Claude Code. Any device. Push notifications.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",