clay-server 2.27.0-beta.8 → 2.27.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.
Files changed (72) hide show
  1. package/README.md +10 -0
  2. package/lib/daemon-projects.js +164 -0
  3. package/lib/daemon.js +13 -126
  4. package/lib/mates-identity.js +132 -0
  5. package/lib/mates-knowledge.js +113 -0
  6. package/lib/mates-prompts.js +398 -0
  7. package/lib/mates.js +40 -599
  8. package/lib/project-connection.js +2 -0
  9. package/lib/project-debate.js +19 -12
  10. package/lib/project-http.js +4 -2
  11. package/lib/project-loop.js +110 -48
  12. package/lib/project-mate-interaction.js +4 -0
  13. package/lib/project-notifications.js +210 -0
  14. package/lib/project-sessions.js +5 -2
  15. package/lib/project-user-message.js +2 -1
  16. package/lib/project.js +26 -2
  17. package/lib/public/app.js +1193 -8521
  18. package/lib/public/css/command-palette.css +14 -0
  19. package/lib/public/css/loop.css +301 -0
  20. package/lib/public/css/notifications-center.css +190 -0
  21. package/lib/public/css/rewind.css +6 -0
  22. package/lib/public/index.html +89 -35
  23. package/lib/public/modules/app-connection.js +160 -0
  24. package/lib/public/modules/app-cursors.js +473 -0
  25. package/lib/public/modules/app-debate-ui.js +389 -0
  26. package/lib/public/modules/app-dm.js +627 -0
  27. package/lib/public/modules/app-favicon.js +212 -0
  28. package/lib/public/modules/app-header.js +229 -0
  29. package/lib/public/modules/app-home-hub.js +600 -0
  30. package/lib/public/modules/app-loop-ui.js +589 -0
  31. package/lib/public/modules/app-loop-wizard.js +439 -0
  32. package/lib/public/modules/app-messages.js +1560 -0
  33. package/lib/public/modules/app-misc.js +299 -0
  34. package/lib/public/modules/app-notifications.js +372 -0
  35. package/lib/public/modules/app-panels.js +888 -0
  36. package/lib/public/modules/app-projects.js +798 -0
  37. package/lib/public/modules/app-rate-limit.js +451 -0
  38. package/lib/public/modules/app-rendering.js +597 -0
  39. package/lib/public/modules/app-skills-install.js +234 -0
  40. package/lib/public/modules/command-palette.js +27 -4
  41. package/lib/public/modules/input.js +31 -20
  42. package/lib/public/modules/scheduler-config.js +1532 -0
  43. package/lib/public/modules/scheduler-history.js +79 -0
  44. package/lib/public/modules/scheduler.js +33 -1554
  45. package/lib/public/modules/session-search.js +13 -1
  46. package/lib/public/modules/sidebar-mates.js +812 -0
  47. package/lib/public/modules/sidebar-mobile.js +1269 -0
  48. package/lib/public/modules/sidebar-projects.js +1449 -0
  49. package/lib/public/modules/sidebar-sessions.js +986 -0
  50. package/lib/public/modules/sidebar.js +232 -4591
  51. package/lib/public/modules/store.js +27 -0
  52. package/lib/public/modules/ws-ref.js +7 -0
  53. package/lib/public/style.css +1 -0
  54. package/lib/sdk-bridge.js +96 -717
  55. package/lib/sdk-message-processor.js +587 -0
  56. package/lib/sdk-message-queue.js +42 -0
  57. package/lib/sdk-skill-discovery.js +131 -0
  58. package/lib/server-admin.js +712 -0
  59. package/lib/server-auth.js +737 -0
  60. package/lib/server-dm.js +221 -0
  61. package/lib/server-mates.js +281 -0
  62. package/lib/server-palette.js +110 -0
  63. package/lib/server-settings.js +479 -0
  64. package/lib/server-skills.js +280 -0
  65. package/lib/server.js +246 -2755
  66. package/lib/sessions.js +11 -4
  67. package/lib/users-auth.js +146 -0
  68. package/lib/users-permissions.js +118 -0
  69. package/lib/users-preferences.js +210 -0
  70. package/lib/users.js +48 -398
  71. package/lib/ws-schema.js +498 -0
  72. package/package.json +1 -1
