clay-server 3.5.1 → 3.6.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.
Files changed (64) hide show
  1. package/README.md +54 -196
  2. package/bin/cli.js +7 -42
  3. package/lib/builtin-mates.js +1 -1
  4. package/lib/cli-wordmark.js +32 -0
  5. package/lib/daemon.js +7 -0
  6. package/lib/os-user-diagnostics-worker.js +18 -0
  7. package/lib/os-user-diagnostics.js +312 -0
  8. package/lib/os-users.js +55 -22
  9. package/lib/pages.js +47 -35
  10. package/lib/public/app.js +2 -10
  11. package/lib/public/apple-touch-icon.png +0 -0
  12. package/lib/public/clay-studio-favicon-32.png +0 -0
  13. package/lib/public/clay-studio-symbol.png +0 -0
  14. package/lib/public/clay-studio-wordmark.svg +20 -0
  15. package/lib/public/css/base.css +143 -61
  16. package/lib/public/css/filebrowser.css +132 -22
  17. package/lib/public/css/icon-strip.css +33 -19
  18. package/lib/public/css/input.css +15 -5
  19. package/lib/public/css/messages.css +17 -14
  20. package/lib/public/css/mobile-nav.css +5 -5
  21. package/lib/public/css/overlays.css +58 -6
  22. package/lib/public/css/pane.css +0 -1
  23. package/lib/public/css/session-actions.css +9 -3
  24. package/lib/public/css/sidebar.css +104 -161
  25. package/lib/public/css/title-bar.css +51 -18
  26. package/lib/public/css/user-settings.css +114 -8
  27. package/lib/public/icon-192.png +0 -0
  28. package/lib/public/icon-512.png +0 -0
  29. package/lib/public/index.html +44 -82
  30. package/lib/public/manifest.json +1 -6
  31. package/lib/public/modules/app-connection.js +0 -3
  32. package/lib/public/modules/app-favicon.js +22 -29
  33. package/lib/public/modules/app-messages.js +1 -2
  34. package/lib/public/modules/app-notifications.js +3 -3
  35. package/lib/public/modules/command-palette.js +1 -1
  36. package/lib/public/modules/filebrowser-tabs.js +42 -3
  37. package/lib/public/modules/filebrowser.js +78 -9
  38. package/lib/public/modules/server-settings.js +0 -77
  39. package/lib/public/modules/sidebar-projects.js +1 -1
  40. package/lib/public/modules/sidebar-sessions.js +18 -5
  41. package/lib/public/modules/sidebar.js +2 -2
  42. package/lib/public/modules/theme.js +88 -249
  43. package/lib/public/modules/user-settings.js +2 -3
  44. package/lib/public/style.css +10 -10
  45. package/lib/public/sw.js +4 -2
  46. package/lib/themes/clay-dark.json +11 -0
  47. package/lib/themes/clay-light.json +11 -0
  48. package/package.json +1 -1
  49. package/lib/public/apple-touch-icon-dark.png +0 -0
  50. package/lib/public/clay-logo.png +0 -0
  51. package/lib/public/favicon-banded-32.png +0 -0
  52. package/lib/public/favicon-banded.png +0 -0
  53. package/lib/public/favicon-dark.svg +0 -1
  54. package/lib/public/favicon.svg +0 -1
  55. package/lib/public/icon-192-dark.png +0 -0
  56. package/lib/public/icon-512-dark.png +0 -0
  57. package/lib/public/icon-banded-76.png +0 -0
  58. package/lib/public/icon-banded-96.png +0 -0
  59. package/lib/public/icon-mono.svg +0 -1
  60. package/lib/public/modules/ascii-logo.js +0 -442
  61. package/lib/public/wordmark-banded-20.png +0 -0
  62. package/lib/public/wordmark-banded-32.png +0 -0
  63. package/lib/public/wordmark-banded-64.png +0 -0
  64. package/lib/public/wordmark-banded-80.png +0 -0