package/lib/users.js CHANGED
@@ -3,32 +3,12 @@ var path = require("path");
3
3
  var crypto = require("crypto");
4
4
  var { execSync } = require("child_process");
5
5
  var { CONFIG_DIR } = require("./config");
6
+ var { attachAuth } = require("./users-auth");
7
+ var { DEFAULT_PERMISSIONS, ALL_PERMISSIONS, attachPermissions } = require("./users-permissions");
8
+ var { attachPreferences } = require("./users-preferences");
6
9
 
7
10
  var USERS_FILE = path.join(CONFIG_DIR, "users.json");
8
11
 
9
- // --- Per-user RBAC permissions (default values for regular users) ---
10
- var DEFAULT_PERMISSIONS = {
11
- terminal: false,
12
- fileBrowser: true,
13
- createProject: true,
14
- deleteProject: false,
15
- skills: true,
16
- sessionDelete: false,
17
- scheduledTasks: false,
18
- projectSettings: false,
19
- };
20
-
21
- var ALL_PERMISSIONS = {
22
- terminal: true,
23
- fileBrowser: true,
24
- createProject: true,
25
- deleteProject: true,
26
- skills: true,
27
- sessionDelete: true,
28
- scheduledTasks: true,
29
- projectSettings: true,
30
- };
31
-
32
12
  // --- Default data ---
33
13
 
34
14
  function defaultData() {
@@ -66,88 +46,12 @@ function saveUsers(data) {
66
46
  fs.renameSync(tmpPath, USERS_FILE);
67
47
  }
68
48
 
69
- // --- Multi-user mode ---
70
-
71
- function isMultiUser() {
72
- var data = loadUsers();
73
- return !!data.multiUser;
74
- }
75
-
76
- function enableMultiUser() {
77
- var data = loadUsers();
78
- if (data.multiUser) {
79
- // Already enabled — check if admin exists
80
- var admin = findAdmin(data);
81
- if (admin) {
82
- return { alreadyEnabled: true, hasAdmin: true, setupCode: null };
83
- }
84
- // Multi-user enabled but no admin — regenerate setup code
85
- var code = generateSetupCode();
86
- data.setupCode = code;
87
- saveUsers(data);
88
- return { alreadyEnabled: true, hasAdmin: false, setupCode: code };
89
- }
90
- var code = generateSetupCode();
91
- data.multiUser = true;
92
- data.setupCode = code;
93
- saveUsers(data);
94
- return { alreadyEnabled: false, hasAdmin: false, setupCode: code };
95
- }
96
-
97
- function disableMultiUser() {
98
- var data = loadUsers();
99
- data.multiUser = false;
100
- data.setupCode = null;
101
- saveUsers(data);
102
- }
103
-
104
- // --- Setup code ---
105
-
106
- function generateSetupCode() {
107
- var chars = "abcdefghijkmnpqrstuvwxyz23456789"; // no ambiguous chars
108
- var code = "";
109
- var bytes = crypto.randomBytes(6);
110
- for (var i = 0; i < 6; i++) {
111
- code += chars[bytes[i] % chars.length];
112
- }
113
- return code;
114
- }
115
-
116
- function getSetupCode() {
117
- var data = loadUsers();
118
- if (data.setupCode) return data.setupCode;
119
- // Defensive: if multi-user is on, no admin, and no code, auto-generate one
120
- if (data.multiUser && !findAdmin(data)) {
121
- var code = generateSetupCode();
122
- data.setupCode = code;
123
- saveUsers(data);
124
- return code;
125
- }
126
- return null;
127
- }
128
-
129
- function clearSetupCode() {
130
- var data = loadUsers();
131
- data.setupCode = null;
132
- saveUsers(data);
133
- }
134
-
135
- function validateSetupCode(code) {
136
- var data = loadUsers();
137
- if (!data.setupCode) return false;
138
- return data.setupCode === code;
139
- }
140
-
141
49
  // --- User CRUD ---
142
50
 
143
51
  function generateUserId() {
144
52
  return crypto.randomUUID();
145
53
  }
146
54
 
147
- function hashPin(pin) {
148
- return crypto.createHash("sha256").update("clay-user:" + pin).digest("hex");
149
- }
150
-
151
55
  function createUser(opts) {
152
56
  var data = loadUsers();
153
57
  // Check username uniqueness
@@ -169,7 +73,7 @@ function createUser(opts) {
169
73
  username: opts.username,
170
74
  email: opts.email || null,
171
75
  displayName: opts.displayName || opts.username,
172
- pinHash: hashPin(opts.pin),
76
+ pinHash: auth.hashPin(opts.pin),
173
77
  role: opts.role || "user",
174
78
  mustChangePin: !!opts.mustChangePin,
175
79
  createdAt: Date.now(),
@@ -244,14 +148,6 @@ function findUserByEmail(email) {
244
148
  return null;
245
149
  }
246
150
 
247
- function authenticateUser(username, pin) {
248
- var user = findUserByUsername(username);
249
- if (!user) return null;
250
- var pinH = hashPin(pin);
251
- if (user.pinHash !== pinH) return null;
252
- return user;
253
- }
254
-
255
151
  function getAllUsers() {
256
152
  var data = loadUsers();
257
153
  return data.users.map(function (u) {
@@ -300,7 +196,7 @@ function updateUserPin(userId, newPin) {
300
196
  var data = loadUsers();
301
197
  for (var i = 0; i < data.users.length; i++) {
302
198
  if (data.users[i].id === userId) {
303
- data.users[i].pinHash = hashPin(newPin);
199
+ data.users[i].pinHash = auth.hashPin(newPin);
304
200
  data.users[i].mustChangePin = false;
305
201
  saveUsers(data);
306
202
  return { ok: true };
@@ -309,19 +205,9 @@ function updateUserPin(userId, newPin) {
309
205
  return { error: "User not found" };
310
206
  }
311
207
 
312
- // Generate a random 6-digit PIN
313
- function generatePin() {
314
- var digits = "";
315
- var bytes = crypto.randomBytes(6);
316
- for (var i = 0; i < 6; i++) {
317
- digits += (bytes[i] % 10).toString();
318
- }
319
- return digits;
320
- }
321
-
322
208
  // Admin creates a user with a temporary PIN (must be changed on first login)
323
209
  function createUserByAdmin(opts) {
324
- var tempPin = generatePin();
210
+ var tempPin = auth.generatePin();
325
211
  var result = createUser({
326
212
  username: opts.username,
327
213
  displayName: opts.displayName || opts.username,
@@ -373,23 +259,6 @@ function updateLinuxUser(userId, linuxUsername) {
373
259
  return { error: "User not found" };
374
260
  }
375
261
 
376
- // --- Auth tokens ---
377
-
378
- function generateUserAuthToken(userId) {
379
- var token = crypto.randomBytes(32).toString("hex");
380
- return userId + ":" + token;
381
- }
382
-
383
- function parseAuthCookie(cookieValue) {
384
- if (!cookieValue) return null;
385
- var idx = cookieValue.indexOf(":");
386
- if (idx < 0) return null;
387
- return {
388
- userId: cookieValue.substring(0, idx),
389
- token: cookieValue.substring(idx + 1),
390
- };
391
- }
392
-
393
262
  // --- Invite links ---
394
263
 
395
264
  function createInvite(createdByUserId, targetEmail) {
@@ -507,256 +376,47 @@ function removeExpiredInvites() {
507
376
  if (data.invites.length !== before) saveUsers(data);
508
377
  }
509
378
 
510
- // --- DM Favorites ---
511
-
512
- function getDmFavorites(userId) {
513
- var data = loadUsers();
514
- for (var i = 0; i < data.users.length; i++) {
515
- if (data.users[i].id === userId) {
516
- return data.users[i].dmFavorites || [];
517
- }
518
- }
519
- return [];
520
- }
521
-
522
- function addDmFavorite(userId, targetUserId) {
523
- var data = loadUsers();
524
- for (var i = 0; i < data.users.length; i++) {
525
- if (data.users[i].id === userId) {
526
- if (!data.users[i].dmFavorites) data.users[i].dmFavorites = [];
527
- if (data.users[i].dmFavorites.indexOf(targetUserId) === -1) {
528
- data.users[i].dmFavorites.push(targetUserId);
529
- saveUsers(data);
530
- }
531
- return data.users[i].dmFavorites;
532
- }
533
- }
534
- return [];
535
- }
536
-
537
- function removeDmFavorite(userId, targetUserId) {
538
- var data = loadUsers();
539
- for (var i = 0; i < data.users.length; i++) {
540
- if (data.users[i].id === userId) {
541
- if (!data.users[i].dmFavorites) data.users[i].dmFavorites = [];
542
- data.users[i].dmFavorites = data.users[i].dmFavorites.filter(function (id) {
543
- return id !== targetUserId;
544
- });
545
- saveUsers(data);
546
- return data.users[i].dmFavorites;
547
- }
548
- }
549
- return [];
550
- }
551
-
552
- // --- DM Hidden (dismissed from strip) ---
553
-
554
- function getDmHidden(userId) {
555
- var data = loadUsers();
556
- for (var i = 0; i < data.users.length; i++) {
557
- if (data.users[i].id === userId) {
558
- return data.users[i].dmHidden || [];
559
- }
560
- }
561
- return [];
562
- }
563
-
564
- function addDmHidden(userId, targetUserId) {
565
- var data = loadUsers();
566
- for (var i = 0; i < data.users.length; i++) {
567
- if (data.users[i].id === userId) {
568
- if (!data.users[i].dmHidden) data.users[i].dmHidden = [];
569
- if (data.users[i].dmHidden.indexOf(targetUserId) === -1) {
570
- data.users[i].dmHidden.push(targetUserId);
571
- saveUsers(data);
572
- }
573
- return data.users[i].dmHidden;
574
- }
575
- }
576
- return [];
577
- }
578
-
579
- function removeDmHidden(userId, targetUserId) {
580
- var data = loadUsers();
581
- for (var i = 0; i < data.users.length; i++) {
582
- if (data.users[i].id === userId) {
583
- if (!data.users[i].dmHidden) data.users[i].dmHidden = [];
584
- data.users[i].dmHidden = data.users[i].dmHidden.filter(function (id) {
585
- return id !== targetUserId;
586
- });
587
- saveUsers(data);
588
- return data.users[i].dmHidden;
589
- }
590
- }
591
- return [];
592
- }
593
-
594
- // --- Deleted built-in mate keys tracking ---
595
-
596
- function getDeletedBuiltinKeys(userId) {
597
- var data = loadUsers();
598
- for (var i = 0; i < data.users.length; i++) {
599
- if (data.users[i].id === userId) {
600
- return data.users[i].deletedBuiltinKeys || [];
601
- }
602
- }
603
- return [];
604
- }
605
-
606
- function addDeletedBuiltinKey(userId, key) {
607
- var data = loadUsers();
608
- for (var i = 0; i < data.users.length; i++) {
609
- if (data.users[i].id === userId) {
610
- if (!data.users[i].deletedBuiltinKeys) data.users[i].deletedBuiltinKeys = [];
611
- if (data.users[i].deletedBuiltinKeys.indexOf(key) === -1) {
612
- data.users[i].deletedBuiltinKeys.push(key);
613
- saveUsers(data);
614
- }
615
- return;
616
- }
617
- }
618
- }
619
-
620
- function removeDeletedBuiltinKey(userId, key) {
621
- var data = loadUsers();
622
- for (var i = 0; i < data.users.length; i++) {
623
- if (data.users[i].id === userId) {
624
- if (!data.users[i].deletedBuiltinKeys) return;
625
- data.users[i].deletedBuiltinKeys = data.users[i].deletedBuiltinKeys.filter(function (k) {
626
- return k !== key;
627
- });
628
- saveUsers(data);
629
- return;
630
- }
631
- }
632
- }
633
-
634
- // --- RBAC permissions ---
635
-
636
- function getEffectivePermissions(user, osUsersMode) {
637
- // OS-mode users with linuxUser are exempt from RBAC (OS handles isolation)
638
- if (osUsersMode && user && user.linuxUser) return ALL_PERMISSIONS;
639
- // Admin always has full permissions
640
- if (user && user.role === "admin") return ALL_PERMISSIONS;
641
- // Merge stored permissions with defaults (handles missing keys for forward-compat)
642
- var stored = (user && user.permissions) || {};
643
- var result = {};
644
- var keys = Object.keys(DEFAULT_PERMISSIONS);
645
- for (var i = 0; i < keys.length; i++) {
646
- var k = keys[i];
647
- result[k] = stored[k] !== undefined ? stored[k] : DEFAULT_PERMISSIONS[k];
648
- }
649
- return result;
650
- }
651
-
652
- function updateUserPermissions(userId, permissions) {
653
- var data = loadUsers();
654
- for (var i = 0; i < data.users.length; i++) {
655
- if (data.users[i].id === userId) {
656
- // Validate: only allow known permission keys with boolean values
657
- var clean = {};
658
- var keys = Object.keys(DEFAULT_PERMISSIONS);
659
- for (var j = 0; j < keys.length; j++) {
660
- var k = keys[j];
661
- clean[k] = permissions[k] === true;
662
- }
663
- data.users[i].permissions = clean;
664
- saveUsers(data);
665
- return { ok: true, permissions: clean };
666
- }
667
- }
668
- return { error: "User not found" };
669
- }
670
-
671
- // --- Project access helpers ---
672
-
673
- function canAccessProject(userId, project) {
674
- if (!project) return false;
675
- // Public projects are accessible to all authenticated users
676
- if (!project.visibility || project.visibility === "public") return true;
677
- // Admin always has access
678
- var user = findUserById(userId);
679
- if (user && user.role === "admin") return true;
680
- // Owner always has access to their own project
681
- if (project.ownerId && project.ownerId === userId) return true;
682
- // Private project — check allowedUsers
683
- var allowed = project.allowedUsers || [];
684
- return allowed.indexOf(userId) >= 0;
685
- }
686
-
687
- function getAccessibleProjects(userId, projects) {
688
- if (!projects) return [];
689
- return projects.filter(function (p) {
690
- return canAccessProject(userId, p);
691
- });
692
- }
693
-
694
- // --- Session visibility helpers ---
695
-
696
- function canAccessSession(userId, session, project) {
697
- // Must have project access first
698
- if (!canAccessProject(userId, project)) return false;
699
- // Sessions without ownerId are legacy — only admin can see them
700
- if (!session.ownerId) {
701
- var user = findUserById(userId);
702
- return !!(user && user.role === "admin");
703
- }
704
- // Owner can always see their own sessions
705
- if (session.ownerId === userId) return true;
706
- // Shared sessions are visible to all project members (default)
707
- if (!session.sessionVisibility || session.sessionVisibility === "shared") return true;
708
- // Private sessions are only visible to the owner
709
- return false;
710
- }
711
-
712
- // --- Per-user chat layout setting ---
713
-
714
- function getChatLayout(userId) {
715
- var data = loadUsers();
716
- for (var i = 0; i < data.users.length; i++) {
717
- if (data.users[i].id === userId) {
718
- return data.users[i].chatLayout || "channel";
719
- }
720
- }
721
- return "channel";
722
- }
723
-
724
- function setChatLayout(userId, layout) {
725
- var val = (layout === "bubble") ? "bubble" : "channel";
726
- var data = loadUsers();
727
- for (var i = 0; i < data.users.length; i++) {
728
- if (data.users[i].id === userId) {
729
- data.users[i].chatLayout = val;
730
- saveUsers(data);
731
- return { ok: true, chatLayout: val };
732
- }
733
- }
734
- return { error: "User not found" };
735
- }
736
-
737
- // --- Per-user auto-continue setting ---
738
-
739
- function getAutoContinue(userId) {
740
- var data = loadUsers();
741
- for (var i = 0; i < data.users.length; i++) {
742
- if (data.users[i].id === userId) {
743
- return !!data.users[i].autoContinueOnRateLimit;
744
- }
745
- }
746
- return false;
747
- }
748
-
749
- function setAutoContinue(userId, enabled) {
750
- var data = loadUsers();
751
- for (var i = 0; i < data.users.length; i++) {
752
- if (data.users[i].id === userId) {
753
- data.users[i].autoContinueOnRateLimit = !!enabled;
754
- saveUsers(data);
755
- return { ok: true, autoContinueOnRateLimit: !!enabled };
756
- }
757
- }
758
- return { error: "User not found" };
759
- }
379
+ // --- Wire extracted modules ---
380
+
381
+ var auth = attachAuth({ loadUsers: loadUsers, saveUsers: saveUsers, findAdmin: findAdmin });
382
+ var permissions = attachPermissions({ loadUsers: loadUsers, saveUsers: saveUsers, findUserById: findUserById });
383
+ var preferences = attachPreferences({ loadUsers: loadUsers, saveUsers: saveUsers });
384
+
385
+ // Alias auth functions
386
+ var isMultiUser = auth.isMultiUser;
387
+ var enableMultiUser = auth.enableMultiUser;
388
+ var disableMultiUser = auth.disableMultiUser;
389
+ var getSetupCode = auth.getSetupCode;
390
+ var clearSetupCode = auth.clearSetupCode;
391
+ var validateSetupCode = auth.validateSetupCode;
392
+ var hashPin = auth.hashPin;
393
+ var generatePin = auth.generatePin;
394
+ var authenticateUser = auth.authenticateUser;
395
+ var generateUserAuthToken = auth.generateUserAuthToken;
396
+ var parseAuthCookie = auth.parseAuthCookie;
397
+
398
+ // Alias permissions functions
399
+ var getEffectivePermissions = permissions.getEffectivePermissions;
400
+ var updateUserPermissions = permissions.updateUserPermissions;
401
+ var canAccessProject = permissions.canAccessProject;
402
+ var getAccessibleProjects = permissions.getAccessibleProjects;
403
+ var canAccessSession = permissions.canAccessSession;
404
+
405
+ // Alias preferences functions
406
+ var getDmFavorites = preferences.getDmFavorites;
407
+ var addDmFavorite = preferences.addDmFavorite;
408
+ var removeDmFavorite = preferences.removeDmFavorite;
409
+ var getDmHidden = preferences.getDmHidden;
410
+ var addDmHidden = preferences.addDmHidden;
411
+ var removeDmHidden = preferences.removeDmHidden;
412
+ var getDeletedBuiltinKeys = preferences.getDeletedBuiltinKeys;
413
+ var addDeletedBuiltinKey = preferences.addDeletedBuiltinKey;
414
+ var removeDeletedBuiltinKey = preferences.removeDeletedBuiltinKey;
415
+ var getChatLayout = preferences.getChatLayout;
416
+ var setChatLayout = preferences.setChatLayout;
417
+ var getAutoContinue = preferences.getAutoContinue;
418
+ var setAutoContinue = preferences.setAutoContinue;
419
+ var setMateOnboarded = preferences.setMateOnboarded;
760
420
 
761
421
  module.exports = {
762
422
  USERS_FILE: USERS_FILE,
@@ -810,17 +470,7 @@ module.exports = {
810
470
  removeDmHidden: removeDmHidden,
811
471
  getChatLayout: getChatLayout,
812
472
  setChatLayout: setChatLayout,
813
- setMateOnboarded: function (userId) {
814
- var data = loadUsers();
815
- for (var i = 0; i < data.users.length; i++) {
816
- if (data.users[i].id === userId) {
817
- data.users[i].mateOnboardingShown = true;
818
- saveUsers(data);
819
- return { ok: true };
820
- }
821
- }
822
- return { error: "User not found" };
823
- },
473
+ setMateOnboarded: setMateOnboarded,
824
474
  getAutoContinue: getAutoContinue,
825
475
  setAutoContinue: setAutoContinue,
826
476
  getDeletedBuiltinKeys: getDeletedBuiltinKeys,