@@ -0,0 +1,312 @@
1
+ // Best-effort, read-only diagnostics for the current OS-user compatibility mode.
2
+
3
+ var fs = require("fs");
4
+ var childProcess = require("child_process");
5
+ var path = require("path");
6
+ var execFileSync = childProcess.execFileSync;
7
+ var osUsers = require("./os-users");
8
+
9
+ function commandAvailable(command, execFile) {
10
+ try {
11
+ execFile(command, ["--help"], { stdio: "ignore", timeout: 5000 });
12
+ return true;
13
+ } catch (e) {
14
+ return false;
15
+ }
16
+ }
17
+
18
+ function credentialPaths(home) {
19
+ if (!home) return [];
20
+ return [
21
+ home + "/.claude",
22
+ home + "/.codex",
23
+ home + "/.config/gh",
24
+ home + "/.config/gcloud",
25
+ ];
26
+ }
27
+
28
+ function accessStatus(checks) {
29
+ var names = ["read", "traverse", "write"];
30
+ for (var i = 0; i < names.length; i++) {
31
+ if (!checks[names[i]] || checks[names[i]].status === "unknown") return "unknown";
32
+ }
33
+ for (var j = 0; j < names.length; j++) {
34
+ if (checks[names[j]].status === "deny") return "deny";
35
+ }
36
+ return "allow";
37
+ }
38
+
39
+ function unknownChecks() {
40
+ return {
41
+ read: { status: "unknown" },
42
+ traverse: { status: "unknown" },
43
+ write: { status: "unknown" },
44
+ };
45
+ }
46
+
47
+ function projectAccessScript(projectPath) {
48
+ return [
49
+ "var fs = require('fs');",
50
+ "var target = " + JSON.stringify(projectPath) + ";",
51
+ "var checks = {};",
52
+ "function check(name, mode) {",
53
+ " try { fs.accessSync(target, mode); checks[name] = { status: 'allow' }; }",
54
+ " catch (error) { checks[name] = { status: 'deny', code: String(error && error.code || 'EACCES') }; }",
55
+ "}",
56
+ "check('read', fs.constants.R_OK);",
57
+ "check('traverse', fs.constants.X_OK);",
58
+ "check('write', fs.constants.W_OK);",
59
+ "process.stdout.write(JSON.stringify(checks));",
60
+ ].join(" ");
61
+ }
62
+
63
+ /**
64
+ * Check directory access as the mapped identity without creating or changing
65
+ * any file. The spawn wrapper intentionally follows the runtime's current
66
+ * supplementary-group behavior, including its setpriv fallback.
67
+ */
68
+ function probeMappedProjectAccess(project, user, options) {
69
+ options = options || {};
70
+ var result = {
71
+ projectSlug: project && project.slug || null,
72
+ userId: user && user.id || null,
73
+ result: "unknown",
74
+ checks: unknownChecks(),
75
+ };
76
+ if (!project || !project.path) {
77
+ result.evidence = "project_path_unavailable";
78
+ return result;
79
+ }
80
+ if (!user || !user.linuxUser) {
81
+ result.evidence = "linux_mapping_missing";
82
+ return result;
83
+ }
84
+
85
+ var resolveUser = options.resolveUser || osUsers.resolveOsUserInfo;
86
+ var wrapSpawn = options.wrapSpawn || osUsers.wrapSpawnAsUser;
87
+ var execFile = options.execFile || execFileSync;
88
+ var nodePath = options.nodePath || process.execPath;
89
+ var account;
90
+ try {
91
+ account = resolveUser(user.linuxUser);
92
+ } catch (e) {
93
+ result.evidence = "linux_account_unavailable";
94
+ return result;
95
+ }
96
+ if (!account || account.uid == null || account.gid == null) {
97
+ result.evidence = "linux_account_invalid";
98
+ return result;
99
+ }
100
+
101
+ try {
102
+ var spawn = wrapSpawn(nodePath, ["-e", projectAccessScript(project.path)], {
103
+ encoding: "utf8",
104
+ timeout: 5000,
105
+ stdio: "pipe",
106
+ uid: account.uid,
107
+ gid: account.gid,
108
+ }, options.setprivCheck);
109
+ var output = execFile(spawn.command, spawn.args, spawn.options);
110
+ var checks = JSON.parse(String(output).trim());
111
+ if (!checks || typeof checks !== "object") throw new Error("Invalid access probe result");
112
+ result.checks = checks;
113
+ result.result = accessStatus(checks);
114
+ result.evidence = "mapped_identity_access";
115
+ } catch (e) {
116
+ result.evidence = "probe_unavailable";
117
+ }
118
+ return result;
119
+ }
120
+
121
+ function collectUserDiagnostics(user, options) {
122
+ options = options || {};
123
+ var resolveUser = options.resolveUser || osUsers.resolveOsUserInfo;
124
+ var exists = options.exists || fs.existsSync;
125
+ var execFile = options.execFile || execFileSync;
126
+ var result = {
127
+ userId: user && user.id || null,
128
+ linuxUser: user && user.linuxUser || null,
129
+ mapping: "missing",
130
+ account: "unknown",
131
+ supplementaryGroups: "unknown",
132
+ credentialPaths: [],
133
+ };
134
+ if (!user || !user.linuxUser) return result;
135
+
136
+ var account;
137
+ try {
138
+ account = resolveUser(user.linuxUser);
139
+ result.mapping = "resolved";
140
+ result.account = "present";
141
+ } catch (e) {
142
+ result.mapping = "unavailable";
143
+ result.account = "missing";
144
+ result.evidence = "linux_account_unavailable";
145
+ return result;
146
+ }
147
+
148
+ try {
149
+ var output = execFile("id", ["-Gn", user.linuxUser], { encoding: "utf8", timeout: 5000, stdio: "pipe" });
150
+ result.supplementaryGroups = output.trim() ? output.trim().split(/\s+/) : [];
151
+ } catch (e) {
152
+ result.supplementaryGroups = "unknown";
153
+ }
154
+ var paths = credentialPaths(account.home);
155
+ for (var i = 0; i < paths.length; i++) {
156
+ var present = "unknown";
157
+ try { present = !!exists(paths[i]); } catch (e) {}
158
+ result.credentialPaths.push({ path: paths[i], present: present });
159
+ }
160
+ return result;
161
+ }
162
+
163
+ function collectOsUserDiagnostics(options) {
164
+ options = options || {};
165
+ var users = options.users || [];
166
+ var projects = options.projects || [];
167
+ var probeProjectAccess = options.probeProjectAccess || probeMappedProjectAccess;
168
+ var execFile = options.execFile || execFileSync;
169
+ var result = {
170
+ collectedAt: Date.now(),
171
+ setprivAvailable: commandAvailable("setpriv", execFile),
172
+ users: [],
173
+ projectAccess: [],
174
+ };
175
+ for (var i = 0; i < users.length; i++) {
176
+ try {
177
+ result.users.push(collectUserDiagnostics(users[i], options));
178
+ } catch (e) {
179
+ result.users.push({
180
+ userId: users[i] && users[i].id || null,
181
+ linuxUser: users[i] && users[i].linuxUser || null,
182
+ mapping: "unknown",
183
+ account: "unknown",
184
+ supplementaryGroups: "unknown",
185
+ credentialPaths: [],
186
+ });
187
+ }
188
+ }
189
+ for (var pi = 0; pi < projects.length; pi++) {
190
+ var project = projects[pi];
191
+ for (var ui = 0; ui < users.length; ui++) {
192
+ var user = users[ui];
193
+ if (!project || !user) continue;
194
+ var probe;
195
+ try {
196
+ probe = probeProjectAccess(project, user, options);
197
+ if (!probe || typeof probe !== "object") probe = { result: "unknown", evidence: "invalid_probe_result" };
198
+ } catch (e) {
199
+ probe = { result: "unknown", evidence: "probe_failed" };
200
+ }
201
+ if (!probe.projectSlug) probe.projectSlug = project.slug || null;
202
+ if (!probe.userId) probe.userId = user.id || null;
203
+ if (!probe.result) probe.result = "unknown";
204
+ result.projectAccess.push(probe);
205
+ }
206
+ }
207
+ return result;
208
+ }
209
+
210
+ function summarizeDiagnostics(result) {
211
+ var users = result.users || [];
212
+ var projectAccess = result.projectAccess || [];
213
+ var unavailableMappings = 0;
214
+ var unknownProbes = 0;
215
+ for (var i = 0; i < users.length; i++) {
216
+ if (users[i].mapping !== "resolved") unavailableMappings++;
217
+ }
218
+ for (var j = 0; j < projectAccess.length; j++) {
219
+ if (projectAccess[j].result === "unknown") unknownProbes++;
220
+ }
221
+ return "OS-user diagnostics: " + users.length + " user mappings, " + unavailableMappings + " unavailable, " + projectAccess.length + " project probes, " + unknownProbes + " unavailable";
222
+ }
223
+
224
+ function diagnosticsPayload(options) {
225
+ var users = typeof options.getUsers === "function" ? options.getUsers() : options.users || [];
226
+ var projects = typeof options.getProjects === "function" ? options.getProjects() : options.projects || [];
227
+ return {
228
+ users: users.map(function(user) {
229
+ return { id: user && user.id || null, linuxUser: user && user.linuxUser || null };
230
+ }),
231
+ projects: projects.map(function(project) {
232
+ return { slug: project && project.slug || null, path: project && project.path || null };
233
+ }),
234
+ };
235
+ }
236
+
237
+ function logDiagnosticMessage(logger, level, message) {
238
+ try {
239
+ if (logger && typeof logger[level] === "function") logger[level]("[daemon] " + message);
240
+ } catch (e) {}
241
+ }
242
+
243
+ /**
244
+ * Run blocking host probes in a short-lived child so diagnostics cannot stall
245
+ * the daemon event loop after it starts serving requests.
246
+ */
247
+ function scheduleOsUserDiagnosticsAsync(options) {
248
+ options = options || {};
249
+ var schedule = options.schedule || setTimeout;
250
+ var clearSchedule = options.clearSchedule || clearTimeout;
251
+ var spawn = options.spawn || childProcess.spawn;
252
+ var logger = options.logger || console;
253
+ var timeoutMs = options.timeoutMs || 30000;
254
+ try {
255
+ schedule(function() {
256
+ var child;
257
+ var timeout;
258
+ var output = "";
259
+ var completed = false;
260
+ function finish(level, message) {
261
+ if (completed) return;
262
+ completed = true;
263
+ if (timeout) clearSchedule(timeout);
264
+ logDiagnosticMessage(logger, level, message);
265
+ }
266
+ try {
267
+ child = spawn(options.nodePath || process.execPath, [path.join(__dirname, "os-user-diagnostics-worker.js")], {
268
+ stdio: ["pipe", "pipe", "ignore"],
269
+ });
270
+ if (!child || !child.stdin || !child.stdout || typeof child.once !== "function") {
271
+ finish("warn", "OS-user diagnostics unavailable");
272
+ return;
273
+ }
274
+ child.stdout.on("data", function(chunk) { output += String(chunk); });
275
+ child.once("error", function() { finish("warn", "OS-user diagnostics unavailable"); });
276
+ child.once("close", function(code) {
277
+ if (completed) return;
278
+ if (code !== 0) {
279
+ finish("warn", "OS-user diagnostics unavailable");
280
+ return;
281
+ }
282
+ try {
283
+ var report = JSON.parse(output);
284
+ if (!report || typeof report.summary !== "string") throw new Error("Invalid diagnostics report");
285
+ finish("log", report.summary);
286
+ } catch (e) {
287
+ finish("warn", "OS-user diagnostics unavailable");
288
+ }
289
+ });
290
+ timeout = schedule(function() {
291
+ try { child.kill(); } catch (e) {}
292
+ finish("warn", "OS-user diagnostics timed out");
293
+ }, timeoutMs);
294
+ child.stdin.end(JSON.stringify(diagnosticsPayload(options)));
295
+ } catch (e) {
296
+ try { if (child && typeof child.kill === "function") child.kill(); } catch (killError) {}
297
+ finish("warn", "OS-user diagnostics unavailable");
298
+ }
299
+ }, 0);
300
+ } catch (e) {
301
+ logDiagnosticMessage(logger, "warn", "OS-user diagnostics could not be scheduled");
302
+ }
303
+ }
304
+
305
+ module.exports = {
306
+ collectOsUserDiagnostics: collectOsUserDiagnostics,
307
+ collectUserDiagnostics: collectUserDiagnostics,
308
+ credentialPaths: credentialPaths,
309
+ probeMappedProjectAccess: probeMappedProjectAccess,
310
+ scheduleOsUserDiagnosticsAsync: scheduleOsUserDiagnosticsAsync,
311
+ summarizeDiagnostics: summarizeDiagnostics,
312
+ };
package/lib/os-users.js CHANGED
@@ -54,14 +54,15 @@ function setprivAvailable() {
54
54
  * Node's uid/gid options (primary group only) so behavior degrades safely.
55
55
  * Requires the parent process to be root, which is the case in os-users mode.
56
56
  */
57
- function wrapSpawnAsUser(command, args, options) {
57
+ function wrapSpawnAsUser(command, args, options, setprivCheck) {
58
58
  args = args || [];
59
59
  options = options || {};
60
60
  var uid = options.uid;
61
61
  var gid = options.gid;
62
62
  if (uid == null || gid == null) return { command: command, args: args, options: options };
63
63
 
64
- if (_inheritGroups && setprivAvailable()) {
64
+ var hasSetpriv = typeof setprivCheck === "function" ? setprivCheck() : setprivAvailable();
65
+ if (_inheritGroups && hasSetpriv) {
65
66
  var wrappedArgs = ["--reuid", String(uid), "--regid", String(gid), "--init-groups", "--", command].concat(args);
66
67
  var newOpts = Object.assign({}, options);
67
68
  delete newOpts.uid;
@@ -348,6 +349,39 @@ function toLinuxUsername(clayUsername) {
348
349
  return name;
349
350
  }
350
351
 
352
+ function findAvailableLinuxUsername(baseName, existsFn, rejectedCandidates) {
353
+ var exists = existsFn || linuxUserExists;
354
+ rejectedCandidates = rejectedCandidates || Object.create(null);
355
+ if (!exists(baseName) && !rejectedCandidates[baseName]) return baseName;
356
+ for (var suffixNumber = 2; suffixNumber < 10000; suffixNumber++) {
357
+ var suffix = "-" + suffixNumber;
358
+ var candidate = baseName.substring(0, 32 - suffix.length).replace(/-+$/, "") + suffix;
359
+ if (!exists(candidate) && !rejectedCandidates[candidate]) return candidate;
360
+ }
361
+ throw new Error("Could not allocate a unique Linux username for " + baseName);
362
+ }
363
+
364
+ function allocateLinuxUsername(baseName, existsFn, createUser) {
365
+ var exists = existsFn || linuxUserExists;
366
+ var rejectedCandidates = Object.create(null);
367
+ for (var attempt = 0; attempt < 9999; attempt++) {
368
+ var linuxName = findAvailableLinuxUsername(baseName, exists, rejectedCandidates);
369
+ try {
370
+ createUser(linuxName);
371
+ return linuxName;
372
+ } catch (e) {
373
+ var msg = (e.stderr || e.message || "").trim();
374
+ if (exists(linuxName) || e.status === 9 || /already exists/i.test(msg)) {
375
+ // NSS can lag a successful concurrent useradd, so remember collisions locally.
376
+ rejectedCandidates[linuxName] = true;
377
+ continue;
378
+ }
379
+ throw e;
380
+ }
381
+ }
382
+ throw new Error("Could not allocate a unique Linux username for " + baseName);
383
+ }
384
+
351
385
  /**
352
386
  * Ensure linger is enabled for a Linux user so systemd creates /run/user/<uid>.
353
387
  * Required for CLI tools like gcloud and gh that need XDG_RUNTIME_DIR.
@@ -484,31 +518,28 @@ function installClaudeCli(linuxName) {
484
518
  * Returns { ok: true, linuxUser: "clay-xxx" } or { error: "..." }.
485
519
  */
486
520
  function provisionLinuxUser(clayUsername) {
487
- var linuxName = toLinuxUsername(clayUsername);
488
-
489
- // Handle name collisions by appending a number
490
- if (linuxUserExists(linuxName)) {
491
- // Check if this is a clay-managed user (reuse it)
492
- // Otherwise find an available name
493
- console.log("[os-users] Linux user " + linuxName + " already exists, reusing.");
494
- return { ok: true, linuxUser: linuxName };
495
- }
496
-
521
+ var baseName = toLinuxUsername(clayUsername);
522
+ var linuxName;
497
523
  try {
498
- execFileSync("useradd", ["-m", "-s", "/bin/bash", linuxName], {
499
- encoding: "utf8",
500
- timeout: 15000,
501
- stdio: "pipe",
524
+ linuxName = allocateLinuxUsername(baseName, linuxUserExists, function(candidate) {
525
+ execFileSync("useradd", ["-m", "-s", "/bin/bash", candidate], {
526
+ encoding: "utf8",
527
+ timeout: 15000,
528
+ stdio: "pipe",
529
+ });
502
530
  });
503
- ensureLinger(linuxName);
504
- console.log("[os-users] Provisioned Linux user: " + linuxName + " (Clay user: " + clayUsername + ")");
505
- installClaudeCli(linuxName);
506
- return { ok: true, linuxUser: linuxName };
507
531
  } catch (e) {
508
532
  var msg = (e.stderr || e.message || "").trim();
509
- console.error("[os-users] Failed to provision Linux user " + linuxName + ": " + msg);
510
- return { error: "Failed to create Linux user " + linuxName + ": " + msg };
533
+ console.error("[os-users] Failed to provision Linux user " + baseName + ": " + msg);
534
+ return { error: "Failed to create Linux user " + baseName + ": " + msg };
535
+ }
536
+ if (linuxName !== baseName) {
537
+ console.log("[os-users] Linux user " + baseName + " already exists; provisioning " + linuxName + " instead");
511
538
  }
539
+ ensureLinger(linuxName);
540
+ console.log("[os-users] Provisioned Linux user: " + linuxName + " (Clay user: " + clayUsername + ")");
541
+ installClaudeCli(linuxName);
542
+ return { ok: true, linuxUser: linuxName };
512
543
  }
513
544
 
514
545
  /**
@@ -627,4 +658,6 @@ module.exports = {
627
658
  getLinuxUserHome: getLinuxUserHome,
628
659
  getLinuxUserUid: getLinuxUserUid,
629
660
  isSafeLinuxUsername: isSafeLinuxUsername,
661
+ findAvailableLinuxUsername: findAvailableLinuxUsername,
662
+ allocateLinuxUsername: allocateLinuxUsername,
630
663
  };
package/lib/pages.js CHANGED
@@ -3,9 +3,9 @@ function pinPageHtml() {
3
3
  '<meta charset="UTF-8">' +
4
4
  '<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">' +
5
5
  '<meta name="mobile-web-app-capable" content="yes">' +
6
- '<link rel="icon" type="image/png" href="/favicon-banded.png">' +
6
+ '<link rel="icon" type="image/png" sizes="32x32" href="/clay-studio-favicon-32.png">' +
7
7
  '<link rel="apple-touch-icon" href="/apple-touch-icon.png">' +
8
- '<title>Clay</title>' +
8
+ '<title>Clay Studio</title>' +
9
9
  '<style>' + authPageStyles + '</style></head><body><div class="c">' +
10
10
  '<h1 id="greeting"></h1>' +
11
11
  '<div class="sub">Enter your PIN to continue</div>' +
@@ -44,9 +44,10 @@ function setupPageHtml(httpsUrl, httpUrl, hasCert, lanMode) {
44
44
  <meta charset="UTF-8">
45
45
  <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
46
46
  <meta name="apple-mobile-web-app-capable" content="yes">
47
+ <link rel="icon" type="image/png" sizes="32x32" href="/clay-studio-favicon-32.png">
47
48
  <link rel="manifest" href="/manifest.json">
48
49
  <link rel="apple-touch-icon" href="/apple-touch-icon.png">
49
- <title>Setup - Clay</title>
50
+ <title>Setup - Clay Studio</title>
50
51
  <style>
51
52
  :root{--s-bg:#282a36;--s-text:#f8f8f2;--s-accent:#ffb86c;--s-muted:#6272a4;--s-border:#44475a;--s-dimmer:#6272a4;--s-success:#50fa7b;--s-accent-15:rgba(255,184,108,0.15);--s-success-10:rgba(80,250,123,0.1);--s-success-15:rgba(80,250,123,0.15);--s-accent-06:rgba(255,184,108,0.06);--s-muted-06:rgba(98,114,164,0.06);--s-muted-15:rgba(98,114,164,0.15)}
52
53
  @media(prefers-color-scheme:light){:root{--s-bg:#FAFAFA;--s-text:#5C6166;--s-accent:#FA8D3E;--s-muted:#A0A6AC;--s-border:#D2D4D8;--s-dimmer:#8A9199;--s-success:#6CBF49;--s-accent-15:rgba(250,141,62,0.15);--s-success-10:rgba(108,191,73,0.1);--s-success-15:rgba(108,191,73,0.15);--s-accent-06:rgba(250,141,62,0.06);--s-muted-06:rgba(160,166,172,0.06);--s-muted-15:rgba(160,166,172,0.15)}}
@@ -662,9 +663,10 @@ function escapeHtml(s) {
662
663
  return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
663
664
  }
664
665
 
665
- // --- Build auth page CSS variables from ayu-light theme (same logic as theme.js computeVars) ---
666
+ // --- Build auth page CSS variables from the Clay Studio theme pair ---
666
667
  var path = require("path");
667
- var _authTheme = require(path.join(__dirname, "themes", "ayu-light.json"));
668
+ var _authTheme = require(path.join(__dirname, "themes", "clay-light.json"));
669
+ var _authDarkTheme = require(path.join(__dirname, "themes", "clay-dark.json"));
668
670
 
669
671
  function _hexToRgb(hex) {
670
672
  hex = hex.replace("#", "");
@@ -689,35 +691,39 @@ function _mixColors(hex1, hex2, weight) {
689
691
  return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
690
692
  }
691
693
 
692
- var _t = {};
693
694
  var _keys = ["base00","base01","base02","base03","base04","base05","base06","base07",
694
695
  "base08","base09","base0A","base0B","base0C","base0D","base0E","base0F"];
695
- for (var _ki = 0; _ki < _keys.length; _ki++) {
696
- _t[_keys[_ki]] = "#" + _authTheme[_keys[_ki]];
697
- }
698
-
699
- var _authVarsObj = {
700
- "--bg": _t.base00,
701
- "--bg-alt": _t.base01,
702
- "--text": _t.base06,
703
- "--text-muted": _t.base04,
704
- "--text-dimmer": _t.base03,
705
- "--accent": _t.base09,
706
- "--accent-15": _hexToRgba(_t.base09, 0.15),
707
- "--accent-20": _hexToRgba(_t.base09, 0.20),
708
- "--border": _t.base02,
709
- "--input-bg": _mixColors(_t.base01, _t.base02, 0.5),
710
- "--error": _t.base08,
711
- };
712
696
 
713
- var _authVarsStr = ":root{";
714
- var _avKeys = Object.keys(_authVarsObj);
715
- for (var _vi = 0; _vi < _avKeys.length; _vi++) {
716
- _authVarsStr += _avKeys[_vi] + ":" + _authVarsObj[_avKeys[_vi]] + ";";
697
+ function _buildAuthVarDeclarations(theme) {
698
+ var t = {};
699
+ for (var ki = 0; ki < _keys.length; ki++) {
700
+ t[_keys[ki]] = "#" + theme[_keys[ki]];
701
+ }
702
+ var vars = {
703
+ "--bg": t.base00,
704
+ "--bg-alt": t.base01,
705
+ "--text": t.base06,
706
+ "--text-muted": t.base04,
707
+ "--text-dimmer": t.base03,
708
+ "--accent": t.base09,
709
+ "--accent-15": _hexToRgba(t.base09, 0.15),
710
+ "--accent-20": _hexToRgba(t.base09, 0.20),
711
+ "--border": t.base02,
712
+ "--input-bg": _mixColors(t.base01, t.base02, 0.5),
713
+ "--error": t.base08,
714
+ };
715
+ var declarations = "";
716
+ var names = Object.keys(vars);
717
+ for (var vi = 0; vi < names.length; vi++) {
718
+ declarations += names[vi] + ":" + vars[names[vi]] + ";";
719
+ }
720
+ return declarations;
717
721
  }
718
- _authVarsStr += "}";
719
722
 
720
- // --- Shared CSS for auth pages (Clay Light theme via CSS variables) ---
723
+ var _authVarsStr = ":root{" + _buildAuthVarDeclarations(_authTheme) + "}" +
724
+ "@media(prefers-color-scheme:dark){:root{" + _buildAuthVarDeclarations(_authDarkTheme) + "}}";
725
+
726
+ // --- Shared CSS for auth pages ---
721
727
  var authPageStyles =
722
728
  _authVarsStr +
723
729
  // Reset & layout
@@ -806,7 +812,8 @@ function adminSetupPageHtml() {
806
812
  return '<!DOCTYPE html><html lang="en"><head>' +
807
813
  '<meta charset="UTF-8">' +
808
814
  '<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">' +
809
- '<title>Admin Setup - Clay</title>' +
815
+ '<link rel="icon" type="image/png" sizes="32x32" href="/clay-studio-favicon-32.png">' +
816
+ '<title>Admin Setup - Clay Studio</title>' +
810
817
  '<style>' + authPageStyles + '</style></head><body><div class="c">' +
811
818
  '<div class="steps-bar"><span class="steps-dot current" id="dot0"></span><span class="steps-dot" id="dot1"></span><span class="steps-dot" id="dot2"></span><span class="steps-dot" id="dot3"></span></div>' +
812
819
 
@@ -901,7 +908,8 @@ function multiUserLoginPageHtml() {
901
908
  return '<!DOCTYPE html><html lang="en"><head>' +
902
909
  '<meta charset="UTF-8">' +
903
910
  '<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">' +
904
- '<title>Login - Clay</title>' +
911
+ '<link rel="icon" type="image/png" sizes="32x32" href="/clay-studio-favicon-32.png">' +
912
+ '<title>Login - Clay Studio</title>' +
905
913
  '<style>' + authPageStyles + '</style></head><body><div class="c">' +
906
914
  '<div class="steps-bar"><span class="steps-dot current" id="dot0"></span><span class="steps-dot" id="dot1"></span></div>' +
907
915
 
@@ -986,7 +994,8 @@ function invitePageHtml(inviteCode) {
986
994
  return '<!DOCTYPE html><html lang="en"><head>' +
987
995
  '<meta charset="UTF-8">' +
988
996
  '<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">' +
989
- '<title>Join - Clay</title>' +
997
+ '<link rel="icon" type="image/png" sizes="32x32" href="/clay-studio-favicon-32.png">' +
998
+ '<title>Join - Clay Studio</title>' +
990
999
  '<style>' + authPageStyles + '</style></head><body><div class="c">' +
991
1000
  '<div class="steps-bar"><span class="steps-dot current" id="dot0"></span><span class="steps-dot" id="dot1"></span><span class="steps-dot" id="dot2"></span></div>' +
992
1001
 
@@ -1067,7 +1076,8 @@ function smtpLoginPageHtml() {
1067
1076
  return '<!DOCTYPE html><html lang="en"><head>' +
1068
1077
  '<meta charset="UTF-8">' +
1069
1078
  '<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">' +
1070
- '<title>Login - Clay</title>' +
1079
+ '<link rel="icon" type="image/png" sizes="32x32" href="/clay-studio-favicon-32.png">' +
1080
+ '<title>Login - Clay Studio</title>' +
1071
1081
  '<style>' + authPageStyles +
1072
1082
  '.otp-input{width:100%;font-size:24px;letter-spacing:8px;text-align:center;padding:12px;' +
1073
1083
  'background:var(--field-bg);border:1px solid var(--field-border);border-radius:8px;color:var(--fg);' +
@@ -1223,7 +1233,8 @@ function smtpInvitePageHtml(inviteCode) {
1223
1233
  return '<!DOCTYPE html><html lang="en"><head>' +
1224
1234
  '<meta charset="UTF-8">' +
1225
1235
  '<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">' +
1226
- '<title>Join - Clay</title>' +
1236
+ '<link rel="icon" type="image/png" sizes="32x32" href="/clay-studio-favicon-32.png">' +
1237
+ '<title>Join - Clay Studio</title>' +
1227
1238
  '<style>' + authPageStyles + '</style></head><body><div class="c">' +
1228
1239
  '<div class="steps-bar"><span class="steps-dot current" id="dot0"></span><span class="steps-dot" id="dot1"></span><span class="steps-dot" id="dot2"></span></div>' +
1229
1240
 
@@ -1311,7 +1322,8 @@ function noProjectsPageHtml() {
1311
1322
  return '<!DOCTYPE html><html lang="en"><head>' +
1312
1323
  '<meta charset="UTF-8">' +
1313
1324
  '<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">' +
1314
- '<title>Clay</title>' +
1325
+ '<link rel="icon" type="image/png" sizes="32x32" href="/clay-studio-favicon-32.png">' +
1326
+ '<title>Clay Studio</title>' +
1315
1327
  '<style>' + authPageStyles + '</style></head><body><div class="c">' +
1316
1328
  '<h1>Hang tight!</h1>' +
1317
1329
  '<div class="sub">No projects have been assigned to your account yet.</div>' +
package/lib/public/app.js CHANGED
@@ -31,12 +31,11 @@ import { initContextSources, updateTerminalList, updateBrowserTabList, handleCon
31
31
  import { initStickyNotes, handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted, openArchive, closeArchive, isArchiveOpen, hideNotes, showNotes, isNotesVisible, createNote } from './modules/sticky-notes.js';
32
32
  import { initTheme, getThemeColor, getComputedVar, onThemeChange, getCurrentTheme, getChatLayout } from './modules/theme.js';
33
33
  import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionResolved, markPermissionCancelled, renderElicitationRequest, markElicitationResolved, renderPlanBanner, renderPlanCard, handleTodoWrite, handleTaskCreate, handleTaskUpdate, startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, addTurnMeta, resetTurnMetaCost, enableMainInput, getTools, getPlanContent, setPlanContent, isPlanFilePath, getTodoTools, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, updateSubagentProgress, initSubagentStop, closeToolGroup, removeToolFromGroup } from './modules/tools.js';
34
- import { initServerSettings, updateSettingsStats, updateSettingsModels, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './modules/server-settings.js';
34
+ import { initServerSettings, updateSettingsStats, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './modules/server-settings.js';
35
35
  import { initProjectSettings, handleInstructionsRead, handleInstructionsWrite, handleProjectEnv, handleProjectEnvSaved, isProjectSettingsOpen, handleProjectSharedEnv, handleProjectSharedEnvSaved, handleProjectOwnerChanged } from './modules/project-settings.js';
36
36
  import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modules/skills.js';
37
37
  import { initMcp } from './modules/mcp-ui.js';
38
38
  import { initScheduler, resetScheduler, handleLoopRegistryUpdated, handleScheduleRunStarted, handleScheduleRunFinished, handleLoopScheduled, openSchedulerToTab, isSchedulerOpen, closeScheduler, enterCraftingMode, exitCraftingMode, handleLoopRegistryFiles, getUpcomingSchedules } from './modules/scheduler.js';
39
- import { initAsciiLogo, startLogoAnimation, stopLogoAnimation } from './modules/ascii-logo.js';
40
39
  import { initPlaybook, openPlaybook, getPlaybooks, getPlaybookForTip, isCompleted as isPlaybookCompleted } from './modules/playbook.js';
41
40
  import { initSTT } from './modules/stt.js';
42
41
  import { initProfile, getProfileLang } from './modules/profile.js';
@@ -58,7 +57,7 @@ import { getWs as _getWsRef, setWs as _setWsRef } from './modules/ws-ref.js';
58
57
  import { initHomeHub, showHomeHub as _hubShowHomeHub, hideHomeHub as _hubHideHomeHub, handleHubSchedules as _hubHandleHubSchedules, renderHomeHub as _hubRenderHomeHub, isHomeHubVisible } from './modules/app-home-hub.js';
59
58
  import { initRateLimit, handleRateLimitEvent as _rlHandleRateLimitEvent, updateRateLimitUsage as _rlUpdateRateLimitUsage, addScheduledMessageBubble as _rlAddScheduledMessageBubble, removeScheduledMessageBubble as _rlRemoveScheduledMessageBubble, handleFastModeState as _rlHandleFastModeState, getScheduledMsgEl, resetRateLimitState } from './modules/app-rate-limit.js';
60
59
  import { initCursors, handleRemoteCursorMove as _curHandleRemoteCursorMove, handleRemoteCursorLeave as _curHandleRemoteCursorLeave, handleRemoteSelection as _curHandleRemoteSelection, clearRemoteCursors as _curClearRemoteCursors, initCursorToggle } from './modules/app-cursors.js';
61
- import { initFavicon, updateFavicon as _favUpdateFavicon, setSendBtnMode as _favSetSendBtnMode, blinkIO as _favBlinkIO, blinkSessionDot as _favBlinkSessionDot, updateCrossProjectBlink as _favUpdateCrossProjectBlink, startUrgentBlink as _favStartUrgentBlink, stopUrgentBlink as _favStopUrgentBlink, setActivity as _favSetActivity, drawFaviconAnimFrame as _favDrawFaviconAnimFrame } from './modules/app-favicon.js';
60
+ import { initFavicon, updateFavicon as _favUpdateFavicon, setSendBtnMode as _favSetSendBtnMode, blinkIO as _favBlinkIO, blinkSessionDot as _favBlinkSessionDot, updateCrossProjectBlink as _favUpdateCrossProjectBlink, startUrgentBlink as _favStartUrgentBlink, stopUrgentBlink as _favStopUrgentBlink, setActivity as _favSetActivity } from './modules/app-favicon.js';
62
61
  import { initHeader, closeSessionInfoPopover as _hdrCloseSessionInfoPopover, updateHistorySentinel as _hdrUpdateHistorySentinel, requestMoreHistory as _hdrRequestMoreHistory, prependOlderHistory as _hdrPrependOlderHistory } from './modules/app-header.js';
63
62
  import { initSessionActions } from './modules/session-actions.js';
64
63
  import { initMisc, flushPendingExtMessages, showImageModal as _miscShowImageModal, closeImageModal as _miscCloseImageModal, showPasteModal as _miscShowPasteModal, closePasteModal as _miscClosePasteModal, showConfirm as _miscShowConfirm, hideConfirm as _miscHideConfirm, showForceChangePinOverlay as _miscShowForceChangePinOverlay, sendExtensionCommand as _miscSendExtensionCommand, handleExtensionResult as _miscHandleExtensionResult } from './modules/app-misc.js';
@@ -504,13 +503,6 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
504
503
  initSplitView();
505
504
  initPaneBridge();
506
505
 
507
- // --- Connect overlay (animated ASCII logo) ---
508
- var asciiLogoCanvas = $("ascii-logo-canvas");
509
- initAsciiLogo(asciiLogoCanvas);
510
- startLogoAnimation();
511
- function startVerbCycle() { startLogoAnimation(); }
512
- function stopVerbCycle() { stopLogoAnimation(); }
513
-
514
506
  // --- Favicon, IO blink, status/activity -> modules/app-favicon.js
515
507
  function startPixelAnim() {}
516
508
  function stopPixelAnim() {}
Binary file
@@ -0,0 +1,20 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4611 1531" role="img" aria-label="Clay Studio">
2
+ <title>Clay Studio</title>
3
+ <style>
4
+ g { fill: #171717; }
5
+ @media (prefers-color-scheme: dark) { g { fill: #f2f2ef; } }
6
+ </style>
7
+ <metadata>Source Serif 4 v4.005, weight 400, optical size 60, tracking -0.01em, word gap 0.18em. Converted to outlines.</metadata>
8
+ <g fill="#171717" transform="translate(90 1116) scale(1 -1)">
9
+ <path d="M343 -16Q278 -16 224.0 8.5Q170 33 130.0 79.0Q90 125 68.5 190.0Q47 255 47 336Q47 416 69.5 480.5Q92 545 132.5 591.0Q173 637 227.0 661.5Q281 686 344 686Q379 686 423.0 677.5Q467 669 510 639L520 484H504L445 651L491 642L495 616Q455 646 419.5 655.5Q384 665 348 665Q288 665 237.5 626.5Q187 588 157.0 514.0Q127 440 127 334Q127 228 155.5 154.5Q184 81 233.0 43.0Q282 5 345 5Q378 5 418.5 14.0Q459 23 499 55V28L453 20L511 188H528L518 32Q475 4 431.5 -6.0Q388 -16 343 -16Z" transform="translate(0 0)"/>
10
+ <path d="M35 0V16L131 28H150L245 16V0ZM105 0Q107 23 107.5 61.0Q108 99 108.5 139.5Q109 180 109 212V677L31 694V708L172 744L180 736L176 563V212Q176 180 176.5 139.5Q177 99 178.0 61.0Q179 23 180 0Z" transform="translate(562 0)"/>
11
+ <path d="M156 -12Q107 -12 73.0 16.5Q39 45 39 100Q39 128 50.0 153.0Q61 178 92.0 200.5Q123 223 184 243Q199 248 219.0 254.0Q239 260 266.5 267.5Q294 275 329 283V267Q278 252 247.5 242.0Q217 232 203 226Q161 208 141.5 189.0Q122 170 116.0 152.0Q110 134 110 117Q110 74 131.5 55.5Q153 37 185 37Q207 37 226.0 42.0Q245 47 267.0 61.5Q289 76 319 106L327 68H297Q276 40 253.5 22.0Q231 4 206.5 -4.0Q182 -12 156 -12ZM369 -8Q331 -8 311.5 13.5Q292 35 291 75V77V308Q291 357 281.5 385.5Q272 414 252.0 426.0Q232 438 200 438Q181 438 163.0 433.5Q145 429 125 422L153 441L134 361Q130 341 119.0 330.0Q108 319 91 319Q74 319 66.0 329.0Q58 339 56 356Q67 405 114.0 434.5Q161 464 225 464Q271 464 300.5 447.5Q330 431 344.5 396.0Q359 361 359 306V83Q359 52 367.5 40.5Q376 29 390 29Q403 29 412.0 35.0Q421 41 434 55L444 47Q434 23 416.0 7.5Q398 -8 369 -8Z" transform="translate(825 0)"/>
12
+ <path d="M57 -259Q42 -259 27.0 -254.5Q12 -250 1.0 -241.0Q-10 -232 -15 -218Q-13 -201 0.5 -191.5Q14 -182 33 -182Q50 -182 63.5 -188.5Q77 -195 91 -206L115 -224L106 -228L85 -234Q112 -219 132.0 -192.5Q152 -166 170.0 -127.5Q188 -89 207 -40L236 35L237 39L313 246L389 452H417L232 -40Q208 -105 182.5 -154.5Q157 -204 127.0 -231.5Q97 -259 57 -259ZM225 -6 44 452H123L266 70L241 25ZM4 436V452H211V437L111 424H91ZM309 437V452H461V436L397 428H385Z" transform="translate(1245 0)"/>
13
+ <path d="M216 -16Q163 -16 117.5 -1.0Q72 14 40 36L48 188H64L105 12L61 36V52Q90 35 113.0 24.5Q136 14 160.5 9.5Q185 5 216 5Q260 5 294.0 20.0Q328 35 347.5 68.0Q367 101 367 155Q367 194 350.0 220.5Q333 247 304.5 266.0Q276 285 243 300L207 317Q169 336 133.0 359.5Q97 383 74.5 418.5Q52 454 52 508Q52 566 78.5 605.5Q105 645 150.0 665.5Q195 686 249 686Q299 686 336.0 673.5Q373 661 401 638L397 494H381L337 659L384 639L388 613Q354 648 320.5 656.5Q287 665 249 665Q212 665 180.5 652.0Q149 639 129.5 608.5Q110 578 110 527Q110 489 127.0 462.5Q144 436 172.5 417.0Q201 398 234 382L269 365Q309 346 345.5 322.5Q382 299 405.5 263.5Q429 228 429 172Q429 114 404.0 71.5Q379 29 331.5 6.5Q284 -16 216 -16Z" transform="translate(1890 0)"/>
14
+ <path d="M121 428V452H276V428ZM185 -12Q138 -12 110.0 13.5Q82 39 82 89Q82 117 82.0 140.5Q82 164 82 198L84 428H8V444L119 462L83 440L133 604H155L151 440V436V97Q151 58 166.0 41.5Q181 25 207 25Q227 25 244.0 33.5Q261 42 275 58L286 49Q276 30 262.5 16.5Q249 3 230.5 -4.5Q212 -12 185 -12Z" transform="translate(2339 0)"/>
15
+ <path d="M213 -12Q156 -12 124.5 21.5Q93 55 93 129V429L114 399L24 418V432L156 464L164 456L160 330V147Q160 82 179.5 59.5Q199 37 242 37Q266 37 289.0 45.5Q312 54 336.0 71.0Q360 88 385 111L393 95H389Q370 70 342.5 45.0Q315 20 282.5 4.0Q250 -12 213 -12ZM379 -10V90V93V403L306 418V432L442 464L450 456L446 330V25L513 16V0Z" transform="translate(2627 0)"/>
16
+ <path d="M225 -12Q176 -12 135.0 13.5Q94 39 69.0 89.5Q44 140 44 216Q44 295 70.5 350.5Q97 406 141.0 435.0Q185 464 237 464Q264 464 289.5 455.0Q315 446 337.5 426.0Q360 406 378 372H392L385 347Q348 391 318.5 408.5Q289 426 248 426Q212 426 181.5 403.5Q151 381 133.0 335.0Q115 289 115 220Q115 153 132.0 110.0Q149 67 179.5 46.0Q210 25 248 25Q289 25 321.0 51.0Q353 77 381 118L387 89H374Q359 61 337.0 38.0Q315 15 287.0 1.5Q259 -12 225 -12ZM365 -10V85V88V376V384V677L290 694V708L428 744L436 736L432 540V25L501 16V0Z" transform="translate(3153 0)"/>
17
+ <path d="M35 0V16L131 28H150L245 16V0ZM105 0Q107 23 107.5 61.0Q108 99 108.5 139.5Q109 180 109 212V258Q109 300 108.5 329.0Q108 358 106 389L31 406V420L172 464L180 456L176 330V212Q176 180 176.5 139.5Q177 99 178.0 61.0Q179 23 180 0ZM140 569Q118 569 102.5 583.0Q87 597 87 622Q87 646 102.5 660.0Q118 674 140 674Q163 674 178.0 660.0Q193 646 193 622Q193 597 178.0 583.0Q163 569 140 569Z" transform="translate(3672 0)"/>
18
+ <path d="M251 -12Q196 -12 149.0 14.5Q102 41 73.0 93.5Q44 146 44 224Q44 302 73.0 355.5Q102 409 149.0 436.5Q196 464 251 464Q306 464 353.0 437.5Q400 411 429.0 359.0Q458 307 458 228Q458 151 429.0 97.0Q400 43 353.0 15.5Q306 -12 251 -12ZM251 9Q292 9 322.0 34.5Q352 60 368.0 109.0Q384 158 384 228Q384 298 368.0 345.5Q352 393 322.0 418.0Q292 443 251 443Q210 443 179.5 417.5Q149 392 133.0 343.0Q117 294 117 224Q117 155 133.0 107.0Q149 59 179.5 34.0Q210 9 251 9Z" transform="translate(3930 0)"/>
19
+ </g>
20
+ </svg>